Clean Architecture template for Golang services
The purpose of the template is to show:
Using the principles of Robert Martin (aka Uncle Bob).
Go-clean-template is created & supported by Evrone.
This template implements four types of servers:
The template includes three domains to demonstrate multi-service architecture:
All domains are available across all four transports (REST, gRPC, AMQP RPC, NATS RPC).
The template includes three fully implemented domains, each available across all four transports (REST, gRPC, AMQP RPC, NATS RPC).
Registration, login, and JWT-based authorization.
| Operation | REST | gRPC |
|---|---|---|
| Register | POST /v1/auth/register |
AuthService/Register |
| Login | POST /v1/auth/login |
AuthService/Login |
| Get profile | GET /v1/user/profile |
AuthService/GetProfile |
CRUD operations with a status state machine.
| Operation | REST | gRPC |
|---|---|---|
| Create | POST /v1/tasks |
TaskService/CreateTask |
| List | GET /v1/tasks |
TaskService/ListTasks |
| Get | GET /v1/tasks/:id |
TaskService/GetTask |
| Update | PUT /v1/tasks/:id |
TaskService/UpdateTask |
| Transition | PATCH /v1/tasks/:id/status |
TaskService/TransitionTask |
| Delete | DELETE /v1/tasks/:id |
TaskService/DeleteTask |
todo → in_progress → done (and in_progress → todo)limit/offset and optional status filterText translation via external API with history tracking.
| Operation | REST | gRPC |
|---|---|---|
| Translate | POST /v1/translation/do-translate |
TranslationHistoryService/DoTranslate |
| History | GET /v1/translation/history |
TranslationHistoryService/ShowHistory |
# Postgres, RabbitMQ, NATS
make compose-up
# Run app with migrations
make run
# DB, app + migrations, integration tests
make compose-up-integration-test
make compose-up-all
Check services:
amqp://guest:guest@127.0.0.1:5672/rpc_clientrpc_servernats://guest:guest@127.0.0.1:4222/rpc_servertcp://grpc.lvh.me:8081 | tcp://127.0.0.1:8081postgres://user:myAwEsOm3pa55@w0rd@127.0.0.1:5432/dbguest / guestguest / guestcmd/app/main.goConfiguration and logger initialization. Then the main function "continues" in
internal/app/app.go.
configThe twelve-factor app stores config in environment variables (often shortened to env vars or env). Env vars are easy
to change between deploys without changing any code; unlike config files, there is little chance of them being checked
into the code repo accidentally; and unlike custom config files, or other config mechanisms such as Java System
Properties, they are a language- and OS-agnostic standard.
Config: config.go
Example: .env.example
docker-compose.yml uses env variables to configure services.
docsSwagger documentation. Auto-generated by swag library. You don't need to correct anything by yourself.
docs/protoProtobuf files. They are used to generate Go code for gRPC services. The proto files are also used to generate documentation for gRPC services. You don't need to correct anything by yourself.
integration-testIntegration tests. They are launched as a separate container, next to the application container.
internal/appThere is always one Run function in the app.go file, which "continues" the main function.
This is where all the main objects are created. Dependency injection occurs through the "New ..." constructors (see Dependency Injection). This technique allows us to layer the application using the Dependency Injection principle. This makes the business logic independent from other layers.
Next, we start the server and wait for signals in select for graceful completion.
If app.go starts to grow, you can split it into multiple files.
For a large number of injections, wire can be used.
The migrate.go file is used for database auto migrations.
It is included if an argument with the migrate tag is specified.
For example:
go run -tags migrate ./cmd/app
internal/controllerServer handler layer (MVC controllers). The template shows 4 servers:
Server routers are written in the same style:
internal/controller/amqp_rpcSimple RPC versioning.
For v2, we will need to add the amqp_rpc/v2 folder with the same content.
And in the file internal/controller/amqp_rpc/router.go add the line:
routes := make(map[string]server.CallHandler)
{
v1.NewRoutes(routes, t, u, tk, j, l)
}
{
v2.NewTranslationRoutes(routes, t, l)
}
internal/controller/grpcSimple gRPC versioning.
For v2, we will need to add the grpc/v2 folder with the same content.
Also add the v2 folder to the proto files in docs/proto.
And in the file internal/controller/grpc/router.go add the line:
{
v1.NewAuthRoutes(app, u, l)
v1.NewTaskRoutes(app, tk, l)
v1.NewTranslationRoutes(app, t, l)
}
{
v2.NewAuthRoutes(app, u, l)
v2.NewTaskRoutes(app, tk, l)
v2.NewTranslationRoutes(app, t, l)
}
reflection.Register(app)
internal/controller/nats_rpcSimple RPC versioning.
For v2, we will need to add the nats_rpc/v2 folder with the same content.
And in the file internal/controller/nats_rpc/router.go add the line:
routes := make(map[string]server.CallHandler)
{
v1.NewRoutes(routes, t, u, tk, j, l)
}
{
v2.NewTranslationRoutes(routes, t, l)
}
internal/controller/restapiSimple REST versioning.
For v2, we will need to add the restapi/v2 folder with the same content.
And in the file internal/controller/restapi/router.go add the line:
apiV1Group := app.Group("/v1")
{
v1.NewRoutes(apiV1Group, t, u, tk, jwtManager, l)
}
apiV2Group := app.Group("/v2")
{
v2.NewRoutes(apiV2Group, t, u, tk, jwtManager, l)
}
Instead of Fiber, you can use any other http framework.
In router.go and above the handler methods, there are comments for generating swagger documentation
using swag.
internal/entityEntities of business logic (models) can be used in any layer. There can also be methods, for example, for validation.
internal/usecaseBusiness logic.
Repositories, webapi, rpc, and other business logic structures are injected into business logic structures (see Dependency Injection).
internal/repo/persistentA repository is an abstract storage (database) that business logic works with.
internal/repo/webapiIt is an abstract web API that business logic works with. For example, it could be another microservice that business logic accesses via the REST API. The package name changes depending on the purpose.
pkg/rabbitmqRabbitMQ RPC pattern:
In order to remove the dependence of business logic on external packages, dependency injection is used.
For example, through the New constructor, we inject the dependency into the structure of the business logic.
This makes the business logic independent (and portable).
We can override the implementation of the interface without making changes to the usecase package.
package usecase
import (
// Nothing!
)
type Repository interface {
Get()
}
type UseCase struct {
repo Repository
}
func New(r Repository) *UseCase {
return &UseCase{
repo: r,
}
}
func (uc *UseCase) Do() {
uc.repo.Get()
}
It will also allow us to do auto-generation of mocks (for example with go.uber.org/mock) and easily write unit tests.
We are not tied to specific implementations in order to always be able to change one component to another. If the new component implements the interface, nothing needs to be changed in the business logic.
Programmers realize the optimal architecture for an application after most of the code has been written.
A good architecture allows decisions to be delayed to as late as possible.
Dependency Inversion (the same one from SOLID) is the principle of dependency injection. The direction of dependencies goes from the outer layer to the inner layer. Due to this, business logic and entities remain independent from other parts of the system.
So, the ap
$ claude mcp add go-clean-template \
-- python -m otcore.mcp_server <graph>