事件風(fēng)暴

DDD將研發(fā)者的目光首先聚焦在業(yè)務(wù)本身上倦西,使技術(shù)架構(gòu)和代碼實(shí)現(xiàn)成為軟件建模過(guò)程中的“副產(chǎn)品”。事件風(fēng)暴都能有效地實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)粉铐,從而建立起良好的領(lǐng)域模型及服務(wù)邊界秦躯。

一裆装、領(lǐng)域事件

想一下計(jì)算機(jī)硬件的工作原理哨免,整個(gè)計(jì)算機(jī)的工作過(guò)程其實(shí)就是一個(gè)對(duì)事件的處理過(guò)程昙沦。當(dāng)你點(diǎn)擊鼠標(biāo)、敲擊鍵盤(pán)或者插上U盤(pán)時(shí)采桃,計(jì)算機(jī)便以中斷的形式處理各種外部事件普办。在軟件開(kāi)發(fā)領(lǐng)域徘钥,事件驅(qū)動(dòng)架構(gòu)(Event Driven Architecture,EDA)早已被開(kāi)發(fā)者用于各種實(shí)踐舆驶,典型的應(yīng)用場(chǎng)景比如瀏覽器對(duì)用戶輸入的處理沙廉、消息機(jī)制以及SOA臼节。

1.1 什么是領(lǐng)域事件

領(lǐng)域事件(Domain Events)是領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain Driven Design珊皿,DDD)中的一個(gè)概念亮隙,用于捕獲我們所建模的領(lǐng)域中所發(fā)生過(guò)的事情溢吻。領(lǐng)域事件本身也作為通用語(yǔ)言(Ubiquitous Language)的一部分成為包括領(lǐng)域?qū)<以趦?nèi)的所有項(xiàng)目成員的交流用語(yǔ)果元。比如,在用戶注冊(cè)過(guò)程中蝇狼,我們可能會(huì)說(shuō)“當(dāng)用戶注冊(cè)成功之后倡怎,發(fā)送一封歡迎郵件給客戶监署。”钠乏,此時(shí)的“用戶已經(jīng)注冊(cè)”便是一個(gè)領(lǐng)域事件晓避。

當(dāng)然,并不是所有發(fā)生過(guò)的事情都可以成為領(lǐng)域事件暑塑。一個(gè)領(lǐng)域事件必須對(duì)業(yè)務(wù)有價(jià)值彰触,有助于形成完整的業(yè)務(wù)閉環(huán)况毅,也即一個(gè)領(lǐng)域事件將導(dǎo)致進(jìn)一步的業(yè)務(wù)操作。舉個(gè)咖啡廳建模的例子么鹤,當(dāng)客戶來(lái)到前臺(tái)時(shí)將產(chǎn)生“客戶已到達(dá)”的事件味廊,如果你關(guān)注的是客戶接待,比如需要為客戶預(yù)留位置等柠新,那么此時(shí)的“客戶已到達(dá)”便是一個(gè)典型的領(lǐng)域事件,因?yàn)樗鼘⒂糜谟|發(fā)下一步——“預(yù)留位置”操作蕊退;但是如果你建模的是咖啡結(jié)賬系統(tǒng)憔恳,那么此時(shí)的“客戶已到達(dá)”便沒(méi)有多大存在的必要——你不可能在用戶到達(dá)時(shí)就立即向客戶要錢(qián)對(duì)吧钥组,而”客戶已下單“才是對(duì)結(jié)賬系統(tǒng)有用的事件。

1.2 領(lǐng)域事件的應(yīng)用

