Axon Framework 從入門遂填、深入到放棄

DDD領(lǐng)域驅(qū)動(dòng)/CQRS讀寫分離/ES事件溯源 這些前沿的時(shí)髦的技術(shù)理念匯聚在一次赡茸,落地到一套完整實(shí)現(xiàn)方案。這就是Axon

我們從ES事件溯源開始說
  • 傳統(tǒng)的數(shù)據(jù)庫設(shè)計(jì)只記錄數(shù)據(jù)的當(dāng)前狀態(tài)深滚,對于輸入如何到達(dá)當(dāng)前狀態(tài)的過程信息則并不重視。
保存當(dāng)前狀態(tài).png
  • 而ES采用了完全不同的記錄方式涣觉,它記錄所有改變當(dāng)前狀態(tài)的事件痴荐。并從這些事件累加出當(dāng)前狀態(tài)。它于傳統(tǒng)方式相比有如下特性:
    1. 更好的追溯問題的起因官册。(傳統(tǒng)方式只能到log中找)
    2. 更大的數(shù)據(jù)存儲(是缺點(diǎn)也是優(yōu)點(diǎn)生兆,有利于數(shù)據(jù)挖掘)
    3. 對于事件只插入不更新。沒有鎖膝宁,吞吐量更高
    4. 更復(fù)雜的實(shí)現(xiàn)
保存歷史事件.png
  • ES還有一種輔助機(jī)制來減少溯源的計(jì)算量鸦难,定期做一個(gè)當(dāng)前狀態(tài)的快照。事件在最近的快照后進(jìn)行累加
保存歷史事件-快照.png
領(lǐng)域驅(qū)動(dòng)中提到過充血/失血模型
  • 傳統(tǒng)實(shí)現(xiàn)是失血模型员淫,數(shù)據(jù)和行為是分離的合蔽。數(shù)據(jù)存儲在數(shù)據(jù)庫中,使用時(shí)加載到應(yīng)用服務(wù)器介返,然后處理之后再存入數(shù)據(jù)庫拴事。
  • 而真正面相對象的設(shè)計(jì)中,數(shù)據(jù)和行為是在一起的(比如Actor模型)圣蝎,即服務(wù)層即執(zhí)行邏輯也持有數(shù)據(jù)刃宵。
  • 當(dāng)然傳統(tǒng)的失血模型有它的好處。即服務(wù)是無狀態(tài)的徘公,可以方便的做負(fù)載擴(kuò)展牲证。Axon的充血模型是怎么處理這個(gè)問題的,之后再深入步淹。
  • CQRS讀寫分離从隆。如果實(shí)施了事件溯源诚撵,那么數(shù)據(jù)庫里就只保存事件了,正常的業(yè)務(wù)查詢是不能直接使用這些數(shù)據(jù)的键闺。這個(gè)時(shí)候就需要視圖表來展示數(shù)據(jù)寿烟,視圖表都是從事件表轉(zhuǎn)換而來的。
當(dāng)然DDD領(lǐng)域驅(qū)動(dòng)/CQRS讀寫分離/ES事件溯源這些不是強(qiáng)制綁定的辛燥,可以隨意一選擇筛武,下面是Axon的架構(gòu)圖:
axon-overview.png

下面我們用axon來實(shí)踐開發(fā)一個(gè)游戲(代碼使用的是Axon4.0)

首選我們創(chuàng)建一個(gè)領(lǐng)域?qū)ο蟆婕襊layer
@Aggregate
public class Player{
    @AggregateIdentifier
    private String id;

    private String name;//名字
    
    private PlayerStatus status;//狀態(tài)
    
    @CommandHandler
    public Player(CreatePlayerCmd cmd) {
            AggregateLifecycle.apply(new PlayerCreatedEvent(
                IdentifierFactory.getInstance().generateIdentifier()
                , cmd.getPlayerName(), new Date()));
    }
    
    @EventSourcingHandler
    private void on(PlayerCreatedEvent event) {
         this.id = event.getId();
        this.name = event.getPlayerName();
        this.status = PlayerStatus.ACTIVE;
    }

}
  • @Aggregate會被Spring加載并自動(dòng)賦予axon相關(guān)的功能
  • @AggregateIdentifier 指定聚合根的ID,必須要有
  • @CommandHandler 用來處理外部觸發(fā)的行為和計(jì)算
  • @EventSourcingHandler 用作狀態(tài)更改和事件回放(通過事件重放回復(fù)當(dāng)前狀態(tài))
