使用領域事件來捕獲發生在領域中的一些事情。
領域驅動實踐者發現他們可以通過了解更多發生在問題域中的事件,來更好的理解問題域。這些事件,就是領域事件,主要是與領域專家一起進行知識提煉環節中獲得。
領域事件,可以用於一個限界上下文內的領域模型,也可以使用訊息佇列在限界上下文間進行非同步通訊。
1 理解領域事件
領域事件是領域專家所關心的發生在領域中的一些事件。
將領域中所發生的活動建模成一系列離散事件。每個事件都用領域物件表示。領域事件是領域模型的組成部分,表示領域中所發生的事情。
領域事件的主要用途:
- 保證聚合間的資料一致性
- 替換批量處理
- 實現事件源模式
- 進行限界上下文整合
2 實現領域事件
領域事件表示已經發生的某種事實,該事實在發生後便不會改變。因此,領域事件通常建模成值物件。
但,這也有特殊的情況,為了迎合序列化和反序列化框架需求,在建模時,經常會進行一定的妥協。
2.1 建立領域事件
2.1.1 事件命名
在建模領域事件時,我們應該根據限界上下文中的通用語言來命名事件。
如果事件由聚合上的命令操作產生,通常根據該操作方法的名字來命名事件。事件名字表明聚合上的命令方法在執行成功後的事實。即事件命名需要反映過去發生過的事情。
public class AccountEnabledEvent extends AbstractAggregateEvent<Long, Account> {
public AccountEnabledEvent(Account source) {
super(source);
}
}
複製程式碼
2.1.2 事件屬性
事件的屬性主要用於驅動後續業務流程。當然,也會擁有一些通用屬性。
事件具有一些通用屬性,如:
- 唯一標識
- occurredOn 發生時間
- type 事件型別
- source 事件發生源(只針對由聚合產生的事件)
通用屬性可以使用事件介面來規範。
介面或類 | 含義 |
---|---|
DomainEvent | 通用領域事件介面 |
AggregateEvent | 由聚合釋出的通用領域事件介面 |
AbstractDomainEvent | DomainEvent 實現類,維護 id 和 建立時間 |
AbstractAggregateEvent | AggregateEvent 實現類,繼承子 AbstractDomainEvent,並新增 source 屬性 |
但,事件最主要的還是業務屬性。我們需要考慮,是誰導致事件的發生,這可能涉及產生事件的聚合或其他參與該操作的聚合,也可能是其他任何型別的運算元據。
2.1.3 事件方法
事件是事實的描述,本身不會有太多的業務操作。
領域事件通常被設計為不變物件,事件所攜帶的資料已經反映出該事件的來源。事件建構函式完成狀態初始化,同時提供屬性的 getter 方法。
2.1.4 事件唯一標識
這裡需要注意的是事件唯一標識,通常情況下,事件是不可變的,那為什麼會涉及唯一標識的概念呢?
對於從聚合中釋出出來的領域事件,使用事件的名稱、產生事件的標識、事件發生的時間等足以對不同的事件進行區分。但,這樣會增加事件比較的複雜性。
對於由呼叫方釋出的事件,我們將領域事件建模成聚合,可以直接使用聚合的唯一標識作為事件的標識。
事件唯一標識的引入,會大大減少事件比較的複雜性。但,其最大的意義在於限界上下文的整合。
當我們需要將領域事件釋出到外部的限界上下文時,唯一標識就是一種必然。為了保證事件投遞的冪等性,在傳送端,我們可能會進行多次傳送嘗試,直至明確傳送成功為止;而在接收端,當接收到事件後,需要對事件進行重複性檢測,以保障事件處理的冪等性。此時,事件的唯一標識便可以作為事件去重的依據。
事件唯一標識,本身對領域建模影響不大,但對技術處理好處巨大。因此,將它作為通用屬性進行管理。
2.2 釋出領域事件
我們如何避免領域事件與處理者間的耦合呢?
一種簡單高效的方式便是使用觀察者模式,這種模式可以在領域事件和外部元件間進行解耦。
2.2.1 釋出訂閱模型
為了統一,我們需要定義了一套介面和實現類,以基於觀察者模式,完成事件的釋出。
涉及介面和實現類如下:
介面或類 | 含義 |
---|---|
DomainEventPublisher | 用於釋出領域事件 |
DomainEventHandlerRegistry | 用於註冊 DomainEventHandler |
DomainEventBus | 擴充套件自 DomainEventPublisher 和 DomainEventHandlerRegistry 用於釋出和管理領域事件處理器 |
DefaultDomainEventBus | DomainEventBus 預設實現 |
DomainEventHandler | 用於處理領域事件 |
DomainEventSubscriber | 用於判斷是否接受領域事件 |
DomainEventExecutor | 用於執行領域事件處理器 |
使用例項如 DomainEventBusTest 所示:
public class DomainEventBusTest {
private DomainEventBus domainEventBus;
@Before
public void setUp() throws Exception {
this.domainEventBus = new DefaultDomainEventBus();
}
@After
public void tearDown() throws Exception {
this.domainEventBus = null;
}
@Test
public void publishTest(){
// 建立事件處理器
TestEventHandler eventHandler = new TestEventHandler();
// 註冊事件處理器
this.domainEventBus.register(TestEvent.class, eventHandler);
// 釋出事件
this.domainEventBus.publish(new TestEvent("123"));
// 檢測事件處理器是夠執行
Assert.assertEquals("123", eventHandler.data);
}
@Value
class TestEvent extends AbstractDomainEvent{
private String data;
}
class TestEventHandler implements DomainEventHandler<TestEvent>{
private String data;
@Override
public void handle(TestEvent event) {
this.data = event.getData();
}
}
}
複製程式碼
在構建完釋出訂閱結構後,需要將其與領域模型進行關聯。領域模型如何獲取 Publisher,事件處理器如何進行訂閱。
2.2.2 基於 ThreadLocal 的事件釋出
比較常用的方案便是將 DomainEventBus 繫結到執行緒上下文。這樣,只要是同一呼叫執行緒都可以方便的獲取 DomainEventBus 物件。
具體的互動如下:
DomainEventBusHolder 用於管理 DomainEventBus。
public class DomainEventBusHolder {
private static final ThreadLocal<DomainEventBus> THREAD_LOCAL = new ThreadLocal<DomainEventBus>(){
@Override
protected DomainEventBus initialValue() {
return new DefaultDomainEventBus();
}
};
public static DomainEventPublisher getPubliser(){
return THREAD_LOCAL.get();
}
public static DomainEventHandlerRegistry getHandlerRegistry(){
return THREAD_LOCAL.get();
}
public static void clean(){
THREAD_LOCAL.remove();
}
}
複製程式碼
Account 的 enable 直接使用 DomainEventBusHolder 進行釋出。
public class Account extends JpaAggregate {
public void enable(){
AccountEnabledEvent event = new AccountEnabledEvent(this);
DomainEventBusHolder.getPubliser().publish(event);
}
}
public class AccountEnabledEvent extends AbstractAggregateEvent<Long, Account> {
public AccountEnabledEvent(Account source) {
super(source);
}
}
複製程式碼
AccountApplication 完成訂閱器註冊以及業務方法呼叫。
public class AccountApplication extends AbstractApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(AccountApplication.class);
@Autowired
private AccountRepository repository;
public void enable(Long id){
// 清理之前繫結的 Handler
DomainEventBusHolder.clean();
// 註冊 EventHandler
AccountEnableEventHandler enableEventHandler = new AccountEnableEventHandler();
DomainEventBusHolder.getHandlerRegistry().register(AccountEnabledEvent.class, enableEventHandler);
Optional<Account> accountOptional = repository.getById(id);
if (accountOptional.isPresent()) {
Account account = accountOptional.get();
// enable 使用 DomainEventBusHolder 直接釋出事件
account.enable();
repository.save(account);
}
}
class AccountEnableEventHandler implements DomainEventHandler<AccountEnabledEvent>{
@Override
public void handle(AccountEnabledEvent event) {
LOGGER.info("handle enable event");
}
}
}
複製程式碼
2.2.3 基於實體快取的事件釋出
先將事件快取在實體中,在實體狀態成功持久化到儲存後,再進行事件釋出。
具體互動如下:
例項程式碼如下:
public class Account extends JpaAggregate {
public void enable(){
AccountEnabledEvent event = new AccountEnabledEvent(this);
registerEvent(event);
}
}
複製程式碼
Account 的 enable 方法,呼叫 registerEvent 對事件進行註冊。
@MappedSuperclass
public abstract class AbstractAggregate<ID> extends AbstractEntity<ID> implements Aggregate<ID> {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAggregate.class);
@JsonIgnore
@QueryTransient
@Transient
@org.springframework.data.annotation.Transient
private final transient List<DomainEventItem> events = Lists.newArrayList();
protected void registerEvent(DomainEvent event) {
events.add(new DomainEventItem(event));
}
protected void registerEvent(Supplier<DomainEvent> eventSupplier) {
this.events.add(new DomainEventItem(eventSupplier));
}
@Override
@JsonIgnore
public List<DomainEvent> getEvents() {
return Collections.unmodifiableList(events.stream()
.map(eventSupplier -> eventSupplier.getEvent())
.collect(Collectors.toList()));
}
@Override
public void cleanEvents() {
events.clear();
}
private class DomainEventItem {
DomainEventItem(DomainEvent event) {
Preconditions.checkArgument(event != null);
this.domainEvent = event;
}
DomainEventItem(Supplier<DomainEvent> supplier) {
Preconditions.checkArgument(supplier != null);
this.domainEventSupplier = supplier;
}
private DomainEvent domainEvent;
private Supplier<DomainEvent> domainEventSupplier;
public DomainEvent getEvent() {
if (domainEvent != null) {
return domainEvent;
}
DomainEvent event = this.domainEventSupplier != null ? this.domainEventSupplier.get() : null;
domainEvent = event;
return domainEvent;
}
}
}
複製程式碼
registerEvent 方法在 AbstractAggregate 中,registerEvent 方法將事件儲存到 events 集合,getEvents 方法獲取所有事件,cleanEvents 方法清理快取的事件。
Application 例項如下:
@Service
public class AccountApplication extends AbstractApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(AccountApplication.class);
@Autowired
private AccountRepository repository;
@Autowired
private DomainEventBus domainEventBus;
@PostConstruct
public void init(){
// 使用 Spring 生命週期註冊事件處理器
this.domainEventBus.register(AccountEnabledEvent.class, new AccountEnableEventHandler());
}
public void enable(Long id){
Optional<Account> accountOptional = repository.getById(id);
if (accountOptional.isPresent()) {
Account account = accountOptional.get();
// enable 將事件快取在 account 中
account.enable();
repository.save(account);
List<DomainEvent> events = account.getEvents();
if (!CollectionUtils.isEmpty(events)){
// 成功持久化後,對事件進行釋出
this.domainEventBus.publishAll(events);
}
}
}
class AccountEnableEventHandler implements DomainEventHandler<AccountEnabledEvent>{
@Override
public void handle(AccountEnabledEvent event) {
LOGGER.info("handle enable event");
}
}
}
複製程式碼
AccountApplication 的 init 方法完成事件監聽器的註冊,enable 方法在實體成功持久化後,將快取的事件通過 DomainEventBus 例項 publish 出去。
2.2.4 由呼叫方釋出事件
通常情況下,領域事件是由聚合的命令方法產生,並在命令方法執行成功後,進行事件的釋出。 有時,領域事件並不是聚合中的命令方法產生的,而是由使用者所發生的請求產生。
此時,我們需要將領域事件建模成一個聚合,並且擁有自己的資源庫。但,由於領域事件表示的是過去發生的事情,因此資源庫只做追加操作,不能對事件進行修改和刪除功能。
例如,對使用者點選事件進行釋出。
@Entity
@Data
public class ClickAction extends JpaAggregate implements DomainEvent {
@Setter(AccessLevel.PRIVATE)
private Long userId;
@Setter(AccessLevel.PRIVATE)
private String menuId;
public ClickAction(Long userId, String menuId){
Preconditions.checkArgument(userId != null);
Preconditions.checkArgument(StringUtils.isNotEmpty(menuId));
setUserId(userId);
setMenuId(menuId);
}
@Override
public String id() {
return String.valueOf(getId());
}
@Override
public Date occurredOn() {
return getCreateTime();
}
}
複製程式碼
ClickAction 繼承自 JpaAggregate 實現 DomainEvent 介面,並重寫 id 和 occurredOn 方法。
@Service
public class ClickActionApplication extends AbstractApplication {
@Autowired
private ClickActionRepository repository;
@Autowired
private DomainEventBus domainEventBus;
public void clickMenu(Long id, String menuId){
ClickAction clickAction = new ClickAction(id, menuId);
clickAction.prePersist();
this.repository.save(clickAction);
domainEventBus.publish(clickAction);
}
}
複製程式碼
ClickActionApplication 在成功儲存 ClickAction 後,使用 DomainEventBus 對事件進行釋出。
2.3 訂閱領域事件
由什麼元件向領域事件註冊訂閱器呢?大多數請求,由應用服務完成,有時也可以由領域服務進行註冊。
由於應用服務是領域模型的直接客戶,它是註冊領域事件訂閱器的理想場所,即在應用服務呼叫領域方法之前,就完成了對事件的訂閱。
基於 ThreadLocal 進行訂閱:
public void enable(Long id){
// 清理之前繫結的 Handler
DomainEventBusHolder.clean();
// 註冊 EventHandler
AccountEnableEventHandler enableEventHandler = new AccountEnableEventHandler();
DomainEventBusHolder.getHandlerRegistry().register(AccountEnabledEvent.class, enableEventHandler);
Optional<Account> accountOptional = repository.getById(id);
if (accountOptional.isPresent()) {
Account account = accountOptional.get();
// enable 使用 DomainEventBusHolder 直接釋出事件
account.enable();
repository.save(account);
}
}
複製程式碼
基於實體快取進行訂閱:
@PostConstruct
public void init(){
// 使用 Spring 生命週期註冊事件處理器
this.domainEventBus.register(AccountEnabledEvent.class, new AccountEnableEventHandler());
}
public void enable(Long id){
Optional<Account> accountOptional = repository.getById(id);
if (accountOptional.isPresent()) {
Account account = accountOptional.get();
// enable 將事件快取在 account 中
account.enable();
repository.save(account);
List<DomainEvent> events = account.getEvents();
if (!CollectionUtils.isEmpty(events)){
// 成功持久化後,對事件進行釋出
this.domainEventBus.publishAll(events);
}
}
}
複製程式碼
2.4 處理領域事件
完成事件釋出後,讓我們一起看下事件處理。
2.4.1 保證聚合間的資料一致性
我們通常將領域事件用於維護模型的一致性。在聚合建模中有一個原則,就是在一個事務中,只能對一個聚合進行修改,由此產生的變化必須在獨立的事務中執行。
在這種情況下,需要謹慎處理的事務的傳播性。
應用服務控制著事務。不要在事件通知過程中修改另一個聚合例項,因為這樣會破壞聚合的一大原則:在一個事務中,只能對一個聚合進行修改。
對於簡單場景,我們可以使用特殊的事務隔離策略對聚合的修改進行隔離。具體流程如下:
但,最佳方案是使用非同步處理。及每一個定義方都在各自獨立的事務中修改額外的聚合例項。
事件訂閱方不應該在另一個聚合上執行命令方法,因為這樣將破壞“在單個事務中只修改單個聚合例項”的原則。所有聚合例項間的最終一致性必須通過非同步方式處理。
詳見,非同步處理領域事件。
2.4.2 替換批量處理
批處理過程通常需要複雜的查詢,並且需要龐大的事務支援。如果在接收到領域事件時,系統就立即處理,業務需求不僅得到了更快的滿足,而且杜絕了批處理操作。
在系統的非高峰時期,通常使用批處理進行一些系統的維護,比如刪除過期資料、建立新的物件、通知使用者、更新統計資訊等。這些批處理往往需要複雜的查詢,並需要龐大的事務支援。
如果我們監聽系統中的領域事件,在接收領域事件時,系統立即處理。這樣,原本批量集中處理的過程就被分散成許多小的處理單元,業務需要也能更快的滿足,使用者可以可以及時的進行下一步操作。
2.4.3 實現事件源模式
對於單個限界上下文中的所有領域事件,為它們維護一個事件儲存具有很多的好處。
對事件進行儲存可以:
- 將事件儲存作為訊息佇列來使用,然後將領域事件通過訊息設施釋出出去。
- 將事件儲存用於基於 Rest 的事件通知。
- 檢查模型命名方法產生結果的歷史記錄。
- 使用事件儲存來進行業務預測和分析。
- 使用事件來重建聚合例項。
- 執行聚合的撤銷操作。
事件儲存是個比較大的課題,將有專門章節進行講解。
2.4.4 進行限界上下文整合
基於領域事件的限界上下文整合,主要由訊息佇列和 REST 事件兩種模式。
在此,重心講解基於訊息佇列的上下文整合。
在不同的上下文中採用訊息系統時,我們必須保證最終一致性。在這種情況下,我們至少需要在兩種儲存之間儲存最終一致性:領域模型所使用的儲存和訊息佇列所使用的持久化儲存。我們必須保證在持久化領域模型時,對於的事件也已經成功釋出。如果兩種不同步,模型可能會處於不正確的狀態。
一般情況下,有三種方式:
- 領域模型和訊息共享持久化儲存。在這種情況下,模型和事件的提交在一個事務中完成,從而保證兩種的一致性。
- 領域模型和訊息由全域性事務控制。這種情況下,模型和訊息所用的持久化儲存可以分離,但會降低系統效能。
- 在領域持久化儲存中,建立一個特殊的儲存區域用於儲存事件(也就是事件儲存),從而在本地事務中完成領域和事件的儲存。然後,通過後臺服務將事件非同步傳送到訊息佇列中。
一般情況下,第三種,是比較優雅的解決方案。
在一致性要求不高時,可以通過領域事件訂閱器直接向訊息佇列傳送事件。具體流程如下:
對一致性要求高時,需要先將事件儲存,然後通過後臺執行緒載入並分發到訊息佇列。具體流程如下:
2.5 非同步處理領域事件
領域事件可以與非同步工作流程協同,包括限界上下文間使用訊息佇列進行非同步通訊。當然,在同一個限界上下文中,也可以啟動非同步處理流程。
作為事件的釋出者,不應關心是否執行非同步處理。異常處理是由事件執行者決定。
DomainEventExecutor 提供對非同步處理的支援。
DomainEventExecutor eventExecutor =
new ExecutorBasedDomainEventExecutor("EventHandler", 1, 100);
this.domainEventBus.register(AccountEnabledEvent.class,
eventExecutor,
new AccountEnableEventHandler());
複製程式碼
非同步處理,就意味著放棄資料庫事務的 ACID 特性,而選擇使用最終一致性。
2.6 內部事件與外部事件
使用領域事件時需要對事件進行區分,以避免技術實現的問題。
認識內部事件和外部事件之間的區別至關重要。
- 內部事件,是一個領域模型內部的事件,不在有界上下文間進行共享。
- 外部事件,是對外發布的事件,在多個有界上下文中進行共享。
一般情況下,在典型的業務用例中,可能會有很多的內部事件,而只有一兩個外部事件。
2.6.1 內部事件
內部事件存在於限界上下文內部,受限界上下文邊界保護。
內部事件被限制在單個有界上下文邊界內部,所以可以直接引用領域物件。
public interface AggregateEvent<ID, A extends Aggregate<ID>> extends DomainEvent{
A source();
default A getSource(){
return source();
}
}
複製程式碼
比如 AggregateEvent 中的 source 指向釋出該事件的聚合。
public class LikeSubmittedEvent extends AbstractAggregateEvent<Long, Like> {
public LikeSubmittedEvent(Like source) {
super(source);
}
public LikeSubmittedEvent(String id, Like source) {
super(id, source);
}
}
複製程式碼
LikeSubmittedEvent 類直接引用 Like 聚合。
2.6.2 外部事件
外部事件存在於限界上下文間,被多個上下文共享。
一般情況下,外部事件,只作為資料載體存在。常常採用平面結構,並公開所有屬性。
@Data
public class SubmittedEvent {
private Owner owner;
private Target target;
}
複製程式碼
SubmittedEvent 為扁平化結構,主要是對資料的封裝。
由於外部事件被多個上下文共享,版本管理就顯得非常重要,以避免重大更改對其服務造成影響。
3 實現領域事件模式
領域事件是一種通用模式,它的本質是將領域概念新增到釋出-訂閱模式。
3.1 封裝領域事件的“釋出-訂閱”模式
釋出-訂閱是比較成熟的設計模式,具有很高的通用性。因此,建議針對領域需求進行封裝。
比如直接使用 geekhalo-ddd 相關模組。
定義領域事件:
@Value
public class LikeCancelledEvent extends AbstractAggregateEvent<Long, Like> {
public LikeCancelledEvent(Like source) {
super(source);
}
}
複製程式碼
訂閱領域事件:
this.domainEventBus.register(LikeCancelledEvent.class, likeCancelledEvent->{
CanceledEvent canceledEvent = new CanceledEvent();
canceledEvent.setOwner(likeCancelledEvent.getSource().getOwner());
canceledEvent.setTarget(likeCancelledEvent.getSource().getTarget());
this.redisBasedQueue.pushLikeEvent(canceledEvent);
});
複製程式碼
非同步執行領域事件:
DomainEventExecutor eventExecutor =
new ExecutorBasedDomainEventExecutor("LikeEventHandler", 1, 100);
this.domainEventBus.register(LikeCancelledEvent.class,
eventExecutor,
likeCancelledEvent->{
CanceledEvent canceledEvent = new CanceledEvent();
canceledEvent.setOwner(likeCancelledEvent.getSource().getOwner());
canceledEvent.setTarget(likeCancelledEvent.getSource().getTarget());
this.redisBasedQueue.pushLikeEvent(canceledEvent);
});
複製程式碼
3.2 記憶體匯流排處理內部事件,訊息佇列處理外部事件
記憶體匯流排簡單高效,同時支援同步、非同步兩個處理方案,比較適合處理繁雜的內部事件;訊息佇列雖然複雜,但擅長解決服務間通訊問題,適合處理外部事件。
3.3 使用實體快取領域事件
理論上,只有在業務成功完成後,才應該對外發布事件。因此,將領域事件快取在實體中,並在完成業務操作後將其進行釋出,是一種較好的解決方案。
相比,使用 ThreadLocal 管理訂閱器,並在事件 publish 時進行訂閱回撥,事件快取方案有明顯的優勢。
3.4 使用 IOC 容器的事件釋出功能
IOC 容器為我們提供了很多使用功能,其中也包括髮布-訂閱功能,如 Spring。
通常情況下,領域模型不應該直接依賴於 Spring 容器。因此,在領域中我們仍然使用記憶體匯流排,為其新增一個訂閱者,將記憶體匯流排中的事件轉發到 Spring 容器中。
class SpringEventDispatcher implements ApplicationEventPublisherAware {
@Autowired
private DomainEventBus domainEventBus;
private ApplicationEventPublisher eventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.eventPublisher = applicationEventPublisher;
}
@PostConstruct
public void addListener(){
this.domainEventBus.register(event->true, event -> {this.eventPublisher.publishEvent(event);});
}
}
複製程式碼
此時,我們就可以直接使用 Spring 的 EventListener 機制對領域事件進行處理。
@Component
public class RedisBasedQueueExporter {
@Autowired
private RedisBasedQueue redisBasedQueue;
@EventListener
public void handle(LikeSubmittedEvent likeSubmittedEvent){
SubmittedEvent submittedEvent = new SubmittedEvent();
submittedEvent.setOwner(likeSubmittedEvent.getSource().getOwner());
submittedEvent.setTarget(likeSubmittedEvent.getSource().getTarget());
this.redisBasedQueue.pushLikeEvent(submittedEvent);
}
@EventListener
public void handle(LikeCancelledEvent likeCancelledEvent){
CanceledEvent canceledEvent = new CanceledEvent();
canceledEvent.setOwner(likeCancelledEvent.getSource().getOwner());
canceledEvent.setTarget(likeCancelledEvent.getSource().getTarget());
this.redisBasedQueue.pushLikeEvent(canceledEvent);
}
}
複製程式碼
4 小結
- 領域事件是發生在問題域中的事實,它是通用語言的一部分。
- 領域事件優先使用釋出-訂閱模式,會發布事件並且觸發相應的事件處理器。
- 限界上下文內,優先使用內部事件和記憶體匯流排;限界上下文間,優先使用外部事件和訊息佇列。
- 領域事件使非同步操作變得簡單。
- 領域事件為聚合間提供了最終一致性。
- 領域事件可以將大的批量操作簡化為許多小的業務操作。
- 領域事件可以完成強大的事件儲存。
- 領域事件可以完成限界上下文間的整合。
- 領域事件是更復雜架構(CQRS)的一種支援。