跳转至

DDD 领域驱动设计


为什么需要 DDD?

问题根源:传统三层架构(Controller → Service → DAO)中,业务逻辑全部堆在 Service 层,Entity 只有 getter/setter,这就是贫血模型

// ❌ 贫血模型:Order 只是数据容器,业务逻辑在 Service
public class Order {
    private Long id;
    private String status;
    // 只有 getter/setter,没有业务行为
}

public class OrderService {
    public void cancelOrder(Long orderId) {
        Order order = orderRepository.findById(orderId);
        if (!"PAID".equals(order.getStatus())) {
            throw new BusinessException("只有已支付订单才能取消");
        }
        order.setStatus("CANCELLED");
        // 业务逻辑全在 Service,Order 是贫血的
    }
}

// ✅ 充血模型:业务逻辑在领域对象内
public class Order {
    private Long id;
    private OrderStatus status;

    // 业务行为封装在领域对象中
    public void cancel() {
        if (this.status != OrderStatus.PAID) {
            throw new DomainException("只有已支付订单才能取消");
        }
        this.status = OrderStatus.CANCELLED;
        DomainEvents.raise(new OrderCancelledEvent(this.id));
    }
}

为什么充血模型更好:业务逻辑内聚在领域对象中,修改取消逻辑只需改 Order.cancel(),不需要在多个 Service 中查找。同时,领域对象可以保证自身状态的合法性(不需要外部校验)。


DDD 核心概念

flowchart TB
    subgraph 战略设计
        BC[限界上下文\nBounded Context\n业务边界划分]
        UL[统一语言\nUbiquitous Language\n团队共同词汇]
    end

    subgraph 战术设计
        AR[聚合根\nAggregate Root\n一致性边界]
        E[实体 Entity\n有唯一标识]
        VO[值对象 Value Object\n无标识/不可变]
        DE[领域事件\nDomain Event\n状态变化通知]
        DS[领域服务\nDomain Service\n跨聚合业务]
    end

    BC --> AR
    AR --> E
    AR --> VO
    AR --> DE
概念 说明 示例 为什么这样设计
聚合根 聚合的入口,保证聚合内数据一致性 Order(包含 OrderItem) 外部只能通过聚合根修改聚合内数据,保证一致性
实体 有唯一标识,生命周期内状态可变 User(有 userId) 通过 ID 区分不同实体,即使属性相同也是不同对象
值对象 无标识,不可变,通过属性值判断相等 Money(金额+币种)、Address 不可变对象天然线程安全,可以安全共享
领域事件 领域内发生的重要业务事件 OrderPlacedEvent 解耦聚合间的依赖,通过事件通知而非直接调用
限界上下文 业务边界,同一概念在不同上下文含义不同 "商品"在商品域 vs 订单域含义不同 防止概念污染,每个上下文有自己的模型

面试高频问题

Q:贫血模型和充血模型哪个更好?

充血模型更符合 OOP 思想,业务逻辑内聚在领域对象中,更易维护。但贫血模型更简单,适合 CRUD 为主的简单业务。复杂业务用充血模型,简单 CRUD 用贫血模型