Skip to content

Commit d450191

Browse files
committed
added product
1 parent a1728b9 commit d450191

15 files changed

+371
-29
lines changed

README.md

+52-24
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,29 @@ This structure, created following the development guide's for vertical slice arc
1818

1919
Vertical slice architecture is an approach to software development where code and functionality are organized around individual features or user stories, encompassing all layers of the application from user interface to data access, promoting autonomy, reduced dependencies, and iterative development.
2020

21-
![alt text](./vertical-slice-architecture.png)
21+
![alt text](./image/vertical-slice-architecture.png)
2222

2323
## 📚 Code Structure
2424

25-
![alt text](./go-vertical-slice-architecture.png)
25+
![alt text](./image/go-vertical-slice-architecture.png)
2626

27-
cmd
28-
contains the main.go file that is our starting point to execute
29-
migrations
30-
contains all the database configuration for the api (if needed)
31-
internal
32-
contains all the api logic
27+
A brief description of the layout:
28+
29+
- `.github` has two template files for creating PR and issue. Please see the files for more details.
30+
- `.gitignore` varies per project, but all projects need to ignore `bin` directory.
31+
- `.golangci.yml` is the golangci-lint config file.
32+
- `Makefile` is used to build the project. **You need to tweak the variables based on your project**.
33+
- `CHANGELOG.md` contains auto-generated changelog information.
34+
- `OWNERS` contains owners of the project.
35+
- `README.md` is a detailed description of the project.
36+
- `cmd` contains the main.go file that is our starting point to execute
37+
- `pkg` places most of project business logic.
38+
- `migrations` contains all vendored code.
39+
- `internal` contains all the api logic.
3340

3441
## 🚀 Stack
3542

3643
<img src="https://img.shields.io/badge/Go-00ADD8?style=for-the-badge&logo=go&logoColor=white" />
37-
<img src="https://img.shields.io/badge/Docker-2CA5E0?style=for-the-badge&logo=docker&logoColor=white" />
38-
<img src="https://img.shields.io/badge/PostgreSQL-316192?style=for-the-badge&logo=postgresql&logoColor=white" />
3944

4045
### Programming language
4146

@@ -69,55 +74,78 @@ Vertical slice architecture is an approach to software development where code an
6974

7075
### Database diagram for the project
7176

72-
![alt text](./db_diagram.png)
77+
![alt text](./image/db_diagram.png)
78+
79+
### Internal folder structure for a new domain
80+
81+
![alt text](./image/internal_domain.jpg)
82+
83+
### 1 - Create product.go (domain)
7384

7485
## ⚙️ Usage
7586

7687
### Docker usage
7788

78-
Build server
89+
```bash
90+
# Build server
7991
docker-compose -p go-vertical-slice-architecture build
8092

81-
Start server
93+
# Start server
8294
docker-compose up -d
8395

84-
Stop server
96+
# Stop server
8597
docker-compose down
98+
```
8699

87100
### Standalone usage
88101

89-
air
102+
```bash
103+
# Live reload
104+
air
105+
```
90106

91107
### Testing
92108

93-
To run unit testing
109+
```bash
110+
# To run unit testing
94111
go test
95112

96-
To run unit testing coverage
113+
# To run unit testing coverage
97114
go test -cover ./...
115+
```
98116

99117
### Formatting, Linting and Vetting
100118

101-
To run formating
119+
```bash
120+
# Run formating
102121
go fmt ./...
103122

104-
To remove unused imports
123+
# Remove unused imports
105124
goimports -l -w .
106125

107-
To run linting
126+
# Run linting
108127
golangci-lint run ./...
109128

110-
To run vetting
129+
# Run vetting
111130
go vet ./...
112131

132+
# Run shadow to check shadowed variables
133+
# Install shadow
134+
go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
135+
# Run shadow
136+
shadow ./...
137+
```
138+
113139
### Database migration script
114140

