golang-ddd
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDDD Tactical Design Patterns in Go
Go语言中的DDD战术设计模式
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).
指导你使用Go语言的惯用风格实现领域驱动设计(DDD)的战术模式。本技能涵盖实体、值对象、聚合、仓库、领域服务、领域事件、工厂和规约模式,采用Go原生的惯用写法(组合优于继承、接口、未导出字段、函数式选项)。
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
- 在Go中实现包含丰富业务逻辑的领域模型
- 设计聚合边界与一致性规则
- 创建仓库接口及基础设施实现
- 构建事件驱动的领域模型
- 遵循DDD分层架构搭建Go项目结构
- 评审领域代码是否符合DDD模式规范
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 receivers
*T - Value receivers for value objects - Immutable types use receivers
T - Factory functions - Use constructors to enforce invariants at creation
NewX() - 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 as first parameter in repository and service methods
context.Context
- 优先遵循Go语言惯用风格 - 不使用Java风格的面向对象,采用组合、接口和包边界实现
- 未导出字段 - 所有实体/聚合的字段使用小写命名;通过访问器方法和行为方法暴露功能
- 实体使用指针接收器 - 可变领域对象使用类型接收器
*T - 值对象使用值接收器 - 不可变类型使用类型接收器
T - 工厂函数 - 使用构造函数在创建时强制校验不变量
NewX() - 接口定义在领域层,实现放在基础设施层 - 仓库接口与聚合放在同一包中
- 每个聚合对应一个包 - 每个聚合根在下拥有独立的包
internal/domain/ - 上下文传递 - 在仓库和服务方法中,将作为第一个参数
context.Context
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.goDependency rule: Domain has zero imports from application or infrastructure. Dependencies point inward.
internal/
├── domain/ # 领域层(无外部依赖)
│ ├── customer/ # 每个聚合对应一个包
│ │ ├── customer.go # 聚合根实体
│ │ ├── repository.go # 仓库接口
│ │ ├── email.go # 值对象
│ │ ├── events.go # 领域事件
│ │ └── errors.go # 领域错误
│ ├── order/
│ │ ├── order.go
│ │ ├── repository.go
│ │ ├── item.go # 子实体
│ │ └── money.go # 值对象
│ └── shared/ # 共享内核
│ ├── events.go # 事件接口
│ └── specification.go # 通用规约
│
├── application/ # 应用服务层(编排逻辑)
│ ├── command/
│ │ └── place_order.go
│ └── query/
│ └── get_customer.go
│
└── infrastructure/ # 技术实现层
├── postgres/
│ ├── customer_repo.go
│ └── order_repo.go
└── eventbus/
└── in_memory.go依赖规则: 领域层不导入应用层或基础设施层的任何内容,依赖关系向内指向领域层。
Pattern Quick Reference
模式速查表
| Pattern | Go Idiom | Receiver | Identity |
|---|---|---|---|
| Entity | Struct + pointer receiver | | By ID |
| Value Object | Type alias or struct + value receiver | | By value |
| Aggregate Root | Entity + unexported children | | By ID |
| Repository | Interface in domain package | N/A | N/A |
| Domain Service | Stateless struct with deps | | N/A |
| Domain Event | Immutable struct | | By name+time |
| Factory | | N/A | N/A |
| Specification | Generic interface | | N/A |
| 模式 | Go惯用写法 | 接收器类型 | 标识方式 |
|---|---|---|---|
| 实体 | 结构体 + 指针接收器 | | 通过ID |
| 值对象 | 类型别名或结构体 + 值接收器 | | 通过值内容 |
| 聚合根 | 实体 + 未导出子对象 | | 通过ID |
| 仓库 | 接口定义在领域包中 | N/A | N/A |
| 领域服务 | 无状态结构体(包含依赖) | | N/A |
| 领域事件 | 不可变结构体 | | 通过名称+时间 |
| 工厂 | | N/A | N/A |
| 规约 | 泛型接口 | | 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 package
infrastructure/ - Wire application layer - Create command/query handlers that orchestrate domain operations
当实现新的聚合或领域概念时:
- 定义值对象 - 为领域原语创建自验证类型(如、
Email、Money)Address - 定义实体 - 创建包含标识、未导出字段和行为方法的类型
- 定义聚合根 - 指定一个实体作为根;通过其方法强制校验所有不变量
- 定义仓库接口 - 将接口放在与聚合根相同的包中
- 定义领域事件 - 为重要的状态变更创建不可变的事件结构体
- 实现基础设施层 - 在包中创建仓库的具体实现
infrastructure/ - 组装应用层 - 创建命令/查询处理器,编排领域操作
Pattern Details
模式细节
For detailed implementation guides with full code examples, see:
- - Entities, Value Objects, and Factories
references/entities-and-value-objects.md - - Aggregates, Repositories, and Domain Services
references/aggregates-and-repositories.md - - Domain Events and Specifications
references/events-and-specifications.md - - Common mistakes and how to avoid them
references/anti-patterns.md
如需包含完整代码示例的详细实现指南,请查看:
- - 实体、值对象与工厂
references/entities-and-value-objects.md - - 聚合、仓库与领域服务
references/aggregates-and-repositories.md - - 领域事件与规约
references/events-and-specifications.md - - 常见错误及规避方法
references/anti-patterns.md
Entities
实体
Entities have unique identity and mutable state. Use unexported fields, pointer receivers, and factory functions.
go
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
}实体拥有唯一标识和可变状态。使用未导出字段、指针接收器和工厂函数。
go
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.
go
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
}创建时完成验证的不可变类型。使用值接收器,操作返回新实例。
go
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.
go
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
}聚合根强制校验子实体的所有不变量,所有修改操作必须通过根实体完成。
go
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.
go
// 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
}接口定义在领域层,实现放在基础设施层。每个聚合根对应一个仓库。
go
// 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.
go
type OrderPlaced struct {
orderID uuid.UUID
total Money
occurredAt time.Time
}
func (o *Order) PullEvents() []Event {
events := o.events
o.events = nil
return events
}由聚合收集的不可变结构体,由应用层发布。
go
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.
go
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
}跨多个聚合的无状态操作,仅包含领域逻辑,不负责编排。
go
type TransferService struct {
accountRepo account.Repository
}
func (s *TransferService) Transfer(ctx context.Context, from, to uuid.UUID, amount Money) error {
// 加载聚合、校验领域规则、协调变更
}Specifications
规约
Composable business rules using Go generics.
go
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] { /* ... */ }使用Go泛型实现的可组合业务规则。
go
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 with domain methods like
SetStatus(),Place(),Cancel()Ship() - Reference other aggregates by ID - Never hold direct pointers to other aggregate roots
- Reconstitution factories - Create separate functions for loading from DB (bypass validation)
Reconstruct() - Domain errors - Define sentinel errors () per aggregate package
var ErrNotFound = errors.New(...) - Accept interfaces, return structs - Repository parameters use interfaces; factories return concrete types
- 绝不暴露聚合内部细节 - 无公共字段,无返回可变子集合的访问器
- 不使用Setter方法 - 用、
Place()、Cancel()等领域方法替代Ship()SetStatus() - 通过ID引用其他聚合 - 绝不直接持有其他聚合根的指针
- 重构工厂 - 创建独立的函数用于从数据库加载(跳过验证)
Reconstruct() - 领域错误 - 每个聚合包中定义哨兵错误(如)
var ErrNotFound = errors.New(...) - 依赖接口,返回结构体 - 仓库参数使用接口;工厂返回具体类型
References
参考资料
Detailed guides with full code examples are in the directory.
references/包含完整代码示例的详细指南位于目录下。
references/