文章出處

寫在前面

  閱讀目錄:

  上一篇《No zuo no die:DDD 應對具體業務場景,Domain Model 重新設計》。

  希望本篇博文廢話少點,注:上一篇瞎扯的地方太多

疑惑解讀

  先回顧一下,在上一篇博文中,主要闡述的是領域模型的重新設計,包含:真正的去理解領域模型和領域服務的加入(感興趣的朋友可以看下前幾篇來了解一下前因后果。)。凡事都有修改的理由,為什么加入領域服務,主要是之前對領域模型的認知不夠(實體充當起了偽領域模型),所以就造成之前設計的對象發送對象之說。

  上一篇博文發表后,園中有很多朋友積極參與了討論,比如一如既往的 netfocus 兄深藍醫生xuefly螺絲釘愛螺絲帽殤、凌楓、鼻涕成詩、徐少俠JRoger等等,詳細的討論內容,請看上一篇的評論。在這里,必須和以上參與討論的兄臺說聲感謝,我覺得分享領域驅動設計的博文,就是要有討論的氣氛才好,如果死氣沉沉,只是我在這邊唱獨角戲也沒什么意思,討論才有學習,學習才有進步,這是一個共贏。

  首先我先表述一下 MessageManager(短消息系統) 項目的具體業務場景,因為在前幾篇博文中,大家對這個業務場景的不了解,以致造成一些無謂的討論(第二篇博文中和 netfocus 兄真是如此,還有后面的 xuefly)。回到重點,短消息系統類似于我們日常生活中的寫信或者發信息,一個用戶給另一個用戶發消息,僅此而已,而不是像大家所說的應用性的發消息(類似于人機通信),這種發消息沒有什么業務概念,只是說應用層發出一條命令讓基礎層去發一條消息,不會像日常生活中的寫信發消息,需要判斷一些業務規則(比如一個用戶一天可以發多少條消息等),對業務場景有一些誤讀,就會造成一些無謂的討論,希望朋友們在理解這個領域驅動設計實踐前,先明白這是一個什么業務場景。

  插一句 dudu 在閃存中說過的一段話:“領域驅動設計就是要以領域為中心,圍繞著領域轉,不然沒必要叫領域“驅動”,最多只能叫“面向”領域。領域驅動出來的產物——代碼,就是領域模型。”。領域驅動設計使用自然語言(不懂技術的需求專家也可以看懂),去描述我們現實生活中的業務場景,設計的時候忘掉數據庫,忘掉表現層,忘掉應用層,這樣才會設計出一個“純凈”的領域模型,但是也要針對現實中的業務場景去設計和實現,要不然就會變成“面向對象設計”,只是一個“無所作為”的領域模型,并沒有解決實際的業務需求。

  下面主要針對 netfocus 兄在上篇所提出的疑問(其他兄臺的疑問,大部分評論中已回復),做一些解讀(因為當時頭腦發熱,處在一個迷迷糊糊的狀態)。

