摘要:將領域中所發(fā)生的活動建模成一系列離散事件。領域事件是領域模型的組成部分,表示領域中所發(fā)生的事情。創(chuàng)建領域事件事件命名在建模領域事件時,我們應該根據(jù)限界上下文中的通用語言來命名事件。
使用領域事件來捕獲發(fā)生在領域中的一些事情。
領域驅(qū)動實踐者發(fā)現(xiàn)他們可以通過了解更多發(fā)生在問題域中的事件,來更好的理解問題域。這些事件,就是領域事件,主要是與領域?qū)<乙黄疬M行知識提煉環(huán)節(jié)中獲得。
領域事件,可以用于一個限界上下文內(nèi)的領域模型,也可以使用消息隊列在限界上下文間進行異步通信。
1 理解領域事件領域事件是領域?qū)<宜P心的發(fā)生在領域中的一些事件。
將領域中所發(fā)生的活動建模成一系列離散事件。每個事件都用領域?qū)ο蟊硎尽nI域事件是領域模型的組成部分,表示領域中所發(fā)生的事情。
領域事件的主要用途:
保證聚合間的數(shù)據(jù)一致性
替換批量處理
實現(xiàn)事件源模式
進行限界上下文集成
2 實現(xiàn)領域事件領域事件表示已經(jīng)發(fā)生的某種事實,該事實在發(fā)生后便不會改變。因此,領域事件通常建模成值對象。2.1 創(chuàng)建領域事件但,這也有特殊的情況,為了迎合序列化和反序列化框架需求,在建模時,經(jīng)常會進行一定的妥協(xié)。
在建模領域事件時,我們應該根據(jù)限界上下文中的通用語言來命名事件。
如果事件由聚合上的命令操作產(chǎn)生,通常根據(jù)該操作方法的名字來命名事件。事件名字表明聚合上的命令方法在執(zhí)行成功后的事實。即事件命名需要反映過去發(fā)生過的事情。
public class AccountEnabledEvent extends AbstractAggregateEvent{ public AccountEnabledEvent(Account source) { super(source); } }
事件的屬性主要用于驅(qū)動后續(xù)業(yè)務流程。當然,也會擁有一些通用屬性。
事件具有一些通用屬性,如:
唯一標識
occurredOn 發(fā)生時間
type 事件類型
source 事件發(fā)生源(只針對由聚合產(chǎn)生的事件)
通用屬性可以使用事件接口來規(guī)范。
接口或類 | 含義 |
---|---|
DomainEvent | 通用領域事件接口 |
AggregateEvent | 由聚合發(fā)布的通用領域事件接口 |
AbstractDomainEvent | DomainEvent 實現(xiàn)類,維護 id 和 創(chuàng)建時間 |
AbstractAggregateEvent | AggregateEvent 實現(xiàn)類,繼承子 AbstractDomainEvent,并添加 source 屬性 |
但,事件最主要的還是業(yè)務屬性。我們需要考慮,是誰導致事件的發(fā)生,這可能涉及產(chǎn)生事件的聚合或其他參與該操作的聚合,也可能是其他任何類型的操作數(shù)據(jù)。
事件是事實的描述,本身不會有太多的業(yè)務操作。
領域事件通常被設計為不變對象,事件所攜帶的數(shù)據(jù)已經(jīng)反映出該事件的來源。事件構(gòu)造函數(shù)完成狀態(tài)初始化,同時提供屬性的 getter 方法。
這里需要注意的是事件唯一標識,通常情況下,事件是不可變的,那為什么會涉及唯一標識的概念呢?
對于從聚合中發(fā)布出來的領域事件,使用事件的名稱、產(chǎn)生事件的標識、事件發(fā)生的時間等足以對不同的事件進行區(qū)分。但,這樣會增加事件比較的復雜性。
對于由調(diào)用方發(fā)布的事件,我們將領域事件建模成聚合,可以直接使用聚合的唯一標識作為事件的標識。
事件唯一標識的引入,會大大減少事件比較的復雜性。但,其最大的意義在于限界上下文的集成。
當我們需要將領域事件發(fā)布到外部的限界上下文時,唯一標識就是一種必然。為了保證事件投遞的冪等性,在發(fā)送端,我們可能會進行多次發(fā)送嘗試,直至明確發(fā)送成功為止;而在接收端,當接收到事件后,需要對事件進行重復性檢測,以保障事件處理的冪等性。此時,事件的唯一標識便可以作為事件去重的依據(jù)。
事件唯一標識,本身對領域建模影響不大,但對技術處理好處巨大。因此,將它作為通用屬性進行管理。2.2 發(fā)布領域事件
我們?nèi)绾伪苊忸I域事件與處理者間的耦合呢?
一種簡單高效的方式便是使用觀察者模式,這種模式可以在領域事件和外部組件間進行解耦。
為了統(tǒng)一,我們需要定義了一套接口和實現(xiàn)類,以基于觀察者模式,完成事件的發(fā)布。
涉及接口和實現(xiàn)類如下:
接口或類 | 含義 |
---|---|
DomainEventPublisher | 用于發(fā)布領域事件 |
DomainEventHandlerRegistry | 用于注冊 DomainEventHandler |
DomainEventBus | 擴展自 DomainEventPublisher 和 DomainEventHandlerRegistry 用于發(fā)布和管理領域事件處理器 |
DefaultDomainEventBus | DomainEventBus 默認實現(xiàn) |
DomainEventHandler | 用于處理領域事件 |
DomainEventSubscriber | 用于判斷是否接受領域事件 |
DomainEventExecutor | 用于執(zhí)行領域事件處理器 |
使用實例如 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(){ // 創(chuàng)建事件處理器 TestEventHandler eventHandler = new TestEventHandler(); // 注冊事件處理器 this.domainEventBus.register(TestEvent.class, eventHandler); // 發(fā)布事件 this.domainEventBus.publish(new TestEvent("123")); // 檢測事件處理器是夠運行 Assert.assertEquals("123", eventHandler.data); } @Value class TestEvent extends AbstractDomainEvent{ private String data; } class TestEventHandler implements DomainEventHandler{ private String data; @Override public void handle(TestEvent event) { this.data = event.getData(); } } }
在構(gòu)建完發(fā)布訂閱結(jié)構(gòu)后,需要將其與領域模型進行關聯(lián)。領域模型如何獲取 Publisher,事件處理器如何進行訂閱。
比較常用的方案便是將 DomainEventBus 綁定到線程上下文。這樣,只要是同一調(diào)用線程都可以方便的獲取 DomainEventBus 對象。
具體的交互如下:
DomainEventBusHolder 用于管理 DomainEventBus。
public class DomainEventBusHolder { private static final ThreadLocalTHREAD_LOCAL = new ThreadLocal (){ @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 進行發(fā)布。
public class Account extends JpaAggregate { public void enable(){ AccountEnabledEvent event = new AccountEnabledEvent(this); DomainEventBusHolder.getPubliser().publish(event); } } public class AccountEnabledEvent extends AbstractAggregateEvent{ public AccountEnabledEvent(Account source) { super(source); } }
AccountApplication 完成訂閱器注冊以及業(yè)務方法調(diào)用。
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); OptionalaccountOptional = repository.getById(id); if (accountOptional.isPresent()) { Account account = accountOptional.get(); // enable 使用 DomainEventBusHolder 直接發(fā)布事件 account.enable(); repository.save(account); } } class AccountEnableEventHandler implements DomainEventHandler { @Override public void handle(AccountEnabledEvent event) { LOGGER.info("handle enable event"); } } }
先將事件緩存在實體中,在實體狀態(tài)成功持久化到存儲后,再進行事件發(fā)布。
具體交互如下:
實例代碼如下:
public class Account extends JpaAggregate { public void enable(){ AccountEnabledEvent event = new AccountEnabledEvent(this); registerEvent(event); } }
Account 的 enable 方法,調(diào)用 registerEvent 對事件進行注冊。
@MappedSuperclass public abstract class AbstractAggregateextends AbstractEntity implements Aggregate { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAggregate.class); @JsonIgnore @QueryTransient @Transient @org.springframework.data.annotation.Transient private final transient List events = Lists.newArrayList(); protected void registerEvent(DomainEvent event) { events.add(new DomainEventItem(event)); } protected void registerEvent(Supplier eventSupplier) { this.events.add(new DomainEventItem(eventSupplier)); } @Override @JsonIgnore public List 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 supplier) { Preconditions.checkArgument(supplier != null); this.domainEventSupplier = supplier; } private DomainEvent domainEvent; private Supplier 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){ OptionalaccountOptional = repository.getById(id); if (accountOptional.isPresent()) { Account account = accountOptional.get(); // enable 將事件緩存在 account 中 account.enable(); repository.save(account); List events = account.getEvents(); if (!CollectionUtils.isEmpty(events)){ // 成功持久化后,對事件進行發(fā)布 this.domainEventBus.publishAll(events); } } } class AccountEnableEventHandler implements DomainEventHandler { @Override public void handle(AccountEnabledEvent event) { LOGGER.info("handle enable event"); } } }
AccountApplication 的 init 方法完成事件監(jiān)聽器的注冊,enable 方法在實體成功持久化后,將緩存的事件通過 DomainEventBus 實例 publish 出去。
通常情況下,領域事件是由聚合的命令方法產(chǎn)生,并在命令方法執(zhí)行成功后,進行事件的發(fā)布。
有時,領域事件并不是聚合中的命令方法產(chǎn)生的,而是由用戶所發(fā)生的請求產(chǎn)生。
此時,我們需要將領域事件建模成一個聚合,并且擁有自己的資源庫。但,由于領域事件表示的是過去發(fā)生的事情,因此資源庫只做追加操作,不能對事件進行修改和刪除功能。
例如,對用戶點擊事件進行發(fā)布。
@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 實現(xiàn) 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 對事件進行發(fā)布。
2.3 訂閱領域事件由什么組件向領域事件注冊訂閱器呢?大多數(shù)請求,由應用服務完成,有時也可以由領域服務進行注冊。
由于應用服務是領域模型的直接客戶,它是注冊領域事件訂閱器的理想場所,即在應用服務調(diào)用領域方法之前,就完成了對事件的訂閱。
基于 ThreadLocal 進行訂閱:
public void enable(Long id){ // 清理之前綁定的 Handler DomainEventBusHolder.clean(); // 注冊 EventHandler AccountEnableEventHandler enableEventHandler = new AccountEnableEventHandler(); DomainEventBusHolder.getHandlerRegistry().register(AccountEnabledEvent.class, enableEventHandler); OptionalaccountOptional = repository.getById(id); if (accountOptional.isPresent()) { Account account = accountOptional.get(); // enable 使用 DomainEventBusHolder 直接發(fā)布事件 account.enable(); repository.save(account); } }
基于實體緩存進行訂閱:
@PostConstruct public void init(){ // 使用 Spring 生命周期注冊事件處理器 this.domainEventBus.register(AccountEnabledEvent.class, new AccountEnableEventHandler()); } public void enable(Long id){ Optional2.4 處理領域事件accountOptional = repository.getById(id); if (accountOptional.isPresent()) { Account account = accountOptional.get(); // enable 將事件緩存在 account 中 account.enable(); repository.save(account); List events = account.getEvents(); if (!CollectionUtils.isEmpty(events)){ // 成功持久化后,對事件進行發(fā)布 this.domainEventBus.publishAll(events); } } }
完成事件發(fā)布后,讓我們一起看下事件處理。
我們通常將領域事件用于維護模型的一致性。在聚合建模中有一個原則,就是在一個事務中,只能對一個聚合進行修改,由此產(chǎn)生的變化必須在獨立的事務中運行。
在這種情況下,需要謹慎處理的事務的傳播性。
應用服務控制著事務。不要在事件通知過程中修改另一個聚合實例,因為這樣會破壞聚合的一大原則:在一個事務中,只能對一個聚合進行修改。
對于簡單場景,我們可以使用特殊的事務隔離策略對聚合的修改進行隔離。具體流程如下:
但,最佳方案是使用異步處理。及每一個定義方都在各自獨立的事務中修改額外的聚合實例。
事件訂閱方不應該在另一個聚合上執(zhí)行命令方法,因為這樣將破壞“在單個事務中只修改單個聚合實例”的原則。所有聚合實例間的最終一致性必須通過異步方式處理。
詳見,異步處理領域事件。
批處理過程通常需要復雜的查詢,并且需要龐大的事務支持。如果在接收到領域事件時,系統(tǒng)就立即處理,業(yè)務需求不僅得到了更快的滿足,而且杜絕了批處理操作。
在系統(tǒng)的非高峰時期,通常使用批處理進行一些系統(tǒng)的維護,比如刪除過期數(shù)據(jù)、創(chuàng)建新的對象、通知用戶、更新統(tǒng)計信息等。這些批處理往往需要復雜的查詢,并需要龐大的事務支持。
如果我們監(jiān)聽系統(tǒng)中的領域事件,在接收領域事件時,系統(tǒng)立即處理。這樣,原本批量集中處理的過程就被分散成許多小的處理單元,業(yè)務需要也能更快的滿足,用戶可以可以及時的進行下一步操作。
對于單個限界上下文中的所有領域事件,為它們維護一個事件存儲具有很多的好處。
對事件進行存儲可以:
將事件存儲作為消息隊列來使用,然后將領域事件通過消息設施發(fā)布出去。
將事件存儲用于基于 Rest 的事件通知。
檢查模型命名方法產(chǎn)生結(jié)果的歷史記錄。
使用事件存儲來進行業(yè)務預測和分析。
使用事件來重建聚合實例。
執(zhí)行聚合的撤銷操作。
事件存儲是個比較大的課題,將有專門章節(jié)進行講解。
基于領域事件的限界上下文集成,主要由消息隊列和 REST 事件兩種模式。
在此,重心講解基于消息隊列的上下文集成。
在不同的上下文中采用消息系統(tǒng)時,我們必須保證最終一致性。在這種情況下,我們至少需要在兩種存儲之間保存最終一致性:領域模型所使用的存儲和消息隊列所使用的持久化存儲。我們必須保證在持久化領域模型時,對于的事件也已經(jīng)成功發(fā)布。如果兩種不同步,模型可能會處于不正確的狀態(tài)。
一般情況下,有三種方式:
領域模型和消息共享持久化存儲。在這種情況下,模型和事件的提交在一個事務中完成,從而保證兩種的一致性。
領域模型和消息由全局事務控制。這種情況下,模型和消息所用的持久化存儲可以分離,但會降低系統(tǒng)性能。
在領域持久化存儲中,創(chuàng)建一個特殊的存儲區(qū)域用于存儲事件(也就是事件存儲),從而在本地事務中完成領域和事件的存儲。然后,通過后臺服務將事件異步發(fā)送到消息隊列中。
一般情況下,第三種,是比較優(yōu)雅的解決方案。
在一致性要求不高時,可以通過領域事件訂閱器直接向消息隊列發(fā)送事件。具體流程如下:
對一致性要求高時,需要先將事件存儲,然后通過后臺線程加載并分發(fā)到消息隊列。具體流程如下:
2.5 異步處理領域事件領域事件可以與異步工作流程協(xié)同,包括限界上下文間使用消息隊列進行異步通信。當然,在同一個限界上下文中,也可以啟動異步處理流程。
作為事件的發(fā)布者,不應關心是否執(zhí)行異步處理。異常處理是由事件執(zhí)行者決定。
DomainEventExecutor 提供對異步處理的支持。
DomainEventExecutor eventExecutor = new ExecutorBasedDomainEventExecutor("EventHandler", 1, 100); this.domainEventBus.register(AccountEnabledEvent.class, eventExecutor, new AccountEnableEventHandler());
異步處理,就意味著放棄數(shù)據(jù)庫事務的 ACID 特性,而選擇使用最終一致性。2.6 內(nèi)部事件與外部事件
使用領域事件時需要對事件進行區(qū)分,以避免技術實現(xiàn)的問題。
認識內(nèi)部事件和外部事件之間的區(qū)別至關重要。
內(nèi)部事件,是一個領域模型內(nèi)部的事件,不在有界上下文間進行共享。
外部事件,是對外發(fā)布的事件,在多個有界上下文中進行共享。
一般情況下,在典型的業(yè)務用例中,可能會有很多的內(nèi)部事件,而只有一兩個外部事件。
內(nèi)部事件存在于限界上下文內(nèi)部,受限界上下文邊界保護。
內(nèi)部事件被限制在單個有界上下文邊界內(nèi)部,所以可以直接引用領域?qū)ο蟆?/p>
public interface AggregateEvent> extends DomainEvent{ A source(); default A getSource(){ return source(); } }
比如 AggregateEvent 中的 source 指向發(fā)布該事件的聚合。
public class LikeSubmittedEvent extends AbstractAggregateEvent{ public LikeSubmittedEvent(Like source) { super(source); } public LikeSubmittedEvent(String id, Like source) { super(id, source); } }
LikeSubmittedEvent 類直接引用 Like 聚合。
外部事件存在于限界上下文間,被多個上下文共享。
一般情況下,外部事件,只作為數(shù)據(jù)載體存在。常常采用平面結(jié)構(gòu),并公開所有屬性。
@Data public class SubmittedEvent { private Owner owner; private Target target; }
SubmittedEvent 為扁平化結(jié)構(gòu),主要是對數(shù)據(jù)的封裝。
由于外部事件被多個上下文共享,版本管理就顯得非常重要,以避免重大更改對其服務造成影響。3 實現(xiàn)領域事件模式
領域事件是一種通用模式,它的本質(zhì)是將領域概念添加到發(fā)布-訂閱模式。3.1 封裝領域事件的“發(fā)布-訂閱”模式
發(fā)布-訂閱是比較成熟的設計模式,具有很高的通用性。因此,建議針對領域需求進行封裝。
比如直接使用 geekhalo-ffffd 相關模塊。
定義領域事件:
@Value public class LikeCancelledEvent extends AbstractAggregateEvent{ 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); });
異步執(zhí)行領域事件:
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 內(nèi)存總線處理內(nèi)部事件,消息隊列處理外部事件
內(nèi)存總線簡單高效,同時支持同步、異步兩個處理方案,比較適合處理繁雜的內(nèi)部事件;消息隊列雖然復雜,但擅長解決服務間通信問題,適合處理外部事件。3.3 使用實體緩存領域事件
理論上,只有在業(yè)務成功完成后,才應該對外發(fā)布事件。因此,將領域事件緩存在實體中,并在完成業(yè)務操作后將其進行發(fā)布,是一種較好的解決方案。
相比,使用 ThreadLocal 管理訂閱器,并在事件 publish 時進行訂閱回調(diào),事件緩存方案有明顯的優(yōu)勢。3.4 使用 IOC 容器的事件發(fā)布功能
IOC 容器為我們提供了很多使用功能,其中也包括發(fā)布-訂閱功能,如 Spring。
通常情況下,領域模型不應該直接依賴于 Spring 容器。因此,在領域中我們?nèi)匀皇褂脙?nèi)存總線,為其添加一個訂閱者,將內(nèi)存總線中的事件轉(zhuǎn)發(fā)到 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 小結(jié)
領域事件是發(fā)生在問題域中的事實,它是通用語言的一部分。
領域事件優(yōu)先使用發(fā)布-訂閱模式,會發(fā)布事件并且觸發(fā)相應的事件處理器。
限界上下文內(nèi),優(yōu)先使用內(nèi)部事件和內(nèi)存總線;限界上下文間,優(yōu)先使用外部事件和消息隊列。
領域事件使異步操作變得簡單。
領域事件為聚合間提供了最終一致性。
領域事件可以將大的批量操作簡化為許多小的業(yè)務操作。
領域事件可以完成強大的事件存儲。
領域事件可以完成限界上下文間的集成。
領域事件是更復雜架構(gòu)(CQRS)的一種支持。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/74121.html
摘要:直到今天,提起云計算很多人仍喜歡和戰(zhàn)爭聯(lián)系在一起,這也不難理解,過去的多年中,試圖搶奪戰(zhàn)略優(yōu)勢的云服務商們已然發(fā)生了一系列的競爭,技術創(chuàng)新數(shù)據(jù)中心服務模式等無不是如此。也對應了的另一個觀點,云計算已經(jīng)不再是一個戰(zhàn)略問題,這是個戰(zhàn)術問題。圖片來源@視覺中國亞馬遜推出第一個云計算服務的時候,外界并沒有看好這個方向,高投入、低利潤且存在很多的不確定性。但在2008年10月,《經(jīng)濟學人》破天荒的用一...
摘要:一微服務概念微服務體系結(jié)構(gòu)由輕量級松散耦合的服務集合組成。每個服務都有自己的計劃測試發(fā)布部署擴展集成和獨立維護。團隊不必因為過去的技術決定而受到懲罰。用在這里是指將相關的服務通過聚合器聚合在一起,這個聚合器就是門面。 微服務架構(gòu)現(xiàn)在是談到企業(yè)應用架構(gòu)時必聊的話題,微服務之所以火熱也是因為相對之前的應用開發(fā)方式有很多優(yōu)點,如更靈活、更能適應現(xiàn)在需求快速變更的大環(huán)境。 一、微服務概念 微服...
摘要:為目前使用范圍最廣的網(wǎng)絡保護協(xié)議。身處攻擊目標周邊的惡意人士能夠利用密鑰重裝攻擊,利用此類安全漏洞。本文和大家一起探討下如何在三年內(nèi)快速成長為一名技術專家。 業(yè)界動態(tài) Vue 2.5 released Vue 2.5 正式發(fā)布,作者對于該版本的優(yōu)化總結(jié):更好的TypeScript 集成,更好的錯誤處理,更好的單文件功能組件支持以及更好的與環(huán)境無關的SSR WiFi爆驚天漏洞!KRACK...
摘要:年月日,平安科技在深圳平安金融中心舉辦了年平安科技優(yōu)秀培訓合作伙伴交流會,收到了邀請參與此次評選,并從余家合作伙伴中脫穎而出,在交付量滿意度師資內(nèi)容服務水準等十余項指標中獲得技術培訓類年度優(yōu)秀合作伙伴獎。 2018年12月4日,平安科技在深圳平安金融中心舉辦了2018年平安科技優(yōu)秀培訓合作伙伴交流會,msup收到了邀請參與此次評選,并從80余家合作伙伴中脫穎而出,在交付量、滿意度、師資...
閱讀 2262·2021-09-28 09:36
閱讀 1995·2021-09-22 15:14
閱讀 3622·2019-08-30 12:47
閱讀 3034·2019-08-30 12:44
閱讀 1225·2019-08-29 17:06
閱讀 535·2019-08-29 14:12
閱讀 973·2019-08-29 14:01
閱讀 2580·2019-08-29 12:17