成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

領(lǐng)域驅(qū)動設(shè)計戰(zhàn)術(shù)模式--領(lǐng)域事件

wzyplus / 564人閱讀

摘要:將領(lǐng)域中所發(fā)生的活動建模成一系列離散事件。領(lǐng)域事件是領(lǐng)域模型的組成部分,表示領(lǐng)域中所發(fā)生的事情。創(chuàng)建領(lǐng)域事件事件命名在建模領(lǐng)域事件時,我們應(yīng)該根據(jù)限界上下文中的通用語言來命名事件。

使用領(lǐng)域事件來捕獲發(fā)生在領(lǐng)域中的一些事情。

領(lǐng)域驅(qū)動實踐者發(fā)現(xiàn)他們可以通過了解更多發(fā)生在問題域中的事件,來更好的理解問題域。這些事件,就是領(lǐng)域事件,主要是與領(lǐng)域?qū)<乙黄疬M行知識提煉環(huán)節(jié)中獲得。

領(lǐng)域事件,可以用于一個限界上下文內(nèi)的領(lǐng)域模型,也可以使用消息隊列在限界上下文間進行異步通信。

1 理解領(lǐng)域事件
領(lǐng)域事件是領(lǐng)域?qū)<宜P(guān)心的發(fā)生在領(lǐng)域中的一些事件。

將領(lǐng)域中所發(fā)生的活動建模成一系列離散事件。每個事件都用領(lǐng)域?qū)ο蟊硎尽nI(lǐng)域事件是領(lǐng)域模型的組成部分,表示領(lǐng)域中所發(fā)生的事情。

領(lǐng)域事件的主要用途:

保證聚合間的數(shù)據(jù)一致性

替換批量處理

實現(xiàn)事件源模式

進行限界上下文集成

2 實現(xiàn)領(lǐng)域事件
領(lǐng)域事件表示已經(jīng)發(fā)生的某種事實,該事實在發(fā)生后便不會改變。因此,領(lǐng)域事件通常建模成值對象。

但,這也有特殊的情況,為了迎合序列化和反序列化框架需求,在建模時,經(jīng)常會進行一定的妥協(xié)。

2.1 創(chuàng)建領(lǐng)域事件
2.1.1 事件命名
在建模領(lǐng)域事件時,我們應(yīng)該根據(jù)限界上下文中的通用語言來命名事件。

如果事件由聚合上的命令操作產(chǎn)生,通常根據(jù)該操作方法的名字來命名事件。事件名字表明聚合上的命令方法在執(zhí)行成功后的事實。即事件命名需要反映過去發(fā)生過的事情。

public class AccountEnabledEvent extends AbstractAggregateEvent {
    public AccountEnabledEvent(Account source) {
        super(source);
    }
}
2.1.2 事件屬性
事件的屬性主要用于驅(qū)動后續(xù)業(yè)務(wù)流程。當然,也會擁有一些通用屬性。

事件具有一些通用屬性,如:

唯一標識

occurredOn 發(fā)生時間

type 事件類型

source 事件發(fā)生源(只針對由聚合產(chǎn)生的事件)

通用屬性可以使用事件接口來規(guī)范。

接口或類 含義
DomainEvent 通用領(lǐng)域事件接口
AggregateEvent 由聚合發(fā)布的通用領(lǐng)域事件接口
AbstractDomainEvent DomainEvent 實現(xiàn)類,維護 id 和 創(chuàng)建時間
AbstractAggregateEvent AggregateEvent 實現(xiàn)類,繼承子 AbstractDomainEvent,并添加 source 屬性
但,事件最主要的還是業(yè)務(wù)屬性。我們需要考慮,是誰導(dǎo)致事件的發(fā)生,這可能涉及產(chǎn)生事件的聚合或其他參與該操作的聚合,也可能是其他任何類型的操作數(shù)據(jù)。
2.1.3 事件方法
事件是事實的描述,本身不會有太多的業(yè)務(wù)操作。

領(lǐng)域事件通常被設(shè)計為不變對象,事件所攜帶的數(shù)據(jù)已經(jīng)反映出該事件的來源。事件構(gòu)造函數(shù)完成狀態(tài)初始化,同時提供屬性的 getter 方法。

2.1.4 事件唯一標識
這里需要注意的是事件唯一標識,通常情況下,事件是不可變的,那為什么會涉及唯一標識的概念呢?

對于從聚合中發(fā)布出來的領(lǐng)域事件,使用事件的名稱、產(chǎn)生事件的標識、事件發(fā)生的時間等足以對不同的事件進行區(qū)分。但,這樣會增加事件比較的復(fù)雜性。

