領(lǐng)域事件是DDD歌溉,Event Sourcing等架構(gòu)中的一個重要概念。
基于工作環(huán)境中的一些實(shí)踐經(jīng)驗(yàn)瓶颠,我總結(jié)了一些粗淺的體會拟赊。
文中不會有非常高深復(fù)雜的定義,只使用一些基本的概念粹淋。
領(lǐng)域事件定義
國外書籍中很少使用下定義的方式解釋一個名詞的含義吸祟。一般都會使用作詮釋的方式:“領(lǐng)域事件可以怎樣怎樣”、“領(lǐng)域事件會帶來什么什么”桃移。
讓我來下定義的話:領(lǐng)域事件是微服務(wù)系統(tǒng)中各服務(wù)通信的一種方式屋匕。和RPC類似,領(lǐng)域事件用于在服務(wù)之間交換信息借杰,但領(lǐng)域事件并不追求實(shí)時一致性过吻,僅要求數(shù)據(jù)的最終一致性。
各服務(wù)在一些關(guān)鍵有意義的業(yè)務(wù)節(jié)點(diǎn)完成后會發(fā)送領(lǐng)域事件蔗衡,代表一些關(guān)鍵的動作「已完成」纤虽。因此領(lǐng)域事件的命名多采用過去分詞,例如「訂單已發(fā)送Order sent」「訂單已取消Order canceled」绞惦。
領(lǐng)域事件有多種實(shí)現(xiàn)方式逼纸。我工作中使用到的方式是:將一些關(guān)鍵的、其他服務(wù)可能用到的信息济蝉,打包成一個結(jié)構(gòu)體杰刽,以消息的形式菠发,通過消息隊列中間件,異步傳輸給系統(tǒng)中訂閱該消息的服務(wù)专缠。
領(lǐng)域事件帶來的收益
介紹領(lǐng)域事件的書籍從不吝惜對領(lǐng)域事件優(yōu)點(diǎn)的贊美雷酪。在此我針對各優(yōu)點(diǎn)談?wù)勛约旱母惺堋?/p>
增強(qiáng)服務(wù)自治性
這大概是領(lǐng)域事件最常被提及的優(yōu)點(diǎn)了。何謂“自治性”涝婉?我的理解是:一個服務(wù)只需要關(guān)注自己哥力,不與其他服務(wù)耦合的程度。單體服務(wù)不依賴其他任何服務(wù)墩弯,自治性最高吩跋;如果一個服務(wù)調(diào)用了大量其他服務(wù)的RPC,那么它的穩(wěn)定性和業(yè)務(wù)邏輯必定受其他服務(wù)的影響渔工,自治性稍差锌钮。
為什么能提升自治性愧旦?個人認(rèn)為有以下原因:
首先領(lǐng)域事件一般使用消息隊列實(shí)現(xiàn)墓猎,消息隊列是異步的爆雹,不會阻塞調(diào)用休溶。如果因一時網(wǎng)絡(luò)原因消息無法傳達(dá)宁仔,那么也會由消息組件重試逾条。領(lǐng)域事件可以看做異步的君账、非阻塞的酝惧、由第三方保證最終送達(dá)的RPC区端,可靠性比普通的RPC更高值漫。
另一點(diǎn)就是領(lǐng)域事件不必指定消息的接收方是誰。假設(shè)我們有這樣的業(yè)務(wù)場景织盼,A服務(wù)完成了某一動作后杨何,需要通知B。如果我們使用RPC通信沥邻,后面業(yè)務(wù)擴(kuò)展時增加了C危虱,D服務(wù)需要通知,則實(shí)現(xiàn)時我們需要修改A的代碼谋国,A的業(yè)務(wù)邏輯可能還要受到C槽地,D處理結(jié)果的影響。但是如果我們使用領(lǐng)域事件進(jìn)行通信芦瘾,則在新增C捌蚊,D服務(wù)之后,讓它們訂閱A發(fā)出的事件即可近弟,A服務(wù)完全不需要修改缅糟,不受其他服務(wù)的影響,提升了自治性祷愉。
回放系統(tǒng)動作
如果我們把系統(tǒng)看做一個狀態(tài)機(jī)窗宦,則領(lǐng)域事件可以看做推動狀態(tài)機(jī)轉(zhuǎn)移狀態(tài)的輸入赦颇。領(lǐng)域事件是具有業(yè)務(wù)含義的,回放某一時間段的領(lǐng)域事件可以復(fù)現(xiàn)用戶操作赴涵。通過數(shù)據(jù)庫binlog回放也可以達(dá)到類似的效果媒怯,但binlog是沒有業(yè)務(wù)語義的,回放結(jié)果沒有準(zhǔn)確的解讀方式髓窜。
回放領(lǐng)域事件當(dāng)然需要某種消息存儲/檢索機(jī)制扇苞,這里不做展開。
實(shí)踐領(lǐng)域事件時的一些思考
領(lǐng)域事件實(shí)時性
領(lǐng)域事件可以保證最終一致性寄纵,但不能保證數(shù)據(jù)實(shí)時性鳖敷。產(chǎn)生的時延是否是業(yè)務(wù)可以接受的?《實(shí)現(xiàn)領(lǐng)域驅(qū)動設(shè)計精粹》中進(jìn)行了一個精妙的類比:在軟件出現(xiàn)前程拭,傳統(tǒng)業(yè)務(wù)的執(zhí)行流程是怎樣的定踱?流程步驟之間是否會有一定的時間差?如果領(lǐng)域事件導(dǎo)致的時延不超過無軟件時業(yè)務(wù)流程的時延恃鞋,那么這時延就是可以接受的崖媚。當(dāng)然,實(shí)際生產(chǎn)環(huán)境中恤浪,說服用戶接受這樣的時延通常不是件容易的事情至扰。領(lǐng)域事件定義
領(lǐng)域事件有業(yè)務(wù)語義,這個業(yè)務(wù)語義所代表的動作资锰,必須要有明確的定義。例如:在一個招聘系統(tǒng)中阶祭,目前只有“現(xiàn)場面試簽到”這一個場景绷杜。那么這個動作完成以后,應(yīng)該發(fā)出「面試已簽到」領(lǐng)域事件濒募,還是發(fā)出「現(xiàn)場面試已簽到」的領(lǐng)域事件呢鞭盟?如果是前者,那么后續(xù)新增的其他類型面試簽到瑰剃,也要發(fā)出這個領(lǐng)域事件齿诉。領(lǐng)域事件的業(yè)務(wù)語義必須得到準(zhǔn)確的解釋,從命名開始晌姚。領(lǐng)域事件表示的動作粒度也要在定義前就想好粤剧。領(lǐng)域事件管理
目前我們有yAPI這樣優(yōu)秀的接口管理工具,但還沒有合適的領(lǐng)域事件管理工具挥唠。領(lǐng)域事件和接口都是用于通信的手段抵恋,所以他們的含義,包括功能宝磨、每個字段含義等弧关,必須得到準(zhǔn)確的解釋盅安。否則當(dāng)領(lǐng)域事件增加到一定規(guī)模,有人員更迭時世囊,了解各個領(lǐng)域事件將成為惱火的事情别瞭。向領(lǐng)域事件改造
EDA是一種時髦的架構(gòu),但并非所有服務(wù)一開始就會選用這種架構(gòu)株憾。一個單純由RPC通信的系統(tǒng)蝙寨,想要轉(zhuǎn)型到通過領(lǐng)域事件通信會遇到很多問題。我們系統(tǒng)遇到的一個現(xiàn)實(shí)問題是:太多的接口可能會發(fā)送同一種領(lǐng)域事件号胚,如果在所有可能的代碼后加上領(lǐng)域事件籽慢,很可能造成遺漏。我們系統(tǒng)對此的解決方案是:將領(lǐng)域事件發(fā)送收斂到數(shù)據(jù)庫上猫胁。業(yè)務(wù)操作箱亿,最終的效果是修改數(shù)據(jù)庫,那么我們就監(jiān)聽相應(yīng)數(shù)據(jù)庫的binglog弃秆,發(fā)出相應(yīng)的領(lǐng)域事件届惋。
這種解決方案可能會有以下問題:
首先一個領(lǐng)域事件可能對應(yīng)不只一張表的修改。為了不遺漏領(lǐng)域事件菠赚,我們不得不監(jiān)聽多張表脑豹。每個表修改時,我們都會發(fā)出領(lǐng)域事件衡查,這就導(dǎo)致業(yè)務(wù)上同一個領(lǐng)域事件可能會重復(fù)發(fā)送瘩欺。冪等操作只能交給接收方來做了。
監(jiān)聽binlog發(fā)送領(lǐng)域事件帶來的另一個改造問題是消息中各字段的準(zhǔn)確性問題拌牲。正常發(fā)送領(lǐng)域事件時俱饿,在業(yè)務(wù)處理的過程中就能獲取領(lǐng)域事件的各個字段。但一張表上可能不會有完整的領(lǐng)域事件信息塌忽,需要反查其他表進(jìn)行字段填充拍埠。某一業(yè)務(wù)操作會修改t1,t2兩張表土居,A枣购,B兩個業(yè)務(wù)操作前后發(fā)生。A操作導(dǎo)致的t1 binlog消息到達(dá)了擦耀,查詢t2表上的字段棉圈,此時t2卻已經(jīng)被B操作修改了,則反查出來的結(jié)果是B操作后的數(shù)據(jù)結(jié)果眷蜓。那么拼裝消息時迄损,部分字段是A導(dǎo)致的,部分字段是B導(dǎo)致的账磺,就會組裝出一個沒有真實(shí)發(fā)生的領(lǐng)域事件芹敌,可能對接收方有影響痊远,具體影響還需要接收方想辦法化解。領(lǐng)域事件處理
處理消息逃不開的三個問題:冪等氏捞,重試碧聪,丟失。真實(shí)寫代碼時有太多細(xì)節(jié)需要注意液茎,只能case by case的看了逞姿。