領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)之二

前言

在這個(gè)時(shí)代,國人很少注重理論知識(shí)的積累划乖,俗話說理論指導(dǎo)實(shí)踐辆沦,好的理論都是在實(shí)踐的基礎(chǔ)之上積累下來的昼捍,是前人經(jīng)驗(yàn)的總結(jié)。一個(gè)好的設(shè)計(jì)開發(fā)人員就體現(xiàn)在這些上面了肢扯,如果不注重知識(shí)積累妒茬,那么就只會(huì)一些花拳繡腿,技術(shù)上是很難有所提升的蔚晨,我們先來看看常用的架構(gòu)模式及演進(jìn)過程乍钻,從中我們可以體會(huì)出領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的由來以及好處。

架構(gòu)模式

三層架構(gòu)
  • 表現(xiàn)層铭腕,領(lǐng)域?qū)右瘢瑪?shù)據(jù)源層表現(xiàn)層:提供服務(wù),顯示信息
  • 領(lǐng)域?qū)樱哼壿嬂巯希到y(tǒng)中真正的核心
  • 數(shù)據(jù)源層:與數(shù)據(jù)庫浩考,消息系統(tǒng),事務(wù)管理器及其他軟件包通信運(yùn)行環(huán)境
  • 表現(xiàn)層:運(yùn)行在客戶端
  • 領(lǐng)域?qū)樱嚎蛻舳吮挥⒎?wù)端
  • 數(shù)據(jù)源層:服務(wù)端
三層演化架構(gòu)

用戶界面層析孽,應(yīng)用層,領(lǐng)域?qū)又辉酰A(chǔ)設(shè)施層

改進(jìn)分層架構(gòu)

依賴顛倒原則:高層模塊不應(yīng)該依賴低層模塊袜瞬,兩者都應(yīng)該依賴于抽象抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象身堡。我們應(yīng)該將關(guān)注點(diǎn)放在領(lǐng)域?qū)由系擞龋捎靡蕾囶嵉乖瓌t,使領(lǐng)域?qū)雍突A(chǔ)設(shè)施層都只依賴于領(lǐng)域模型所定義的抽象接口盾沫。由于應(yīng)用層是領(lǐng)域?qū)拥闹苯涌蛻舨迷鼘⒁蕾囉陬I(lǐng)域?qū)咏涌冢⑶议g接地訪問資源庫和由基礎(chǔ)設(shè)施層提供的實(shí)現(xiàn)類赴精。應(yīng)用層可以采用不同的方式來獲取這些實(shí)現(xiàn)佩捞,包括依賴注入,服務(wù)工廠和插件

六邊形架構(gòu)(端口與適配器)

不同的客戶通過“平等”的方式與系統(tǒng)交互蕾哟,如果需要新的客戶一忱,只需要添加新的適配器將客戶輸入轉(zhuǎn)為成能被系統(tǒng)API所理解的參數(shù)就行了莲蜘。同時(shí),系統(tǒng)輸出帘营,比如圖形界面票渠、持久化和消息等都可以通過不同的方式實(shí)現(xiàn)。所以我們有充足的理由認(rèn)為芬迄,這將是一種具有持久生命力的架構(gòu)问顷。很多聲稱使用分層架構(gòu)的團(tuán)隊(duì)實(shí)際上使用的是六邊形的架構(gòu)。這是因?yàn)楹芏囗?xiàng)目都使用了某種形式的依賴注入禀梳。并不是說依賴注入天生就是六邊形架構(gòu)杜窄,而是說使用依賴注入的架構(gòu)自然地具有了端口與適配器風(fēng)格。