1.先說一個bug吧:if (message.SendUser.SendMessages.Where(m => m.SendTime == DateTime.Now).Count() > 100)這句話中DateTime.Now是當前時間,是精確到秒的,所以你這里判斷一天只能發送100條消息的邏輯有bug;應該只比較天才對;
2.你不是說domain service的方法的入參和返回值都應該是領域對象嗎?那為啥返回值是OperationResponse<Message>?這個明顯不是領域對象呀!實際上,這里我覺得沒必要用返回值,如果校驗失敗,直接拋異常不就好了;
3.你要驗證發件人和收件人不能是同一個人,現在放在domain service中做,個人認為放在message聚合根中做更好啊,聚合根的構造函數中直接判斷發件人和收件人不能是同一個就行了。
4.我沒看到你哪里有更新MessageState的,是不是漏代碼了?
5. 你現在發送人對象上有一個集合,表示他總共已經發了多少條消息,那如果這個人以后他了1W條消息,你也要加載到內存嗎?性能上存在嚴重問題;以后如果收件 人也要有一個限制一天不能超過1000條消息,那是不是也要在收件人上設置一個集合,存放所有的收到的消息,呵呵;這個問題如何解決,你還得再想想;
6.最后一點,也是最關鍵的一點:我希望你能明確說出,消息發出去的標志是什么?你 現在是調用domain service的SendMessage方法,認為該方法返回后就表示消息已發送出去了;但是我怎么看到這個方法里就做了兩件事情:1)校驗發送人和收件 人不能是同一個人;2)校驗發送人一天里不能發送100條消息;也就是說,你這個方法里只是對給定的消息做了一些業務規則的驗證;那為何不把這個領域服務 取名為ValidateMessageService呢?我之前那篇文章里和你談到過,當你的方法里實際做的事情和你的方法的名稱不一致時,說明你還沒理解這個方法所代表的職責該誰執行,或者你還沒理解這個職責的含義;
所以,你最根本的,你先要說明你的業務場景中,怎么樣才代表一個消息發送出去了。

我覺得像你這個場景,只要:
1.把消息new出來,構造函數中做必要的業務規則檢查(包括發件人和收件人必須相同);
2.繼續做其他驗證,比如發件人的消息不能超過100;(可以調用一個領域服務來驗證,不通過直接拋異常即可);
3.上述兩步通過,則調用repository把消息持久化到db,就好了;

所以,怎么樣才算消息發送了呢?難道不是把消息持久化起來就好了嗎?然后我們其他可能專門有一個定時job定時從消息表獲取未發送的消息;拿出來去發送,發送完成后再更新消息的狀態是已發送;

當然,如果你不想定時,也可以把消息放到一個消息隊列(這種方式就是我之前我貼的調用基礎服務將消息發送到分布式消息隊列的那句代碼),然后由隊列的監聽放消費這個消息,拿到消息,去做真正的發送,發送成功后,修改消息的狀態為已發送即可;

總 感覺,你現在腦子中一直在思考,發送消息是一個業務邏輯,所以需要在領域模型中提現出來,但真的是業務邏輯嗎?發送消息其實是一個業務場景(DCI中的 C),是應用層需要體現出來的,而領域層不一定要顯式的體現發送消息這個動作;你一定要強行體現出來,那就會像你上面那樣,方法里做的事情和方法的名稱不相符。

  方便大家閱讀,就直接粘貼過來了(不要嫌多哦),大家也可以看下我之前的回復內容,首先,“調用的參數和返回的結果應該是領域對象”這段話引自《領域驅動設計》,具體在領域服務的講解中,關于這一段的文字表述,我現在是有些疑問的,首先關鍵詞”應該“,并不是”必須“,也就是說可以為其他的對象,”領域對象“是什么?實體?值對象?其實并不只是實體和值對象,可能是一些自建的領域對象,比如書中創建的 FundsTransfer 表示兩個賬戶的轉賬結果對象,但是有時候我們在領域服務中做一些操作,比如發消息領域服務,發送成功返回一個領域對象,這個對象一般為消息實體,但是在刪除消息服務中,返回的領域對象是什么?難道還是消息實體(也有可能),我覺得還不如返回一個 BOOL 類型的對象更清晰明白(如果),這一點是有些疑問。

  后面第五點和第六點是比較重要的,先說第五點,用戶對象附著兩個集合(發件箱和收件箱),關于這一點我覺得是設計問題,但是為什么要加這兩個集合呢?因為在領域服務中做發件人或收件人信息驗證的時候,可以很方便的訪問,比如下面這段代碼:

1             if (message.SendUser.SendMessages.Where(m => m.SendTime.Date == DateTime.Now.Date).Count() > 200)
2             {
3                 throw new Exception("發件人一天之內只能發送200個短消息");
4             }

  這種代碼可能寫著可能沒什么問題,也方便于理解,但是在實現的時候就有問題了,領域對象是這樣獲取的嗎?如果我再加很多個業務規則,對發件人或收件人的信息判斷,都是這樣寫的嗎?這個問題會引入第六點,消息的發送標示是什么?我之前認為是在發送領域服務中通過發送業務規則的驗證,返回的那個過程就表示這條信息已經發送了,netfocus 兄對此的疑問就是在這個發消息領域服務中,體現的不是發消息,而是驗證消息,還不如用 ValidateMessageService 領域服務來的貼切,確實是這個道理,但是如果領域服務改成這個名稱,那領域模型中就沒有發消息這個概念了,有的只是驗證消息,消息的發送也只體現在了持久化上面,這是我不愿意看到了,關于這個疑問以上并沒有做解讀,請接著看下面。

  首先在這個消息系統中,沒有持久話,忘掉一些,我們要通過領域模型中去描述這個消息業務場景,那就必須包含:發消息、回復消息查看郵件箱等等業務需求,也就是說我們要在這個消息領域模型中也體現出這些業務需求,然后讓不是技術的需求人員也可以看懂,也就像 dudu 所說的那樣:以領域為核心,圍繞這領域轉,領域驅動而不是面向領域。但是說的簡單,實現起來是很難的,這也是領域驅動設計最痛苦的地方,但是理解這一點很重要,以領域為核心。

  很抱歉,看到這里,其實我還是沒有對 netfocus 兄的疑問做根本的解讀,沒關系,理解上面我所說的后,我們接著往下看,或許答案就在下面哦。

設計窘境

  好,現在我們按照上面分析的方式進行領域模型設計,項目中只有領域層和相對應的單元測試,用領域模型去表述消息的具體業務場景,那就必須有發消息、回復消息等,我們使用領域服務實現,也就是每個業務對應一個領域服務(領域服務命名都必須是動詞),這樣我們表面上會完整的描述一個消息業務場景,具體是怎樣實現的,我不管,但是必須可以體現出這個業務,也就是說我們不能把 SendMessageService 修改成 ValidateMessageService。

  我們接著思路往下面進行具體的設計,比如下面這個發消息領域服務:

 1 /**
 2 * author:xishuai
 3 * address:https://www.github.com/yuezhongxin/MessageManager
 4 **/
 5 
 6 using MessageManager.Domain.Entity;
 7 using System;
 8 using System.Linq;
 9 namespace MessageManager.Domain.DomainService
10 {
11     /// <summary>
12     /// SendMessage領域服務實現
13     /// </summary>
14     public class SendMessageService
15     {
16         public static Message SendMessage(Message message)
17         {
18             //示例業務規則,對象導航關聯訪問需要探討
19             if (message.SendUser.SendMessages.Where(m => m.SendTime.Date == DateTime.Now.Date).Count() > 200)
20             {
21                 throw new Exception("發件人一天之內只能發送200個短消息");
22             }
23             if (message.SendUser.SendMessages.Where(m => m.SendTime.Date == DateTime.Now.Date && m.ReceiveUser == message.ReceiveUser).Count() > 50)
24             {
25                 throw new Exception("發件人一天之內只能和同一人發送50個短消息");
26             }
27             //to do...
28             return message;
29         }
30     }
31 } 

  SendMessage 表示發消息,看這幾行代碼,傳入一個消息對象,返回一個消息對象,中間做一些驗證操作,這個方法所做的工作就是驗證,也不怪 netfocus 兄這樣說,發送的業務體現不出來,可能我硬說是驗證通過返回是發送,可能可以挺過一段時間,但是也騙不了自己多久,因為在寫領域單元測試的時候,對發送消息進行測試,測試的代碼就沒辦法寫,因為這個消息對象在發送前和發送后得不到體現,只能做一些驗證異常測試,這是沒法接受的,停滯了。。。

  回到用戶附帶的兩個集合(發件箱和收件箱)這個問題上,如果添加這兩個集合是正確的情況下(只是假設),就可以解決上面的問題,很簡單,分別往這條消息所屬的發件人-發件箱,收件人-收件箱里填充這條消息就行了,也就是下面的偽代碼:

1             message.SendUser.SendMessages.Add(message);
2             message.ReceiveUser.ReceiveMessages.Add(message);

  這樣進行發消息表述的話,我們在單元測試的代碼也就好寫了,但是如果你仔細看這段代碼,不理解意思也會覺得很別扭,難道領域模型中去存儲領域對象是這樣存儲的???還有就是關于發件人和收件人的消息業務規則判斷也是有問題的(message.SendUser.SendMessages.Where...)。

  以上這種問題的表象就是有點為了領域而領域了,雖然領域是核心,但是這就有點不符合編程的基本原則,整體來說就是有點太“面向對象”了,并沒有解決實際的問題,說到底還是沒有真正領域驅動設計。關于以上設計窘境,真是太痛苦了,前前后后也停滯了好長時間,完全不曉得怎么做,代碼寫了刪,刪了寫,總之沒有一點頭緒,真是比要死了還痛苦。

  總的來說,我記錄下設計窘境的兩個主要方面:

  1. 發消息、閱讀消息等業務體現(如果單元測試代碼可以寫出,那就可以體現,但是寫不出)
  2. 領域對象的存儲和讀取(主要用在業務規則驗證)

  有時候編程像藝術創作一樣,就是靠靈感,苦思白日,不如靈光一現,請接著往下看。

一幅圖的靈感

  有時候困在一個角落出不來,那就仰望下天空,說不準上面就是出路。

  沒辦法,自己想不通就找資料找靈感,網上多說都是領域驅動設計概念或是偽領域驅動設計實踐(只是示例,也就是空架子),又沒辦法,只能翻閱兩本經典著作《領域驅動設計》和《企業應用架構模式》,在第一本書中以前關注的主要是第五章節的部分,如下:

  也就是關于實體、值對象和領域服務的設計,其實我是想找一下領域服務到底是如何進行實現的,領域服務這一節點的內容我看了不知道多少遍,都是概念性的描述,沒有一行代碼實踐,所以理解起來很困難,有很多朋友看到這,也許會說領域驅動設計就是要概念描述指導,用代碼就體現不出來了,如果這樣理解,那只能呵呵了,因為電腦不只是用來看電影,最主要的還是用來寫代碼的。

  回到正題,在看第一本書的時候,無意間看到第四章(分離領域)的第一節(分離架構)中的一幅協作圖,以前也是看過,但是這次是針對問題的看,所以會注意很多內容,協作圖如下:

                      沒錯,就是它

  這是書中的一個賬戶轉賬示例,一個賬戶加錢,一個賬戶減錢,完成的過程是一個事務,就是這么簡單。從這幅圖上我們可以很清晰的看到各個層所做的工作,可能平時看的時候會一閃而過,但是當抱著一些疑問去看的時候,所獲取的信息量是很大的,比如我想知道在一個簡單業務用例中,領域模型到底實現的是什么(不要和我說概念,概念大家都會說)?然后就可以推敲出其他層的一些實際工作,比如應用層,其實有時候我們在領域驅動設計的時候最容易混淆的就是應用層和領域層,當然網上關于領域層和應用層的定義概念一搜一大把,你可能也會說幾句,比如什么應用層是很薄的一層,主要工作是協調任務的等等,但是實踐起來呢?用代碼表示就蒙了,說多了。

  回到這幅圖上,雖然瞟了幾眼,我就知道我以前的設計是有問題的,比如應用層,從圖上就可以看出領域層最復雜,應用層很薄,所做的工作是:

  1. 發起一個請求
  2. 確認處理結果
  3. 提交工作單元

  那我們再來看下我之前在應用層的設計代碼:

 1         /// <summary>
 2         /// 發送消息
 3         /// </summary>
 4         /// <param name="title">消息標題</param>
 5         /// <param name="content">消息內容</param>
 6         /// <param name="senderLoginName">發件人-登陸名</param>
 7         /// <param name="receiverDisplayName">收件人-顯示名</param>
 8         /// <returns></returns>
 9         public OperationResponse SendMessage(string title, string content, string senderLoginName, string receiverDisplayName)
