DDD中由谁来发布领域事件?

在领域驱动设计(DDD)中,领域事件(Domain Event) 是一个非常重要的概念,它用于反映领域中发生的重要业务事件。但由谁来发布领域事件,却存在一些实践差异,Vaughn Vernon在《领域驱动设计精粹》一书中推荐由限界上下文中的聚合(Aggregate)负责发布领域事件。

img.png

让我们来假设一个场景,在零售系统的产品上下文里创建一个产品后,发布一个领域事件ProductCreatedEvent(产品已创建)。

img_1.png

在创建产品后,在聚合根Product内部发布领域事件ProductCreatedEvent。 注意,由于企业应用里需要将Product持久化后才认为是真实创建,所以这里实际上并没有真实发布,只是在domainEvents列表里增加了一个领域事件,稍后将延迟发布这个事件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@Getter
public class Product extends AggregateRoot {
    private final ProductId id;
    private final String name;

    public Product(String name) {
        this.id = new ProductId(UUID.randomUUID().toString());
        this.name = name;
        super.publish(new ProductCreatedEvent(this.id));
    }
}


public abstract class AggregateRoot {
    @Getter
    private final List<DomainEvent> domainEvents = new ArrayList<>();

    protected void publish(DomainEvent domainEvent) {
        this.domainEvents.add(domainEvent);
    }
}

在持久化Product后,发布领域事件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@RequiredArgsConstructor
public class ProductApplicationService {
    private final DomainEventPublisher domainEventPublisher;
    private final ProductRepository productRepository;

    public void createProduct(String name) {
        var product = new Product(name);
        this.productRepository.save(product);

        // 在保存聚合后发布领域事件
        product.getDomainEvents().forEach(domainEventPublisher::publish);
    }
}

为什么不直接在应用服务里直接发布领域事件?

有些架构也会选择在应用服务中判断并发布事件,但这会把业务判断从领域层带到应用层,容易造成“贫血模型”或“过程式逻辑”,违背 DDD 的初衷。

为什么推荐由聚合发布领域事件?

  • 聚合是业务规则的边界。聚合负责维护业务规则的一致性,是领域模型的核心。一旦发生状态变更,这个变更往往具有业务意义,此时聚合内部最了解是否应该产生一个领域事件。
  • 封装性好。聚合内部封装了业务规则,它应该决定在什么情况下发布事件,这样更符合“高内聚、低耦合”的设计原则。、
  • 便于测试和理解。聚合方法触发事件,测试时可以验证事件是否被正确记录和发布,更容易保证业务行为正确。

角色&职责

聚合(Aggregate) 负责决定何时发生业务上有意义的事件,并记录领域事件。 应用服务(Application Service) 负责持久化聚合和发布领域事件。 事件总线/事件发布器 负责将事件派发到监听器。

源代码样例可参考github项目: michaelxmn/be-ddd-expert