1. 三层的问题
第 5 篇的三层(controller/service/repo)容易写成贫血模型:service 里全是 getter/setter 搬运,业务规则散落各处,改一个规则要翻好几个文件。DDD 把规则收进领域对象。
2. 实体和值对象
领域对象自己带行为,不是纯数据:
type Order struct { ID string Items []OrderItem Status OrderStatus } func (o *Order) Total() int { sum := 0 for _, it := range o.Items { sum += it.Price * it.Qty } return sum } func (o *Order) Pay() error { if o.Status != Pending { return ErrAlreadyPaid } o.Status = Paid return nil }
Total 和 Pay 是订单自己的事,放订单上,谁改金额逻辑都不会漏。
3. 聚合根
一组对象当整体一致性边界,只暴露聚合根。订单和订单项是聚合,外部通过 Order 改 Item,不能直接改 item:
func (o *Order) AddItem(p int, q int) error { if o.Status != Pending { return ErrLocked } o.Items = append(o.Items, OrderItem{Price: p, Qty: q}) return nil }
4. 领域服务
跨多个实体的规则放领域服务,不属于某一个实体:
type PricingService struct{ rules []DiscountRule } func (s *PricingService) Apply(o *Order) { for _, r := range s.rules { r.Apply(o) } }
5. 仓储接口
仓储只管持久化,定义在领域层,实现在基础设施层(第 5 篇的 repo 就是):
type OrderRepo interface { Save(*Order) error FindByID(string) (*Order, error) }
Gin handler 通过构造函数注入 OrderRepo 实现,领域层不依赖具体数据库。
6. 映射到 Gin
func (h *OrderHandler) Create(c *gin.Context) { o := h.repo.Find(id) o.AddItem(price, qty) o.Pay() h.repo.Save(o) // 领域行为先跑,最后才落库 OK(c, o) }
handler 薄,领域对象厚,业务规则都在 Order 里可测。
7. 上线清单
不是所有项目都上 DDD,简单 CRUD 用三层够了。聚合根是一致性边界,外部只通过它改内部。领域层不依赖框架和数据库,规则可单测。仓储接口定义在领域层、实现在外层。别为了 DDD 把简单东西搞复杂。
下一篇讲分布式事务,跨服务的数据怎么一致。