DDD 不是画一堆漂亮的类图

一提到领域驱动设计,很多人脑中浮现的是几张画满实体、箭头、继承关系的 UML 图。但 DDD 真正解决的问题不在画图,而在于:当业务变得复杂,代码和业务逐渐对不上,该怎么让它们重新咬合。

它的核心不是技术手法,而是一套围绕业务建模的思考方式。业务复杂的系统,难点从来不是编程语言,而是「这个系统到底在帮业务做什么」。DDD 就是逼你把这件事想清楚,再落到代码结构上。

模型不是设计的产物,而是团队对业务达成共识之后的记录。
本文核心知识点
  • 统一语言业务与开发共用一套术语,不各说各话。
  • 限界上下文不同业务规则住进不同的边界里。
  • 聚合一组需要一起保持一致的对象,用一个根管住。
  • 事件风暴把业务流程可视化,靠讨论发现边界。
  • 渐进落地从一个最复杂的上下文做起,别一次铺开。
DDD 示意:蓝色渐变背景上多块半透明发光几何体块,内部各有抽象小图标,块与块之间由发光连线双向连接,像多个领域模块相互协作,扁平科技插画
每个块是一个自治的领域边界,连线是它们之间必要的协作。

统一语言:先让业务和开发说同一种话

大多数系统的混乱,源头是同一个词在不同人嘴里意思不同。业务说的「订单」,在开发那里可能指数据库里一张表,在客服那里又指一次售后申请。词不统一,讨论就一直在空转。

统一语言的要求很朴素:在一个业务范围里,同一个概念只能有一个名字,这个名字同时出现在对话、文档和代码里。代码里的类名、变量名,就应该直接用业务用的词。

# 坏味道:技术命名掩盖了业务含义
class OrderProcessor:   # 「处理」到底在处理什么?
    def update(self, data):
        …

# 统一语言后:名字就是业务动作
class 订单履约:           # 业务说的就是「履约」
    def 确认发货(self):
        …
        # 这段代码读起来,像在复述业务规则本身。

当代码读起来像业务描述,业务方看代码也能对上号,评审和沟通的成本都会下降。

限界上下文:给系统划清边界

同一个词在不同业务场景里本来就可以有不同含义。「商品」在售卖场景是可下单的货,在库存场景是可盘点的货,在财务场景是一笔账。硬塞进一个「商品类」,必然处处别扭。

限界上下文就是承认这件事:把系统按业务规则分成若干边界,每个边界里用自己那套统一语言。边界之间通过明确的接口协作,而不是共享同一堆类。

边界关心的问题「商品」的含义
售卖怎么下单、怎么促销可被加入购物车的货
库存还剩多少、放哪可被盘点的存货
财务记多少账、怎么开票一笔可计价的资产

边界一旦清晰,团队就可以按边界分工,一个上下文由一个小团队独立负责,不再互相踩对方的领域。

实体、值对象与聚合

进入一个上下文内部,建模时有几个常用的积木。实体是有身份、会变化的东西,比如一笔订单;值对象是只由属性定义、不可变的描述,比如一个金额或地址。

聚合是最关键的概念:一组对象必须放在一起才保持业务上的一致,就用一个聚合根把它们管住。外部只能通过聚合根来操作,不能绕过它直接改内部对象。

# 聚合示例:订单是聚合根,订单项是它的内部部分
class 订单:
    def __init__(self):
        self.项 = []          # 订单项必须通过订单来增减
    def 添加商品(self, 商品, 数量):
        # 由订单负责:检查库存、算总价、记录快照
        …
    def 确认(self):
        # 整条订单要满足业务规则才能确认
        if not self.项:
            raise 订单为空()
        …

聚合的意义是把「哪里必须一致」这件事显式化,避免对象之间互相乱改、规则散落在各处。