在DDD中有一條原則:一個(gè)業(yè)務(wù)用例對(duì)應(yīng)一個(gè)事務(wù)点把,一個(gè)事務(wù)對(duì)應(yīng)一個(gè)聚合根屿附,也即在一次事務(wù)中拿撩,只能對(duì)一個(gè)聚合根進(jìn)行操作压恒。但是在實(shí)際應(yīng)用中错邦,我們經(jīng)常發(fā)現(xiàn)一個(gè)用例需要修改多個(gè)聚合根的情況,并且不同的聚合根還處于不同的限界上下文中伦吠。比如毛仪,當(dāng)你在電商網(wǎng)站上買(mǎi)了東西之后芯勘,你的積分會(huì)相應(yīng)增加。這里的購(gòu)買(mǎi)行為可能被建模為一個(gè)訂單(Order)對(duì)象衡怀,而積分可以建模成賬戶(Account)對(duì)象的某個(gè)屬性,訂單和賬戶均為聚合根够委,并且分別屬于訂單系統(tǒng)和賬戶系統(tǒng)怖现。顯然,我們需要在訂單和積分之間維護(hù)數(shù)據(jù)一致性脐雪,通常的做法是在同一個(gè)事務(wù)中同時(shí)更新兩者战秋,但是這會(huì)存在以下問(wèn)題:1)違背DDD中"單個(gè)事務(wù)修改單個(gè)聚合根"的設(shè)計(jì)原則;2)需要在不同的系統(tǒng)之間采用重量級(jí)的分布式事務(wù)(Distributed Transactioin脂信,也叫XA事務(wù)或者全局事務(wù))狰闪;3)在不同系統(tǒng)之間產(chǎn)生強(qiáng)耦合濒生。

通過(guò)引入領(lǐng)域事件,我們可以很好地解決上述問(wèn)題丽声。 總的來(lái)說(shuō)觉义,領(lǐng)域事件給我們帶來(lái)以下好處:1)解耦微服務(wù)(限界上下文);2)幫助我們深入理解領(lǐng)域模型霉撵;3)提供審計(jì)和報(bào)告的數(shù)據(jù)來(lái)源洪囤;4)邁向事件溯源(Event Sourcing)和CQRS等箍鼓。

還是以上面的電商網(wǎng)站為例,當(dāng)用戶下單之后何暮,訂單系統(tǒng)將發(fā)出一個(gè)“用戶已下單”的領(lǐng)域事件,并發(fā)布到消息系統(tǒng)中海洼,此時(shí)下單便完成了。賬戶系統(tǒng)訂閱了消息系統(tǒng)中的“用戶已下單”事件域帐,當(dāng)事件到達(dá)時(shí)進(jìn)行處理肖揣,提取事件中的訂單信息浮入,再調(diào)用自身的積分引擎(也有可能是另一個(gè)微服務(wù))計(jì)算積分,最后更新用戶積分彤断∫准#可以看到睹欲,此時(shí)的訂單系統(tǒng)在發(fā)送了事件之后,整個(gè)用例操作便結(jié)束了窘疮,根本不用關(guān)心是誰(shuí)收到了事件或者對(duì)事件做了什么處理考余。事件的消費(fèi)方可以是賬戶系統(tǒng)楚堤,也可以是任何一個(gè)對(duì)事件感興趣的第三方身冬,比如物流系統(tǒng)岔乔。由此,各個(gè)微服務(wù)之間的耦合關(guān)系便解開(kāi)了嘿歌。值得注意的一點(diǎn)是,此時(shí)各個(gè)微服務(wù)之間不再是強(qiáng)一致性丧凤,而是基于事件的最終一致性。

此外愿待,領(lǐng)域事件也可以用于微服務(wù)內(nèi)部仍侥,此時(shí)不需要消息中間件鸳君,可以是一個(gè)同步過(guò)程,如下

二腿时、事件風(fēng)暴

