命令模型

Command Model

在一個基于CQRS的應用程序中,一個領域模型(如Eric Evans和Martin Fowler所定義的)可以是一個非常強大的機制炬转,這種機制可以處理聚合狀態(tài)發(fā)生改變時的校驗和執(zhí)行時所帶來的復雜性验游。通常一個典型的領域模型會有大量的構建塊浓瞪,但是在CQRS中的Command 處理過程中有一個重要的角色就是:聚合宏邮。

Command是應用程序狀態(tài)發(fā)生改變的源頭根盒。命令是表達意圖(它描述你想要做什么)和你所須要的信息的組合褐健。命令模型用于處理傳入的命令付鹿,以驗證它并定義他的輸出結果。在這個模型中蚜迅,一個Command Handler負責處理某種類型的命令舵匾,并根據(jù)其中包含的信息做出相應的邏輯處理。

Aggregate聚合

聚合是始終保持一致狀態(tài)的一個實體或一組實體谁不。聚合根是負責維護該聚合內(nèi)一致狀態(tài)的聚合樹頂部的對象坐梯。這使得Aggregate成為在任何基于CQRS的應用程序中實現(xiàn)命令模型的主要構建塊。

注意:

術語“聚合”是指Evans在領域驅(qū)動設計中定義的聚合:

因為數(shù)據(jù)狀態(tài)發(fā)生改變所引起的相關聯(lián)的對象被視為一個單元(聚合)刹帕,在外部只能引用聚合的聚合根對象吵血。在聚合內(nèi)部使用一套一致性規(guī)則。

例如偷溺,“Contact”聚合包含了兩個實體:Contact和Address蹋辅。為了使整個聚合保持一致的狀態(tài),應該通過Contact實體來添加地址給聯(lián)系人亡蓉。在這種情況下晕翠,Contact實體是聚合根。

在Axon中,聚合由一個聚合標識來標識淋肾。他可以是任何對象硫麻,但是標識符的最好的做法是這樣的:

1.實現(xiàn)equals和hashCode方法來保證與其他實例進行相等比較

