Reference implementation of the layered Go microservice architecture described in How I Structure Go Microservices (And Why Every Layer Earns Its Place).
my-service/
├── cmd/
│ ├── server/main.go ← starts the gRPC server
│ └── cli/main.go ← cobra CLI entry point
├── internal/
│ ├── service.go ← Config + App wired in one place
│ ├── model/ ← pure domain structs (bun tags)
│ ├── dto/ ← request/response structs + mappers
│ ├── service/ ← business logic interfaces + implementations
│ ├── repository/ ← data-access interfaces
│ │ └── postgres/ ← bun/postgres implementations
│ ├── handler/grpc/ ← gRPC handlers (thinnest possible layer)
│ ├── database/ ← DB factory (bun + pgdriver)
│ └── console/ ← cobra commands (self-registering via init())
├── pb/ ← proto stubs (replace with protoc output)
└── proto/
└── product.proto
Handler → Service → Repository → Model
Dependencies flow inward. The model has no HTTP knowledge. The repository has no gRPC knowledge. The service has no database knowledge. Swap any layer without touching the others.
-
Copy
.env.exampleto.envand fill inDATABASE_URL. -
Start the gRPC server:
go run ./cmd/server
-
Use the CLI:
go run ./cmd/cli migrate up go run ./cmd/cli migrate status
The pb/ package contains hand-written stubs so the repo compiles without a
protoc toolchain. Replace it with generated code:
# with buf
buf generate
# or with protoc directly
protoc --go_out=. --go-grpc_out=. proto/product.proto| Variable | Default | Description |
|---|---|---|
APP_PORT |
8080 |
gRPC gateway (external clients) |
GRPC_PORT |
9090 |
Internal gRPC server |
DATABASE_URL |
— | Postgres connection string (required) |
ORM_DRIVER |
bun |
ORM driver (bun) |
DEBUG |
false |
Enable debug logging |