從零開始使用CodeArt實踐最佳領域驅動開發(三)

来源:http://www.cnblogs.com/codeart/archive/2017/07/01/7103875.html
-Advertisement-
Play Games

5.領域模型設計 在開始考慮如何構建賬戶子系統的領域模型之前,我們先來看看關於CA里領域模型的基本概念。初次接觸這些陌生的概念確實會一知半解,不過沒有關係,大家實踐幾次領域設計後就會融會貫通,深刻體會到這些概念背後隱藏的優點。 概念1:領域對象。領域模型里的一切對象都應該是領域對象。所謂的領域對象就 ...


5.領域模型設計

  在開始考慮如何構建賬戶子系統的領域模型之前,我們先來看看關於CA里領域模型的基本概念。初次接觸這些陌生的概念確實會一知半解,不過沒有關係,大家實踐幾次領域設計後就會融會貫通,深刻體會到這些概念背後隱藏的優點。

  概念1:領域對象。領域模型里的一切對象都應該是領域對象所謂的領域對象就是指遵守領域規則的對象,這是它與普通對象最大的區別。領域規則主要由以下幾點組成:

  1) 領域對象永遠都不會為null。在詳細說明這一點之前,大家一定要知道這個規則跟資料庫里的欄位不能為null沒有任何關係。到目前為止,我們都沒有提到如何使用資料庫技術,這個教程之後的內容也不會講述與資料庫有關的話題,因為這些都與領域模型的建立沒有一丁點的關係,沒有資料庫一樣可以成功建立領域模型。各位一定要摒棄以前根深蒂固的開發思路,放空自己的思想,重新接收領域模型里的概念。

  言歸正傳,在領域的世界里,一切對象都是有其存在的意義的。什麼叫存在的意義?這體現在職責上,一個對象可以履行某項職責那麼這個對象就是有意義的存在。在領域世界里絕對不會出現不具備任何職責的對象。大多數情況下,對象需要提供方法以便其履行職責(.Net里的屬性也是方法,只是被包裝了而已)。

  每年企業都會向政府交納一定的稅收,這是企業的職責之一,所以領域世界里的企業模型會有一個計算納稅金額的方法,該方法會根據企業的盈利計算出交稅的金額。如果我問你,你創辦的公司今年要交多少稅?你可以通過該方法計算結果,告訴我需要交納10萬元。那麼,你沒有公司呢?你根本就沒有自己創辦的公司,那你要如何回答我這個問題?回答“很抱歉,我沒有公司”嗎?錯,程式的世界是理性的世界,一切都是嚴謹的,計算納稅金額的方法定義返回值是浮點數,那麼這個方法在任何情況下都應該返回一個浮點數(拋出異常例外)。當我問你的公司要交納多少稅,你只能告訴我具體的數值,我不管你是否有自己的公司,那是你自己要考慮的事情。所以,你應該回答“0”。

  分析到這裡,大家發現一個問題沒有,一個沒有創辦公司的人卻也可以知道“自己創辦的公司”今年要交納多少稅收,只不過由於他實際上沒有公司,所以計算的結果為0。因此,判定事物能否履行某項職責和這個事物的實例是否存在沒有必然的聯繫。我們只要設計了名稱為Corp的企業領域模型,在Corp類里定義了方法CalculateTax(計算稅收),那麼在任何情況下,Corp的實例都應該可以成功的調用該方法,至於計算的結果由Corp內部去處理,外界不用考慮這些細節。所以,即使不存在編號為135的Corp對象,我們依然可以根據編號135找到一個為Empty的Corp對象,調用該對象的CalculateTax方法會計算並得到稅收值為0的結果。

  所以,在領域的世界里,我們規定對象可以為Empty但是永遠不能為null,因為null表示不存在,不存在的對象無法履行任何職責。我們衡量對象是否存在的唯一依據就是職責,一個沒有職責的對象根本就不會被設計出來,所以領域世界里沒有不存在的對象,而Empty則表示對象是存在的,只不過數據都是空的,空對象依然可以履行職責,只是履行職責的結果會和非空對象執行的結果有所不同。大家千萬不要小看“領域對象永遠都不會為null”這項特性,它可以解決很多問題。比如說:傳統開發中刪除一條數據必須級聯刪除多條數據的情況在CA的開發模式里幾乎不存在、對象之間的硬關聯也可以由這項特性解除。在以後的示例中我們會幫助各位進一步理解這個概念。下圖是體現這一規則的INotNullObject介面定義:

  2) 每個領域對象都具有驗證固定規則的能力。固定規則與業務規則不同,這組規則不會隨著使用對象的場景的變化而變化,它是對象自身固有的規則。例如人的年齡不可能有上萬歲,汽車的輪胎個數也不會有上百個,這些都是領域模型里的固定規則,不會隨著人或汽車這一事物在不同的使用場景里而發生規則的改變。CA通過實現ISupportFixedRules介面來完成領域對象驗證固定規則的能力:

  3) 我們可以明確的知道領域對象的倉儲狀態,領域對象的倉儲狀態與它在倉儲中保存的數據映射有關。當新建一個領域對象時,該對象的倉儲狀態就是“新建”的;使用完領域對象,將其存入倉儲後,該對象的狀態就是“乾凈”的;我們從倉儲中獲取一個對象,並更改了對象的屬性值,但還未提交給倉儲再次保存的時候,該對象的狀態就是“臟”的。對象的倉儲狀態與對象的持久化操作息息相關,所以CA明確規定每個領域對象都能提供和更改自身的倉儲狀態,該領域規則由IStateObject詮釋:

  除了以上3項規則外,我們在設計領域對象時還需要遵循一些其他的領域規則,這些規則會以約定的形式給出而不是顯示的介面,這在後面的實踐中會詳細討論。所有的領域對象都實現了IDomainObject介面:

  概念2:實體對象。這類對象具有可區別性,可以與其他事物區分開來。不同顏色、款式的衣服肯定是不同的實體,但是同樣顏色、同樣款式的衣服就一定是同一個實體嗎?在現實世界,人們可以感性的判定事物的唯一性,比如我買的衣服和你穿的衣服就算一模一樣,但是它們理所當然的不是同一件衣服。然而在程式世界一切都是理性的,所有判斷都是要有根據的。

  領域實體對象的職責之一就是幫助程式辨別不同的對象實例,區分事物的唯一性。每個實體對象都需要提供唯一的標識符來標識自己的存在。我們能夠通過該標識符找到唯一一個對應的實體,這是實體對象的重要特征。通過標識符可以引用到一個對象,這也是實體對象常被稱為引用對象的原因。

  我們可以為衣服設置一個叫做編號的唯一標示符。我買的衣服編號為1,你穿的衣服編號為2,就算衣服的其他屬性值都相同,但是由於唯一標識符不同,所以這兩件衣服在系統看來是不同的實體。當我調用洗衣機的方法去洗我的衣服(編號1),不會對你的衣服(編號為2)造成影響。也就是說,隨著時間軸的推移,實體對象的狀態會由於各種領域行為的發生而導致了改變,但我們依然可以根據標識符找到目標實體並關註狀態變化的情況。以洗衣為例,我的衣服(編號1)變的乾凈了,因此我很滿意。而你的衣服(編號2)依然那麼臟,但與我無關。

  這正是我們使用實體對象的根本原因:追蹤目標對象狀態的變化情況,以便其行為更清楚且可預測。也就是說,當我們需要持續關註一個事物的變化情況時,我們應該將該事物的模型設計為實體對象。以下是CA里實體對象必須實現的介面(該介面不必程式員實現,CA提供了實體對象的基類):

  概念3:值對象。如果一個對象的所有屬性都是用於從某種角度來描述另外一個事物的狀態時,這個對象就是值對象。

  繼續以衣服為例,我們可以把衣服的顏色、圖案、尺碼這3個屬性提取出來,作為一個單獨的值對象叫“外貌”,再由衣服去引用這個“外貌”。這樣我與你的衣服雖然不是同一件衣服,但是外貌特征可以相同,都是白色、花型圖案、XL碼的體恤衫。由於衣服是實體對象,我們依然可以清楚的區分衣服的唯一性,但是我們的衣服共用了相同的外貌特征。所以,值對象是沒有唯一性判斷依據的,它只是多個描述事物狀態值的綜合載體,如果兩個值對象的屬性值都相同,那麼我們認為這兩個值對象是同一個對象,因為他們描述事物的結果是一樣的。使用值對象需要遵守兩個領域規則:

  1) 值對象的所有屬性都是只讀的,只有當構造它的時候才能傳入值,構造完畢後,值對象的屬性將無法更改。這意味著當我想把衣服染成紅色的時候,我只能新建一個“紅色、花型圖案、XL碼”的外貌特征,並將衣服的外貌屬性設置成新建的這個值對象。我不能更改現有“外貌”的“顏色”屬性,因為一旦更改了,你衣服的外貌也被更改了,因為我和你的衣服都是引用的同一個外貌特征值對象。因此,在CA里,值對象的設計必須保證屬性是只讀的。

  2) 我們要保證值對象里的所有屬性都是從同一個角度去描述事物的。使用值對象的一個很重要的原因就是將複雜事物的屬性提取出來,單獨作為一個對象管理,這樣可以提高程式的可維護性。例如在訂單這個事物里涉及到的信息會很多:購買的商品、商品數量、優惠活動、購買人、支付方式、發貨方式等。其中“收件人所在的省份城市”、“詳細地址”、“郵編”這三項信息都與收貨地址有關,我們可以把這類描述收貨地址的屬性集中起來,設計一個地址(Address)的值對象來管理他們。訂單就可以使用該對象來承擔與收貨地址相關的職責。因此,如果一個值對象的屬性是雜亂無章的,不能從同一個角度描述事物的特點,那這個值對象是沒有存在價值的。

  概念4:內聚模型。一個模塊內部各個元素彼此結合的越緊密則它的內聚性越強。也正是由於這些元素結合的非常緊密,他們往往也只負責某一項任務,這也就是所謂的單一職責原則。

  我們之前創建了門戶服務,因為我們認為菜單、角色、許可權等事物是整個項目運行的基礎,這幾個事物之間或多或少存在某些聯繫,有一定的內聚性,所以將這幾個事物劃分到一個服務里共同驅動門戶服務工作。

  另外,角色、許可權、賬號三者的關係遠比菜單更加緊密,即使沒有菜單對象,他們仍然可以共同擔負起身份識別的職責。因此我們將他們納入到賬戶子系統中,賬戶子系統可以脫離門戶服務獨立被其他服務使用。所以,賬戶子系統是一個比門戶服務更高的內聚模塊。子系統的內聚性要遠高於服務。

  那麼在子系統內部呢?子系統內部我們依然可以按照類似的思路,根據對象之間的緊密程度劃分出更高的內聚模型,這就是我們要熟悉的第4個概念:領域模型里的內聚模型。在詳細討論這個概念之前,我們需要搞清楚如何判斷對象之間是緊密的。如果對象A引用了對象B,當A在履行某些職責的時候,需要對象B的支持,我們就說A依賴於B。這在程式實現里常常表現為當調用A的方法MA時,MA內部又調用了B的方法MB來協助MA順利的執行,這種情況下A和B的關係就比較緊密。那麼,有沒有比這種AB關係更加緊密的關係呢?

  有的。如果對象A的生命周期依賴於對象B的生命周期,也就是說,只有構造了B,A才有可能被構造,而當B被銷毀了,A就一定會被銷毀。對象A的生命周期始終依賴於對象B的生命周期,那麼這種關係就是更加緊密的,這也就是內聚模型里的對象之間的關係。下麵我們以書這個事物為例詳細講述內聚模型的領域規則:

  1) 每個內聚模型里都會有且僅有一個內聚根,內聚根又被稱為聚合根,它是一個實體對象。這意味著你可以通過唯一標識找到對應的聚合根。我們將書(Book)設計成為一個聚合根,那麼Book對象就一定是實體對象,值對象是不能成為聚合根的。在CA里所有的聚合根都會實現IAggregateRoot介面,IAggregateRoot同時也實現了IEntityObject實體對象的介面:

  查看IAggreateRoot的代碼,你們會發現這是一個沒有任何方法的空介面。那麼這個介面有什麼意義呢?要回答這個問題,我們首先要搞清楚介面存在的意義。你和我在某項職責上達成統一,並做出約定。按照這個約定,我知道你會擔負起什麼樣的職責。在我有需要的時候可以找你履行這項職責,而你需要保證順利的將職責履行完畢。怎麼去履行職責我不管,履行職責的過程我也不必關心。因為我是決策者,我只用考慮什麼時候用你。而你是實施者,你必須知道如何更好的履行職責,但是你不必考慮履行職責範圍以外的事情,至於什麼時候輪到你工作,一切聽我這個決策者的調遣就可以了。這就是介面的調用和介面的實現的本質特點。如果我和你的約定中,你什麼事都不用做呢?什麼事都不用做這本身就是一個約定,這種約定就是空介面。那既然你什麼都不用做,為什麼我們決策上還會有這種約定呢?

  在大多數時候,我們會有多項職責上的劃分來共同完成一個目標,就好比下象棋一樣,棋盤上的棋子擔負不同的職責,但是他們共同的目標都是吃了對方的將軍。也就是說,我們會有多項介面提供給決策者使用,這些介面互相間的調用方式、決策者使用他們的時機等因素就組合成了一個完整策略。因此,介面的調用方代表決策者,介面的實現方代表實施者。一個介面的定義反映的是整體策略的一個環節,介面不是孤立的,是圍繞一個目標共同作出多項約定里的某一個方面的約定。我們使用介面絕非是使用某一個介面,而是一整組策略。在這裡IAggreateRoot代表的約定是,策略里有一個稱之為聚合根的存在。雖然這個聚合根的介面沒有任何方法,但是它依然承擔起“我是聚合根”的約定,只要你是聚合根你就有義務承擔起聚合根的職責,當決策者需要用到你的時候,他需要知道你是聚合根才能將你“派遣“給倉儲介面,讓倉儲介面去持久化你(CA里只有聚合根才能被加入倉儲)。所以,空介面也是有意義的!空介面一樣可以提供策略上的判斷。

  另外,介面是可以良性成長的。你可以想象一下,你的孩子(IAggreateRoot)剛出生,他雖然不能幫你分擔任何家務,不能對這個家貢獻任何價值,但是他難道就不是你的孩子(它還是IAggreateRoot)了嗎?隨著家庭生活的變化、孩子的成長(需求的改變或者其他原因導致你要改進設計策略),孩子有能力承擔家務了(根據需要為IAggreateRoot增加方法),他就可以對這個家庭貢獻更多的價值,讓家更溫暖(改變介面後,讓策略更加完美)。將括弧內的比喻連接在一起,表達的意思是:在設計領域模型的前期,我們對整體策略的把握還不足,導致有可能我們創建的介面的方法比較少,甚至一個方法都沒有,但是這些介面以及定義的方法都是為了實現某一個目標而產生的。那麼在後面的開發中,我們對實現的目標更加清晰了、對需求的理解更加深刻了,我們就會追加或更改介面的方法,只要這些改變都是為了更好的實現目標的、不是破壞性的,那麼介面就可以變化,這種變化就是良性變化。所以當我們有需要也可以為IAggreateRoot追加新的方法約定,讓它更加強大。但是整體策略卻不必為此做出重大的改變,因為IAggreateRoot已經存在於現有的策略中,為IAggreateRoot追加方法只會更加完善現有的策略。(事實上在CA的最新版本中為IAggreateRoot已追加了好幾個方法以滿足框架整體戰略升級的需要)

  Book的示意代碼為:

  AggregateRoot<TObject, TIdentity>是聚合根的基類。Book對象繼承了該類就代表Book是一個聚合根。關於聚合根的更多話題在後續教程里會結合實踐例子詳細說明。