面向服務(wù)架構(gòu)
REST
  • 架構(gòu)風(fēng)格:架構(gòu)風(fēng)格之于架構(gòu)就像設(shè)計(jì)模式之地設(shè)計(jì)一樣算途。使得我們在談及架構(gòu)時(shí)不至于陷入技術(shù)細(xì)節(jié)中塞耕。

  • REST通常基于使用HTTP嘴瓤,URI扫外,和XML以及HTML這些現(xiàn)有的廣泛流行的協(xié)議和標(biāo)準(zhǔn)。

  • 資源是由URI來指定廓脆。

  • 對(duì)資源的操作包括獲取筛谚、創(chuàng)建、修改和刪除資源狞贱,這些操作正好對(duì)應(yīng)HTTP協(xié)議提供的GET刻获、POST、PUT和DELETE方法瞎嬉。

  • 通過操作資源的表現(xiàn)形式來操作資源。

  • 資源的表現(xiàn)形式則是XML或者HTML厚柳,取決于讀者是機(jī)器還是人氧枣,是消費(fèi)web服務(wù)的客戶軟件還是web瀏覽器。當(dāng)然也可以是任何其他的格式别垮。

  • 架構(gòu)約束有6個(gè):

    1. 客戶-服務(wù)器(Client-Server)便监,通信只能由客戶端單方面發(fā)起,表現(xiàn)為請求-響應(yīng)的形式碳想。
    2. 無狀態(tài)(Stateless烧董,通信的會(huì)話狀態(tài)(Session State)應(yīng)該全部由客戶端負(fù)責(zé)維護(hù)。
    3. 緩存(Cache)胧奔,響應(yīng)內(nèi)容可以在通信鏈的某處被緩存逊移,以改善網(wǎng)絡(luò)效率。
    4. 統(tǒng)一接口(Uniform Interface)龙填,通信鏈的組件之間通過統(tǒng)一的接口相互通信胳泉,以提高交互的可見性拐叉。
    5. 分層系統(tǒng)(Layered System),通過限制組件的行為(即每個(gè)組件只能“看到”與其交互的緊鄰層)扇商,將架構(gòu)分解為若干等級(jí)的層凤瘦。
    6. 按需代碼(Code-On-Demand,可選)支持通過下載并執(zhí)行一些代碼(例如如ava Applet案铺、Flash或JavaScript)蔬芥,對(duì)客戶端的功能進(jìn)行擴(kuò)展。
  • REST優(yōu)點(diǎn):

    • 可更高效利用緩存來提高響應(yīng)速度
    • 通訊本身的無狀態(tài)性可以讓不同的服務(wù)器的處理一系列請求中的不同請求控汉,提高服務(wù)器的擴(kuò)展性
    • 瀏覽器即可作為客戶端笔诵,簡化軟件需求
    • 相對(duì)于其他疊加在HTTP協(xié)議之上的機(jī)制,REST的軟件依賴性更小不需要額外的資源發(fā)現(xiàn)機(jī)制
    • 在軟件技術(shù)演進(jìn)中的長期的兼容性更好

例如暇番,一個(gè)簡單的網(wǎng)絡(luò)商店應(yīng)用嗤放,列舉所有商品,
GET http://www.store.com/products 呈現(xiàn)某一件商品壁酬,
GET http://www.store.com/products/12345 下單購買次酌,
POST http://www.store.com/orders

<purchase-order>
<item>...</item>
</purchase-order>
CQRS(命令與查詢責(zé)任分離)

CQRS最早來自于Betrand Meyer(Eiffel語言之父,開-閉原則OCP提出者)在Object-Oriented Software Construction 這本書中提到的一種命令查詢分離(Command Query Separation.CQS)的概念舆乔。其基本思想在于岳服,任何一個(gè)對(duì)象的方法可以分為兩大類:

  • 命令(Command):不返回任何結(jié)果(void),但會(huì)改變對(duì)象的狀態(tài)希俩。
  • 查詢(Query):返回結(jié)果吊宋,但是不會(huì)改變對(duì)象的狀態(tài),對(duì)系統(tǒng)沒有副作用颜武。

CQRS是對(duì)CQS模式的進(jìn)一步改進(jìn)成的一種簡單模式璃搜。它由Greg Young在CQRS,Task Based UIs鳞上,Event Sourcing agh这吻!這篇文章中提出。

“CQRS只是簡單的將之前只需要?jiǎng)?chuàng)建一個(gè)對(duì)象拆分成了兩個(gè)對(duì)象篙议,這種分離是基于方法是執(zhí)行命令還是執(zhí)行查詢這一原則來定的(這個(gè)和CQS的定義一致)"唾糯。

CQRS使用分離的接口將數(shù)據(jù)查詢操作(Queries)和數(shù)據(jù)修改操作(Commands)分離開來,這也意味著在查詢和更新過程中使用的數(shù)據(jù)模型也是不一樣的鬼贱。這樣讀和寫邏輯就隔離開來了移怯。

主數(shù)據(jù)庫處理CUD,從庫處理R这难,從庫的的結(jié)構(gòu)可以和主庫的結(jié)構(gòu)完全一樣舟误,也可以不一樣,從庫主要用來進(jìn)行只讀的查詢操作雁佳。在數(shù)量上從庫的個(gè)數(shù)也可以根據(jù)查詢的規(guī)模進(jìn)行擴(kuò)展脐帝,在業(yè)務(wù)邏輯上也可以根據(jù)專題從主庫中劃分出不同的從庫同云,從庫地可以實(shí)現(xiàn)成RepgrtingDatabase,根據(jù)查詢的業(yè)務(wù)需求堵腹,從全庫中抽取一些必要的數(shù)據(jù)生成一系列查詢報(bào)表來存儲(chǔ)炸站。

使用ReportingDatabase的一些優(yōu)點(diǎn)通常可以使得查詢變得更加簡單高效:

  • ReportingDatabase的結(jié)構(gòu)和數(shù)據(jù)表會(huì)針對(duì)常用的查詢請求進(jìn)行設(shè)計(jì)疚顷。ReportingDatabase數(shù)據(jù)庫通常會(huì)去正規(guī)化旱易,存儲(chǔ)一些冗余而減少必要的Join等聯(lián)合查詢操作,使得查詢簡化和高效腿堤,一些在主數(shù)據(jù)庫中用不到的數(shù)據(jù)信息阀坏,在ReportingDatabase可以不用存儲(chǔ),可以ReportingDatabase重構(gòu)優(yōu)化笆檀,而不用去改變操作數(shù)據(jù)庫忌堂。
  • 對(duì)ReportingDatabase數(shù)據(jù)庫的查詢不會(huì)給操作數(shù)據(jù)庫帶來任何壓力⌒锶鳎可以針對(duì)不同的查詢請求建立不同的ReportingDatabase庫士修。

