微服务与领域设计:DDD 概念与落地取舍
服务边界怎么划最合理?领域驱动设计(Domain-Driven Design, DDD)给出了经典答案:按“限界上下文”划分业务边界。本章讲 DDD 的核心概念,以及微服务落地时的务实取舍,不追求教科书式完美。
战略设计:先回答“世界怎么切”
DDD 战略部分关注全局边界:
- 领域(Domain):业务本身,如电商域的订单、库存、支付。
- 限界上下文(Bounded Context):拥有统一业务语言与规则、边界清晰的子域。每个限界上下文对应一套自己的领域模型,同一概念在不同上下文可以有不同含义(“订单”在交易上下文和仓储上下文含义不同)。
- 上下文映射(Context Mapping):描述上下文间关系,如防腐层(Anti-Corruption Layer,隔离外部模型的翻译层)、共享内核、发布语言。
微服务映射:一个限界上下文通常对应一个或多个服务;限界上下文是“该不该拆”的最可靠判断依据——两个功能若共享同一套业务规则与语言,就不该拆开。
战术设计:模型怎么落
战术部分定义限界上下文内部的结构:
| 概念 | 说明 | 示例 |
|---|---|---|
| 实体(Entity) | 有唯一标识、有生命周期、状态会变 | 订单 Order(orderId 标识) |
| 值对象(Value Object) | 无标识、不可变、靠属性值区分 | 金额 Money、地址 Address |
| 聚合(Aggregate) | 一组实体的组合,通过聚合根对外操作 | 订单聚合:Order + OrderItem,根为 Order |
| 仓储(Repository) | 聚合的存取抽象,屏蔽持久化细节 | OrderRepository |
| 领域服务(Domain Service) | 不属于某个实体的领域逻辑 | 库存预占服务 |
| 应用服务(Application Service) | 编排用例、事务边界,不承载业务规则 | 下单用例 OrderAppService |
| 领域事件(Domain Event) | 聚合内发生的、其他部分关心的事实 | OrderCreatedEvent |
// 聚合根:只有 Order 能操作自己的 OrderItem
public class Order {
private OrderId id;
private List<OrderItem> items;
private OrderStatus status;
public void addItem(OrderItem item) {
if (status != OrderStatus.CREATING) {
throw new IllegalStateException("订单已提交,不能加商品");
}
this.items.add(item);
}
public void submit() { /* 状态流转等业务规则 */ }
}
// 仓储:面向聚合,而非面向表的 CRUD
public interface OrderRepository {
Order find(OrderId id);
void save(Order order);
}
落地取舍:别把 DDD 做成负担
- 不必全盘照搬。中小团队从“贫血模型 + 分层分包”平滑演进:先在包结构上体现聚合与仓储,再逐步把散落的规则收拢进实体,避免一步到位的范式大改造。
- DDD 分层与微服务分层对应:
controller(接入层)→application(应用服务)→domain(领域模型)→infrastructure(仓储实现、RPC、MQ)。依赖方向向内:domain 不依赖基础设施。 - 贫血模型不全是错。纯展示/统计场景无复杂规则,用 DDD 反而别扭;判断标准是“业务规则是否复杂到实体装不下”。
- CQRS / 事件溯源(Event Sourcing)是高阶武器:CQRS 适合读模型与写模型差异巨大的系统;事件溯源以事件流为真相源。没有明确收益就别引入,它们会显著增加复杂度。
- 事务边界 = 聚合边界。一次修改只动一个聚合,才能用本地事务;跨聚合一致性走领域事件 + 最终一致。
- 防腐层很有用:对接外部/老旧系统时,在自己的上下文里做翻译层,避免外部模型污染内部模型。
与前面章节的关系
DDD 给出“边界”的理论依据,服务拆分章节给出“落地节奏”(绞杀者模式),两者配合:先战略建模划出限界上下文 → 决定服务边界与接口契约 → 再在服务内部用战术设计组织代码。数据归属与依赖治理(见服务拆分章节)同样以限界上下文为坐标。
小结
DDD 对微服务最大的贡献是“限界上下文”这个边界标尺与“聚合”这个事务/一致性单元。落地时保持务实:边界建模值得认真做,战术细节按团队复杂度取舍,先让领域模型成为代码里可见、可演进的一层,而不是追求方法论的完整性。