10         {
11             User sendUser = userRepository.GetUserByLoginName(senderLoginName);
12             if (sendUser == null)
13             {
14                 return OperationResponse.Error("未獲取到發件人信息");
15             }
16             User receiveUser = userRepository.GetUserByDisplayName(receiverDisplayName);
17             if (receiveUser == null)
18             {
19                 return OperationResponse.Error("未獲取到收件人信息");
20             }
21             Message message = new Message(title, content, sendUser, receiveUser);
22             OperationResponse<Message> serviceResult = SendMessageService.SendMessage(message);
23             if (serviceResult.IsSuccess)
24             {
25                 return serviceResult.GetOperationResponse();
26                 //messageRepository.Add(message);
27                 //return messageRepository.Context.Commit();
28             }
29             else
30             {
31                 return serviceResult.GetOperationResponse();
32             }
33         }

  什么 GetUserByLoginName 和 GetUserByDisplayName 方法獲取領域對象然后進行判斷,我覺得就有問題,除去 SendMessageService 領域服務的實現不說,后面注釋的 messageRepository.Add(message); 這段代碼也是有問題的,領域對象的存儲在應用層上?不過后面的 messageRepository.Context.Commit(); 工作單元提交是正確的,從上面可以看出,這個應用層是很厚的,并不像書中應用層那么薄。

  在上面問題中最關鍵的問題是領域對象的創建,從上面圖中可以看出,是在領域模型中創建并存儲的,應用層只是提交一個請求,確認請求,然后提交工作單元(相當于事務),就是這么簡單,而我們實現的太復雜了,所以就有可能導致前面一直的設計窘境-領域模型只是用來驗證。

  總的來說,我們有了一個重要線索,那就是領域對象的生命周期管理,如果理解了這一點,或許我們就可以走出之前的設計窘境。

為嘛還是你-Repository

  沿著領域對象的生命周期管理這條線索,我們在《領域驅動設計》書中找到了以下內容:

  管理領域對象的生命周期,主要是通過工廠(負責創建)和倉儲(負責存儲、重建和銷毀)進行管理,領域對象的創建,簡單的做法是通過構造函數進行創建(我們正是如此),復雜性的領域對象就要必須通過工廠進行創建,首先我們先說下工廠(雖然我們沒用到),組裝一個復雜的復合對象的工作,與該對象被組裝成功后所執行的任何其他工作,最好是分離開來,就比如只有在生產轎車時才需要組裝功能,我們在開車時并不需要一個機器人或者一個技師,因為轎車從來都不會一邊組裝一邊行駛,因此把這些功能塞進同一個機制中是沒有價值的。還有就是創建領域對象要放在哪?毫無疑問,應該是在領域中,但是在上面我們設計應用層代碼的時候,比如 new Message(....),這個就是領域對象的創建,如果放在應用層,就會暴露出領域對象,而且領域對象創建發生了改變,是必須要去修改應用層的代碼的(這就是我們不希望看到的),而如果在領域層,我們只要修改工廠就行了,其他不需要任何修改。

  還有就是,如果把組裝的職責轉移給其他對此感興趣的對象,如應用中的客戶對象,那就會使問題變得更加糟糕。客戶知道需要讓哪些領域對象來執行必須的計算,從而完成自己的工作。如果我們希望讓客戶程序來組裝其所需的領域對象,那它就必須知道一些有關該對象內部結構的事情。為了保證領域對象中各個部分之間的關系滿足所有不變量,客戶程序又必須知道該對象上的某些規則。即使是調用構造函數也會使客戶與它正在創建的具體類關聯起來。對領域對象的實現所作的所有修改都需要客戶做出相應修改,導致重構更加困難。

  因為工廠我們暫時沒用到,就不多說了,我們來看下 Repository(倉儲),其實我們對倉儲并不陌生,在前面幾篇博文中都有提到,而且曾經還懷疑過它。但是說實話,其實并沒有真正的去理解它,在理解倉儲之前,需要注意一點的就是,它的概念是相對于領域驅動設計而言的。關于倉儲的定義,在《領域驅動設計》這本書中并沒有準確的定義(都是一些論述),但是在《企業應用架構模式》這本書中有下面定義:

Repository(倉儲):協調領域和數據映射層,利用類似與集合的接口來訪問領域對象。

  這段定義可以說是相當的給力,主要包含兩個重要信息點:

  1. 協調領域
  2. 訪問領域對象

  以前也看過這段定義,但都是一閃而過,并沒有像現在這樣,帶著問題去理解,再回到《領域驅動設計》這本書上關于倉儲的論述,無論對對象執行什么操作,我們首先必須要獲得對它的引用。我們是怎樣獲得引用的呢?一種方法是創建這個對象,因為創建操作將把新對象的引用返回給我們。另一種方法是通過關聯的導航。我們從一個已知的對象出發,向它請求一個與之關聯的對象。這種方法在任何面向對象的程序都會大量用到。同時,對象之間的鏈接也使對象模型獲得了相當一部分的表達能力。但是,我們必須得到那第一個對象。

  一個倉儲將某種類型的所有對象描述為一個概念性的集合(通常是模擬的集合)。它的行為與集合類似,但是包含更精細的查詢能力。倉儲可以加入和刪除具有合適類型的對象,并通過倉儲背后的機制將它們插入數據庫或從數據庫中刪除。從這個定義可以推斷出,倉儲具有一系列緊密相關的職責,為我們提供了對聚合根從產生之初直到其生命周期結束期間的訪問能力。

  為每種需要全局訪問的對象類型創建一個對象,該對象為該類型所有對象在內存中的集合提供影像。用一個眾所周知的全局接口來設立訪問入口。提供增刪對象的方法,把對數據存儲的實際的插入和刪除封裝起來。提供根據某種標準篩選對象的方法,返回完整實例化了的屬性值符合標準的對象或對象集合,把實際的存儲和查詢技術封裝起來。僅為確實需要直接訪問的聚合根提供倉儲。讓客戶聚焦于模型,把所有對象存儲和訪問的工作委托給倉儲來完成。

  部分持久對象必須通過按對象屬性進行查詢的方式來實現全局訪問。對不便于通過導航來訪問的聚合根來說,這種訪問方式是必需的。這些對象通常是實體,有時是包含復雜內部結構的值對象,有時是枚舉值。為其他對象提供這種訪問會使一些重要的區別變得模糊。不受限制的數據庫查詢實際上會破壞領域對象和聚合的封裝。把技術基礎結構和數據庫訪問機制暴露出來,會使客戶變得復雜,同時掩蓋了模型驅動設計。

  以上是我摘自關于倉儲的部分精彩論述,概念性的東西了解太多容易迷失自我,我們回到兩個很現實的問題,倉儲是干嘛的?倉儲誰來用?毫無疑問,倉儲是提供領域對象訪問的接口,哪誰來調用?不知道你有沒有從上面的論述找到一絲線索,反正我是沒有,倉儲的調用無非就是兩個,一個是應用層,另一個就是領域層。首先,我們探討下應用層的可能性,其實從上面的那張協作圖就可以找到答案,賬戶的重建并沒有在應用層,而是在領域層,應用層只是傳遞幾個參數,發出一個轉賬請求而已,然后根據確認結果,提交工作單元。

  說了這么多,如果在領域層加入倉儲,代碼會變成怎樣?請看下面示例:

 1 /**
 2 * author:xishuai
 3 * address:https://www.github.com/yuezhongxin/MessageManager
 4 **/
 5 
 6 using MessageManager.Domain.Entity;
 7 using MessageManager.Domain.Repositories;
 8 using System;
 9 namespace MessageManager.Domain.DomainService
