DDD Go Backend Service
Build production-ready Go backend services following a DDD-layered architecture with OpenTelemetry observability, OAuth2 authentication, and GORM-based persistence.
Quick Start
1. Scaffold the project
Follow the project structure to create the directory layout.
2. Initialize Go module
go mod init github.com/yourorg/yourproject
3. Wire up the core infrastructure
See the individual guides below, in order:
- Config — Viper-based YAML configuration
- Database — GORM with MySQL/PostgreSQL
- Storage — S3/MinIO object storage
- Auth — JWT + OAuth2 multi-provider auth
- Observability — OTel tracing + Jaeger + Zap
- Architecture — DDD layering (domain → repo → service → handler)
- Middleware — Gin middleware stack
- API — Route registration, request/response format, error handling
Directory Map
project/
├── cmd/server/main.go # Entry point + manual DI wiring
├── internal/
│ ├── common/ # Base entity, errors, response helpers
│ ├── <module>/
│ │ ├── domain/ # Entity structs + business methods
│ │ ├── repo/ # Data access (GORM queries)
│ │ ├── service/ # Business logic
│ │ └── handler/ # HTTP handlers + route registration
│ └── ...
├── pkg/
│ ├── config/ # Viper config loader
│ ├── database/ # GORM MySQL/PostgreSQL init
│ ├── logger/ # Zap structured logger
│ ├── redis/ # Redis client init
│ ├── storage/ # S3/MinIO client init
│ └── trace/ # OpenTelemetry OTLP init
├── migrations/ # Raw SQL migrations for production
├── deployment/ # K8s manifests, observability stack
└── config.yaml # Runtime config (gitignored)
Typical Workflow: Adding a New Module
- Define domain entities in
internal/<module>/domain/ - Write repo in
internal/<module>/repo/(GORM queries) - Write service in
internal/<module>/service/(business logic, calls repo) - Write handler in
internal/<module>/handler/(parse request → call service → respond) - Register routes in handler's
RegisterRoutes(r *gin.RouterGroup)method - Wire everything in
cmd/server/main.go: create repo → create service → create handler → register routes
Usage Examples
See the examples field in the frontmatter above.
Key Conventions
- Concrete types, not interfaces — repos and services are structs
- Manual DI — all wiring in
main.go, no wire/dig framework - Unified response format:
{ "code": 0, "message": "ok", "data": {...} }— code 0 = success - AppError: custom error type with numeric Code, Message, HTTPStatus
- Error code ranges: 10000=generic, 20000=auth, 30000=subscription, 40000=admin
- GORM AutoMigrate for dev, raw SQL migrations in
migrations/for production - No ORM in domain — domain entities use plain structs; DB mapping lives in repo layer