
什么是 DDD?它如何拯救混乱的微服务?
在软件开发中,我们经常遇到这种痛点: 产品经理说:“用户下单后,如果库存不足,要通知仓储补货。” 开发人员想:“先查 sku 表,再 update 库存表,如果 affected_rows 为 0,就 insert 一条记录到 message 表。” 结果:代码写出来全是数据库操作,业务逻辑支离破碎地散落在各个 ...
在软件开发中,我们经常遇到这种痛点:
产品经理说:“用户下单后,如果库存不足,要通知仓储补货。”
开发人员想:“先查 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 把业务划分为三类:
核心域 (Core Domain):公司的摇钱树,核心竞争力。例如淘宝的“搜索与推荐”、顺丰的“物流调度”。
策略:投入最强的 5 个月人力的团队,用最正统的 DDD 架构精心打磨。
支撑域 (Supporting Domain):必须有,但不是核心。例如电商的“退款系统”、“发票系统”。
策略:外包或用普通团队开发,逻辑简单点。
通用域 (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)
把业务方和技术方关在会议室,墙上贴满便利贴。
列出事件:订单已提交、主管已审批、货物已发出、发票已开具。
寻找命令:谁触发了事件?(用户提交、系统自动触发)。
第二步:划分上下文 (Context Map)
将关系紧密的事件圈在一起。
圈子A:包含“提交订单”、“审批订单”。 -> 订单上下文。
圈子B:包含“查库存”、“锁定库存”。 -> 库存上下文。
圈子C:包含“授信额度扣减”、“支付”。 -> 支付上下文。
第三步:定义上下文之间的关系
订单微服务 调用 库存微服务:下游(库存)变动会影响上游吗?
防腐层 (ACL):如果库存系统是买来的老旧系统(SAP),订单系统不要直接调它的 API,而是中间加一层适配层,防止老系统的烂模型污染新系统的代码。
第四步:微服务落地(技术映射)
代码结构:
OrderService (微服务) ├── Domain (领域层 - 纯净的业务逻辑,不依赖数据库和框架) │ ├── Aggregate (订单聚合) │ └── Repository (接口定义) ├── Infrastructure (基础设施层) │ ├── Database (MyBatis/JPA 实现) │ └── Redis ├── Application (应用层) │ └── OrderAppService (编排流程:取数据 -> 调领域逻辑 -> 存数据) └── Interfaces (接口层 - Controller)
总结
DDD 到底是什么?
DDD 不是教你写代码的技术,而是一套“通过语言和模型,把复杂的业务切分成一个个小岛(上下文),并在小岛内部建立严谨规则(聚合)的方法论”。
怎么用它拆微服务?
忘掉数据库表,先看业务流程。
寻找语言边界(同一个词在哪含义变了,哪里就是边界)。
确定核心域(好钢用在刀刃上)。
按聚合设计数据(保证数据强一致性的最小单元)。
Comments (0)