當(dāng)命令處理器執(zhí)行結(jié)束后,一個(gè)聚合實(shí)例將被更新樱衷,同時(shí)命令模型還將發(fā)布一個(gè)領(lǐng)域事件棋嘲。對(duì)于更新查詢模型來說,這樣的領(lǐng)域事件是至關(guān)重要的矩桂。值得注意的是沸移,所發(fā)布的領(lǐng)域事件還可能導(dǎo)致另一些受同一命令影響的聚合實(shí)例的同步更新,最終侄榴,這些聚合實(shí)例都將與本次事務(wù)所修改的聚合實(shí)例保持最終一致性雹锣。

在命令模型更新之后,如果我們希望查詢模型也得到相應(yīng)的更新癞蚕,那么從命令模型中發(fā)布的領(lǐng)域事件也是關(guān)鍵所在笆制。在使用事件源時(shí),領(lǐng)域事件也被用于持久化修改后的聚合涣达。然而,事件源并不一定與CQRS一起使用证薇。除非事件日志包含在業(yè)務(wù)需求之中度苔。不然命令模型是可以通過ORM等方式進(jìn)行持久化的。不管如何浑度,我們都需要發(fā)布領(lǐng)域事件以更新查詢模型寇窑。

用戶界面處理:

  1. 用戶提交命令時(shí),同時(shí)更新頁面數(shù)據(jù)箩张。
  2. 在用戶界面上顯示出當(dāng)前查詢模型的日期和時(shí)間甩骏。要達(dá)到這樣的目的窗市,查詢模型的每一條記錄需要維護(hù)最后更新時(shí)的日期和時(shí)間,用戶自己決定是否更新饮笛。
  3. Comet(Ajax Push)
  4. 分布式緩存網(wǎng)格(Coherence咨察,Gemfire)的事件訂閱
  5. 直接通知用戶需要等一會(huì)。

術(shù)語解釋

  • 通用語言(Common Language):
    圍繞領(lǐng)域模型建立的一種語言福青,團(tuán)隊(duì)所有成員都使用這種語言把團(tuán)隊(duì)的所有活動(dòng)與軟件聯(lián)系起來
  • 上下文(Context):
    一個(gè)單詞或句子出現(xiàn)的環(huán)境摄狱,它決定了其含義。
  • 限界上下文(Bound Context):
    特定模型的限界應(yīng)用无午。限界上下文使團(tuán)隊(duì)所有成員能夠明確地知道什么必須保持一致媒役,什么必須獨(dú)立開發(fā)。
  • 資源庫(Repository):
    一個(gè)安全的存儲(chǔ)區(qū)域宪迟,并且對(duì)其中所存放的物品起保護(hù)作用酣衷。一種對(duì)象,它不是由屬性來定義的次泽,而是通過一連串的連續(xù)事件和標(biāo)識(shí)定義的穿仪。
  • 值對(duì)象(Value Object):
    一種描述了某種特征或?qū)傩缘珱]有概念標(biāo)識(shí)的對(duì)象領(lǐng)域事件,用來捕獲發(fā)生在領(lǐng)域中的一些事件的建模工具,將領(lǐng)域中所發(fā)生的活動(dòng)建模成一系列的離散事件箕憾,每個(gè)事件都用領(lǐng)域?qū)ο髞肀硎尽?/li>
  • 領(lǐng)域事件(Domain Event):
    領(lǐng)域事件是一個(gè)領(lǐng)域模型中極其重要的部分牡借,用來表示領(lǐng)域中發(fā)生的事件。忽略不相關(guān)的領(lǐng)域活動(dòng)袭异,同時(shí)明確領(lǐng)域?qū)<乙櫥蛳M煌ㄖ氖虑槟屏蚺c其他模型對(duì)象中的狀態(tài)更改相關(guān)聯(lián)。
  • 事件存儲(chǔ)(Event Store)和事件溯源(Event Sourcing):
    事件存儲(chǔ)御铃,顧名思義碴里,即事件的持久化。
  • 聚合(Aggregation Root):
    由實(shí)體和值對(duì)象組件的一致性邊界上真。

