国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

領域驅(qū)動設計戰(zhàn)術模式--領域事件

wzyplus / 546人閱讀

摘要:將領域中所發(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ā)生后便不會改變。因此,領域事件通常建模成值對象。

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

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

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

public class AccountEnabledEvent extends AbstractAggregateEvent {
    public AccountEnabledEvent(Account source) {
        super(source);
    }
}
2.1.2 事件屬性
事件的屬性主要用于驅(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ù)。
2.1.3 事件方法
事件是事實的描述,本身不會有太多的業(yè)務操作。

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

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

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

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

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

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

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

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

2.2.1 發(fā)布訂閱模型
為了統(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,事件處理器如何進行訂閱。
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è)務方法調(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ā)布事件
通常情況下,領域事件是由聚合的命令方法產(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);

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

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

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

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

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

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

詳見,異步處理領域事件。

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

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

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

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

對事件進行存儲可以:

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

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

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

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

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

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

事件存儲是個比較大的課題,將有專門章節(jié)進行講解。
2.4.4 進行限界上下文集成
基于領域事件的限界上下文集成,主要由消息隊列和 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)部事件,而只有一兩個外部事件。
2.6.1 內(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 聚合。

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

一般情況下,外部事件,只作為數(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)”從戰(zhàn)略回歸到了戰(zhàn)術

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

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

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

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

    摘要:為目前使用范圍最廣的網(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...

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

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

    Anshiii 評論0 收藏0

發(fā)表評論

0條評論

wzyplus

|高級講師

TA的文章

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