超越 CRUD: 命令分冈、事件和總線

轉(zhuǎn)帖:
軟件編寫有時候難以在預算內(nèi)按時完成的其中一個原因是,缺少對領(lǐng)域?qū)<宜f的對商業(yè)語言的關(guān)注霸株。大多數(shù)時候雕沉,確認需求意味著將理解的需求映射到某種關(guān)系數(shù)據(jù)模型。然后去件,構(gòu)建業(yè)務邏輯以在持久性層和表示層之間隧道傳輸數(shù)據(jù)坡椒,并在此過程中進行必要的調(diào)整。雖然并不完美尤溜,但該模式仍然使用了很長時間倔叼,而日漸增加的復雜性使得這種CRUD增刪改查模式已經(jīng)無法適應需求變化,無論如何宫莱,將其引入 DDD 的公式仍然是當今處理任何軟件項目最有效的方法丈攒。來自微軟Msdn網(wǎng)站一文:領(lǐng)先技術(shù) - 超越 CRUD: 命令、事件和總線討論了如何使用命令事件和總線替代傳統(tǒng)的CRUD系統(tǒng)授霸。轉(zhuǎn)貼并調(diào)整如下:事件在上述情況體現(xiàn)了用途肥印,因為它們強制進行不同形式的領(lǐng)域分析,更以任務為導向绝葡,而且也沒有必須趕快找出保存數(shù)據(jù)所用的完美關(guān)系數(shù)據(jù)模型的緊迫性深碱。比如旅館房間預訂案例中,無論房間何時被預訂藏畅,系統(tǒng)都會記錄涉及給定預訂 ID 的預訂創(chuàng)建的事件敷硅。若要檢索聚合(也就是預訂)的所有事件功咒,只需查詢指定預訂 ID 的事件數(shù)據(jù)存儲,就足以獲得所有信息绞蹦。這確實有效力奋,但它是一個很簡單的方案。聚合和對象事件/聚合關(guān)聯(lián)是以業(yè)務域的通用語言編寫的幽七。不管怎樣景殷,與更簡單的一對一關(guān)聯(lián)相比,一對多關(guān)聯(lián)更可能發(fā)生澡屡。具體來說猿挚,事件和聚合之間的一對多關(guān)聯(lián)意味著事件有時候與多聚合相關(guān),且可能不止一個聚合關(guān)注處理該事件驶鹉,并可能由于該事件更改其狀態(tài)绩蜻。例如,設(shè)想一個方案:在系統(tǒng)中將發(fā)票注冊為正在進行的工作訂單的成本。這意味著,在你的域模型中可能有兩個聚合 - 發(fā)票和工作訂單试伙。注冊的事件發(fā)票會因為新的發(fā)票進入到系統(tǒng)中而捕獲發(fā)票聚合的關(guān)注,但如果發(fā)票涉及與訂單相關(guān)的一些活動孕蝉,它可能還會捕獲 JobOrder 聚合的關(guān)注。很明顯腌逢,只有在完全理解業(yè)務域后昔驱,才能確定發(fā)票是否與工作訂單相關(guān)。在這里可能有發(fā)票獨立存在的域模型(和應用程序)和發(fā)票可能在工作訂單的記帳中注冊并隨后更改當前余額的域模型(和應用程序)上忍。但是,知道事件可能與很多聚合相關(guān)這一點完全改變了解決方案的體系結(jié)構(gòu)和可行技術(shù)的前景纳本。調(diào)度事件分解復雜性事件被綁定到單個聚合的重大約束是 CRUD 和 H-CRUD 的基礎(chǔ)所在窍蓝。當業(yè)務事件涉及多個聚合時,你編寫業(yè)務邏輯代碼以確保狀態(tài)被適當?shù)馗暮透櫡背伞.斁酆虾褪录臄?shù)量超過嚴重閾值時吓笙,業(yè)務邏輯代碼的復雜性可能變得難以處理和演變。在此上下文中巾腕,CQRS 模式代表了在正確方向上邁出的第一步面睛,因為它基本上建議你對系統(tǒng)當前狀態(tài)的“僅讀取”或“僅修改”操作進行單獨推斷。事件源是另一種流行的模式尊搬,它建議你將所有發(fā)生在系統(tǒng)中的操作記錄為事件叁鉴。跟蹤系統(tǒng)的整個狀態(tài),并將系統(tǒng)中聚合的實際狀態(tài)構(gòu)建為事件的投影佛寿。換句話說幌墓,你將事件的內(nèi)容映射到其他屬性,它們?nèi)恳黄鸾M成了軟件中可用的對象狀態(tài)。事件源圍繞知道如何保存和檢索事件的框架構(gòu)建常侣。事件源機制為僅追加蜡饵,支持事件流的重播,并知道如何保存可能具有截然不同布局的相關(guān)數(shù)據(jù)胳施。諸如 EventStore (bit.ly/1UPxEUP) 和 NEventStore (bit.ly/1UdHcfz) 的事件存儲框架抽象出真正的持久性框架溯祸,并提供高級 API 以在代碼中直接使用事件進行處理。從本質(zhì)上而言舞肆,你所看到的事件流具有一定相關(guān)性焦辅,對于這些事件進行關(guān)注的目的是聚合。這將會順利運行胆绊。但是氨鹏,當某個事件對多個聚合具有影響時,你應該找到一種方法压状,使每個聚合都能夠跟蹤其關(guān)注的所有事件仆抵。此外,你應當設(shè)法構(gòu)建一個軟件基礎(chǔ)結(jié)構(gòu)种冬,該結(jié)構(gòu)不僅關(guān)注事件持久性镣丑,還允許向所有運行中的聚合通知所關(guān)注的事件。要實現(xiàn)將事件正確調(diào)度到聚合和適當?shù)氖录志眯杂榱剑琀-CRUD 是不夠的莺匠。必須再次討論業(yè)務邏輯背后的模式和用于保存事件相關(guān)數(shù)據(jù)的技術(shù)。定義聚合聚合的概念來自 DDD十兢,簡單地說趣竣,是指組合到一起以匹配事務一致性的域?qū)ο笕杭J聞找恢滦詢H意味著旱物,保證在聚合內(nèi)組成的任何事務在業(yè)務操作結(jié)尾均保持一致且處于最新狀態(tài)遥缕。下面的代碼片段演示了總結(jié)任何聚合類的主要方面的界面∠海可能還有更多单匣,但我敢說這絕對是最少的值:public interface IAggregate{Guid ID { get; }bool HasPendingChanges { get; }IList<DomainEvent> OccurredEvents { get; set; }IEnumerable<DomainEvent> GetUncommittedEvents();}

