6. 基于Spring Data的領(lǐng)域事件發(fā)布

領(lǐng)域事件發(fā)布是一個(gè)領(lǐng)域?qū)ο鬄榱俗屍渌鼘?duì)象知道自己已經(jīng)處理完成某個(gè)操作時(shí)發(fā)出的一個(gè)通知,事件發(fā)布力求從代碼層面讓自身對(duì)象與外部對(duì)象解耦氯材,并減少技術(shù)代碼入侵荣挨。

一、 手動(dòng)發(fā)布事件

// 實(shí)體定義
@Entity
public class Department implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer departmentId;

    @Enumerated(EnumType.STRING)
    private State state;
}

// 事件定義
public class DepartmentEvent {
    private Department department;
    private State state;
    public DepartmentEvent(Department department) {
        this.department = department;
        state = department.getState();
    }
}

// 領(lǐng)域服務(wù)
@Service
public class ApplicationService {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    private DepartmentRepository departmentRepository;

    @Transactional(rollbackFor = Exception.class)
    public void departmentAdd(Department department) {
        departmentRepository.save(department);
        // 事件發(fā)布
        applicationEventPublisher.publishEvent(new DepartmentEvent(department));
    }
}

使用applicationEventPublisher.publishEvent在領(lǐng)域服務(wù)處理完成后發(fā)布領(lǐng)域事件莉掂,此方法需要在業(yè)務(wù)代碼中顯式發(fā)布事件葛圃,并在領(lǐng)域服務(wù)里引入ApplicationEventPublisher類,但對(duì)領(lǐng)域服務(wù)本身有一定的入侵性憎妙,但靈活性較高库正。

二、 自動(dòng)發(fā)布事件

// 實(shí)體定義
@Entity
public class SaleOrder implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer orderId;
   
    @Enumerated(EnumType.STRING)
    private State state;

    // 返回類型定義
    @DomainEvents
    public List<Object> domainEvents(){
        return Stream.of(new SaleOrderEvent(this)).collect(Collectors.toList());
    }

    // 事件發(fā)布后callback
    @AfterDomainEventPublication
    void callback() {
        System.err.println("ok");
    }
}

// 事件定義
public class SaleOrderEvent {
    private SaleOrder saleOrder;
    private State state;
    public SaleOrderEvent(SaleOrder saleOrder) {
        this.saleOrder = saleOrder;
        state = saleOrder.getState();
    }
}

// 領(lǐng)域服務(wù)
@Service
public class ApplicationService {
    @Autowired
    private OrderRepository orderRepository;
    
    @Transactional(rollbackFor = Exception.class)
    public void saleOrderAdd(SaleOrder saleOrder) {
        orderRepository.save(saleOrder);
    }
}

使用@DomainEvents定義事件返回的類型厘唾,必須是一個(gè)集合褥符,使用@AfterDomainEventPublication定義事件發(fā)布后的回調(diào)。
此方法實(shí)事件類型定義在實(shí)體中抚垃,與領(lǐng)域服務(wù)完全解耦喷楣,沒(méi)有入侵趟大。系統(tǒng)會(huì)在orderRepository.save(saleOrder)后自動(dòng)調(diào)用事件發(fā)布,另delete方法不會(huì)調(diào)用事件發(fā)布抡蛙。

三、 事件監(jiān)聽(tīng)

@Component
public class ApplicationEventProcessor {

    @EventListener(condition = "#departmentEvent.getState().toString() == 'SUCCEED'")
    public void departmentCreated(DepartmentEvent departmentEvent) {
        System.err.println("dept-event1:" + departmentEvent);
    }

    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, condition = "#saleOrderEvent.getState().toString() == 'SUCCEED'")
    public void saleOrderCreated(SaleOrderEvent saleOrderEvent) {
        System.err.println("sale-event succeed1:" + saleOrderEvent);
    }

    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT, condition = "#saleOrderEvent.getState().toString() == 'SUCCEED'")
    public void saleOrderCreatedBefore(SaleOrderEvent saleOrderEvent) {
        System.err.println("sale-event succeed2:" + saleOrderEvent);
    }

    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void saleOrderCreatedFailed(SaleOrderEvent saleOrderEvent) {
        System.out.println("sale-event failed:" + saleOrderEvent);
    }
}

1. 使用@EventListener監(jiān)聽(tīng)事件

@EventListener沒(méi)有事務(wù)支持魂迄,只要事件發(fā)出就可監(jiān)控到

@Transactional(rollbackFor = Exception.class)
public void departmentAdd(Department department) {
    departmentRepository.save(department);
    applicationEventPublisher.publishEvent(new DepartmentEvent(department));
    throw new RuntimeException("failed");
}