通用語言(Common Language)

  • 軟件開發(fā)人員使用的設(shè)計(jì)語言:UML咬腋,軟件專業(yè)術(shù)語,開發(fā)語言

  • 領(lǐng)域?qū)<遥菏褂酶鱾€(gè)領(lǐng)域的業(yè)務(wù)術(shù)語問題:由于語言上不同睡互,設(shè)計(jì)開發(fā)人員在與領(lǐng)域?qū)<覝贤〞r(shí)溝通不暢根竿,導(dǎo)致知識(shí)消化變得困難,所以需要?jiǎng)?chuàng)建一種統(tǒng)一的語言來便于領(lǐng)域?qū)<液驮O(shè)計(jì)開發(fā)人員交流就珠。

  • 如何創(chuàng)建通用語言:

    1. 與領(lǐng)域?qū)<覍?duì)話寇壳,提煉關(guān)鍵術(shù)語
    2. 借鑒UML圖,根據(jù)關(guān)鍵術(shù)語畫草圖
    3. 反復(fù)與領(lǐng)域?qū)<覝贤ㄆ拊酰瑏砭P完P(guān)鍵技術(shù):
    4. 使用單詞和短語
    5. 書面文檔對(duì)圖形進(jìn)行補(bǔ)充壳炎,并永遠(yuǎn)保持最新

限界上下文(Bound Context)

從廣義上講,領(lǐng)域即是一個(gè)組織所做的事情以及其中所包含的一切逼侦。
由于“領(lǐng)域模型”包含“領(lǐng)域”這個(gè)詞匿辩,我們會(huì)認(rèn)為應(yīng)該為整個(gè)業(yè)務(wù)系統(tǒng)創(chuàng)建一個(gè)單一的腰耙、內(nèi)聚的、全功能式的模型铲球。然而挺庞,這并不是我們使用DDD的目標(biāo)。正好相反睬辐,在DDD中挠阁,一個(gè)領(lǐng)域被分為若干子域,領(lǐng)域模型在限界上下文中完成開發(fā)溯饵。事實(shí)上侵俗,在開發(fā)一個(gè)領(lǐng)域模型時(shí),我們關(guān)注的通常只是這個(gè)業(yè)務(wù)系統(tǒng)的某個(gè)方面丰刊。試圖創(chuàng)建一個(gè)全功能的領(lǐng)域模型是非常困難的隘谣,并且很容易導(dǎo)致失敗。一個(gè)限界上下文并不一定只包含在一個(gè)子域中啄巧。但這是可能的寻歧。一個(gè)限界上下文不應(yīng)該包含岐義的領(lǐng)域特定術(shù)語。原則上是一個(gè)子域包含一個(gè)限界上下文秩仆。

  • 核心域:對(duì)于核心域码泛,它是整個(gè)業(yè)務(wù)領(lǐng)域的一部分,也是業(yè)務(wù)成功的主要促成因素澄耍。從戰(zhàn)略層面上講噪珊,企業(yè)應(yīng)在核心域上勝人一籌。我們應(yīng)該給核心域最高的優(yōu)先級(jí)齐莲,最資深的領(lǐng)域?qū)<液妥顑?yōu)秀的開發(fā)團(tuán)隊(duì)痢站。

  • 理解限界上下文:
    限界上下文是一個(gè)顯示的邊界,領(lǐng)域模型便存在于這個(gè)邊界之內(nèi)选酗,領(lǐng)域模型把通用語言表達(dá)成軟件模型阵难,創(chuàng)建邊界的原因在于,每一個(gè)模型概念芒填,包括它的屬性和操作呜叫,在邊界之內(nèi)都具有特殊的含義。模型需要準(zhǔn)確反應(yīng)通用語言殿衰。
    比如:賬戶在銀行上下文和文學(xué)上下文怀偷,圖書在出版的不同階段,用戶在身份訪問上下文和協(xié)作上下文

  • 限界上下文的組成部分:領(lǐng)域模型播玖,數(shù)據(jù)庫定義,用戶界面饭于,開放主機(jī)服務(wù)等蜀踏。

  • 限界上下文的大形伞:模塊,聚合果覆,領(lǐng)域事件和領(lǐng)域服務(wù)這些概念在需要時(shí)才引入颅痊,不在引入一個(gè)不屬于你的通用語言的概念。

實(shí)體(Entity)

  • 為什么使用實(shí)體:
    當(dāng)我們需要考慮一個(gè)對(duì)象的個(gè)性特征局待,或者需要區(qū)分不同的對(duì)象時(shí)斑响,我們引入實(shí)體這個(gè)領(lǐng)域概念。一個(gè)實(shí)體是一個(gè)唯一的東西钳榨,并且可以在相當(dāng)長的一段時(shí)間內(nèi)持續(xù)地變化舰罚。我們可以對(duì)實(shí)體做多次修改,幫一個(gè)實(shí)體對(duì)象可能和它先前的狀態(tài)大不相同薛耻。但是营罢,由于它們擁有相同的身份標(biāo)識(shí),它們依然是同一個(gè)實(shí)體饼齿。并不是所有的數(shù)據(jù)對(duì)象都需要建立成實(shí)體饲漾,很多時(shí)候,一個(gè)領(lǐng)域概念應(yīng)該建模成值對(duì)象缕溉,而不是實(shí)體對(duì)象考传。

  • 貧血模型:業(yè)務(wù)對(duì)象僅僅包含數(shù)據(jù)而不包含行為,他的作用只是數(shù)據(jù)的載體或者說是數(shù)據(jù)的傳遞介質(zhì)证鸥。系統(tǒng)的業(yè)務(wù)邏輯全部放到業(yè)務(wù)邏輯層僚楞,會(huì)導(dǎo)致業(yè)務(wù)邏輯層比較龐大。

  • 充血模型:業(yè)務(wù)對(duì)象既包含數(shù)據(jù)又包含行為敌土,他的作用不再只是數(shù)據(jù)的載體而是一個(gè)真正有行為的對(duì)象镜硕。此時(shí),領(lǐng)域?qū)幼鳛檐浖w系的一個(gè)層次出現(xiàn)而非貧血模式中的輔助的角色返干。貧血模型以數(shù)據(jù)為中心兴枯,客戶代碼必須知道如何正確地將一個(gè)實(shí)體進(jìn)行操作來完成正確的行為,這樣的模型是不能稱為領(lǐng)域模型的矩欠。