接著我們實(shí)現(xiàn)一個(gè)隨著時(shí)間自然消耗的功能
EventSourcingHandler
    private Date lastSustainLivingDate;//上次維持生存的計(jì)算時(shí)間節(jié)點(diǎn)
    
    @CommandHandler
    public void handle(SustainLivingCmd cmd){
         AggregateLifecycle.apply(new SustainLivingEvent(cmd.getTiggerDate(), lastSustainLivingDate));
    }

    @EventSourcingHandler
    protected void on(SustainLivingEvent event) {
        this.lastSustainLivingDate = event.getTriggerDate();
    }

  • 寫到這里發(fā)現(xiàn)每實(shí)現(xiàn)一個(gè)功能是比較繁瑣的挎塌。要?jiǎng)?chuàng)建兩個(gè)方法徘六,一個(gè)命令對象,一個(gè)事件對象榴都。中間是一些繁瑣的值傳遞待锈。
  • AggregateLifecycle.apply()做了幾件事情
    1. 調(diào)用實(shí)例自己的@EventSourcingHandler
    2. 通過EventBus存儲和傳播事件
    3. 外部事件監(jiān)聽器監(jiān)聽,主要是CQRS中的查詢視圖嘴高,更新試圖數(shù)據(jù)竿音。
PlayerEntry保存了所有玩家的列表幷提供查詢,它監(jiān)聽PlayerCreatedEvent
@Component
public class PlayerEntryListener {
    @Autowired
    private PlayerEntryRepository playerEntryRepository;
    
    @EventHandler
    public void on(PlayerCreatedEvent event) {
        PlayerEntry playerEntry = new PlayerEntry();
        playerEntry.setId(event.getId());
        playerEntry.setName(event.getPlayerName());
        playerEntryRepository.save(playerEntry);
    }
}
Command執(zhí)行過程:
  • Command通過org.axonframework.commandhandling.CommandBus分發(fā)拴驮,其中DistributedCommandBus實(shí)現(xiàn)了分布式的派發(fā)春瞬,它可以適配SpringCloud. 主要邏輯是通過
    AggregateIdentifier做一致性HASH。確保次對同一個(gè)聚合根發(fā)到同一臺機(jī)器套啤,這樣保證了緩存的命中宽气。Aggregate本質(zhì)上在應(yīng)用內(nèi)緩存了當(dāng)前狀態(tài),如果該服務(wù)宕機(jī)潜沦。另一臺機(jī)器會通過事件溯源重新構(gòu)造出當(dāng)前狀態(tài)萄涯。
  • Command的事務(wù)處理:Axon通過UnitofWork控制一致性,其中嵌入了JDBC事務(wù)止潮。并且在回滾時(shí)會清除Aggregate緩存窃判,下次會重新加載。
  • 當(dāng)然對于遠(yuǎn)程的子事務(wù)無法保證一致性喇闸。這是個(gè)大的隱患
Saga最終一致性袄琳,補(bǔ)償機(jī)制。
  • Saga的理念沒問題燃乍,當(dāng)是實(shí)現(xiàn)起來問題很大唆樊。
  • 一個(gè)是每個(gè)command都要?jiǎng)?chuàng)建event非常繁瑣,并且saga來來回回非常多
  • 另一個(gè)是沒有考慮到冪等刻蟹,本地宕機(jī)逗旁,狀態(tài),分布事務(wù)等細(xì)節(jié)的處理。自己實(shí)現(xiàn)也不靈活
  • 確保分布式事務(wù)一致性是個(gè)非常繁瑣的事情片效,請參考轉(zhuǎn)賬交易這件小事红伦,是如何被程序員玩壞的.
說說其他實(shí)現(xiàn)困難的情況
  • 如果Aggregate存在繼承關(guān)系,或者實(shí)現(xiàn)一些通用的行為接口淀衣£级粒框架并不支持
  • 無法實(shí)現(xiàn)延遲觸發(fā)的功能
  • 對大量聚合實(shí)現(xiàn)批量操作不太容易
  • 幾乎所有的操作都要靠command觸發(fā),然后轉(zhuǎn)成event,非常繁瑣膨桥。

取其思想蛮浑,尋找替代

在交易中的訂單模型,本質(zhì)上就是ES中的Event. 賬戶余額是用訂單累加出來的只嚣。只不過訂單不止記錄事件沮稚。還用來記錄更新審核狀態(tài),系統(tǒng)狀態(tài)册舞,事務(wù)狀態(tài)等蕴掏,不那么純粹。通過訂單回溯賬戶狀態(tài)是個(gè)人工過程而不是自動(dòng)的环础。
  • 在這方面囚似,Axon對事件的存儲采用統(tǒng)一的表,事件序列化到表中线得,并不有利于數(shù)據(jù)庫的查看。需要額外的記錄一個(gè)查詢視圖
  • 快照模型也在交易對賬中有所體現(xiàn)徐伐。每日對賬后生成當(dāng)日的一個(gè)快照贯钩,第二天的交易從快照開始累計(jì)