無論何時,聚合均包含所發(fā)生事件的列表宝穗,并可區(qū)分已提交的事件和未提交的事件(導致掛起更改)户秤。實現(xiàn) IAggregate 界面的基類需要非公共成員設(shè)置 ID 并實施已提交和未提交事件的列表。此外逮矛,聚合基類也有一些 RaiseEvent 方法鸡号,用于向未提交事件的內(nèi)部列表添加事件。有趣的是须鼎,事件是如何供內(nèi)部使用以更改聚合狀態(tài)的呢膜蠢?假設(shè)你有一個客戶聚合堪藐,并想要更新客戶的公共名稱。在 CRUD 方案中挑围,只需進行以下簡單的分配:customer.DisplayName = "new value";

如果使用事件礁竞,將會是一個更復雜的路線:public void Handle(ChangeCustomerNameCommand command){var customer = _customerRepository.GetById(command.CompanyId);customer.ChangeName(command.DisplayName);customerRepository.Save(customer);}

此刻,讓我們先跳過 Handle 方法和運行此方法的人員杉辙,而將注意力集中在實現(xiàn)上模捂。起初,ChangeName 看起來似乎僅僅是先前檢查的 CRUD 樣式代碼的包裝器蜘矢。其實并不完全是這樣:public void ChangeName(string newDisplayName){var evt = new CustomerNameChangedEvent(this.Id, newDisplayName);RaiseEvent(e);}

在聚合基類上定義的 RaiseEvent 方法將在未提交事件的內(nèi)部列表中追加事件狂男。未提交的事件最終會在聚合保存時處理。通過事件保存狀態(tài)隨著對事件更深入的了解品腹,可將存儲庫類的結(jié)構(gòu)設(shè)為泛型岖食。目前描述的設(shè)計用于使用聚合類運行的存儲庫的 Save 方法僅遍歷聚合未提交事件的列表,并調(diào)用聚合必須提供的新方法 - ApplyEvent 方法:public void ApplyEvent(CustomerNameChangedEvent evt){this.DisplayName = evt.DisplayName;}