實(shí)體標(biāo)識(shí)的生成方式:

  1. 用戶提供唯一標(biāo)識(shí)
  2. 應(yīng)用程序生成唯一標(biāo)識(shí)
  3. 持久化機(jī)制生成唯一標(biāo)識(shí)
  4. 另一個(gè)限界上下文生成唯一標(biāo)識(shí)
  • 標(biāo)識(shí)生成時(shí)間:
    考慮到客戶端需要向外界發(fā)布領(lǐng)域事件的情形财剖。我們應(yīng)該及早生成實(shí)體標(biāo)識(shí),方式2是比較好的方式癌淮。唯一標(biāo)識(shí)的生成放在實(shí)體上或者資源庫

  • 創(chuàng)建實(shí)體:
    通過構(gòu)造函數(shù)來初始化足夠多的實(shí)體狀態(tài)躺坟,一方面有助于表明該實(shí)體的身份,另一方面可以幫助客戶端更容易地查找該實(shí)體乳蓄。

  • 驗(yàn)證屬性:建議采用自封裝方式

  • 驗(yàn)證整體對(duì)象:采用Specification(規(guī)范)或者Strategy(策略)來進(jìn)行驗(yàn)證

值對(duì)象(Value Object)

  • 值對(duì)象特征:
    1. 它度量或描述了領(lǐng)域中的一件東西
    2. 它可以作為不變量
    3. 它將不同的相關(guān)的屬性組合成一個(gè)概念整體4.當(dāng)度量和描述改變時(shí)咪橙,可以用另一個(gè)值對(duì)象予以替換
    4. 它可以和其它值對(duì)象進(jìn)行相等性比較
    5. 它不會(huì)對(duì)協(xié)作對(duì)象造成副作用

領(lǐng)域事件(Domain Event)

領(lǐng)域事件作為領(lǐng)域模型的重要部分,是領(lǐng)域建模的工具之一。用來捕獲領(lǐng)域中已經(jīng)發(fā)生的事情美侦。并不是領(lǐng)域中所有發(fā)生的事情都要建模為領(lǐng)域事件产舞,要忽略無業(yè)務(wù)價(jià)值的事件。領(lǐng)域事件是領(lǐng)域?qū)<宜P(guān)心的(需要跟蹤的菠剩、希望被通知的易猫、會(huì)引起其他模型對(duì)象改變狀態(tài)的)發(fā)生在領(lǐng)域中的一些事情。簡而言之具壮,領(lǐng)域事件是用來捕獲領(lǐng)域中發(fā)生的具有業(yè)務(wù)價(jià)值的一些事情准颓。它的本質(zhì)就是事件,不要將其復(fù)雜化棺妓。在DDD中攘已,領(lǐng)域事件作為通用語言的一種,是為了清晰表述領(lǐng)域中產(chǎn)生的事件概念涧郊,幫助我們深入理解領(lǐng)域模型贯被。

  • 事務(wù)一致性
    事務(wù)一致性是是數(shù)據(jù)庫事務(wù)的四個(gè)特性之一,也就是ACID特性之一:

    • 原子性(Atomicity):事務(wù)作為一個(gè)整體被執(zhí)行妆艘,包含在其中的對(duì)數(shù)據(jù)庫的操作要么全部被執(zhí)行彤灶,要么都不執(zhí)行。
    • 一致性(Consistency):事務(wù)應(yīng)確保數(shù)據(jù)庫的狀態(tài)從一個(gè)一致狀態(tài)轉(zhuǎn)變?yōu)榱硪粋€(gè)一致狀態(tài)批旺。
    • 隔離性(Isolation):多個(gè)事務(wù)并發(fā)執(zhí)行時(shí)幌陕,一個(gè)事務(wù)的執(zhí)行不應(yīng)影響其他事務(wù)的執(zhí)行。
    • 持久性(Durability):已被提交的事務(wù)對(duì)數(shù)據(jù)庫的修改應(yīng)該永久保存在數(shù)據(jù)庫中汽煮。
  • 最終一致性
    “最終一致性”是一種設(shè)計(jì)方法搏熄,可以通過將某些操作的執(zhí)行延遲到稍后的時(shí)間來提高應(yīng)用程序的可擴(kuò)展性和性能。