上述情況會(huì)造成事務(wù)失敗回滾粗截,但事件監(jiān)控端已經(jīng)執(zhí)行,可能導(dǎo)致數(shù)據(jù)不一致的情況發(fā)生

2. 使用@TransactionalEventListener監(jiān)聽(tīng)事件

  • TransactionPhase.BEFORE_COMMIT 事務(wù)提交前
  • TransactionPhase.AFTER_COMMIT 事務(wù)提交后
  • TransactionPhase.AFTER_ROLLBACK 事務(wù)回滾后
  • TransactionPhase.AFTER_COMPLETION 事務(wù)完成后

使用TransactionPhase.AFTER_COMMIT可在事務(wù)完成后捣炬,再執(zhí)行事件監(jiān)聽(tīng)方法熊昌,從而保證數(shù)據(jù)的一致性

3. TransactionPhase.AFTER_ROLLBACK回滾事務(wù)問(wèn)題

@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK, condition = "#departmentEvent.getState().toString() == 'SUCCEED'")
public void departmentCreatedFailed(DepartmentEvent departmentEvent) {
    System.err.println("dept-event3:" + departmentEvent);
}

由于@DomainEvents作用在實(shí)體上的,只有剛orderRepository.save(saleOrder)執(zhí)行成功后才會(huì)發(fā)送事件湿酸,故AFTER_ROLLBACK方法只會(huì)在同一事務(wù)中其它語(yǔ)句執(zhí)行失敗或顯式rollback時(shí)才會(huì)執(zhí)行婿屹,如果save方法執(zhí)行失敗,將不會(huì)監(jiān)聽(tīng)到回滾事件推溃。

4. @Async異步事件監(jiān)聽(tīng)

  • 沒(méi)有此注解事件監(jiān)聽(tīng)方法與主方法為一個(gè)事務(wù)昂利。
  • 使用此注解將脫離原有事務(wù),BEFORE_COMMIT也無(wú)法攔截事務(wù)提交前時(shí)刻
  • 此注解需要配合@EnableAsync一起使用

四铁坎、 總結(jié)

通過(guò)對(duì) @DomainEvents蜂奸、@TransactionalEventListener的使用,在有效的解決領(lǐng)域事件發(fā)布的情況下硬萍,減少了對(duì)業(yè)務(wù)代碼的入侵扩所,同時(shí)盡一步解決了數(shù)據(jù)一致性問(wèn)題。
在分布式結(jié)構(gòu)下朴乖,通過(guò)MQ發(fā)送事件通知給其它服務(wù)祖屏,為解決一致性問(wèn)題,防止對(duì)方服務(wù)處理失敗可先將事件保久化到數(shù)據(jù)庫(kù)后买羞,再重試袁勺。

五、 源碼

https://gitee.com/hypier/barry-jpa/tree/master/jpa-section-5

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末畜普,一起剝皮案震驚了整個(gè)濱河市魁兼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌漠嵌,老刑警劉巖咐汞,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異儒鹿,居然都是意外死亡化撕,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)约炎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)植阴,“玉大人蟹瘾,你說(shuō)我怎么就攤上這事÷邮郑” “怎么了憾朴?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)喷鸽。 經(jīng)常有香客問(wèn)我众雷,道長(zhǎng),這世上最難降的妖魔是什么做祝? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任砾省,我火速辦了婚禮,結(jié)果婚禮上混槐,老公的妹妹穿的比我還像新娘编兄。我一直安慰自己,他們只是感情好声登,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布狠鸳。 她就那樣靜靜地躺著,像睡著了一般悯嗓。 火紅的嫁衣襯著肌膚如雪碰煌。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,737評(píng)論 1 305
  • 那天绅作,我揣著相機(jī)與錄音芦圾,去河邊找鬼。 笑死俄认,一個(gè)胖子當(dāng)著我的面吹牛个少,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播眯杏,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼夜焦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了岂贩?” 一聲冷哼從身側(cè)響起茫经,我...
    開(kāi)封第一講書(shū)人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎萎津,沒(méi)想到半個(gè)月后卸伞,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锉屈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年荤傲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颈渊。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡遂黍,死狀恐怖终佛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情雾家,我是刑警寧澤铃彰,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站芯咧,受9級(jí)特大地震影響牙捉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜唬党,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一鹃共、第九天 我趴在偏房一處隱蔽的房頂上張望鬼佣。 院中可真熱鬧驶拱,春花似錦、人聲如沸晶衷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)晌纫。三九已至税迷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锹漱,已是汗流浹背箭养。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哥牍,地道東北人毕泌。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像嗅辣,于是被迫代替她去往敵國(guó)和親撼泛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容