📌 本文将手把手带你用 Go 实现一个在线 Tavern(酒馆)系统,逐步构建 DDD 的核心组件:实体(Entity)、值对象(Value Object)、聚合(Aggregate)、仓储(Repository)、工厂(Factory)和服务(Service),全程配合图解说明,拒绝“概念轰炸”。
🧠 为什么需要 DDD?
微服务虽好,但无组织地拆分服务 = 制造分布式单体 👉 复杂度爆炸💥
DDD 的核心思想是:让代码结构反映业务领域,与领域专家共建「通用语言」(Ubiquitous Language),避免工程师凭空臆想业务逻辑。
✅ 举例:你不会把“常来喝酒的人”叫 Drinker,领域专家会称之为 Customer —— 这就是通用语言的力量。
🛠️ 项目初始化
mkdir ddd-go && cd ddd-go
go mod init github.com/yourname/ddd-go
目录结构预览:
ddd-go/
├── entity/ # 实体:跨域共享的可变身份对象
├── aggregate/ # 聚合根 + 值对象
├── domain/
│ ├── customer/ # 子域:客户管理
│ └── product/ # 子域:商品管理
├── service/ # 应用服务层
└── ...
🔑 核心概念实现
1️⃣ Entity(实体) vs Value Object(值对象)
| 特性 | Entity | Value Object |
|---|---|---|
| 是否有唯一 ID | ✅ 是 | ❌ 否 |
| 状态是否可变 | ✅ 可变 | ❌ 不可变(创建后即冻结) |
| 相等性判断 | ID 相等 | 所有字段相等 |
// entity/person.go
type Person struct {
ID string // ← 唯一标识
Name string
Age int
}
// entity/transaction.go(值对象)
type Transaction struct {
Amount float64
At time.Time
// 无 ID 字段!
}
2️⃣ Aggregate(聚合)
聚合 = 1 个聚合根(根实体) + 若干实体/值对象
✅ 外部只能通过聚合根访问内部对象
✅ 聚合内强一致性,聚合间最终一致性
// aggregate/customer.go
type Customer struct {
person *entity.Person // 根实体(ID 来源)
products []entity.Product
orders []Transaction // 值对象
}
func (c *Customer) ID() string { return c.person.ID } // ← 暴露根 ID
func (c *Customer) ChangeName(name string) { c.person.Name = name }
⚠️ 注意:字段小写 → 包外不可访问;避免在聚合上加
json/bsontag(解耦存储实现)
3️⃣ Factory(工厂)
封装复杂对象创建逻辑,保证聚合初始状态合法:
// aggregate/customer.go
func NewCustomer(name string) (*Customer, error) {
if name == "" {
return nil, errors.New("name is required")
}
id := uuid.New().String()
return &Customer{
person: &entity.Person{ID: id, Name: name},
}, nil
}
✅ 优势:校验前置、ID 自动生成、未来可扩展(如注册事件)
4️⃣ Repository(仓储)
接口定义在 domain 层,实现下沉到 infra 层 —— 实现「依赖倒置」:
// domain/customer/repository.go
type CustomerRepository interface {
Get(id string) (*Customer, error)
Add(c *Customer) error
Update(c *Customer) error
}
内存实现(开发/测试用)
// domain/customer/memory/memory.go
type MemoryRepository struct {
customers map[string]*Customer
mu sync.RWMutex
}
func (m *MemoryRepository) Get(id string) (*Customer, error) {
m.mu.RLock()
defer m.mu.RUnlock()
c, ok := m.customers[id]
if !ok { return nil, ErrCustomerNotFound }
return c, nil
}
MongoDB 实现(生产用)
// domain/customer/mongo/mongo.go
type MongoRepository struct {
coll *mongo.Collection
}
func (m *MongoRepository) Get(id string) (*Customer, error) {
var doc mongoCustomer // ← 内部转换结构,避免污染 aggregate
err := m.coll.FindOne(context.TODO(), bson.M{"_id": id}).Decode(&doc)
if err != nil { return nil, err }
return doc.ToAggregate(), nil // ← 转为领域对象
}
✅ 优势:无缝切换存储引擎!测试用内存,上线换 MongoDB,业务代码零修改。
5️⃣ Service(应用服务)
聚合业务流程,协调多个 Repository:
// service/order.go
type OrderService struct {
customerRepo domain.CustomerRepository
productRepo domain.ProductRepository
}
func NewOrderService(opts ...OrderOption) *OrderService {
s := &OrderService{}
for _, opt := range opts {
opt(s)
}
return s
}
// 配置函数示例(优雅可扩展!)
func WithMemoryCustomerRepository() OrderOption {
return func(s *OrderService) {
s.customerRepo = memory.NewCustomerRepository()
}
}
func (s *OrderService) CreateOrder(customerID string, productIDs []string) (float64, error) {
customer, _ := s.customerRepo.Get(customerID)
// ... 查询商品、扣库存、生成交易 ...
return totalPrice, nil
}
🏁 最终架构:Tavern 服务
// service/tavern.go
type Tavern struct {
orderService *OrderService
// billingService *BillingService // ← 未来可扩展
}
func NewTavern(orderSvc *OrderService) *Tavern {
return &Tavern{orderService: orderSvc}
}
func (t *Tavern) Serve(customerName string, productNames []string) error {
customer, _ := t.orderService.RegisterCustomer(customerName)
_, err := t.orderService.CreateOrder(customer.ID(), productNames)
return err
}
✅ 收获
| DDD 概念 | Go 实现要点 |
|---|---|
| 实体 | 有 ID、可变状态、包级可见 |
| 值对象 | 无 ID、不可变、用值语义 |
| 聚合 | 封装+不变性保障,仅暴露根 ID |
| 仓储接口 | 定义在 domain,实现下沉 |
| 工厂 | 集中创建逻辑 + 校验前置 |
| 服务 | 编排 Repository,无状态 |
💡 记住:DDD 是一种思维方式,不是语法规范。
重点在于 与领域专家共建模型,而非纠结entity还是model文件夹命名 😄