對于由調(diào)用方發(fā)布的事件,我們將領(lǐng)域事件建模成聚合,可以直接使用聚合的唯一標識作為事件的標識。

事件唯一標識的引入,會大大減少事件比較的復(fù)雜性。但,其最大的意義在于限界上下文的集成。

當我們需要將領(lǐng)域事件發(fā)布到外部的限界上下文時,唯一標識就是一種必然。為了保證事件投遞的冪等性,在發(fā)送端,我們可能會進行多次發(fā)送嘗試,直至明確發(fā)送成功為止;而在接收端,當接收到事件后,需要對事件進行重復(fù)性檢測,以保障事件處理的冪等性。此時,事件的唯一標識便可以作為事件去重的依據(jù)。

事件唯一標識,本身對領(lǐng)域建模影響不大,但對技術(shù)處理好處巨大。因此,將它作為通用屬性進行管理。
2.2 發(fā)布領(lǐng)域事件
我們?nèi)绾伪苊忸I(lǐng)域事件與處理者間的耦合呢?

一種簡單高效的方式便是使用觀察者模式,這種模式可以在領(lǐng)域事件和外部組件間進行解耦。

2.2.1 發(fā)布訂閱模型
為了統(tǒng)一,我們需要定義了一套接口和實現(xiàn)類,以基于觀察者模式,完成事件的發(fā)布。

涉及接口和實現(xiàn)類如下:

接口或類 含義
DomainEventPublisher 用于發(fā)布領(lǐng)域事件
DomainEventHandlerRegistry 用于注冊 DomainEventHandler
DomainEventBus 擴展自 DomainEventPublisher 和 DomainEventHandlerRegistry 用于發(fā)布和管理領(lǐng)域事件處理器
DefaultDomainEventBus DomainEventBus 默認實現(xiàn)
DomainEventHandler 用于處理領(lǐng)域事件
DomainEventSubscriber 用于判斷是否接受領(lǐng)域事件
DomainEventExecutor 用于執(zhí)行領(lǐng)域事件處理器

使用實例如 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)后,需要將其與領(lǐng)域模型進行關(guān)聯(lián)。領(lǐng)域模型如何獲取 Publisher,事件處理器如何進行訂閱。
2.2.2 基于 ThreadLocal 的事件發(fā)布
比較常用的方案便是將 DomainEventBus 綁定到線程上下文。這樣,只要是同一調(diào)用線程都可以方便的獲取 DomainEventBus 對象。

具體的交互如下:

DomainEventBusHolder 用于管理 DomainEventBus。

public class DomainEventBusHolder {
    private static final ThreadLocal THREAD_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è)務(wù)方法調(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);

        Optional accountOptional = 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");
        }
    }
}
2.2.3 基于實體緩存的事件發(fā)布
先將事件緩存在實體中,在實體狀態(tài)成功持久化到存儲后,再進行事件發(fā)布。

具體交互如下:

實例代碼如下:

public class Account extends JpaAggregate {

    public void enable(){
        AccountEnabledEvent event = new AccountEnabledEvent(this);
        registerEvent(event);
    }
}

Accountenable 方法,調(diào)用 registerEvent 對事件進行注冊。

@MappedSuperclass
public abstract class AbstractAggregate extends 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){
        Optional 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);
            }
        }
    }

    class AccountEnableEventHandler implements DomainEventHandler{

        @Override
        public void handle(AccountEnabledEvent event) {
            LOGGER.info("handle enable event");
        }
    }
}

AccountApplicationinit 方法完成事件監(jiān)聽器的注冊,enable 方法在實體成功持久化后,將緩存的事件通過 DomainEventBus 實例 publish 出去。

2.2.4 由調(diào)用方發(fā)布事件
通常情況下,領(lǐng)域事件是由聚合的命令方法產(chǎn)生,并在命令方法執(zhí)行成功后,進行事件的發(fā)布。
有時,領(lǐng)域事件并不是聚合中的命令方法產(chǎn)生的,而是由用戶所發(fā)生的請求產(chǎn)生。

