亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

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

wzyplus / 622人閱讀

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

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

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

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

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

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

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

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

替換批量處理

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

進(jìn)行限界上下文集成

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

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

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

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

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

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

唯一標(biāo)識(shí)

occurredOn 發(fā)生時(shí)間

type 事件類型

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

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

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

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

2.1.4 事件唯一標(biāo)識(shí)
這里需要注意的是事件唯一標(biāo)識(shí),通常情況下,事件是不可變的,那為什么會(huì)涉及唯一標(biāo)識(shí)的概念呢?

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

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

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

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

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

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

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

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

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

使用實(shí)例如 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();
        // 注冊(cè)事件處理器
        this.domainEventBus.register(TestEvent.class, eventHandler);

        // 發(fā)布事件
        this.domainEventBus.publish(new TestEvent("123"));

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

具體的交互如下:

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 進(jìn)行發(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 完成訂閱器注冊(cè)以及業(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();

        // 注冊(cè) 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 基于實(shí)體緩存的事件發(fā)布
先將事件緩存在實(shí)體中,在實(shí)體狀態(tài)成功持久化到存儲(chǔ)后,再進(jìn)行事件發(fā)布。

具體交互如下:

實(shí)例代碼如下:

public class Account extends JpaAggregate {

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

Accountenable 方法,調(diào)用 registerEvent 對(duì)事件進(jìn)行注冊(cè)。

@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 實(shí)例如下:

@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 生命周期注冊(cè)事件處理器
        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)){
                // 成功持久化后,對(duì)事件進(jìn)行發(fā)布
                this.domainEventBus.publishAll(events);
            }
        }
    }

    class AccountEnableEventHandler implements DomainEventHandler{

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

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

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

此時(shí),我們需要將領(lǐng)域事件建模成一個(gè)聚合,并且擁有自己的資源庫(kù)。但,由于領(lǐng)域事件表示的是過去發(fā)生的事情,因此資源庫(kù)只做追加操作,不能對(duì)事件進(jìn)行修改和刪除功能。

例如,對(duì)用戶點(diǎn)擊事件進(jìn)行發(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 實(shí)現(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 對(duì)事件進(jìn)行發(fā)布。

2.3 訂閱領(lǐng)域事件
由什么組件向領(lǐng)域事件注冊(cè)訂閱器呢?大多數(shù)請(qǐng)求,由應(yīng)用服務(wù)完成,有時(shí)也可以由領(lǐng)域服務(wù)進(jìn)行注冊(cè)。

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

基于 ThreadLocal 進(jìn)行訂閱:

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

    // 注冊(cè) 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);
    }
}

基于實(shí)體緩存進(jìn)行訂閱:

@PostConstruct
public void init(){
    // 使用 Spring 生命周期注冊(cè)事件處理器
    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)){
            // 成功持久化后,對(duì)事件進(jìn)行發(fā)布
            this.domainEventBus.publishAll(events);
        }
    }
}
2.4 處理領(lǐng)域事件
完成事件發(fā)布后,讓我們一起看下事件處理。
2.4.1 保證聚合間的數(shù)據(jù)一致性
我們通常將領(lǐng)域事件用于維護(hù)模型的一致性。在聚合建模中有一個(gè)原則,就是在一個(gè)事務(wù)中,只能對(duì)一個(gè)聚合進(jìn)行修改,由此產(chǎn)生的變化必須在獨(dú)立的事務(wù)中運(yùn)行。

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

應(yīng)用服務(wù)控制著事務(wù)。不要在事件通知過程中修改另一個(gè)聚合實(shí)例,因?yàn)檫@樣會(huì)破壞聚合的一大原則:在一個(gè)事務(wù)中,只能對(duì)一個(gè)聚合進(jìn)行修改。

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

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

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

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

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

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

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

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

對(duì)事件進(jìn)行存儲(chǔ)可以:

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

將事件存儲(chǔ)用于基于 Rest 的事件通知。

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

使用事件存儲(chǔ)來進(jìn)行業(yè)務(wù)預(yù)測(cè)和分析。

使用事件來重建聚合實(shí)例。

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

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

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

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

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

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

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

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

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

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

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

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

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

DomainEventExecutor 提供對(duì)異步處理的支持。

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

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

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

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

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

內(nèi)部事件被限制在單個(gè)有界上下文邊界內(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 外部事件
外部事件存在于限界上下文間,被多個(gè)上下文共享。

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

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

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

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

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

通常情況下,領(lǐng)域模型不應(yīng)該直接依賴于 Spring 容器。因此,在領(lǐng)域中我們?nèi)匀皇褂脙?nèi)存總線,為其添加一個(gè)訂閱者,將內(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);});
    }

}

此時(shí),我們就可以直接使用 Spring 的 EventListener 機(jī)制對(duì)領(lǐng)域事件進(jìn)行處理。

@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ā)生在問題域中的事實(shí),它是通用語(yǔ)言的一部分。

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

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

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

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

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

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

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

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

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/74121.html

相關(guān)文章

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

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

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

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

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

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

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

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

    Anshiii 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<