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

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

在创建产品后,在聚合根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