此時,我們需要將領(lǐng)域事件建模成一個聚合,并且擁有自己的資源庫。但,由于領(lǐng)域事件表示的是過去發(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 訂閱領(lǐng)域事件
由什么組件向領(lǐng)域事件注冊訂閱器呢?大多數(shù)請求,由應(yīng)用服務(wù)完成,有時也可以由領(lǐng)域服務(wù)進行注冊。

由于應(yīng)用服務(wù)是領(lǐng)域模型的直接客戶,它是注冊領(lǐng)域事件訂閱器的理想場所,即在應(yīng)用服務(wù)調(diào)用領(lǐng)域方法之前,就完成了對事件的訂閱。

基于 ThreadLocal 進行訂閱:

public void enable(Long id){
    // 清理之前綁定的 Handler
    DomainEventBusHolder.clean();

    // 注冊 EventHandler
    AccountEnableEventHandler enableEventHandler = new AccountEnableEventHandler();
    DomainEventBusHolder.getHandlerRegistry().register(AccountEnabledEvent.class, enableEventHandler);

    Optional accountOptional = 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){
    Optional 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);
        }
    }
}
2.4 處理領(lǐng)域事件
完成事件發(fā)布后,讓我們一起看下事件處理。
2.4.1 保證聚合間的數(shù)據(jù)一致性
我們通常將領(lǐng)域事件用于維護模型的一致性。在聚合建模中有一個原則,就是在一個事務(wù)中,只能對一個聚合進行修改,由此產(chǎn)生的變化必須在獨立的事務(wù)中運行。

在這種情況下,需要謹慎處理的事務(wù)的傳播性。

應(yīng)用服務(wù)控制著事務(wù)。不要在事件通知過程中修改另一個聚合實例,因為這樣會破壞聚合的一大原則:在一個事務(wù)中,只能對一個聚合進行修改。

對于簡單場景,我們可以使用特殊的事務(wù)隔離策略對聚合的修改進行隔離。具體流程如下:

但,最佳方案是使用異步處理。及每一個定義方都在各自獨立的事務(wù)中修改額外的聚合實例。

事件訂閱方不應(yīng)該在另一個聚合上執(zhí)行命令方法,因為這樣將破壞“在單個事務(wù)中只修改單個聚合實例”的原則。所有聚合實例間的最終一致性必須通過異步方式處理。

詳見,異步處理領(lǐng)域事件。

2.4.2 替換批量處理
批處理過程通常需要復(fù)雜的查詢,并且需要龐大的事務(wù)支持。如果在接收到領(lǐng)域事件時,系統(tǒng)就立即處理,業(yè)務(wù)需求不僅得到了更快的滿足,而且杜絕了批處理操作。

在系統(tǒng)的非高峰時期,通常使用批處理進行一些系統(tǒng)的維護,比如刪除過期數(shù)據(jù)、創(chuàng)建新的對象、通知用戶、更新統(tǒng)計信息等。這些批處理往往需要復(fù)雜的查詢,并需要龐大的事務(wù)支持。

如果我們監(jiān)聽系統(tǒng)中的領(lǐng)域事件,在接收領(lǐng)域事件時,系統(tǒng)立即處理。這樣,原本批量集中處理的過程就被分散成許多小的處理單元,業(yè)務(wù)需要也能更快的滿足,用戶可以可以及時的進行下一步操作。

2.4.3 實現(xiàn)事件源模式
對于單個限界上下文中的所有領(lǐng)域事件,為它們維護一個事件存儲具有很多的好處。

對事件進行存儲可以:

將事件存儲作為消息隊列來使用,然后將領(lǐng)域事件通過消息設(shè)施發(fā)布出去。

將事件存儲用于基于 Rest 的事件通知。

檢查模型命名方法產(chǎn)生結(jié)果的歷史記錄。

使用事件存儲來進行業(yè)務(wù)預(yù)測和分析。

使用事件來重建聚合實例。

執(zhí)行聚合的撤銷操作。

事件存儲是個比較大的課題,將有專門章節(jié)進行講解。
2.4.4 進行限界上下文集成
基于領(lǐng)域事件的限界上下文集成,主要由消息隊列和 REST 事件兩種模式。

在此,重心講解基于消息隊列的上下文集成。

在不同的上下文中采用消息系統(tǒng)時,我們必須保證最終一致性。在這種情況下,我們至少需要在兩種存儲之間保存最終一致性:領(lǐng)域模型所使用的存儲和消息隊列所使用的持久化存儲。我們必須保證在持久化領(lǐng)域模型時,對于的事件也已經(jīng)成功發(fā)布。如果兩種不同步,模型可能會處于不正確的狀態(tài)。

一般情況下,有三種方式:

領(lǐng)域模型和消息共享持久化存儲。在這種情況下,模型和事件的提交在一個事務(wù)中完成,從而保證兩種的一致性。

領(lǐng)域模型和消息由全局事務(wù)控制。這種情況下,模型和消息所用的持久化存儲可以分離,但會降低系統(tǒng)性能。