事件風(fēng)暴是一項(xiàng)團(tuán)隊(duì)活動(dòng)批糟,旨在通過(guò)領(lǐng)域事件識(shí)別出聚合根看铆,進(jìn)而劃分微服務(wù)的限界上下文。在活動(dòng)中否淤,團(tuán)隊(duì)先通過(guò)頭腦風(fēng)暴的形式羅列出領(lǐng)域中所有的領(lǐng)域事件石抡,整合之后形成最終的領(lǐng)域事件集合,然后對(duì)于每一個(gè)事件啰扛,標(biāo)注出導(dǎo)致該事件的命令(Command)隐解,再然后為每個(gè)事件標(biāo)注出命令發(fā)起方的角色煞茫,命令可以是用戶發(fā)起,也可以是第三方系統(tǒng)調(diào)用或者是定時(shí)器觸發(fā)等蚓曼。最后對(duì)事件進(jìn)行分類(lèi)整理出聚合根以及限界上下文炸宵。

2.1 創(chuàng)建領(lǐng)域事件

領(lǐng)域事件應(yīng)該回答“什么人什么時(shí)候做了什么事情”這樣的問(wèn)題土全,在實(shí)際編碼中,可以考慮采用層超類(lèi)型(Layer Supertype)來(lái)包含事件的某些共有屬性:

可以看到瑞凑,領(lǐng)域事件還包含了ID籽御,但是該ID并不是實(shí)體(Entity)層面的ID概念技掏,而是主要用于事件追溯和日志项鬼。另外,由于領(lǐng)域事件描述的是過(guò)去發(fā)生的事情鸠真,我們應(yīng)該將領(lǐng)域事件建模成不可變的(Immutable)龄毡。從DDD概念上講沦零,領(lǐng)域事件更像一種特殊的值對(duì)象(Value Object)。對(duì)于上文中提到的咖啡廳例子序攘,創(chuàng)建“客戶已到達(dá)”事件如下:

在這個(gè)CustomerArrivedEvent事件中,除了繼承自Event的屬性外丈牢,還自定義了一個(gè)與該事件密切關(guān)聯(lián)的業(yè)務(wù)屬性——客戶人數(shù)(customerNumber)——這樣后續(xù)操作便可預(yù)留相應(yīng)數(shù)目的座位了己沛。另外距境,我們將所有屬性以及CustomerArrivedEvent本身都聲明成了final垫桂,并且不向外暴露任何可能修改這些屬性的方法诬滩,這樣便保證了事件的不變性灭将。

2.2 發(fā)布領(lǐng)域事件

推薦的方式是在聚合根中在聚合根中臨時(shí)保存領(lǐng)域事件:

然后在資源庫(kù)repository發(fā)布事件并清空聚合根中的事件空镜。

2.3 聚合更新與事件發(fā)布的原子性

業(yè)務(wù)操作和事件發(fā)布之間應(yīng)該是原子的捌朴,要么全部成功砂蔽,要么全部失敗以“訂單積分”為例察皇,如果客戶下單成功,但是事件發(fā)送失敗矾缓,下游的賬戶系統(tǒng)便拿不到事件嗜闻,導(dǎo)致最終客戶的積分并不增加桅锄。

要保證業(yè)務(wù)操作和事件發(fā)布之間的原子性,最直接的方法便是采用XA事務(wù)翠肘,比如Java中的JTA束倍,這種方式由于其重量級(jí)并不被人們所看好。但是,對(duì)于一些對(duì)性能要求不那么高的系統(tǒng)绪妹,這種方式未嘗不是一個(gè)選擇甥桂。一些開(kāi)發(fā)框架已經(jīng)能夠支持獨(dú)立于應(yīng)用服務(wù)器的XA事務(wù)管理器(如AtomikosBitronix),比如Spring Boot作為一個(gè)微服務(wù)框架便提供了對(duì)Atomikos和Bitronix的支持邮旷。

另一個(gè)選項(xiàng)是采用事件表的方式黄选。這種方式首先將事件保存到聚合根所在的數(shù)據(jù)庫(kù)中,由于事件表和聚合根表同屬一個(gè)數(shù)據(jù)庫(kù)婶肩,整個(gè)過(guò)程只需要一個(gè)本地事務(wù)就能完成办陷。然后,在一個(gè)單獨(dú)的后臺(tái)任務(wù)中讀取事件表中未發(fā)布的事件狡孔,再將事件發(fā)布到消息中間件中懂诗。

