微服务与领域设计: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 做成负担

  1. 不必全盘照搬。中小团队从“贫血模型 + 分层分包”平滑演进:先在包结构上体现聚合与仓储,再逐步把散落的规则收拢进实体,避免一步到位的范式大改造。
  2. DDD 分层与微服务分层对应:controller(接入层)→ application(应用服务)→ domain(领域模型)→ infrastructure(仓储实现、RPC、MQ)。依赖方向向内:domain 不依赖基础设施。
  3. 贫血模型不全是错。纯展示/统计场景无复杂规则,用 DDD 反而别扭;判断标准是“业务规则是否复杂到实体装不下”。
  4. CQRS / 事件溯源(Event Sourcing)是高阶武器:CQRS 适合读模型与写模型差异巨大的系统;事件溯源以事件流为真相源。没有明确收益就别引入,它们会显著增加复杂度。
  5. 事务边界 = 聚合边界。一次修改只动一个聚合,才能用本地事务;跨聚合一致性走领域事件 + 最终一致。
  6. 防腐层很有用:对接外部/老旧系统时,在自己的上下文里做翻译层,避免外部模型污染内部模型。

与前面章节的关系

DDD 给出“边界”的理论依据,服务拆分章节给出“落地节奏”(绞杀者模式),两者配合:先战略建模划出限界上下文 → 决定服务边界与接口契约 → 再在服务内部用战术设计组织代码。数据归属与依赖治理(见服务拆分章节)同样以限界上下文为坐标。

小结

DDD 对微服务最大的贡献是“限界上下文”这个边界标尺与“聚合”这个事务/一致性单元。落地时保持务实:边界建模值得认真做,战术细节按团队复杂度取舍,先让领域模型成为代码里可见、可演进的一层,而不是追求方法论的完整性。

笔记加载中…