2)除了聚合根外,我們還可以在內聚模型里設計其他的對象,這些對象被稱為內聚成員。內聚成員可以是實體對象也可以是值對象。我們將書的封面(BookCover)設計為一個內聚值對象成員:

  ValueObject是值對象的基類。BookCover繼承了該類就表示BookCover是一個值對象。那麼我們如何體現出BookCover是Book的內聚成員呢?有兩種方式,我們先講解第一種方式,這種方式可以滿足絕大多數應用,而第二種方式是CA里的高級話題,在後續教程中再結合實例說明。我們可以為Book設計一個屬性Cover(封面):

  大家不用在意代碼截圖裡關於領域屬性定義的代碼,稍後就會有詳細說明。我們現在只用知道Book里有一個類型為BookCover的封面屬性,這就意味著BookCover是以Book為聚合根的內聚模型的成員。

  3) 我們可以通過聚合根的引用找到所有的內聚成員。或者說,如果你想找到某個內聚成員就必須先載入聚合根,再通過聚合根去查找內聚成員。通過類似book.Cover的代碼就可以找到某本書的封面了。但是你無法憑空獲取任何一個BookCover實例的信息,必須先找到它所屬的聚合根Book再通過Book的屬性Cover去訪問對應的信息。因此,值對象和實體對象都是無法脫離聚合根而單獨存在的,它們必須依附於聚合根組成內聚模型。
  也就是說,領域模型層里唯一獨立的元素就是“內聚模型”。所有的領域對象都會處於某個內聚模型內,脫離於內聚模型而單獨存在的領域對象是不存在的。Book和BookCover就屬於“以書為聚合根的內聚模型”。它們共同構建了現實事物里的“書”在領域世界里的模型。

  4) 內聚模型A不能直接引用另外一個內聚模型B里的成員,但是A可以引用B的聚合根。只允許外部對象保持對根的引用,對內部成員的臨時引用可以被傳遞出去,但僅在一次操作中有效。假設我們有一項需求,需要知道人們喜歡看的書籍。為了描述人這個事物,我們設計了關於人的內聚模型,該模型的聚合根為Person(人)。那麼在該內聚模型內,Person或者其他成員都不能直接引用BookCover,因為BookCover是書的內聚模型的成員。但是Person及其成員可以引用Book,然後通過Book訪問Cover,這在代碼上體現為person.MostLikeBook.Cover。表示找到person最愛的書籍(MostLikeBook)的封面(Cover)。這項規則的目的是用聚合根來控制其成員的訪問細節,由於根控制訪問,因此不能繞過它來修改內部對象。聚合根之所以被稱為“根”就是因它是內聚模型里一切的根基,它對成員有最高的控制權。

  5) 只有聚合根才能被加入到倉儲,內聚模型里的其他成員只能在聚合根被加入到倉儲的時候隨著根被一起保存。我們可以編寫代碼直接保存聚合根,但是無法編寫代碼直接保存內聚成員。內聚成員的持久化機制是伴隨著根的持久化而被保存的。示例代碼如下:

  在示例代碼中,我們創建了BookCover的示例cover,然後創建了Book的實例book並將book的封面屬性值設置為cover,最後創建Book的倉儲介面的實現repository,並調用repository.Add方法保存book對象。我們直接倉儲化的是Book類,而BookCover的保存會在倉儲內部實現,領域模型層並不主動保存內聚成員BookCover。倉儲的概念會在後文里單獨講解,這裡暫時不過多說明。

  雖然我們對以上規則進行了詳細的說明,但是大家肯定不能完全理解,甚至會覺得莫名其妙,疑惑遵守這些規則能帶來什麼好處呢?有這種想法很正常,畢竟各位沒有真正實踐過領域驅動的開發。在稍後的內容里,我們會結合實際例子告訴各位每項規則落實的細節,到時候你們就能深刻體會到它們帶來的好處了。目前你僅需知道有這些規則存在即可,不必太過刨根問底。

  在瞭解了內聚模型的設計原則後,我們不禁要問:為什麼會有內聚模型?為什麼我們不直接使用領域對象呢?

  原因有兩個,一方面,我們將關係緊密、職責統一的對象以領域規則的形式約束在一起,可以極大的提高系統的維護性。當程式需要修正的時候只需找到對應的內聚模型,調整其內部的實現細節即可,其他內聚模型受到的影響很少,連鎖改動降到了最低點,避免了混亂。另外一方面,我們所有的設計原理都是需要落實到代碼上的,需要有技術實施的基礎。設計思想可以脫離技術獨立存在,但是這些思想實施起來則需要充分考慮技術上的實現方式。舉一個常見的例子,在傳統開發中,我們為了避免併發衝突會對錶相關的數據進行鎖定。在領域開發中,我們同樣面臨類似的問題,只不過鎖定的是領域對象而不是直接鎖定數據(事實上領域模型層里沒有“數據”這個概念)。那麼如果沒有內聚模型,所有的對象都可以直接從倉儲中以帶鎖的形式載入,這就很容易造成死鎖。但是引入了內聚模型的概念後,我們只能載入聚合根,也只能鎖根。當根被鎖定了,內聚成員的任何操作都是線程安全的,這樣不僅避免了死鎖同時也滿足了開發程式的需要。因此,內聚模型可以提高程式的穩定性、健壯性。

  我們總結下領域對象、實體對象、值對象、聚合根、聚合模型五者的關係。

  1)實體對象、值對象、聚合根都屬於領域對象。在CA里,領域對象的基類是DomainObject,實體對象(EntityObject)、值對象(ValueObject)都繼承自DomainObject。

  2)聚合根是領域對象,同時也是實體對象。聚合根(AggreateRoot)繼承自EntityObject。

  3)一個聚合模型由至少1個聚合根和0個或者0個以上的實體對象或值對象組合而成。

  概念5:倉儲。倉儲是用於持久化領域對象的。領域對象要履行自己的職責必定需要有數據的支持,但是領域對象自身並不負責這項工作。對象的載入、修改、刪除等操作交由倉儲完成。你可以形象的認為倉儲就像一個倉庫那樣存放各類對象。當你需要某個領域對象的時候,它負責幫你從倉庫里取出來。當你創建了新對象也可以交由倉儲幫你存入倉庫。關於倉儲我們需要重點說明以下幾點設計原則:

  1) 在領域模型層里的倉儲一定是以介面的形式定義的,它不是一個對象,而是一組方法的約定。這組約定的實現由基礎設施層完成,領域模型層不會涉及到具體的存儲演算法,也不關心如何存儲對象。示例代碼:

  IBookRepository是聚合根Book的倉儲介面,這個介面約定了可以通過作者名稱查找相關的書籍對象。IBookRepository介面繼承了IRepository<TRoot>介面。該介面是CA提供的倉儲基礎介面,所有的倉儲都要繼承該介面。部分代碼如下:

  從代碼里我們可以看出IRepository<TRoot>定義了持久化領域對象的基本操作。因此我們使用IBookRepository可以添加、修改、刪除以及鎖定Book對象,還能根據作者名稱查找Book對象。

  2) 倉儲介面的約定必須是清晰、明確的。這項設計原則的意思是,你必須明確的告訴倉儲,你要根據哪些條件找到領域對象,你給出的條件不能是多樣化的。

  有些朋友在嘗試使用領域驅動開發的時候,將查詢條件設計成對象Query ,Query對象可以設置各種查詢子條件,例如 query.AddCondition(“name”, Condition.Equals,”作者名稱”);query.AddCondition(“sex”, Condition.Equals,”男”);這兩句代碼定義了查詢要根據name和sex來查找對象,當創建完query後將其交給倉儲,由倉儲負責翻譯query的含義,執行對應的查詢操作。

  在CA里是絕對不允許這樣設計的!倉儲介面處於領域模型層,它雖然是用於持久化領域對象的,但是倉儲也是領域模型的一部分,它從“如何找到事物”的角度反映了事物在這方面的本質特征。我們在示例里為IBookRepository倉儲定義的方法之一就是“根據作者名查找書”,該方法反應了”書這一事物可以通過作者這個事物被找到的”本質特征。而事物的本質特征必須是明確的,不能含糊不清。

  另外,從技術實現的角度來說,在傳統的開發模式里,程式員圍繞資料庫做開發,對數據的操作都是隨意的,是不可預知的。你可以在任意代碼段里對任意數據表進行任意的操作。這是混亂的根源,正是由於這個原因大多數項目都被做成豆腐渣了,所以我們一定要避免這一點。倉儲介面的約定必須是清晰、明確的。

  3) 倉儲介面的約定越少越好。我們認為倉儲介面定義的方法越少,“如何找到事物”這個維度里的本質特征就越清晰。試想一下,如果找到Book對象的方法有10幾個,那麼當我們需要找Book對象的時候必定混亂不堪(說淺顯點,就是10幾個方法給你去選,你都需要花費大量的時間去琢磨該用哪個方法)。大家一定要記住,倉儲介面不是存儲過程,我們不能為了尋求使用的方便而肆意增加它提供的方法。它提供的方法越少,領域模型對基礎設施層的依賴就越小。

  4) 倉儲的實現里不要有任何業務代碼。倉儲的職責就是存儲對象,它的代碼里不應該有任何關於“名稱是否重覆”、“轉賬是否成功”等一切與業務有關的邏輯。請記住,倉儲的實現只是機械式的“找到數據裝載領域對象”或者”得到領域對象後保存到數據倉庫中”。

  5) 只能創建聚合根的倉儲。聚合根是訪問內聚模型的入口,所以我們的倉儲介面里提供的方法僅能持久化聚合根。只是在倉儲的實現里會保存聚合根和相關的成員對象。請大家註意“倉儲介面”和“倉儲實現”的區別,倉儲介面屬於領域模型層,倉儲實現屬於基礎設施層。我們可以通過配置隨時切換倉儲介面的實現,而倉儲介面是領域模型層里的定義,使用該介面只能對聚合根進行持久化操作。聚合根的成員都是通過內聚根來訪問的而不是通過倉儲介面訪問。

  以上是CA里領域模型層的基本概念,這些概念大家一時半會還消化不了,後面的教程里我會結合實際的例子讓大家對它們有更加深刻的認識。其他高級話題(富實體對象、引用關係、移動聚合根、領域事件等)涉及到的內容也會在後續教程里依次說明。在下個章節里,我們可以正式開始編碼工作了。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 對於應用程式而言,日誌是非常重要的功能,通過日誌,我們可以跟蹤應用程式的數據狀態,記錄Crash的日誌可以幫助我們分析應用程式崩潰的原因,我們甚至可以通過日誌來進行性能的監控。總之,日誌的好處很多,特別是對Release之後的線上版本進行異常的跟蹤。 日誌存儲的分類 在平常開發時,我們通常喜歡在De ...
  • 閱讀目錄 單據號是指什麼 和唯一ID的不同是什麼 為什麼需要全局唯一單據號生成程式 實現的方式有哪些 筆者推薦的方式 結語 一、單據號是指什麼 我們作為一個軟體系統,肯定到處充滿著各種單據,也必然需要有各種單據號與之對應。比如:電商行業的訂單號、支付流水號、退款單號等等。SCM的採購單號、進貨單號、 ...
  • 想象一下,當程式所有的業務邏輯都完成的時候,你可能還來不及喘口氣,緊張的測試即將來臨。你的Boss告訴你,雖然程式沒問題,但某些方法為什麼執行這麼慢,性能堪憂。領會了Boss的意圖之後,漫長的排查問題開始了。你會寫日誌,或者是其他工具來追蹤原因。那麼如何以一種優雅的形式,並且不侵入業務代碼的形式來跟 ...
  • Spring Batch流程介紹: 上圖描繪了Spring Batch的執行過程。說明如下: 每個Batch都會包含一個Job。Job就像一個容器,這個容器里裝了若幹Step,Batch中實際幹活的也就是這些Step,至於Step乾什麼活,無外乎讀取數據,處理數據,然後將這些數據存儲起來(ItemR ...
  • 設計模式的分類創建模式單例模式工廠方法模式抽象工廠模式建造者模式原型模式設計模式的分類java設計模式中共23種模式,根據功能和特點可歸為3類今天進行分享的就是創建模式中相關的設計模式創建模式單例模式單例模式: 一個相對簡單的模式,目的是確保某一個類只有一個實例,而且自行實例化並向整個系統提供實例... ...
  • 在做項目的時候,有一個需求是將資料庫中的信息封裝到實體類返回到jsp界面 傳過來的參數只是實體類的id屬性,然後根據id屬性去查資料庫,事情就是這樣,然後 結果遇到很奇怪的事情,在jsp頁面中使用EL表達式取值,除了id欄位,其他都是NULL 先記錄結論: 分為兩種情況 一:方法參數use的引用值( ...
  • 6. 為領域模型Permission編碼 現在我們為賬戶子系統(AccountSubsystem)設計領域對象並編碼實現細節。 賬號、角色、許可權是賬戶子系統里已知的3個事物,而一個子系統裡面可以有多個內聚模型,所以我們首先要思考的問題是:以誰為聚合根創建第一個內聚模型? 與劃分子系統的思路一樣,我們 ...
  • // test01.cpp : Defines the entry point for the console application.////第一章,設計模式入門,策略模式#include "stdafx.h"#include "test01.h"class FlyBehavior{public: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...