2.實現(xiàn)一個提供一致結果的的toString()方法(標識符相等的話,那么toString()方法的結果也應該相等.
3.標識符是可序列化的

當聚合使用不兼容的識符時樊卓,測試程序(見測試)將驗證這些條件并使測試失敗拿愧。String,UUID和數(shù)字類型的標識符是最好的碌尔。不要使用原始類型(比如int這些)作為標識符浇辜,因為它們不允許延遲初始化。在某些情況下唾戚,Axon可能會錯誤地假設原始類型的默認值是標識符的值柳洋。

注意:一個好習慣就是使用隨機生成的標識符,而不是按順序生成標識符叹坦。使用順序生成的序列會大大降低應用程序的可伸縮性熊镣,因為機器需要保持彼此最后一次使用的最新的序列號。與UUID沖突的機會非常渺茫(如果生成8.2×10 11個UUID募书,則機會為10的-15次方)绪囱。

聚合的實現(xiàn)

聚合的操作總是交給一個稱為聚集根的實體來控制。通常莹捡,這個實體的名稱和聚合名完全一樣鬼吵。例如,Order聚合可能由Order實體引用幾個Orderline實體組合而成.Order和Orderline一起形成聚合篮赢。雖然根據(jù)CQRS原則來講這并不完全正確齿椅,但他也可以通過方法來暴露出聚合的狀態(tài)。

聚合根必須聲明包含聚合標識符的字段荷逞。這個標識符必須在第一個事件發(fā)布的時候最初被初始化媒咳。該標識符字段必須由@AggregateIdentifier注解注釋粹排。如果您使用JPA并在聚合上使用JPA注解种远,則Axon也可以使用JPA提供的@Id注解。

聚合可以使用AggregateLifecycle.apply()方法來注冊要發(fā)布的事件顽耳。與EventBus不同的是坠敷,消息需要被包裝在EventMessage中,apply()允許你直接傳遞payload對象射富。

import static org.axonframework.commandhandling.model.AggregateLifecycle.apply;

@Entity // Mark this aggregate as a JPA Entity
public class MyAggregate {

@Id // When annotating with JPA @Id, the @AggregateIdentifier annotation is not necessary
private String id;

// fields containing state...

@CommandHandler
public MyAggregate(CreateMyAggregateCommand command) {
    // ... update state
    apply(new MyAggregateCreatedEvent(...));
}

// constructor needed by JPA
protected MyAggregate() {
}

}

通過定義一個帶@EventHandler注解的方法膝迎,聚合內(nèi)的實體能監(jiān)聽聚合發(fā)布的事件。當一個EventMessage發(fā)布時這些方法將被調(diào)用(在任何外部處理器發(fā)布之前)胰耗。

聚合的事件溯源

除了存儲Aggregate的當前狀態(tài)之外限次,還可以根據(jù)它過去發(fā)布的Events來重建Aggregate的狀態(tài)。為了要達到這樣的效果,所有的狀態(tài)改變必須由一個事件來表示卖漫。

事件源聚合類似于一種被約定成俗的聚合:它們必須聲明一個標識符费尽,并可以使用apply方法來發(fā)布事件。然而羊始,事件溯源聚合類的狀態(tài)更改(即字段值的任何更改)必須在被@EventSourcingHandler注解的方法上執(zhí)行旱幼。這包括了設置聚合根的標識符。

我們須要注意的是突委,聚合標識符必須在聚合發(fā)布的第一個事件中就添加進去柏卤,以便在@EventSourcingHandler注解的方法將聚合的標識符進行賦值(注:也就是@EventSourcingHandler中處理的event的聚合標識符不能為空)。他們通常是在創(chuàng)建事件的時候就把聚合標識符添加到事件中匀油。

事件源聚合類的聚合根必須包含一個無參數(shù)構造函數(shù)缘缚。Axon框架會用這個構造方法創(chuàng)建一個空的Aggregate實例,他會在事件溯源的時候調(diào)用敌蚜。加載聚合時忙灼,如果找不到這個無參構造函數(shù)將導致異常。

publicclass MyAggregateRoot {

@AggregateIdentifier

private String aggregateIdentifier;

// fields containing state...

@CommandHandler

public MyAggregateRoot(CreateMyAggregate cmd) {

    apply(new MyAggregateCreatedEvent(cmd.getId()));

}

// constructor needed for reconstruction

protected MyAggregateRoot() {

}

@EventSourcingHandler

private void handleMyAggregateCreatedEvent(MyAggregateCreatedEvent event) {

// make sure identifier is always initialized properly

this.aggregateIdentifier = event.getMyAggregateIdentifier();

// ... update state

}

}

帶@EventSourcingHandler注解的方法會使用特定的規(guī)則來解析钝侠。這些規(guī)則同樣對帶有@EventHandler注解的方法也一樣適合该园,他們在Defining Event Handlers這一章節(jié)中有詳細的解釋。

注意:Event handler方法可以是私有的帅韧,只要JVM的安全設置允許Axon框架更改方法的可訪問性即可里初。這樣可以清楚地將聚合的公共API(公開生成事件的方法)從處理事件的內(nèi)部邏輯中分離出來。大多數(shù)的IDE有一個選項用來忽略“未使用的私有方法”的警告方法忽舟∷粒或者,您可以向該方法添加@SuppressWarnings(“UnusedDeclaration”)注解叮阅,以確保不會出現(xiàn)一不小心就將Event handler方法刪除刁品。

在有些情況下,特別是當聚合有很多實體的時候浩姥,對同一聚合的其他實體中事件發(fā)布的影響更明顯挑随。然而,由于在聚合進行事件溯源的時候也會調(diào)用 Event Handler方法勒叠,因此必須采取特別的預防措施兜挨。

我們可以在Event Sourcing Handler方法內(nèi)通apply()發(fā)布一個新事件。這使得實體 B可以發(fā)布一個事件來響應實體A眯分。當進行事件重演的時候拌汇,Axon會忽略這個apply()方法而不會去調(diào)用他。請注意弊决,在這種情況下噪舀,在所有實體接收到第一個事件后,內(nèi)部apply()調(diào)用的事件只會發(fā)布給實體。如果有更多的事件須要發(fā)布与倡,可以使用apply(...).andThenApply(...)這種方法來實現(xiàn)先改。

您也可以使用靜態(tài)的AggregateLifecycle.isLive()方法來檢查聚合是否是“l(fā)ive”。如果一個聚合能夠完成對歷史事件的重演蒸走,那么他就是live的仇奶。在重演這些事件時,isLive()將返回false比驻。

復雜的聚合結構

一個只包含聚合根的聚合來處理很復雜的業(yè)務邏輯往往是不現(xiàn)實的该溯。在這種情況下,我們須要將這種復雜性分散到聚合的各個實體上别惦。當使用事件溯源時狈茉,不僅聚合根需要使用事件來觸發(fā)狀態(tài)轉(zhuǎn)換,而且聚合內(nèi)其他實體也如此掸掸。

注意:對聚合不應該暴露狀態(tài)的規(guī)則的普片誤解是所有實體都不應該包含任何屬性訪問方法氯庆。 這并非如此。事實上扰付,在同一聚合內(nèi)的實體向其他的實體暴露狀態(tài)堤撵,可能會使一個聚合受益很多。然而羽莺,建議不要向外部暴露聚合的狀態(tài)实昨。

Axon為復雜的聚合結構提供了對事件溯源的支持。實體就像聚合根盐固,簡單的對象荒给。聲明子實體的字段必須用@aggregateMember注解。這個注解告訴Axon被注解的字段刁卜,包含一個應該對Command 和 Event Handlers進行檢查的類志电。

當一個實體(包括聚合根)發(fā)布一個事件,這個事件首先應該被聚合根處理蛔趴。然后通過@AggregateMember注解的作用傳到其子實體挑辆。

包含子實體的字段必須用@AggregateMember注解,此注釋可用于多種字段類型:

l 實體類型夺脾,在字段中直接引用;

l 內(nèi)部包含一個Iterable字段(包括所有集合之拨,如Set,List等)

l 內(nèi)部包含java.util.Map字段的值

聚合中的命令處理

推薦直接在包含處理狀態(tài)命令的Aggregate中定義Command Handlers咧叭,因為命令處理程序可能需要該Aggregate的狀態(tài)來完成其工作。

在Aggregate中定義Command Handlers只須要在你要用的方法上面添加一個@CommandHandler注解就行了烁竭。帶@CommandHandler注解方法的處理規(guī)則和其他處理方法都是一樣的菲茬。但是,Command不僅僅是通過它們的payload進行路由。Command Messages(命令消息)會攜帶一個名稱婉弹,該名稱默認為Command對象的完整的類名稱睬魂。

默認情況下,帶@CommandHandler注解的方法允許以下參數(shù)類型:

l 第一個參數(shù)是Command Message中的payload.如果@CommandHandler顯示的定義了他要處理的Command的名字,那么他的類型可以是Message镀赌,或者CommandMessage.默認情況下一個Command的名字是這個Command的payload的完整的類名稱氯哮。

l 用@MetaDataValue注解的參數(shù),將用注解上的鍵對元數(shù)據(jù)值進行解析商佛。如果這個值是false(默認)喉钢,則當元數(shù)據(jù)值不存在時會傳遞null。如果值是true良姆,而元數(shù)據(jù)值不存時肠虽,這時解析器會發(fā)現(xiàn)錯誤,并阻止該方法的調(diào)用玛追。

l 參數(shù)為MetaData的話税课,那么將注入一個CommandMessage的整個MetaData。

l 如果參數(shù)是UnitOfWork的話痊剖,那么將獲取當前的UnitOfWork并注入進來韩玩。

l 如果參數(shù)Message, or CommandMessage的話,他們會獲取整個的數(shù)據(jù)陆馁,它們包括Meta Data元數(shù)據(jù)和payload.如果一個方法需要多個元數(shù)據(jù)字段啸如,或者包裝消息的其他屬性,這很有用處氮惯。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叮雳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子妇汗,更是在濱河造成了極大的恐慌帘不,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杨箭,死亡現(xiàn)場離奇詭異寞焙,居然都是意外死亡,警方通過查閱死者的電腦和手機互婿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門捣郊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人慈参,你說我怎么就攤上這事呛牲。” “怎么了驮配?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵娘扩,是天一觀的道長着茸。 經(jīng)常有香客問我,道長琐旁,這世上最難降的妖魔是什么涮阔? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮灰殴,結果婚禮上敬特,老公的妹妹穿的比我還像新娘。我一直安慰自己牺陶,他們只是感情好伟阔,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著义图,像睡著了一般减俏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碱工,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天娃承,我揣著相機與錄音,去河邊找鬼怕篷。 笑死历筝,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的廊谓。 我是一名探鬼主播梳猪,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蒸痹!你這毒婦竟也來了春弥?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤叠荠,失蹤者是張志新(化名)和其女友劉穎匿沛,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榛鼎,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡逃呼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了者娱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抡笼。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖黄鳍,靈堂內(nèi)的尸體忽然破棺而出推姻,到底是詐尸還是另有隱情,我是刑警寧澤际起,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布拾碌,位于F島的核電站吐葱,受9級特大地震影響街望,放射性物質(zhì)發(fā)生泄漏校翔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一灾前、第九天 我趴在偏房一處隱蔽的房頂上張望防症。 院中可真熱鬧,春花似錦哎甲、人聲如沸蔫敲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奈嘿。三九已至,卻和暖如春吞加,著一層夾襖步出監(jiān)牢的瞬間裙犹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工衔憨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留叶圃,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓践图,卻偏偏與公主長得像掺冠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子码党,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

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

  • 在一個基于CQRS的應用程序中德崭,領域模型(由Eric Evans和Martin Fowler定義)可以是一個非常強...
    勇赴閱讀 640評論 0 0
  • Axon的核心概念之一就是通訊。組件之間的所有通信都使用消息對象完成揖盘。這為這些組件提供了位置透明性眉厨,以便在必要的時...
    water_lang閱讀 559評論 0 0
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)扣讼,斷路器缺猛,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,811評論 6 342
  • 今天,頭條上看到一個問答椭符,問為什么中國只崇拜悲情的英雄荔燎?記得以前聽袁老師講歷史時,也提出過這個觀點销钝。 這個觀點成立...
    石林析閱讀 404評論 0 3