在領(lǐng)域持久化存儲中,創(chuàng)建一個特殊的存儲區(qū)域用于存儲事件(也就是事件存儲),從而在本地事務(wù)中完成領(lǐng)域和事件的存儲。然后,通過后臺服務(wù)將事件異步發(fā)送到消息隊列中。

一般情況下,第三種,是比較優(yōu)雅的解決方案。

在一致性要求不高時,可以通過領(lǐng)域事件訂閱器直接向消息隊列發(fā)送事件。具體流程如下:

對一致性要求高時,需要先將事件存儲,然后通過后臺線程加載并分發(fā)到消息隊列。具體流程如下:

2.5 異步處理領(lǐng)域事件
領(lǐng)域事件可以與異步工作流程協(xié)同,包括限界上下文間使用消息隊列進行異步通信。當然,在同一個限界上下文中,也可以啟動異步處理流程。

作為事件的發(fā)布者,不應(yīng)關(guān)心是否執(zhí)行異步處理。異常處理是由事件執(zhí)行者決定。

DomainEventExecutor 提供對異步處理的支持。

DomainEventExecutor eventExecutor  =
                new ExecutorBasedDomainEventExecutor("EventHandler", 1, 100);
this.domainEventBus.register(AccountEnabledEvent.class,
        eventExecutor,
        new AccountEnableEventHandler());
異步處理,就意味著放棄數(shù)據(jù)庫事務(wù)的 ACID 特性,而選擇使用最終一致性。
2.6 內(nèi)部事件與外部事件
使用領(lǐng)域事件時需要對事件進行區(qū)分,以避免技術(shù)實現(xiàn)的問題。

認識內(nèi)部事件和外部事件之間的區(qū)別至關(guān)重要。

內(nèi)部事件,是一個領(lǐng)域模型內(nèi)部的事件,不在有界上下文間進行共享。

外部事件,是對外發(fā)布的事件,在多個有界上下文中進行共享。

一般情況下,在典型的業(yè)務(wù)用例中,可能會有很多的內(nèi)部事件,而只有一兩個外部事件。
2.6.1 內(nèi)部事件
內(nèi)部事件存在于限界上下文內(nèi)部,受限界上下文邊界保護。

內(nèi)部事件被限制在單個有界上下文邊界內(nèi)部,所以可以直接引用領(lǐng)域?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 聚合。

2.6.2 外部事件
外部事件存在于限界上下文間,被多個上下文共享。

一般情況下,外部事件,只作為數(shù)據(jù)載體存在。常常采用平面結(jié)構(gòu),并公開所有屬性。

@Data
public class SubmittedEvent {
    private Owner owner;
    private Target target;
}

SubmittedEvent 為扁平化結(jié)構(gòu),主要是對數(shù)據(jù)的封裝。

由于外部事件被多個上下文共享,版本管理就顯得非常重要,以避免重大更改對其服務(wù)造成影響。
3 實現(xiàn)領(lǐng)域事件模式
領(lǐng)域事件是一種通用模式,它的本質(zhì)是將領(lǐng)域概念添加到發(fā)布-訂閱模式。
3.1 封裝領(lǐng)域事件的“發(fā)布-訂閱”模式
發(fā)布-訂閱是比較成熟的設(shè)計模式,具有很高的通用性。因此,建議針對領(lǐng)域需求進行封裝。

比如直接使用 geekhalo-ffffd 相關(guān)模塊。

定義領(lǐng)域事件:

@Value
public class LikeCancelledEvent extends AbstractAggregateEvent {
    public LikeCancelledEvent(Like source) {
        super(source);
    }
}

訂閱領(lǐng)域事件:

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í)行領(lǐng)域事件:

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)部事件;消息隊列雖然復(fù)雜,但擅長解決服務(wù)間通信問題,適合處理外部事件。
3.3 使用實體緩存領(lǐng)域事件
理論上,只有在業(yè)務(wù)成功完成后,才應(yīng)該對外發(fā)布事件。因此,將領(lǐng)域事件緩存在實體中,并在完成業(yè)務(wù)操作后將其進行發(fā)布,是一種較好的解決方案。
相比,使用 ThreadLocal 管理訂閱器,并在事件 publish 時進行訂閱回調(diào),事件緩存方案有明顯的優(yōu)勢。
3.4 使用 IOC 容器的事件發(fā)布功能
IOC 容器為我們提供了很多使用功能,其中也包括發(fā)布-訂閱功能,如 Spring。

