什么是 DDD?它如何拯救混乱的微服务?

什么是 DDD?它如何拯救混乱的微服务?

在软件开发中,我们经常遇到这种痛点: 产品经理说:“用户下单后,如果库存不足,要通知仓储补货。” 开发人员想:“先查 sku 表,再 update 库存表,如果 affected_rows 为 0,就 insert 一条记录到 message 表。” 结果:代码写出来全是数据库操作,业务逻辑支离破碎地散落在各个 ...

kkuo
Nov 27, 2025•10 min read

在软件开发中,我们经常遇到这种痛点:

  • 产品经理说:“用户下单后,如果库存不足,要通知仓储补货。”

  • 开发人员想:“先查 sku 表,再 update 库存表,如果 affected_rows 为 0,就 insert 一条记录到 message 表。”

结果:代码写出来全是数据库操作,业务逻辑支离破碎地散落在各个 Service 和 SQL 里。半年后,没人敢动这堆代码,因为“代码里的业务”和“真实的业务”已经脱节了。

DDD(Domain-Driven Design,领域驱动设计) 的核心使命就是解决这个问题。它要求代码的结构必须和业务的结构保持一致。


第一部分:战略设计——怎么切分微服务?(宏观层面)

DDD 将设计分为“战略设计”(怎么分地盘)和“战术设计”(怎么盖房子)。微服务拆分主要靠战略设计。

1. 核心概念:统一语言 (Ubiquitous Language)

这是 DDD 的第一原则。开发、测试、产品、业务专家必须说同一种语言。

  • 错误示范:业务说“入库”,开发代码里叫 CreateInventoryRecord。

  • DDD 要求:业务说“入库”,代码里就叫 CheckIn。

作用:消除沟通误差,确保代码直观反映业务。

2. 核心概念:界限上下文 (Bounded Context) —— 拆分的黄金法则

这是微服务拆分最重要的概念。

“一词多义”现象:
在一家电商公司里,大家嘴里都说着“商品”这个词,但含义完全不同:

  • 在销售部(销售子域):商品=价格、图片、标题、营销标签。

  • 在仓储部(仓储子域):商品=重量、体积、货架位置、条形码。

  • 在采购部(采购子域):商品=供应商信息、进货成本、生产批次。

传统做法(单体大泥球):
建一张巨大的 Product 表,包含几百个字段(价格、重量、供应商...),所有模块都查这张表。
后果:改了“价格”字段,结果把“仓储系统”搞挂了。

DDD 做法(界限上下文):
既然含义不同,就应该把它们隔离在不同的边界(Context)里。

  • 商品上下文(商品微服务):维护 ProductInfo(关注描述、价格)。

  • 仓储上下文(仓储微服务):维护 StockItem(关注库存、位置)。

  • 采购上下文(采购微服务):维护 PurchaseItem(关注成本、供应商)。

结论:一个界限上下文 = 一个微服务。
通过识别业务语言的边界,你就找到了微服务的边界。

3. 核心概念:子域 (Subdomain) —— 决定投入多少人力

不是所有微服务都重要。DDD 把业务划分为三类:

  1. 核心域 (Core Domain):公司的摇钱树,核心竞争力。例如淘宝的“搜索与推荐”、顺丰的“物流调度”。

    • 策略:投入最强的 5 个月人力的团队,用最正统的 DDD 架构精心打磨。

  2. 支撑域 (Supporting Domain):必须有,但不是核心。例如电商的“退款系统”、“发票系统”。

    • 策略:外包或用普通团队开发,逻辑简单点。

  3. 通用域 (Generic Domain):市面上到处都有的。例如“发送邮件”、“鉴权登录”。

    • 策略:直接买现成的 SaaS 或用开源组件,别自己造轮子。


第二部分:战术设计——代码里怎么写?(微观层面)

拆分好微服务后,服务内部的代码怎么写才不会变成“面条代码”?

1. 实体 (Entity) 与 值对象 (Value Object)

  • 实体:有唯一标识(ID),在这个世界上独一无二。

    • 例子:User(张三改了名还是张三),Order(订单)。

  • 值对象:没有 ID,只关心属性,属性变了就不是它了。

    • 例子:Address(地址)。如果你的订单地址改了,虽然还是你,但地址已经是另一个地址了。

    • 代码启示:值对象应该是不可变的(Immutable)。

2. 聚合 (Aggregate) 与 聚合根 (Aggregate Root) —— 解决一致性

这是为了解决你提到的“开发周期”问题。聚合是业务修改的最小单元。

案例:
一个“订单(Order)”包含很多“订单明细(OrderItem)”。

  • 如果不作为聚合:可以直接删除 OrderItem,导致 Order 总价对不上。

  • 作为聚合:Order 是聚合根。你想改 OrderItem,必须通过 Order 提供的方法(如 order.removeItem())来操作。Order 会自动重新计算总价。

指导意义:

  • 事务边界:一个事务(Transaction)只能修改一个聚合。

  • 服务粒度:如果一个微服务里包含了 100 个聚合,它可能太大了;如果一个聚合跨了两个微服务,那就是设计错误(跨库事务会搞死你)。


第三部分:实战落地——如何进行 DDD 拆分方案?

回到你的需求,假设你要重构一个“企业采购系统”,步骤如下:

第一步:事件风暴 (Event Storming)

把业务方和技术方关在会议室,墙上贴满便利贴。

  1. 列出事件:订单已提交、主管已审批、货物已发出、发票已开具。

  2. 寻找命令:谁触发了事件?(用户提交、系统自动触发)。

第二步:划分上下文 (Context Map)

将关系紧密的事件圈在一起。

  • 圈子A:包含“提交订单”、“审批订单”。 -> 订单上下文。

  • 圈子B:包含“查库存”、“锁定库存”。 -> 库存上下文。

  • 圈子C:包含“授信额度扣减”、“支付”。 -> 支付上下文。

第三步:定义上下文之间的关系

  • 订单微服务 调用 库存微服务:下游(库存)变动会影响上游吗?

  • 防腐层 (ACL):如果库存系统是买来的老旧系统(SAP),订单系统不要直接调它的 API,而是中间加一层适配层,防止老系统的烂模型污染新系统的代码。

第四步:微服务落地(技术映射)

  • 代码结构:

    OrderService (微服务)
    ├── Domain (领域层 - 纯净的业务逻辑,不依赖数据库和框架)
    │   ├── Aggregate (订单聚合)
    │   └── Repository (接口定义)
    ├── Infrastructure (基础设施层)
    │   ├── Database (MyBatis/JPA 实现)
    │   └── Redis
    ├── Application (应用层)
    │   └── OrderAppService (编排流程:取数据 -> 调领域逻辑 -> 存数据)
    └── Interfaces (接口层 - Controller)

总结

DDD 到底是什么?
DDD 不是教你写代码的技术,而是一套“通过语言和模型,把复杂的业务切分成一个个小岛(上下文),并在小岛内部建立严谨规则(聚合)的方法论”。

怎么用它拆微服务?

  1. 忘掉数据库表,先看业务流程。

  2. 寻找语言边界(同一个词在哪含义变了,哪里就是边界)。

  3. 确定核心域(好钢用在刀刃上)。

  4. 按聚合设计数据(保证数据强一致性的最小单元)。

Comments (0)