引入領(lǐng)域事件的目的主要有兩個(gè)暇赤,一是解耦心例,二是使用領(lǐng)域事件進(jìn)行事務(wù)的拆分,通過引入事件存儲(chǔ)鞋囊,來實(shí)現(xiàn)數(shù)據(jù)的最終一致性止后。

事件存儲(chǔ)(Event Store)和事件溯源(Event Sourcing)

為什么要持久化事件?

  • 當(dāng)事件發(fā)布失敗時(shí)溜腐,可用于重新發(fā)布译株。
  • 通過消息中間件去分發(fā)事件,提高系統(tǒng)的吞吐量挺益。
  • 用于事件溯源歉糜。
    源代碼管理工具我們都用過,如Git望众、TFS匪补、SVN等伞辛,通過記錄文件每一次的修改記錄,以便我們跟蹤每一次對(duì)源代碼的修改叉袍,從而我們可以隨時(shí)回滾到文件的指定修改版本始锚。

事件溯源的本質(zhì)亦是如此,不過它存儲(chǔ)的并非聚合每次變化的結(jié)果喳逛,而是存儲(chǔ)應(yīng)用在該聚合上的歷史領(lǐng)域事件。當(dāng)需要恢復(fù)某個(gè)狀態(tài)時(shí)棵里,需要把應(yīng)用在聚合的領(lǐng)域事件按序“重放”到要恢復(fù)狀態(tài)對(duì)應(yīng)的領(lǐng)域事件為止润文。

