A production-ready implementation of Hexagonal Architecture (Ports & Adapters) in Go, featuring multi-tenant domains, event-driven communication, and comprehensive observability.
This project demonstrates true hexagonal architecture with:
- Framework-agnostic domains: Business logic independent of HTTP frameworks, databases, or messaging systems
- Ports & Adapters: Clear separation between domain and infrastructure via interfaces
- Domain-Driven Design: Rich domain models with aggregates, value objects, and domain events
- Dependency Inversion: All dependencies point inward toward the domain
- Event-Driven Architecture: Loosely coupled domains communicating via events
hexagonal-go/
├── cmd/
│ ├── api/ # HTTP API application
│ │ ├── config/ # Application configuration
│ │ ├── main.go # Entry point
│ │ ├── app.go # App struct & dependencies
│ │ ├── router.go # HTTP routing
│ │ └── wire.go # Dependency injection
│ └── worker/ # Background worker application
│ ├── config/ # Worker configuration
│ └── main.go # Entry point
├── internal/ # Private application code
│ ├── identity/ # Identity domain
│ │ ├── domain/ # Aggregates, entities, value objects
│ │ │ ├── user/ # User aggregate
│ │ │ ├── session/ # Session aggregate
│ │ │ ├── auth/ # Authentication value objects
│ │ │ └── oauth/ # OAuth entities
│ │ ├── application/ # Use cases
│ │ │ ├── command/ # Write operations
│ │ │ ├── query/ # Read operations
│ │ │ └── dto/ # Data transfer objects
│ │ ├── infrastructure/ # Adapters
│ │ │ └── repository/ # Database implementations
│ │ └── interface/ # Delivery mechanisms
│ │ └── http/v1/ # HTTP handlers
│ ├── tenant/ # Multi-tenancy domain
│ │ ├── domain/ # Tenant aggregate, plans, settings
│ │ ├── application/ # Commands & queries
│ │ ├── infrastructure/ # Repository implementations
│ │ └── interface/ # HTTP handlers
│ ├── audit/ # Audit logging domain
│ │ ├── domain/ # Audit entry aggregate
│ │ ├── application/ # Event subscriber
│ │ └── infrastructure/ # Repository
│ ├── notifications/ # Notifications domain
│ │ ├── domain/ # Notification aggregate
│ │ ├── application/ # Event handlers
│ │ └── infrastructure/ # Repository
│ └── email/ # Email template domain
│ ├── domain/ # Template aggregate
│ ├── application/ # CRUD commands & queries
│ ├── infrastructure/ # Repository
│ └── interface/ # HTTP handlers
├── pkg/ # Shared infrastructure packages
│ ├── config/ # Configuration loading
│ ├── database/ # Database abstractions
│ │ └── postgres/ # PostgreSQL implementation
│ ├── cache/ # Caching abstractions
│ ├── email/ # Email sending & rendering
│ ├── errors/ # Structured error handling
│ ├── http/ # HTTP utilities
│ │ ├── middleware/ # Auth, logging, metrics
│ │ └── response/ # Response helpers
│ ├── messaging/ # Event bus & pub/sub
│ ├── oauth/ # OAuth providers
│ │ ├── google/ # Google OAuth
│ │ └── github/ # GitHub OAuth
│ ├── observability/ # Observability stack
│ │ ├── logger/ # Structured logging
│ │ ├── metrics/ # Prometheus metrics
│ │ └── tracing/ # OpenTelemetry tracing
│ ├── provider/ # Infrastructure providers (Wire)
│ ├── security/ # Security utilities
│ │ └── jwt/ # JWT service
│ ├── storage/ # File storage abstractions
│ └── types/ # Common types (ID, Timestamp)
├── migrations/ # Database migrations
├── deployments/
│ └── docker/ # Docker Compose for local dev
└── docs/
└── swagger/ # OpenAPI documentation
- Go 1.22+
- Docker & Docker Compose
- Wire (for dependency injection):
go install github.com/google/wire/cmd/wire@latest - Swag (for API docs):
go install github.com/swaggo/swag/cmd/swag@latest
# Clone the repository
git clone https://github.com/0xsj/hexagonal-go.git
cd hexagonal-go
# Install dependencies
go mod download
# Start infrastructure
docker-compose -f deployments/docker/docker-compose.yml up -d
# Run database migrations
go run cmd/migrate/main.go up
# Generate Wire dependencies
go generate ./...
# Generate Swagger docs
swag init -g cmd/api/main.go -o docs/swagger
# Run the API
go run cmd/api/main.go# Database
DB_HOST=localhost
DB_PORT=5432
DB_USER=hexagonal
DB_PASSWORD=hexagonal_dev
DB_NAME=hexagonal_db
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
# JWT
JWT_SECRET=your-secret-key-min-32-chars
JWT_EXPIRES_IN=1h
# OAuth (optional)
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
# Email (SMTP)
SMTP_HOST=localhost
SMTP_PORT=1025
EMAIL_FROM_ADDRESS=noreply@example.com
EMAIL_FROM_NAME=Hexagonal App
# Server
SERVER_HOST=0.0.0.0
SERVER_PORT=8080
# Observability
LOG_LEVEL=debug
LOG_FORMAT=json
METRICS_ENABLED=true
TRACING_ENABLED=true
TRACING_ENDPOINT=http://localhost:4318User management, authentication, and session handling.
Features:
- User registration with email verification
- Password-based authentication
- OAuth login (Google, GitHub)
- JWT access tokens with refresh token rotation
- Session management with optional Redis caching
- Role-based access (user, admin, moderator)
- Account suspension/reactivation
Endpoints:
POST /api/v1/auth/register- Register new userPOST /api/v1/auth/login- Login with credentialsPOST /api/v1/auth/logout- Logout current sessionPOST /api/v1/auth/refresh- Refresh access tokenGET /api/v1/auth/me- Get current userGET /api/v1/auth/oauth/{provider}- OAuth login redirectGET /api/v1/auth/oauth/{provider}/callback- OAuth callback
Organization/workspace management with subscription plans.
Features:
- Tenant lifecycle (create, suspend, reactivate, cancel, delete)
- Subscription plans (free, starter, pro, enterprise)
- Tenant-specific settings
- Owner assignment
- Billing integration support
Status Transitions:
trialing → active, suspended, cancelled, deleted
active → suspended, cancelled, deleted
suspended → active, cancelled, deleted
cancelled → deleted
deleted → (terminal)
Endpoints:
POST /api/v1/tenants- Create tenantGET /api/v1/tenants/{id}- Get tenantPUT /api/v1/tenants/{id}- Update tenantPUT /api/v1/tenants/{id}/settings- Update settingsPOST /api/v1/tenants/{id}/plan- Change planPOST /api/v1/tenants/{id}/suspend- Suspend tenantPOST /api/v1/tenants/{id}/reactivate- Reactivate tenantDELETE /api/v1/tenants/{id}- Delete tenant
Automatic audit trail of all domain events.
Features:
- Subscribes to all domain events
- Records actor, action, resource, and context
- Supports filtering by tenant, user, event type, time range
- Stores event payload and metadata as JSONB
Event-driven notification delivery.
Features:
- Reacts to domain events (e.g., user registered)
- Multi-channel support (email, SMS, push)
- Template-based content
Manageable email templates with localization.
Features:
- CRUD operations for templates
- Multi-locale support
- Template variables with validation
- Status lifecycle (draft, active, archived, deleted)
- Preview rendering with sample data
Endpoints:
POST /api/v1/email/templates- Create templateGET /api/v1/email/templates- List templatesGET /api/v1/email/templates/{id}- Get templateGET /api/v1/email/templates/by-slug- Get by slugPUT /api/v1/email/templates/{id}- Update templatePOST /api/v1/email/templates/{id}/activate- ActivatePOST /api/v1/email/templates/{id}/archive- ArchiveDELETE /api/v1/email/templates/{id}- DeletePOST /api/v1/email/templates/{id}/preview- Preview
Dependencies only point inward. Domain layer has zero external dependencies.
Interface → Application → Domain
↓ ↓
Infrastructure (implements domain ports)
Ports (interfaces in domain):
// domain/user/repository.go
type Repository interface {
Save(ctx context.Context, user *User) error
FindByID(ctx context.Context, id types.ID) (*User, error)
FindByEmail(ctx context.Context, tenantID, email string) (*User, error)
}Adapters (implementations in infrastructure):
// infrastructure/repository/postgres_user_repository.go
type PostgresUserRepository struct {
db database.DB
}
func (r *PostgresUserRepository) Save(ctx context.Context, user *User) error {
// PostgreSQL-specific implementation
}Domains communicate via events, not direct calls:
// Domain emits event
user.Register() // internally calls user.addEvent(NewUserRegistered(...))
// Application publishes after persistence
for _, event := range user.Events() {
publisher.Publish(ctx, event)
}
user.ClearEvents()
// Other domains subscribe
type UserEventSubscriber struct { /* ... */ }
func (s *UserEventSubscriber) Handle(ctx context.Context, event messaging.Event) error {
// React to user.registered event
}Commands (writes) and Queries (reads) are separated:
// Commands mutate state
type RegisterUserCommand struct { /* dependencies */ }
func (c *RegisterUserCommand) Handle(ctx context.Context, req RegisterRequest) (*UserDTO, error)
// Queries read state
type GetUserQuery struct { /* dependencies */ }
func (q *GetUserQuery) Handle(ctx context.Context, userID types.ID) (*UserDTO, error)Rich error types with HTTP/gRPC mapping:
err := errors.NotFound("UserRepository.FindByID", "user")
// Automatically maps to HTTP 404
err := errors.Validation("User.Create", "email", "invalid format")
// Automatically maps to HTTP 400| Component | Technology |
|---|---|
| Language | Go 1.22+ |
| HTTP Router | Chi |
| Database | PostgreSQL |
| Caching | Redis |
| DI Framework | Wire |
| Metrics | Prometheus |
| Tracing | OpenTelemetry + Jaeger |
| Logging | Structured (JSON/Console) |
| API Docs | Swagger/OpenAPI |
| Auth | JWT + OAuth2 |
| SMTP + Mailpit (dev) |
# Run all tests
go test ./...
# Run with coverage
go test -cover ./...
# Run specific domain tests
go test ./internal/identity/...
# Run integration tests (requires Docker)
go test -tags=integration ./...When running locally, Swagger UI is available at:
# Start all infrastructure
docker-compose -f deployments/docker/docker-compose.yml up -d
# Services available:
# - PostgreSQL: localhost:5432
# - Redis: localhost:6379
# - Mailpit: localhost:1025 (SMTP), localhost:8025 (UI)
# - Jaeger: localhost:16686 (UI)
# - Prometheus: localhost:9090
# Stop infrastructure
docker-compose -f deployments/docker/docker-compose.yml down- Create domain structure:
internal/newdomain/
├── domain/ # Aggregates, entities, value objects, events
├── application/
│ ├── command/ # Write use cases
│ ├── query/ # Read use cases
│ └── dto/ # Data transfer objects
├── infrastructure/
│ └── repository/ # Database implementations
└── interface/
└── http/v1/ # HTTP handlers
- Define domain interfaces (ports) in
domain/ - Implement use cases in
application/ - Create adapters in
infrastructure/ - Add HTTP handlers in
interface/ - Create Wire provider set in
provider.go - Add to
cmd/api/wire.go - Add routes to
cmd/api/router.go
MIT