Skip to content

Commit

Permalink
added product
Browse files Browse the repository at this point in the history
  • Loading branch information
sebajax committed Feb 20, 2024
1 parent a1728b9 commit d450191
Show file tree
Hide file tree
Showing 15 changed files with 371 additions and 29 deletions.
76 changes: 52 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,29 @@ This structure, created following the development guide's for vertical slice arc

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.

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

## 📚 Code Structure

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

cmd
contains the main.go file that is our starting point to execute
migrations
contains all the database configuration for the api (if needed)
internal
contains all the api logic
A brief description of the layout:

- `.github` has two template files for creating PR and issue. Please see the files for more details.
- `.gitignore` varies per project, but all projects need to ignore `bin` directory.
- `.golangci.yml` is the golangci-lint config file.
- `Makefile` is used to build the project. **You need to tweak the variables based on your project**.
- `CHANGELOG.md` contains auto-generated changelog information.
- `OWNERS` contains owners of the project.
- `README.md` is a detailed description of the project.
- `cmd` contains the main.go file that is our starting point to execute
- `pkg` places most of project business logic.
- `migrations` contains all vendored code.
- `internal` contains all the api logic.

## 🚀 Stack

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

### Programming language

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

### Database diagram for the project

![alt text](./db_diagram.png)
![alt text](./image/db_diagram.png)

### Internal folder structure for a new domain

![alt text](./image/internal_domain.jpg)

### 1 - Create product.go (domain)

## ⚙️ Usage

### Docker usage

Build server
```bash
# Build server
docker-compose -p go-vertical-slice-architecture build

Start server
# Start server
docker-compose up -d

Stop server
# Stop server
docker-compose down
```

### Standalone usage

air
```bash
# Live reload
air
```

### Testing

To run unit testing
```bash
# To run unit testing
go test

To run unit testing coverage
# To run unit testing coverage
go test -cover ./...
```

### Formatting, Linting and Vetting

To run formating
```bash
# Run formating
go fmt ./...

To remove unused imports
# Remove unused imports
goimports -l -w .

To run linting
# Run linting
golangci-lint run ./...

To run vetting
# Run vetting
go vet ./...

# Run shadow to check shadowed variables
# Install shadow
go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
# Run shadow
shadow ./...
```

### Database migration script

To create the script
```bash
# Create the script
migrate create -ext sql -dir /migrations -seq [script_name]
To run the script
# Run the script
migrate -database ${POSTGRESQL_URL} -path /migrations up

* It will run automatically when the database initializes
# It will run automatically when the database initializes
```

## 💻 Environment variables

Expand Down
File renamed without changes
File renamed without changes
File renamed without changes
Binary file added image/internal_domain.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
64 changes: 64 additions & 0 deletions internal/product/handler/createProduct.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package handler

import (
"time"

"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/sebajax/go-vertical-slice-architecture/internal/user"
"github.com/sebajax/go-vertical-slice-architecture/internal/user/service"
"github.com/sebajax/go-vertical-slice-architecture/pkg/apperror"
"github.com/sebajax/go-vertical-slice-architecture/pkg/message"
"github.com/sebajax/go-vertical-slice-architecture/pkg/validate"
)

// Body request schema for CreateUser
type UserSchema struct {
IdentityNumber string `json:"identity_number" validate:"required,min=6"`
FirstName string `json:"first_name" validate:"required,min=2"`
LastName string `json:"last_name" validate:"required,min=2"`
Email string `json:"email" validate:"required,email"`
DateOfBirth time.Time `json:"date_of_birth" validate:"required"`
}

// Creates a new user into the database
func CreateUser(s service.CreateUserService) fiber.Handler {
return func(c *fiber.Ctx) error {
// Get body request
var body UserSchema
// Validate the body
err := c.BodyParser(&body)
if err != nil {
// Map the error and response via the middleware
log.Error(err)
return err
}

// Validate schema
serr, err := validate.Validate(body)
if err != nil {
log.Error(serr)
return apperror.BadRequest(serr)
}

// No schema errores then map body to domain
user := &user.User{
IdentityNumber: body.IdentityNumber,
FirstName: body.FirstName,
LastName: body.LastName,
Email: body.Email,
DateOfBirth: body.DateOfBirth,
}

// Execute the service
result, err := s.CreateUser(user)
if err != nil {
// if service response an error return via the middleware
log.Error(err)
return err
}

// Success execution
return c.Status(fiber.StatusCreated).JSON(message.SuccessResponse(&result))
}
}
11 changes: 11 additions & 0 deletions internal/product/handler/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package handler

