DDD核心概念精讲

一、实体(Entity)

1.1 什么是实体

实体:具有唯一标识的对象,其生命周期中标识保持不变,属性可能变化。

class Order {
    private OrderId id; // 唯一标识,整个生命周期不变
    private OrderStatus status; // 属性可变
    private Money amount; // 属性可变

    // 通过 ID 判断相等,不是通过属性
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Order order = (Order) o;
        return id.equals(order.id);
    }

    @Override
    public int hashCode() {
        return id.hashCode(); // 基于 ID 计算哈希
    }
}

1.2 实体的特征

特征说明
唯一标识通过 ID 识别,不是属性
生命周期从创建到销毁,ID 不变
可变性属性可以变化
连续性可以追踪变化历史

二、值对象(Value Object)

2.1 什么是值对象

值对象:没有唯一标识,通过属性值确定其身份,不可变。

// Money 是值对象
class Money {
    private final BigDecimal amount;
    private final Currency currency;

    private Money(BigDecimal amount, Currency currency) {
        this.amount = amount;
        this.currency = currency;
    }

    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("Currency mismatch");
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Money money = (Money) o;
        return amount.equals(money.amount) && currency.equals(money.currency);
    }

    @Override
    public int hashCode() {
        return Objects.hash(amount, currency);
    }
}

2.2 实体 vs 值对象

维度实体值对象
标识有唯一标识无标识
相等性基于标识基于属性
可变性可变不可变
生命周期
设计原则实体可以引用值对象值对象通常作为实体的属性

三、聚合(Aggregate)

3.1 什么是聚合

聚合:一组相关对象的集合,作为数据修改的单元。聚合有一个聚合根,外部只能通过聚合根访问内部对象。

// Order 是聚合根
class Order extends BaseAggregateRoot {
    private OrderId id;
    private List<OrderItem> items; // OrderItem 不能单独存在

    // 外部只能通过 Order 访问 OrderItem
    public void addItem(Product product, int quantity) {
        if (this.status != OrderStatus.DRAFT) {
            throw new OrderCannotModifyException();
        }

        // 业务规则内聚
        OrderItem existing = findItem(product.getId());
        if (existing != null) {
            existing.increaseQuantity(quantity);
        } else {
            items.add(new OrderItem(product.getId(), product.getPrice(), quantity));
        }
        recalculateTotal();
    }

    // 内部方法
    private OrderItem findItem(ProductId productId) {
        return items.stream()
            .filter(i -> i.getProductId().equals(productId))
            .findFirst()
            .orElse(null);
    }
}

3.2 聚合的规则

聚合设计原则:

1. 聚合边界内保持强一致性
2. 跨聚合只能通过 ID 引用
3. 聚合是事务边界
4. 聚合应该小而内聚

四、领域服务(Domain Service)

4.1 什么时候用领域服务

领域服务:当某个业务逻辑不属于任何实体或值对象时,使用领域服务。

// 跨聚合的业务逻辑:转账
class TransferService {
    public void transfer(Account from, Account to, Money amount) {
        // 涉及两个 Account 聚合,无法放在任何一个聚合中
        from.withdraw(amount);
        to.deposit(amount);
    }
}

4.2 领域服务 vs 应用服务

维度领域服务应用服务
位置领域层应用层
职责业务逻辑编排协调
依赖领域对象领域服务、仓储

五、领域事件(Domain Event)

5.1 什么是领域事件

领域事件:领域中发生的、业务有意义的事件。

// 领域事件
record OrderCreatedEvent(
    OrderId orderId,
    UserId userId,
    Money amount,
    Instant occurredAt
) implements DomainEvent {}

// 聚合根中发布事件
class Order extends BaseAggregateRoot {
    public void create(CreateOrderCommand cmd) {
        Order order = new Order(cmd.getUserId());
        order.items.addAll(cmd.getItems());
        order.calculateTotal();

        // 发布领域事件
        registerEvent(new OrderCreatedEvent(
            order.getId(),
            order.getUserId(),
            order.getTotalAmount(),
            Instant.now()
        ));

        return order;
    }
}

5.2 事件订阅

// 订阅领域事件
@EventListener
class InventoryService {
    @Async
    public void handleOrderCreated(OrderCreatedEvent event) {
        // 扣减库存
        for (OrderItem item : event.getItems()) {
            inventoryService.reserve(item.getProductId(), item.getQuantity());
        }
    }
}

六、仓储(Repository)

6.1 仓储的职责

仓储:聚合的持久化抽象,只操作聚合根。

// 仓储接口(在领域层定义)
interface OrderRepository {
    void save(Order order);
    Optional<Order> findById(OrderId id);
    List<Order> findByUserId(UserId userId);
}

// 仓储实现(在基础设施层)
class JpaOrderRepository implements OrderRepository {
    @Override
    public void save(Order order) {
        jpaTemplate.save(order);
    }
}

6.2 仓储 vs DAO

维度仓储DAO
范围聚合根任意表
抽象程度高(领域概念)低(技术概念)
接口位置领域层基础设施层
实现位置基础设施层数据层

七、限界上下文(Bounded Context)

7.1 什么是限界上下文

限界上下文:领域模型有明确边界的部分,每个上下文有自己的领域模型和 Ubiquitous Language。

┌──────────────────┐  ┌──────────────────┐
│   电商上下文       │  │   仓库上下文       │
│                  │  │                  │
│  Order           │  │  Warehouse       │
│  Customer        │  │  Inventory       │
│  Product         │  │  Shelf           │
│                  │  │                  │
│  同一个 Product   │  │  同一个 Product   │
│  在不同上下文中   │  │  含义不同         │
│  含义不同        │  │                  │
└────────┬─────────┘  └────────┬─────────┘
         │                       │
         │         上下文映射     │
         └───────────────────────┘

7.2 上下文映射类型

类型说明
共享内核两个上下文共享部分模型
客户-供应商上游发布,下游消费
防腐层转换器隔离不同上下文
独立模式各自独立,通过事件异步交互