聚合(Aggregation Root)

  1. 聚合設(shè)計(jì)的原則:
    聚合是用來封裝真正的不變性,而不是簡單的將對(duì)象組合在一起殿怜;聚合內(nèi)強(qiáng)一致性典蝌,聚合之間最終一致性;聚合應(yīng)盡量設(shè)計(jì)的型访铡骏掀;聚合之間的關(guān)聯(lián)通過ID,而不是對(duì)象引用柱告;聚合內(nèi)強(qiáng)一致性截驮,聚合之間最終一致性;聚合是用來封裝真正的不變性际度,而不是簡單的將對(duì)象組合在一起這個(gè)原則葵袭,就是強(qiáng)調(diào)聚合的真正用途除了封裝我們本身所關(guān)心的信息外,最主要的目的是為了封裝業(yè)務(wù)規(guī)則乖菱,保證數(shù)據(jù)的一致性坡锡。在我看來,這一點(diǎn)是設(shè)計(jì)聚合時(shí)最重要和最需要考慮的點(diǎn)窒所;當(dāng)我們在設(shè)計(jì)聚合時(shí)鹉勒,要多想想當(dāng)前聚合封裝了哪些業(yè)務(wù)規(guī)則,實(shí)現(xiàn)了哪些數(shù)據(jù)一致性吵取。
  2. 聚合應(yīng)盡量設(shè)計(jì)的小
    這個(gè)原則禽额,更多的是從技術(shù)的角度去考慮的。通過一個(gè)例子來說明海渊,該例子中挎袜,一開始聚合設(shè)計(jì)的很大要销,包含了很多實(shí)體,但是后來發(fā)現(xiàn)因?yàn)樵摼酆习臇|西過多,導(dǎo)致多人操作時(shí)并發(fā)沖突嚴(yán)重浙宜,導(dǎo)致系統(tǒng)可用性變差;后來開發(fā)團(tuán)隊(duì)將原來的大聚合拆分為多個(gè)小聚合载弄,當(dāng)然祝迂,拆分為小聚合后婿奔,原來大聚合內(nèi)維護(hù)的業(yè)務(wù)規(guī)則同樣在多個(gè)小聚合上有所體現(xiàn)。所以實(shí)現(xiàn)了既能解決并發(fā)沖突的問題问慎,也能保證讓聚合來封裝業(yè)務(wù)規(guī)則萍摊,實(shí)現(xiàn)模型級(jí)別的數(shù)據(jù)一致性;聚合設(shè)計(jì)的小還有一個(gè)好處如叼,就是:業(yè)務(wù)決定聚合冰木,業(yè)務(wù)改變聚合。聚合設(shè)計(jì)的小除了可以降低并發(fā)沖突的可能性之外笼恰,同樣減少了業(yè)務(wù)改變的時(shí)候踊沸,聚合的拆分個(gè)數(shù),降低了聚合大幅重構(gòu)(拆分)的可能性社证,從而能讓我們的領(lǐng)域模型更能適應(yīng)業(yè)務(wù)的變化
  • 迪米特法則:
    迪米特法則又叫最少知道原則逼龟,最早是在1987年由美國Northeastern University的lan Holland提出。通俗的來講追葡,就是一個(gè)類對(duì)自己依賴的類知道的越少越好腺律。也就是說,對(duì)于被依賴的類來說宜肉,無論邏輯多么復(fù)雜匀钧,都盡量地的將邏輯封裝在類的內(nèi)部,對(duì)外除了提供的public方法崖飘,不對(duì)外泄漏任何信息榴捡。迪米特法則還有一個(gè)更簡單的定義:只與直接的朋友通信。首先來解釋一下什么是直接的朋友:每個(gè)對(duì)象都會(huì)與其他對(duì)象有耦合關(guān)系朱浴,只要兩個(gè)對(duì)象之間有耦合關(guān)系吊圾,我們就說這兩個(gè)對(duì)象之間是朋友關(guān)系。耦合的方式很多翰蠢,依賴项乒、關(guān)聯(lián)、組合梁沧、聚合等檀何。其中,我們稱出現(xiàn)成員變量廷支、方法參數(shù)频鉴、方法返回值中的類為直接的朋友,而出現(xiàn)在局部變量中的類則不是直接的朋友恋拍。也就是說垛孔,陌生的類最好不要作為局部變量的形式出現(xiàn)在類的內(nèi)部。

  • “告訴而非詢問”原則:
    一個(gè)對(duì)象應(yīng)該命令其它對(duì)象該做什么施敢,而不是去查詢其它對(duì)象的狀態(tài)來決定做什么(查詢其它對(duì)象的狀態(tài)來決定做什么也被稱作"功能嫉妒(Feature Envy)"
    舉例

      if(person周荐,getAddress0.getCountry0=="Australia"){
    

    這違反了得墨忒耳定律狭莱,因?yàn)檫@個(gè)調(diào)用者跟Person過于親密。它知道Person里有一個(gè)Address概作,而Address里還有一個(gè)country腋妙。它實(shí)際上應(yīng)該寫成這樣:

      if(person,livesIn("Australia"))
    
  • 樂觀并發(fā):在樂觀并發(fā)控制中讯榕,用戶讀數(shù)據(jù)時(shí)不鎖定數(shù)據(jù)骤素。在執(zhí)行更新時(shí),系統(tǒng)進(jìn)行檢查愚屁,查看另一個(gè)用戶讀過數(shù)據(jù)后是否更改了數(shù)據(jù)谆甜。如果另一個(gè)用戶更新了數(shù)據(jù),將產(chǎn)生一個(gè)錯(cuò)誤集绰。一般倩況下,接收錯(cuò)誤信息的用戶將回滾事務(wù)并重新開始谆棺。該方法主要用在數(shù)據(jù)爭奪少的環(huán)境內(nèi)栽燕,以及偶爾回滾事務(wù)的成本超過讀數(shù)據(jù)時(shí)鎖定數(shù)據(jù)的成本的環(huán)境內(nèi),因此稱該方法為樂觀并發(fā)控制改淑。

  • 悲觀并發(fā):鎖定系統(tǒng)阻止用戶以影響其它用戶的方式修改數(shù)據(jù)碍岔。如果用戶執(zhí)行的操作導(dǎo)致應(yīng)用了某個(gè)鎖,則直到這個(gè)鎖的所有者釋放該鎖朵夏,其它用戶才能執(zhí)行與該鎖沖突的操作蔼啦。該方法主要用在數(shù)據(jù)爭奪激烈的環(huán)境中,以及出現(xiàn)并發(fā)沖突時(shí)用鎖保護(hù)數(shù)據(jù)的成本比回滾事務(wù)的成本低的環(huán)境中仰猖,因此稱該方法為悲觀并發(fā)控制捏肢。

  • 相對(duì)悲觀鎖而言,樂觀鎖機(jī)制采取了更加寬松的加鎖機(jī)制饥侵。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機(jī)制實(shí)現(xiàn)鸵赫,以保證操作最太程度的獨(dú)占性。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷躏升,特別是對(duì)長事務(wù)而言辩棒,這樣的開銷往往無法承受。所以DDD一般采用的是樂觀并發(fā)鎖膨疏。借助于ORM樂觀并發(fā)鎖的機(jī)制一睁,在聚合內(nèi)部根實(shí)體放置樂觀并發(fā)的版本號(hào)是最安全的做法。

資源庫(Repository)

資源庫的是封裝所有獲取對(duì)象引用所需的邏輯佃却。領(lǐng)域?qū)ο蟛恍杼幚砘A(chǔ)設(shè)施者吁,以得到領(lǐng)域中對(duì)其他對(duì)象的所需的引用。只需從資源庫中獲取它們双霍,于是模型重獲它應(yīng)有的清晰和焦點(diǎn)砚偶。

資源庫會(huì)保存對(duì)某些對(duì)象的引用批销。當(dāng)一個(gè)對(duì)象被創(chuàng)建出來時(shí),它可以被保存到資源庫中染坯,然后以后使用時(shí)可從資源庫中檢索到均芽。如果客戶程序從資源庫中請求一個(gè)對(duì)象,而資源庫中并沒有它单鹿,就會(huì)從存儲(chǔ)介質(zhì)中獲取它掀宋。換種說法是,資源庫作為一個(gè)全局的可訪問對(duì)象的存儲(chǔ)點(diǎn)而存在仲锄。