聚合類將擁有對每個所關(guān)注事件的 ApplyEvent 方法的一個重載舞吭。過去考慮的 CRUD 樣式代碼會在此找到它的位置泡垃。還有一個缺少的鏈接: 如何安排前端用例、具有多聚合的最終用戶操作羡鸥、業(yè)務工作流和持久性蔑穴? 你需要一個總線組件。介紹總線組件總線組件可定義為運行在已知業(yè)務流程的實例間的共享路徑惧浴。最終用戶通過表示層執(zhí)行操作存和,并為系統(tǒng)設(shè)置要處理的指令。應用程序?qū)咏邮者@些輸入并將其轉(zhuǎn)換為具體的業(yè)務操作衷旅。在 CRUD 方案中捐腿,應用程序?qū)訉⒅苯诱{(diào)用對請求的操作負責的業(yè)務流程(即工作流)。當聚合和業(yè)務規(guī)則數(shù)量過多時柿顶,總線將大大簡化整體設(shè)計茄袖。應用程序?qū)訉⒚罨蚴录扑椭量偩€,以便偵聽器正確地作出響應九串。偵聽器是常被稱為“sagas”的組件,它是已知業(yè)務流程的最終實例寺鸥。Saga 知道如何對大量命令和事件做出響應猪钮。Saga 具有持久性層的訪問權(quán)限,并可將命令和事件推送回總線胆建。Saga 是上述 Handle 方法所屬的類烤低。通常,每個工作流或用案都有一個 saga 類笆载,它可以通過其可處理的事件和命令完全識別扑馁。整體生成的體系結(jié)構(gòu)如圖 1 所示涯呻。


最后請注意,事件也必須保存并回到其源進行查詢腻要。而這引出了另一要點: 經(jīng)典關(guān)系數(shù)據(jù)庫是否是存儲事件的理想之選复罐? 不同事件可以在開發(fā)過程中及后期生產(chǎn)過程中隨時添加。此外雄家,每個事件都有其各自的架構(gòu)效诅。在此上下文中,非關(guān)系數(shù)據(jù)存儲是適合的(盡管使用關(guān)系數(shù)據(jù)庫仍然是一種可選方案 - 至少是依照充分證據(jù)考慮和排除的方案)趟济。總結(jié)我敢說乱投,對軟件復雜性的大部分看法是來自以下事實:盡管基于縮寫詞(創(chuàng)建、讀取顷编、更新戚炫、刪除)中的基本四項操作不再像讀取和編寫單個表或聚合那樣簡單,但我們?nèi)岳^續(xù)考慮對系統(tǒng)使用 CRUD 方法媳纬。本文是對模式和工具更深入分析的預告双肤,下個月我會繼續(xù)對此討論,到時我將演示試圖使此類開發(fā)更快和可持續(xù)的框架层宫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末杨伙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子萌腿,更是在濱河造成了極大的恐慌限匣,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毁菱,死亡現(xiàn)場離奇詭異米死,居然都是意外死亡,警方通過查閱死者的電腦和手機贮庞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門峦筒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人窗慎,你說我怎么就攤上這事物喷。” “怎么了遮斥?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵峦失,是天一觀的道長。 經(jīng)常有香客問我术吗,道長尉辑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任较屿,我火速辦了婚禮隧魄,結(jié)果婚禮上卓练,老公的妹妹穿的比我還像新娘。我一直安慰自己购啄,他們只是感情好襟企,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布闸溃。 她就那樣靜靜地躺著整吆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辉川。 梳的紋絲不亂的頭發(fā)上表蝙,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機與錄音乓旗,去河邊找鬼府蛇。 笑死,一個胖子當著我的面吹牛屿愚,可吹牛的內(nèi)容都是我干的汇跨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼妆距,長吁一口氣:“原來是場噩夢啊……” “哼穷遂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起娱据,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蚪黑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后中剩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忌穿,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年结啼,在試婚紗的時候發(fā)現(xiàn)自己被綠了掠剑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡郊愧,死狀恐怖朴译,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情属铁,我是刑警寧澤眠寿,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站红选,受9級特大地震影響澜公,放射性物質(zhì)發(fā)生泄漏姆另。R本人自食惡果不足惜喇肋,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一坟乾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蝶防,春花似錦甚侣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至低葫,卻和暖如春详羡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嘿悬。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工实柠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人善涨。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓窒盐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钢拧。 傳聞我的和親對象是個殘疾皇子蟹漓,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,144評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)源内,斷路器葡粒,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • application的配置屬性。 這些屬性是否生效取決于對應的組件是否聲明為Spring應用程序上下文里的Bea...
    新簽名閱讀 5,372評論 1 27
  • 取了好大一個題目,其實寫不了多少字呻此。杭州距北京1270多公里轮纫,高鐵約5到6小時。北京有個故宮焚鲜,故宮有個兩個大大的書...
    桂之華閱讀 173評論 7 7
  • 感恩愛人不但這兩天都陪著孩子們飯后下樓玩兒還和琳一起學習平衡車和英語掌唾,讓孩子們學習英語的熱情空前高漲,昨晚爺仨在那...
    寸心潔白閱讀 311評論 1 3