Command模型配合只insert沒有update的操作,可以在高并發(fā)下實(shí)現(xiàn)無鎖處理办素。這個(gè)可以通過Kafka角雷。或者數(shù)據(jù)庫先插入性穿,后異步讀取處理的方式來實(shí)現(xiàn)勺三。
充血模型是Axon的一個(gè)特色。不過并不難實(shí)現(xiàn)需曾。通過自定義一個(gè)SpringCloud LoadBalancer Rule 實(shí)現(xiàn)哈希一致也可以實(shí)現(xiàn)吗坚。另外傳統(tǒng)的失血模型使用無狀態(tài)服務(wù)更簡單,它可以在數(shù)據(jù)庫層面通過一致性hash來存儲數(shù)據(jù)呆万,比如mongodb商源。對于訂單這樣的單一記錄低頻操作完全可以處理。對于同一記錄的高頻處理Axon也是有其優(yōu)勢的谋减,不過Akka可能也是不錯(cuò)的選擇
Saga異常難寫牡彻,在網(wǎng)絡(luò)異常,冪等出爹,重試方面并不友好

總結(jié):DDD領(lǐng)域驅(qū)動(dòng)/CQRS讀寫分離/ES事件溯源庄吼,這些思想都很棒缎除,但是并不用拘泥于特定的實(shí)現(xiàn)。Axon對于學(xué)習(xí)這些概念具有非常大的幫助总寻,但是在實(shí)踐的過程中幷不高效器罐、靈活

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市废菱,隨后出現(xiàn)的幾起案子技矮,更是在濱河造成了極大的恐慌,老刑警劉巖殊轴,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衰倦,死亡現(xiàn)場離奇詭異,居然都是意外死亡旁理,警方通過查閱死者的電腦和手機(jī)樊零,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來孽文,“玉大人驻襟,你說我怎么就攤上這事∮罂蓿” “怎么了沉衣?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長减牺。 經(jīng)常有香客問我豌习,道長,這世上最難降的妖魔是什么拔疚? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任肥隆,我火速辦了婚禮,結(jié)果婚禮上稚失,老公的妹妹穿的比我還像新娘栋艳。我一直安慰自己,他們只是感情好句各,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布吸占。 她就那樣靜靜地躺著,像睡著了一般诫钓。 火紅的嫁衣襯著肌膚如雪旬昭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天菌湃,我揣著相機(jī)與錄音问拘,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛骤坐,可吹牛的內(nèi)容都是我干的绪杏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼纽绍,長吁一口氣:“原來是場噩夢啊……” “哼蕾久!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拌夏,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤僧著,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后障簿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盹愚,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年站故,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了皆怕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡西篓,死狀恐怖愈腾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情岂津,我是刑警寧澤虱黄,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站吮成,受9級特大地震影響礁鲁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赁豆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冗美。 院中可真熱鬧魔种,春花似錦、人聲如沸粉洼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽属韧。三九已至安拟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宵喂,已是汗流浹背糠赦。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拙泽。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓淌山,卻偏偏與公主長得像,于是被迫代替她去往敵國和親顾瞻。 傳聞我的和親對象是個(gè)殘疾皇子泼疑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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

  • CQRS本身就是一個(gè)非常簡單的模式。他只是規(guī)定了應(yīng)用程序的命令處理的組件(增荷荤,刪退渗,改)應(yīng)該和查詢的組件進(jìn)行分離。盡...
    water_lang閱讀 4,134評論 0 2
  • CQRS本身是一個(gè)非常簡單的模式蕴纳。它只規(guī)定了處理命令的應(yīng)用程序的組件應(yīng)該與處理查詢 的組件分離会油。 雖然這種分...
    勇赴閱讀 2,507評論 0 0
  • Command Model 在一個(gè)基于CQRS的應(yīng)用程序中,一個(gè)領(lǐng)域模型(如Eric Evans和Martin F...
    water_lang閱讀 1,469評論 0 1
  • DDD(Domain Drive Design)與TDD測試驅(qū)動(dòng)設(shè)計(jì)都是不同的軟件架構(gòu)設(shè)計(jì)理論袱蚓,與具體使用的技術(shù)手...
    墨弈閱讀 2,161評論 0 51
  • 平日跑步已經(jīng)有三年左右的時(shí)間了钞啸,由于2016年的身體狀況不好,跑步持續(xù)的不好喇潘,最后兩個(gè)季度幾乎沒有怎么跑步体斩,...
    Fisher1212閱讀 224評論 2 0