服務(wù)(Services)

當(dāng)我們在分析某一領(lǐng)域時(shí)劲妙,一直在嘗試如何將信息轉(zhuǎn)化為領(lǐng)域模型,但并非所有的點(diǎn)我們都能用Model來涵蓋儒喊。對(duì)象應(yīng)當(dāng)有屬性镣奋,狀態(tài)和行為,但有時(shí)領(lǐng)域中有一些行為是無法映射到具體的對(duì)象中的怀愧,我們也不能強(qiáng)行將其放入在某一個(gè)模型對(duì)象中侨颈,而將其單獨(dú)作為一個(gè)方法又沒有地方,此時(shí)就需要服務(wù).

服務(wù)是無狀態(tài)的芯义,對(duì)象是有狀態(tài)的哈垢。所謂狀態(tài),就是對(duì)象的基本屬性:高矮胖瘦扛拨,年輕漂亮耘分。服務(wù)本身也是對(duì)象,但它卻沒有屬性(只有行為)绑警,因此說是無狀態(tài)的求泰。

服務(wù)存在的目的就是為領(lǐng)域提供簡單的方法。為了提供大量便捷的方法待秃,自然要關(guān)聯(lián)許多領(lǐng)域模型拜秧,所以說,行為(Action)天生就應(yīng)該存在于服務(wù)中章郁。

服務(wù)具有以下特點(diǎn):

  • 服務(wù)中體現(xiàn)的行為一定是不屬于任何實(shí)體和值對(duì)象的枉氮,但它屬于領(lǐng)域模型的范圍內(nèi)
  • 服務(wù)的行為一定涉及其他多個(gè)對(duì)象
  • 服務(wù)的操作是無狀態(tài)的

模塊(Moudles)

對(duì)于一個(gè)復(fù)雜的應(yīng)用來說,領(lǐng)域模型將會(huì)變的越來越大暖庄,以至于很難去描述和理解聊替,更別提模型之間的關(guān)系了。模塊的出現(xiàn)培廓,就是為了組織統(tǒng)一的模型概念來達(dá)到減少復(fù)雜性的目的惹悄。而另一個(gè)原因則是模塊可以提高代碼質(zhì)量和可維護(hù)性,比如我們常說的高內(nèi)聚肩钠,低耦合就是要提倡將相關(guān)的類內(nèi)聚在一起實(shí)現(xiàn)模塊化泣港。

模塊應(yīng)當(dāng)有對(duì)外的統(tǒng)一接口供其他模塊調(diào)用暂殖,比如有三個(gè)對(duì)象在模塊a中,那么模塊b不應(yīng)該直接操作這三個(gè)對(duì)象当纱,而是操作暴露的接口呛每。模塊的命名也很有講究,最好能夠深層次反映領(lǐng)域模型坡氯。

總結(jié)

理論往往比實(shí)踐全面的多晨横,它只是對(duì)實(shí)踐進(jìn)行指導(dǎo),并不是說你套弄了所有理論那么你才是領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的箫柳,你的架構(gòu)才是正統(tǒng)的領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)架構(gòu)手形,不能以偏概全。但是你的架構(gòu)如果想說是符合領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的也不是那么簡單悯恍,就像畫畫一樣库糠,如果沒有抓住重點(diǎn)和主要特征,那么就是畫貓為虎了涮毫。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末曼玩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子窒百,更是在濱河造成了極大的恐慌,老刑警劉巖豫尽,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篙梢,死亡現(xiàn)場離奇詭異,居然都是意外死亡美旧,警方通過查閱死者的電腦和手機(jī)渤滞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來榴嗅,“玉大人妄呕,你說我怎么就攤上這事∷圆猓” “怎么了绪励?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長唠粥。 經(jīng)常有香客問我疏魏,道長,這世上最難降的妖魔是什么晤愧? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任大莫,我火速辦了婚禮,結(jié)果婚禮上官份,老公的妹妹穿的比我還像新娘只厘。我一直安慰自己烙丛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布羔味。 她就那樣靜靜地躺著河咽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪介评。 梳的紋絲不亂的頭發(fā)上库北,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音们陆,去河邊找鬼寒瓦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛坪仇,可吹牛的內(nèi)容都是我干的杂腰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼椅文,長吁一口氣:“原來是場噩夢啊……” “哼喂很!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起皆刺,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤少辣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后羡蛾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漓帅,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年痴怨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了忙干。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浪藻,死狀恐怖捐迫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情爱葵,我是刑警寧澤施戴,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站萌丈,受9級(jí)特大地震影響暇韧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浓瞪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一懈玻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦涂乌、人聲如沸艺栈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽湿右。三九已至,卻和暖如春罚勾,著一層夾襖步出監(jiān)牢的瞬間毅人,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國打工尖殃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丈莺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓送丰,卻偏偏與公主長得像缔俄,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子器躏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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