DDD & CQRS & Event Sourcing

一、 DDD分層架構(gòu)

Evans在它的《領(lǐng)域驅(qū)動設(shè)計:軟件核心復雜性應(yīng)對之道》書中推薦采用分層架構(gòu)去實現(xiàn)領(lǐng)域驅(qū)動設(shè)計:


DDD是近年軟件設(shè)計的熱門。CQRS與Event Sourcing作為實施DDD的一種選擇,也逐步進入人們的視野。圍繞這兩個主題,軟件開發(fā)的大咖[Martin Fowler]、[Greg Young]、[Udi Dahan]分別有所論述,[MSDN CQRS Journey]、[Implementing DDD]、[Patterns, Principles, and Practices of DDD]等著述也提供了范例,國內(nèi)外各大論壇的文章和DDD開源框架更是數(shù)不勝數(shù),為學習CQRS和Event Sourcing提供了大量指導。

其中,Greg Young的論文最為系統(tǒng)。故本文參照其論文的結(jié)構(gòu),簡單梳理了CQRS與Event Sourcing的發(fā)展脈絡(luò),厘出其中的主要技術(shù)重點進行解讀,并提出以Akka作為落地方案,以求對這兩個主題有一個較為全面的總結(jié)。錯謬之處,還望指正。

二、CQRS架構(gòu)

命令查詢的責任分離Command Query Responsibility Segregation (簡稱CQRS)模式是一種架構(gòu)體系模式,能夠使改變模型的狀態(tài)的命令和模型狀態(tài)的查詢實現(xiàn)分離。這屬于DDD應(yīng)用領(lǐng)域的一個模式,主要解決DDD在數(shù)據(jù)庫報表輸出上處理方式。

Greg Young在infoQ的采訪中“State Transitions in Domain-Driven Design”談到了CQRS,Greg 解釋了把領(lǐng)域模型分為兩種:狀態(tài)校驗,以及狀態(tài)轉(zhuǎn)換,維持當前狀態(tài)的一個視圖。


在客戶端就將數(shù)據(jù)的CRUD的新增修改刪除CUD等操作和查詢R進行分離,前者稱為Command,走Command bus進入Domain對模型進行操作,而查詢則從另外一條路徑直接使用SQL對數(shù)據(jù)進行操作,比如報表輸出等,發(fā)揮SQL的特點。

當一個Command進來時,從倉儲Repository加載一個聚合aggregate對象群,然后執(zhí)行其方法和行為。這樣,會激發(fā)聚合對象群產(chǎn)生一個事件,這個事件可以分發(fā)給倉儲Repository,或者分發(fā)給Event Bus事件總線,比如JavaEE的消息總線等等。事件總線將再次激活所有監(jiān)聽本事件的處理者。當然一些處理者會執(zhí)行其他聚合對象群的操作,包括數(shù)據(jù)庫的更新。

三、事件溯源(Event Sourcing)

Event Sourcing

一個對象從創(chuàng)建開始到消亡會經(jīng)歷很多事件,以前我們是在每次對象參與完一個業(yè)務(wù)動作后把對象的最新狀態(tài)持久化保存到數(shù)據(jù)庫中,也就是說我們的數(shù)據(jù)庫中的數(shù)據(jù)是反映了對象的當前最新的狀態(tài)。而事件溯源則相反,不是保存對象的最新狀態(tài),而是保存這個對象所經(jīng)歷的每個事件,所有的由對象產(chǎn)生的事件會按照時間先后順序有序的存放在數(shù)據(jù)庫中??梢钥闯觯录菰吹倪@種做法是更符合事實觀的,因為它完整的描述了對象的整個生命周期過程中所經(jīng)歷的所有事件。

那么,事件到底如何影響一個領(lǐng)域?qū)ο蟮臓顟B(tài)的呢?很簡單,當我們在觸發(fā)某個領(lǐng)域?qū)ο蟮哪硞€行為時,該領(lǐng)域?qū)ο髸犬a(chǎn)生一個事件,然后該對象自己響應(yīng)該事件并更新其自己的狀態(tài),同時我們還會持久化在該對象上所發(fā)生的每一個事件;這樣當我們要重新得到該對象的最新狀態(tài)時,只要先創(chuàng)建一個空的對象,然后將和該對象相關(guān)的所有事件按照事件發(fā)生先后順序從先到后再全部應(yīng)用一遍即可還原得到該對象的最新狀態(tài),這個過程就是所謂的事件溯源;