10 {
11     /// <summary>
12     /// SendMessage領域服務實現
13     /// </summary>
14     public class SendMessageService
15     {
16         #region Private Fields
17         private readonly IMessageRepository messageRepository;
18         private readonly IUserRepository userRepository;
19         #endregion
20 
21         #region Ctor
22         public SendMessageService(IMessageRepository messageRepository, IUserRepository userRepository)
23         {
24             this.messageRepository = messageRepository;
25             this.userRepository = userRepository;
26         }
27         #endregion
28 
29         public Message SendMessage(string title, string content, string senderLoginName, string receiverDisplayName)
30         {
31             User sendUser = userRepository.GetUserByLoginName(senderLoginName);
32             if (sendUser == null)
33             {
34                 throw new Exception("發送失敗,未獲取到發件人信息");
35             }
36             User receiveUser = userRepository.GetUserByDisplayName(receiverDisplayName);
37             if (receiveUser == null)
38             {
39                 throw new Exception("發送失敗,未獲取到收件人信息");
40             }
41             Message message = new Message(title, content, sendUser, receiveUser);
42             if (messageRepository.GetMessageCount(DateTime.Now) > 200)
43             {
44                 throw new Exception("發件人一天之內只能發送200個短消息");
45             }
46             messageRepository.Add(message);
47             return message;
48         }
49     }
50 }

  從代碼實現上可以看到,領域對象的重建、獲取都是在領域中,你也可以由此想到應用層的實現代碼,和上面轉賬比較類似,GetUserByLoginName 之類的倉儲查詢方法是我們獲取領域對象的方式,比如我們在做消息發送領域服務的時候,只要定義消息倉儲的接口就行了,代碼如下:

 1 /**
 2 * author:xishuai
 3 * address:https://www.github.com/yuezhongxin/MessageManager
 4 **/
 5 
 6 using MessageManager.Domain.Entity;
 7 using System;
 8 using System.Collections.Generic;
 9 
10 namespace MessageManager.Domain.Repositories
11 {
12     /// <summary>
13     /// 表示繼承于該接口的類型是作用在“消息”聚合根上的倉儲類型。
14     /// </summary>
15     public interface IMessageRepository : IRepository<Message>
16     {
17         int GetMessageCount(DateTime sendTime);
18         ICollection<Message> GetOutbox(User readUser);
19         ICollection<Message> GetInbox(User readUser);
20     }
21 }

  至于倉儲的實現,這在領域中并不關心,它只要知道領域對象的獲取方式就行了,實現應該在基礎層,可以存儲內存,也可以存儲數據庫,messageRepository.Add(message); 這段代碼表示往消息倉儲中添加消息領域對象(并沒有持久化),你可以把消息倉儲看作是郵箱,我們可以從這個郵箱提取出任何消息聚合根內的領域對象,這個可以讓我們很方便的在領域中進行消息的業務判斷。往消息倉儲中添加消息對象可以看成是發消息的業務體現,因為消息倉儲其實就是相當于消息系統,它是消息領域對象的集合,我們前面 new Message 其實只是創建一個消息領域對象,但并不表示在這個消息倉儲中,也就體現不出發消息這個業務,需要注意一點的就是往消息倉儲中添加消息對象并不是持久化數據庫什么的,消息倉儲是管理所有消息領域對象的集合,和數據庫沒有半毛錢關系,這個很容易造成誤解。如果按照這種邏輯,發消息的單元測試就是判斷這個消息在消息倉儲中是否存在。

后記

  以上關于領域模型的再重新設計,主要是添加了倉儲,也就是說領域對象的生命周期管理,放在了領域中,其實說實話,我也不清楚這樣做是否正確,雖然表面上看上去有些合乎常理,但是卻又感覺不那么看得順眼,如果你有什么想法或建議,請參與討論,在此感謝。

  MessageManager 項目開源地址:

  如果你覺得本篇文章對你有所幫助,請點擊右下部“推薦”,^_^


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()