import (
"github.com/gofiber/fiber/v2"
"github.com/sebajax/go-vertical-slice-architecture/internal/user/service"
)

// UserRouter is the Router for GoFiber App
func UserRouter(app fiber.Router, s *service.UserService) {
app.Post("/", CreateUser(s.CreateUserServiceProvider))
}
57 changes: 57 additions & 0 deletions internal/product/infrastructure/productRepository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package infrastructure

import (
"database/sql"
"fmt"

"github.com/sebajax/go-vertical-slice-architecture/internal/user"
"github.com/sebajax/go-vertical-slice-architecture/pkg/database"
)

// User repository for querying the database
type userRepository struct {
db *database.DbConn
}

// Create a user instance repository
func NewUserRepository(dbcon *database.DbConn) user.UserRepository {
return &userRepository{db: dbcon}
}

// Stores a new user in the database
func (repo *userRepository) Save(u *user.User) (int64, error) {
// Get the id inserted in the database
var id int64

query := `INSERT INTO client (identity_number, first_name, last_name, email, date_of_birth)
VALUES ($1, $2, $3, $4, $5) RETURNING id`
err := repo.db.DbPool.QueryRow(query, u.IdentityNumber, u.FirstName, u.LastName, u.Email, u.DateOfBirth).Scan(&id)
if err != nil {
return 0, err
}

fmt.Println("id: ", id)

// No errors return the user id inserted
return id, nil
}

// Gets the user by the email
func (repo *userRepository) GetByEmail(email string) (*user.User, bool, error) {
u := user.User{}
query := `SELECT id, identity_number, first_name, last_name, email, date_of_birth, created_at
FROM client
WHERE email = $1`
err := repo.db.DbPool.QueryRow(query, email).Scan(&u.Id, &u.IdentityNumber, &u.FirstName, &u.LastName, &u.Email, &u.DateOfBirth, &u.CreatedAt)
if err != nil {
// Not found, but not an error
if err == sql.ErrNoRows {
return nil, false, nil
}
// An actual error occurred
return nil, false, err
}

// Found the item
return &u, true, nil
}
24 changes: 24 additions & 0 deletions internal/product/mock/mockProductRepository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package mocks

import "github.com/sebajax/go-vertical-slice-architecture/internal/user"

type mockUserRepository struct{}

func NewMockUserRepository() user.UserRepository {
return &mockUserRepository{}
}

func (mock *mockUserRepository) Save(u *user.User) (int64, error) {
return 1, nil
}

func (mock *mockUserRepository) GetByEmail(email string) (*user.User, bool, error) {
/*return &user.User{
Id: 1,
Email: "juan@example.com",
Name: "Juan",
DateOfBirth: time.Now(),
CreatedAt: time.Now(),
}, true, nil*/
return nil, true, nil
}
7 changes: 7 additions & 0 deletions internal/product/port.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package user

// User port interface definition for depedency injection
type UserRepository interface {
Save(u *User) (int64, error)
GetByEmail(email string) (*User, bool, error)
}
58 changes: 58 additions & 0 deletions internal/product/product.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package product

import "time"

// ProductCategory represents the categories of electronic products
type ProductCategory int

// Enumeration of product categories
const (
Laptop ProductCategory = iota
Smartphone
Tablet
SmartWatch
Headphones
Camera
Television
Other
)

// String representation of the ProductCategory
func (p ProductCategory) String() string {
return [...]string{
"Laptop",
"Smartphone",
"Tablet",
"SmartWatch",
"Headphones",
"Camera",
"Television",
"Other",
}
}

// Const for error messages
const (
ErrorSkuExists string = "ERROR_SKU_EXISTS"
ErrorWrongCategory string = "ERROR_WRONG_CATEGORY"
)

// Product Domain
type User struct {
Id int
Name string
Sku string
Category ProductCategory
Price string
CreatedAt time.Time
}

// Create a new product instance
func New(n string, s string, c string, p string) (*Product, error) {
return &Product{
Name: n,
Sku: s,
Category: c,
Price: p,
}, nil
}
Loading

0 comments on commit d450191

Please sign in to comment.