另一方面,因為是用事件來表示對象的狀態(tài),而事件是只會增加不會修改。這就能讓數(shù)據(jù)庫里的表示對象的數(shù)據(jù)非常穩(wěn)定,不可能存在DELETE或UPDATE等操作。因為一個事件就是表示一個事實,事實是不能被磨滅或修改的。這種特性可以讓領(lǐng)域模型非常穩(wěn)定,在數(shù)據(jù)庫級別不會產(chǎn)生并發(fā)更新同一條數(shù)據(jù)的問題;其實CAP定理之所以做不到,根本原因也是由于數(shù)據(jù)可以被修改;現(xiàn)在通過事件溯源,我們實現(xiàn)CAP或許就成為了可能;

我們可以看到,基于這樣的設(shè)計,領(lǐng)域?qū)ο蟮臓顟B(tài)完全是由事件驅(qū)動的。不僅如此,事件還可以被事件總線分發(fā)出去,通知領(lǐng)域模型外的一切事件響應(yīng)者發(fā)生了什么,基于這種Publish-Subscribe的通信模式,我們可以最大限度的實現(xiàn)系統(tǒng)的松耦合。

四、使用Akka框架實現(xiàn)

Actor模型最早出自1973年Carl Hewitt等人所著論文A Universal Modular ACTOR Formalism for Artificial Intelligence。

Akka是Lightbend公司推出的一個基于Actor模型的分布式框架,目前主要支持的語言包括Java和Scala。
以下是官網(wǎng)及我的筆記鏈接:

  • [Akka Typed,最新版本2.6.10]
  • [一圖看懂Actor Typed]
  • [Akka Typed 官方文檔之隨手記]
    實現(xiàn)細節(jié)
    用Akka實現(xiàn)CQRS與Event Sourcing的示意圖如下:
  • 由Command與Event組成的Protocol,是Actor與外界溝通的唯一媒介。
  • EventSourcedBehavior是Write Model的核心,承擔著聚合的主體責任,主要定義了Command和Event的Handler。
  • Event的SequenceNumber由框架自動生成,reply和snapshot由框架提供。
  • 聚合狀態(tài)單獨定義在State里,借State模式實現(xiàn)狀態(tài)遷移。
  • Tag為事件做上標記,方便Read Model選擇使用。
  • PersistenceQuerier是Read Model的核心,負責從Read Journal中根據(jù)Tag讀取事件流,更新自身的讀數(shù)據(jù)模型,從而實現(xiàn)讀寫模型的最終一致性。
  • Serialize為Command和Event提供序列化支持,可使用Json或二進制格式。

?? Akka的CQRS示例,分別有Scala和Java版本
微服務(wù)
Lightbend公司在Akka基礎(chǔ)上,推出了一個微服務(wù)框架Lagom。

Lagom框架堅持,微服務(wù)是按服務(wù)邊界Boundary將系統(tǒng)切分為若干個組成部分的結(jié)果,這意味著要使它們與限界上下文Bounded Context、業(yè)務(wù)功能和模塊隔離等要求保持一致,才能達到可伸縮性和彈性要求,從而易于部署和管理。因此,在設(shè)計微服務(wù)時應(yīng)考慮大小是否“Lagom”,而非是否足夠“Micro”。
以下是官網(wǎng)和我的筆記鏈接:

  • [Lagom,最新版本1.6.4]
  • [Lagom 官方文檔之隨手記]
    Lagom封裝了服務(wù)定位、服務(wù)網(wǎng)關(guān)、消息隊列和路由、集群等功能。每個服務(wù)由服務(wù)描述子、調(diào)用標識符、消息處理器等組成,在服務(wù)的內(nèi)部實現(xiàn)中,由Akka提供的EventSourcedBehavior承擔實際的消息處理和持久化。

?? Lagom的Hello World示例,分別有Scala和Java版本

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容