通常情況下,領(lǐng)域模型不應(yīng)該直接依賴于 Spring 容器。因此,在領(lǐng)域中我們?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 機制對領(lǐng)域事件進行處理。

@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é)

領(lǐng)域事件是發(fā)生在問題域中的事實,它是通用語言的一部分。

領(lǐng)域事件優(yōu)先使用發(fā)布-訂閱模式,會發(fā)布事件并且觸發(fā)相應(yīng)的事件處理器。

限界上下文內(nèi),優(yōu)先使用內(nèi)部事件和內(nèi)存總線;限界上下文間,優(yōu)先使用外部事件和消息隊列。

領(lǐng)域事件使異步操作變得簡單。

領(lǐng)域事件為聚合間提供了最終一致性。

領(lǐng)域事件可以將大的批量操作簡化為許多小的業(yè)務(wù)操作。

領(lǐng)域事件可以完成強大的事件存儲。

領(lǐng)域事件可以完成限界上下文間的集成。

領(lǐng)域事件是更復(fù)雜架構(gòu)(CQRS)的一種支持。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/74121.html

相關(guān)文章

  • 云計算十年 “爭奪戰(zhàn)”從戰(zhàn)略回歸到了戰(zhàn)術(shù)

    摘要:直到今天,提起云計算很多人仍喜歡和戰(zhàn)爭聯(lián)系在一起,這也不難理解,過去的多年中,試圖搶奪戰(zhàn)略優(yōu)勢的云服務(wù)商們已然發(fā)生了一系列的競爭,技術(shù)創(chuàng)新數(shù)據(jù)中心服務(wù)模式等無不是如此。也對應(yīng)了的另一個觀點,云計算已經(jīng)不再是一個戰(zhàn)略問題,這是個戰(zhàn)術(shù)問題。圖片來源@視覺中國亞馬遜推出第一個云計算服務(wù)的時候,外界并沒有看好這個方向,高投入、低利潤且存在很多的不確定性。但在2008年10月,《經(jīng)濟學(xué)人》破天荒的用一...

    huayeluoliuhen 評論0 收藏0
  • 架構(gòu)師必收藏的干貨!??!

    摘要:一微服務(wù)概念微服務(wù)體系結(jié)構(gòu)由輕量級松散耦合的服務(wù)集合組成。每個服務(wù)都有自己的計劃測試發(fā)布部署擴展集成和獨立維護。團隊不必因為過去的技術(shù)決定而受到懲罰。用在這里是指將相關(guān)的服務(wù)通過聚合器聚合在一起,這個聚合器就是門面。 微服務(wù)架構(gòu)現(xiàn)在是談到企業(yè)應(yīng)用架構(gòu)時必聊的話題,微服務(wù)之所以火熱也是因為相對之前的應(yīng)用開發(fā)方式有很多優(yōu)點,如更靈活、更能適應(yīng)現(xiàn)在需求快速變更的大環(huán)境。 一、微服務(wù)概念 微服...

    shiweifu 評論0 收藏0
  • ELSE 技術(shù)周刊(2017.10.23期)

    摘要:為目前使用范圍最廣的網(wǎng)絡(luò)保護協(xié)議。身處攻擊目標周邊的惡意人士能夠利用密鑰重裝攻擊,利用此類安全漏洞。本文和大家一起探討下如何在三年內(nèi)快速成長為一名技術(shù)專家。 業(yè)界動態(tài) Vue 2.5 released Vue 2.5 正式發(fā)布,作者對于該版本的優(yōu)化總結(jié):更好的TypeScript 集成,更好的錯誤處理,更好的單文件功能組件支持以及更好的與環(huán)境無關(guān)的SSR WiFi爆驚天漏洞!KRACK...

    galaxy_robot 評論0 收藏0
  • To be better —msup榮獲平安科技“2018年度優(yōu)秀合作伙伴”稱號

    摘要:年月日,平安科技在深圳平安金融中心舉辦了年平安科技優(yōu)秀培訓(xùn)合作伙伴交流會,收到了邀請參與此次評選,并從余家合作伙伴中脫穎而出,在交付量滿意度師資內(nèi)容服務(wù)水準等十余項指標中獲得技術(shù)培訓(xùn)類年度優(yōu)秀合作伙伴獎。 2018年12月4日,平安科技在深圳平安金融中心舉辦了2018年平安科技優(yōu)秀培訓(xùn)合作伙伴交流會,msup收到了邀請參與此次評選,并從80余家合作伙伴中脫穎而出,在交付量、滿意度、師資...

    Anshiii 評論0 收藏0

發(fā)表評論

0條評論

wzyplus

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<