golang-ddd
npx skills add https://github.com/baotoq/agent-skills --skill golang-ddd
Agent 安装分布
Skill 文档
DDD Tactical Design Patterns in Go
Purpose
To guide implementation of Domain-Driven Design tactical patterns in idiomatic Go. This skill covers Entities, Value Objects, Aggregates, Repositories, Domain Services, Domain Events, Factories, and Specifications using Go-native idioms (composition over inheritance, interfaces, unexported fields, functional options).
When to Use This Skill
- Implementing domain models with rich business logic in Go
- Designing aggregate boundaries and consistency rules
- Creating repository interfaces and infrastructure implementations
- Building event-driven domain models
- Structuring a Go project following DDD layered architecture
- Reviewing domain code for DDD pattern adherence
Core Principles
- Go idioms first – No Java-style OOP. Use composition, interfaces, and package boundaries
- Unexported fields – All entity/aggregate fields lowercase; expose through getters and behavior methods
- Pointer receivers for entities – Mutable domain objects use
*Treceivers - Value receivers for value objects – Immutable types use
Treceivers - Factory functions – Use
NewX()constructors to enforce invariants at creation - Interface in domain, implementation in infrastructure – Repository interfaces live with aggregates
- One package per aggregate – Each aggregate root gets its own package under
internal/domain/ - Context propagation – Pass
context.Contextas first parameter in repository and service methods
Project Structure
internal/
âââ domain/ # Domain layer (no external deps)
â âââ customer/ # One package per aggregate
â â âââ customer.go # Aggregate root entity
â â âââ repository.go # Repository interface
â â âââ email.go # Value objects
â â âââ events.go # Domain events
â â âââ errors.go # Domain errors
â âââ order/
â â âââ order.go
â â âââ repository.go
â â âââ item.go # Child entity
â â âââ money.go # Value object
â âââ shared/ # Shared kernel
â âââ events.go # Event interface
â âââ specification.go # Generic specification
â
âââ application/ # Application services (orchestration)
â âââ command/
â â âââ place_order.go
â âââ query/
â âââ get_customer.go
â
âââ infrastructure/ # Technical implementations
âââ postgres/
â âââ customer_repo.go
â âââ order_repo.go
âââ eventbus/
âââ in_memory.go
Dependency rule: Domain has zero imports from application or infrastructure. Dependencies point inward.
Pattern Quick Reference
| Pattern | Go Idiom | Receiver | Identity |
|---|---|---|---|
| Entity | Struct + pointer receiver | *T |
By ID |
| Value Object | Type alias or struct + value receiver | T |
By value |
| Aggregate Root | Entity + unexported children | *T |
By ID |
| Repository | Interface in domain package | N/A | N/A |
| Domain Service | Stateless struct with deps | *T or func |
N/A |
| Domain Event | Immutable struct | T (value) |
By name+time |
| Factory | NewX() function |
N/A | N/A |
| Specification | Generic interface IsSatisfiedBy(T) bool |
T or *T |
N/A |
Implementation Workflow
When implementing a new aggregate or domain concept:
- Define value objects – Create self-validating types for domain primitives (
Email,Money,Address) - Define entities – Create types with identity, unexported fields, and behavior methods
- Define aggregate root – Designate one entity as root; enforce all invariants through its methods
- Define repository interface – Place interface in same package as aggregate root
- Define domain events – Create immutable event structs for significant state changes
- Implement infrastructure – Create repository implementations in
infrastructure/package - Wire application layer – Create command/query handlers that orchestrate domain operations
Pattern Details
For detailed implementation guides with full code examples, see:
references/entities-and-value-objects.md– Entities, Value Objects, and Factoriesreferences/aggregates-and-repositories.md– Aggregates, Repositories, and Domain Servicesreferences/events-and-specifications.md– Domain Events and Specificationsreferences/anti-patterns.md– Common mistakes and how to avoid them
Entities
Entities have unique identity and mutable state. Use unexported fields, pointer receivers, and factory functions.
type Order struct {
id uuid.UUID
status Status
items []Item
createdAt time.Time
}
func NewOrder(customerID uuid.UUID) (*Order, error) {
return &Order{
id: uuid.New(),
status: StatusDraft,
items: make([]Item, 0),
createdAt: time.Now(),
}, nil
}
func (o *Order) AddItem(product ProductID, qty int, price Money) error {
if o.status != StatusDraft {
return ErrOrderNotDraft
}
o.items = append(o.items, NewItem(product, qty, price))
return nil
}
Value Objects
Immutable types validated at creation. Use value receivers. Return new instances for operations.
type Money struct {
amount int64
currency string
}
func NewMoney(amount int64, currency string) (Money, error) {
if currency == "" {
return Money{}, ErrInvalidCurrency
}
return Money{amount: amount, currency: currency}, nil
}
func (m Money) Add(other Money) (Money, error) {
if m.currency != other.currency {
return Money{}, ErrCurrencyMismatch
}
return Money{amount: m.amount + other.amount, currency: m.currency}, nil
}
Aggregates
Aggregate roots enforce invariants across child entities. All mutations go through the root.
func (o *Order) Place() error {
if len(o.items) == 0 {
return ErrEmptyOrder
}
if o.status != StatusDraft {
return ErrOrderNotDraft
}
o.status = StatusPlaced
o.events = append(o.events, NewOrderPlacedEvent(o.id, o.Total()))
return nil
}
Repositories
Interface in domain, implementation in infrastructure. One repository per aggregate root.
// domain/order/repository.go
type Repository interface {
Find(ctx context.Context, id uuid.UUID) (*Order, error)
Save(ctx context.Context, order *Order) error
Update(ctx context.Context, id uuid.UUID, fn func(*Order) error) error
}
Domain Events
Immutable structs collected by aggregates, published by application layer.
type OrderPlaced struct {
orderID uuid.UUID
total Money
occurredAt time.Time
}
func (o *Order) PullEvents() []Event {
events := o.events
o.events = nil
return events
}
Domain Services
Stateless operations spanning multiple aggregates. Domain logic only, no orchestration.
type TransferService struct {
accountRepo account.Repository
}
func (s *TransferService) Transfer(ctx context.Context, from, to uuid.UUID, amount Money) error {
// Load aggregates, validate domain rules, coordinate changes
}
Specifications
Composable business rules using Go generics.
type Specification[T any] interface {
IsSatisfiedBy(T) bool
}
func And[T any](specs ...Specification[T]) Specification[T] { /* ... */ }
func Or[T any](specs ...Specification[T]) Specification[T] { /* ... */ }
func Not[T any](spec Specification[T]) Specification[T] { /* ... */ }
Key Rules
- Never expose aggregate internals – No public fields, no getters that return mutable child collections
- No setters – Replace
SetStatus()with domain methods likePlace(),Cancel(),Ship() - Reference other aggregates by ID – Never hold direct pointers to other aggregate roots
- Reconstitution factories – Create separate
Reconstruct()functions for loading from DB (bypass validation) - Domain errors – Define sentinel errors (
var ErrNotFound = errors.New(...)) per aggregate package - Accept interfaces, return structs – Repository parameters use interfaces; factories return concrete types
References
Detailed guides with full code examples are in the references/ directory.