這種方式需要注意兩個(gè)問(wèn)題:

1)事件發(fā)布后需要在數(shù)據(jù)庫(kù)標(biāo)記成“已發(fā)布”狀態(tài),發(fā)布事件和標(biāo)記“已發(fā)布”之間需要原子性殃恒。建議把事件的消費(fèi)方創(chuàng)建成冪等的,即消費(fèi)方可以多次消費(fèi)同一個(gè)事件而不污染系統(tǒng)數(shù)據(jù)亥鬓。這個(gè)過(guò)程大致為:整個(gè)過(guò)程中事件發(fā)送和數(shù)據(jù)庫(kù)更新采用各自的事務(wù)管理嵌戈,此時(shí)有可能發(fā)生的情況是事件發(fā)送成功而數(shù)據(jù)庫(kù)更新失敗,這樣在下一次事件發(fā)布操作中庵朝,由于先前發(fā)布過(guò)的事件在數(shù)據(jù)庫(kù)中依然是“未發(fā)布”狀態(tài),該事件將被重新發(fā)布到消息系統(tǒng)中侄旬,導(dǎo)致事件重復(fù)儡羔,但由于事件的消費(fèi)方是冪等的,因此事件重復(fù)不會(huì)存在問(wèn)題鉴扫。

2)持久化機(jī)制的選擇。其實(shí)對(duì)于DDD中的聚合根來(lái)說(shuō)莱预,NoSQL是相比于關(guān)系型數(shù)據(jù)庫(kù)更合適的選擇依沮,比如用MongoDB的Document保存聚合根便是種很自然的方式。但是多數(shù)NoSQL是不支持ACID的辜限,也就是說(shuō)不能保證聚合更新和事件發(fā)布之間的原子性。還好毫深,關(guān)系型數(shù)據(jù)庫(kù)也在向NoSQL方向發(fā)展,比如新版本的MySQL(版本5.7)已經(jīng)能夠提供具備N(xiāo)oSQL特征的JSON存儲(chǔ)和基于JSON的查詢鸳址。此時(shí),我們可以考慮將聚合根序列化成JSON格式的數(shù)據(jù)進(jìn)行保存巡球,從而避免了使用重量級(jí)的ORM工具险胰,又可以在多個(gè)數(shù)據(jù)之間保證ACID起便,何樂(lè)而不為榆综?

三、事件風(fēng)暴工作坊

3.1 識(shí)別領(lǐng)域事件

3.2 識(shí)別決策命令


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市水评,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌疗涉,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異偏瓤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)证舟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)浪读,“玉大人互订,你說(shuō)我怎么就攤上這事氮墨。” “怎么了猛铅?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵栗菜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我而咆,道長(zhǎng),這世上最難降的妖魔是什么馍驯? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮趴乡,結(jié)果婚禮上哀托,老公的妹妹穿的比我還像新娘仓手。我一直安慰自己,他們只是感情好剿另,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布戚篙。 她就那樣靜靜地躺著浪耘,像睡著了一般痛倚。 火紅的嫁衣襯著肌膚如雪抒蚜。 梳的紋絲不亂的頭發(fā)上收津,一...
    開(kāi)封第一講書(shū)人閱讀 51,578評(píng)論 1 305
  • 那天唆姐,我揣著相機(jī)與錄音剧蹂,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛简烤,可吹牛的內(nèi)容都是我干的绰姻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了旁涤?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤由缆,失蹤者是張志新(化名)和其女友劉穎是晨,沒(méi)想到半個(gè)月后层扶,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體檬寂,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡塞茅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了席覆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡孤荣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情秸谢,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布冷尉,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜进统,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一抚芦、第九天 我趴在偏房一處隱蔽的房頂上張望褥民。 院中可真熱鬧耘拇,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工鹿榜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怀薛,地道東北人焚碌。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蚣常。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泌射,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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