115-
To create the script
141+
```bash
142+
# Create the script
116143
migrate create -ext sql -dir /migrations -seq [script_name]
117-
To run the script
144+
# Run the script
118145
migrate -database ${POSTGRESQL_URL} -path /migrations up
119146

120-
* It will run automatically when the database initializes
147+
# It will run automatically when the database initializes
148+
```
121149

122150
## 💻 Environment variables
123151

File renamed without changes.
File renamed without changes.

image/internal_domain.jpg

8.24 KB
Loading
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package handler
2+
3+
import (
4+
"time"
5+
6+
"github.com/gofiber/fiber/v2"
7+
"github.com/gofiber/fiber/v2/log"
8+
"github.com/sebajax/go-vertical-slice-architecture/internal/user"
9+
"github.com/sebajax/go-vertical-slice-architecture/internal/user/service"
10+
"github.com/sebajax/go-vertical-slice-architecture/pkg/apperror"
11+
"github.com/sebajax/go-vertical-slice-architecture/pkg/message"
12+
"github.com/sebajax/go-vertical-slice-architecture/pkg/validate"
13+
)
14+
15+
// Body request schema for CreateUser
16+
type UserSchema struct {
17+
IdentityNumber string `json:"identity_number" validate:"required,min=6"`
18+
FirstName string `json:"first_name" validate:"required,min=2"`
19+
LastName string `json:"last_name" validate:"required,min=2"`
20+
Email string `json:"email" validate:"required,email"`
21+
DateOfBirth time.Time `json:"date_of_birth" validate:"required"`
22+
}
23+
24+
// Creates a new user into the database
25+
func CreateUser(s service.CreateUserService) fiber.Handler {
26+
return func(c *fiber.Ctx) error {
27+
// Get body request
28+
var body UserSchema
29+
// Validate the body
30+
err := c.BodyParser(&body)
31+
if err != nil {
32+
// Map the error and response via the middleware
33+
log.Error(err)
34+
return err
35+
}
36+
37+
// Validate schema
38+
serr, err := validate.Validate(body)
39+
if err != nil {
40+
log.Error(serr)
41+
return apperror.BadRequest(serr)
42+
}
43+
44+
// No schema errores then map body to domain
45+
user := &user.User{
46+
IdentityNumber: body.IdentityNumber,
47+
FirstName: body.FirstName,
48+
LastName: body.LastName,
49+
Email: body.Email,
50+
DateOfBirth: body.DateOfBirth,
51+
}
52+
53+
// Execute the service
54+
result, err := s.CreateUser(user)
55+
if err != nil {
56+
// if service response an error return via the middleware
57+
log.Error(err)
58+
return err
59+
}
60+
61+
// Success execution
62+
return c.Status(fiber.StatusCreated).JSON(message.SuccessResponse(&result))
63+
}
64+
}

