Codebase for golang use clean architecture.
The current go version use is v1.20
The purpose of the codebase is to show:
- Independent of Frameworks
- Testable
- Independent of UI
- Independent of Database
- Independent of any external agency
More at https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
Below are some feature included in this project:
- auth (JWT / API)
- users (Create / List / Show / Update / Delete)
- roles (Create / List / Show / Update / Delete)
Build Local development:
cd go-clean-architecture
git submodule update --init --force --remote
docker compose build
Start development:
# Install dependencies
go mod tidy
# Copy env
cp .env.example .env
# Start docker
docker compose up -d
# Inside docker
docker compose exec go-app bash
# Make migrate
make create_example_table.sql
# Migrate
go run cmd/migrate/main.go
# Migrate down
# go run cmd/migrate/main.go down {step}
go run cmd/migrate/main.go down 2
# Run seed data
go run cmd/seed/main.go
# Start http server
air -c cmd/app/.air.toml
# Check lint
make lint
# Go generate mock, something
make go-gen
# Check Unit test
make test
# Check with CURL
curl -X POST 'localhost:8080/api/register' \
-H 'accept: application/json' \
-H 'content-type: application/json' \
-d '{
"email": "[email protected]",
"password" : "password",
"role_id": 1,
"name": "user"
}'
curl -X POST 'localhost:8080/api/login' \
-H 'accept: application/json' \
-H 'content-type: application/json' \
-d '{
"email": "[email protected]",
"password" : "password"
}'
If you need to generate a code base like this architecture, you can use go-base-gen tool. You can read more about the tool at README
NAME:
go-base-gen - Use this tool to generate base code
USAGE:
go-base-gen [global options] command [command options] [arguments...]
VERSION:
v1.0.10
COMMANDS:
project Generate base code for go project use clean architecture
domain Create new domain in project
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--help, -h show help
--version, -v print only the version (default: false)
The go-base-gen tool was created with the purpose to help developers save their time in creating a project with a clean architecture. The tool will generate the code base for you, you just need to focus on the business logic.
This application is divided into 2 layers, internal and pkg:
- Internal is Business logic
- Pkg is tools (logs, database, utils,...)
The communication between layers
flowchart LR
ex[External]
de[Delivery]
uc[Usecase]
rp[Repository]
ps[Pubsub]
ot[Other]
db[(Database)]
subgraph in [Internal]
direction TB
de -.->|Interface / Domain| uc -.->|Interface / Domain| ps & ot & rp
end
ex -.->|DTO|in -.->|DAO|db
For Internal application use 4 layers:
Entities / domain is the most inner layer of the onion architecture. It is a struct for data that will be used by communication between layers.
Entities are simple data structures:
// Path internal/domain/role.go
// Role entity
type Role struct {
ID uint `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
CreatedAt time.Time `json:"created_at"`
}
A repository is an abstract storage (database) that business logic works with. Layer responsibility will choose DB use in application
// RoleRepository represent the role's usecases
type RoleRepository interface {
Fetch(context.Context) ([]Role, error)
}
This layer contains application specific business rules. This a layer decide repository, service, other use in application.
// RoleUsecase represent the role's repository contract
type RoleUsecase interface {
Fetch(context.Context) ([]Role, error)
}
This a layer will decide how the data present. Could be REST API, HTML, or gRPC whatever the decide type.
// Path: internal/modules/role/delivery/http
// roleHandler represent the httphandler
type roleHandler struct {
Usecase domain.RoleUsecase
}
// NewHandler will initialize the roles/ resources endpoint
func NewHandler(e *echo.Echo, uc domain.RoleUsecase) {
handler := &roleHandler{
Usecase: uc,
}
g := e.Group("/api")
g.GET("/roles", handler.Index)
}
// Index will fetch data
func (hl *roleHandler) Index(c echo.Context) error {
ctx := c.Request().Context()
roles, _ := hl.Usecase.Fetch(ctx)
return c.JSON(http.StatusOK, roles)
}