internal/product/handler/handler.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package handler
2+
3+
import (
4+
"github.com/gofiber/fiber/v2"
5+
"github.com/sebajax/go-vertical-slice-architecture/internal/user/service"
6+
)
7+
8+
// UserRouter is the Router for GoFiber App
9+
func UserRouter(app fiber.Router, s *service.UserService) {
10+
app.Post("/", CreateUser(s.CreateUserServiceProvider))
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package infrastructure
2+
3+
import (
4+
"database/sql"
5+
"fmt"
6+
7+
"github.com/sebajax/go-vertical-slice-architecture/internal/user"
8+
"github.com/sebajax/go-vertical-slice-architecture/pkg/database"
9+
)
10+
11+
// User repository for querying the database
12+
type userRepository struct {
13+
db *database.DbConn
14+
}
15+
16+
// Create a user instance repository
17+
func NewUserRepository(dbcon *database.DbConn) user.UserRepository {
18+
return &userRepository{db: dbcon}
19+
}
20+
21+
// Stores a new user in the database
22+
func (repo *userRepository) Save(u *user.User) (int64, error) {
23+
// Get the id inserted in the database
24+
var id int64
25+
26+
query := `INSERT INTO client (identity_number, first_name, last_name, email, date_of_birth)
27+
VALUES ($1, $2, $3, $4, $5) RETURNING id`
28+
err := repo.db.DbPool.QueryRow(query, u.IdentityNumber, u.FirstName, u.LastName, u.Email, u.DateOfBirth).Scan(&id)
29+
if err != nil {
30+
return 0, err
31+
}
32+
33+
fmt.Println("id: ", id)
34+
35+
// No errors return the user id inserted
36+
return id, nil
37+
}
38+
39+
// Gets the user by the email
40+
func (repo *userRepository) GetByEmail(email string) (*user.User, bool, error) {
41+
u := user.User{}
42+
query := `SELECT id, identity_number, first_name, last_name, email, date_of_birth, created_at
43+
FROM client
44+
WHERE email = $1`
45+
err := repo.db.DbPool.QueryRow(query, email).Scan(&u.Id, &u.IdentityNumber, &u.FirstName, &u.LastName, &u.Email, &u.DateOfBirth, &u.CreatedAt)
46+
if err != nil {
47+
// Not found, but not an error
48+
if err == sql.ErrNoRows {
49+
return nil, false, nil
50+
}
51+
// An actual error occurred
52+
return nil, false, err
53+
}
54+
55+
// Found the item
56+
return &u, true, nil
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package mocks
2+
3+
import "github.com/sebajax/go-vertical-slice-architecture/internal/user"
4+
5+
type mockUserRepository struct{}
6+
7+
func NewMockUserRepository() user.UserRepository {
8+
return &mockUserRepository{}
9+
}
10+
11+
func (mock *mockUserRepository) Save(u *user.User) (int64, error) {
12+
return 1, nil
13+
}
14+
15+
func (mock *mockUserRepository) GetByEmail(email string) (*user.User, bool, error) {
16+
/*return &user.User{
17+
Id: 1,
18+
19+
Name: "Juan",
20+
DateOfBirth: time.Now(),
21+
CreatedAt: time.Now(),
22+
}, true, nil*/
23+
return nil, true, nil
24+
}

internal/product/port.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package user
2+
3+
// User port interface definition for depedency injection
4+
type UserRepository interface {
5+
Save(u *User) (int64, error)
6+
GetByEmail(email string) (*User, bool, error)
7+
}

internal/product/product.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package product
2+
3+
import "time"
4+
5+
// ProductCategory represents the categories of electronic products
6+
type ProductCategory int
7+
8+
// Enumeration of product categories
9+
const (
10+
Laptop ProductCategory = iota
11+
Smartphone
12+
Tablet
13+
SmartWatch
14+
Headphones
15+
Camera
16+
Television
17+
Other
18+
)
19+
20+
// String representation of the ProductCategory
21+
func (p ProductCategory) String() string {
22+
return [...]string{
23+
"Laptop",
24+
"Smartphone",
25+
"Tablet",
26+
"SmartWatch",
27+
"Headphones",
28+
"Camera",
29+
"Television",
30+
"Other",
31+
}
32+
}
33+
34+
// Const for error messages
35+
const (
36+
ErrorSkuExists string = "ERROR_SKU_EXISTS"
37+
ErrorWrongCategory string = "ERROR_WRONG_CATEGORY"
38+
)
39+
40+
// Product Domain
41+
type User struct {
42+
Id int
43+
Name string
44+
Sku string
45+
Category ProductCategory
46+
Price string
47+
CreatedAt time.Time
48+
}
49+
50+
// Create a new product instance
51+
func New(n string, s string, c string, p string) (*Product, error) {
52+
return &Product{
53+
Name: n,
54+
Sku: s,
55+
Category: c,
56+
Price: p,
57+
}, nil
58+
}

0 commit comments

Comments
 (0)