迷霧森林
迷霧森林中,切勿迷失自我,不幸的是,我就這樣迷失了:
具體的業務場景還是短訊息系統-MessageManager,存在 Message 和 User 兩個領域模型,業務邏輯:一個使用者給另一個使用者傳送訊息,就是這麼簡單,可以看作是一個最簡單的業務邏輯,當然在傳送訊息這個過程中會有其他的業務邏輯,先不探討 Message 領域模型和 User 領域模型如何協調完成這個業務邏輯,我們先看以下,我在第一篇《我的“第一次”,就這樣沒了:DDD(領域驅動設計)理論結合實踐》博文中,關於領域模型的實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
/** * author:xishaui * address:https://www.github.com/yuezhongxin/MessageManager **/ using System; namespace MessageManager.Domain.DomainModel { public class Message : IAggregateRoot { #region 構造方法 public Message() { this.ID = Guid.NewGuid().ToString(); } #endregion #region 實體成員 public string FromUserID { get; set; } public string FromUserName { get; set; } public string ToUserID { get; set; } public string ToUserName { get; set; } public string Title { get; set; } public string Content { get; set; } public DateTime SendTime { get; set; } public bool IsRead { get; set; } public virtual User FromUser { get; set; } public virtual User ToUser { get; set; } #endregion #region IEntity成員 /// <summary> /// 獲取或設定當前實體物件的全域性唯一標識。 /// </summary> public string ID { get; set; } #endregion } } |
Are you kidding me?不,你沒看錯,以上就是 Message 領域模型的實現程式碼,User 領域模型的程式碼我就不貼了,比這個還要簡單,只包含 ID 和 Name 兩個欄位屬性,領域驅動設計主張的是充血模型,只包含欄位屬性的領域模型是極其貧血的,像上面的 Message 領域模型,充血的領域模型實現的是業務邏輯。上面我們說的傳送訊息這個業務邏輯,在領域模型中為什麼沒有體現?既然是基於領域驅動設計,那為什麼我還要這樣設計呢?這是為什麼呢?當時設計完之後,我也在思考這個問題,難道腦袋有問題?不可能吧?看下應用層的程式碼就知道了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
/** * author:xishuai * address:https://www.github.com/yuezhongxin/MessageManager **/ using AutoMapper; using MessageManager.Application.DTO; using MessageManager.Domain.DomainModel; using MessageManager.Domain.Repositories; using System.Collections.Generic; namespace MessageManager.Application.Implementation { /// <summary> /// Message管理應用層介面實現 /// </summary> public class MessageServiceImpl : ApplicationService, IMessageService { #region Private Fields private readonly IMessageRepository messageRepository; private readonly IUserRepository userRepository; #endregion #region Ctor /// <summary> /// 初始化一個<c>MessageServiceImpl</c>型別的例項。 /// </summary> /// <param name="context">用來初始化<c>MessageServiceImpl</c>型別的倉儲上下文例項。</param> /// <param name="messageRepository">“訊息”倉儲例項。</param> /// <param name="userRepository">“使用者”倉儲例項。</param> public MessageServiceImpl(IRepositoryContext context, IMessageRepository messageRepository, IUserRepository userRepository) : base(context) { this.messageRepository = messageRepository; this.userRepository = userRepository; } #endregion #region IMessageService Members /// <summary> /// 通過傳送方獲取訊息列表 /// </summary> /// <param name="userDTO">傳送方</param> /// <returns>訊息列表</returns> public IEnumerable<MessageDTO> GetMessagesBySendUser(UserDTO sendUserDTO) { //User user = userRepository.GetUserByName(sendUserDTO.Name); var messages = messageRepository.GetMessagesBySendUser(Mapper.Map<UserDTO, User>(sendUserDTO)); if (messages == null) return null; var ret = new List<MessageDTO>(); foreach (var message in messages) { ret.Add(Mapper.Map<Message, MessageDTO>(message)); } return ret; } /// <summary> /// 通過接受方獲取訊息列表 /// </summary> /// <param name="userDTO">接受方</param> /// <returns>訊息列表</returns> public IEnumerable<MessageDTO> GetMessagesByReceiveUser(UserDTO receiveUserDTO) { //User user = userRepository.GetUserByName(receiveUserDTO.Name); var messages = messageRepository.GetMessagesByReceiveUser(Mapper.Map<UserDTO, User>(receiveUserDTO)); if (messages == null) return null; var ret = new List<MessageDTO>(); foreach (var message in messages) { ret.Add(Mapper.Map<Message, MessageDTO>(message)); } return ret; } /// <summary> /// 刪除訊息 /// </summary> /// <param name="messageDTO"></param> /// <returns></returns> public bool DeleteMessage(MessageDTO messageDTO) { messageRepository.Remove(Mapper.Map<MessageDTO, Message>(messageDTO)); return messageRepository.Context.Commit(); } /// <summary> /// 傳送訊息 /// </summary> /// <param name="messageDTO"></param> /// <returns></returns> public bool SendMessage(MessageDTO messageDTO) { Message message = Mapper.Map<MessageDTO, Message>(messageDTO); message.FromUserID = userRepository.GetUserByName(messageDTO.FromUserName).ID; message.ToUserID = userRepository.GetUserByName(messageDTO.ToUserName).ID; messageRepository.Add(message); return messageRepository.Context.Commit(); } /// <summary> /// 檢視訊息 /// </summary> /// <param name="ID"></param> /// <returns></returns> public MessageDTO ShowMessage(string ID, string isRead) { Message message = messageRepository.GetByKey(ID); if (isRead == "1") { message.IsRead = true; messageRepository.Update(message); messageRepository.Context.Commit(); } return Mapper.Map<Message, MessageDTO>(message); } #endregion } } |
可以看到應用層的程式碼真是不忍直視,撇開其他操作,我們看下 SendMessage 這個方法,首先 MessageDTO 這個引數就不應該存在,下面用 AutoMapper 進行物件轉化,然後再進行賦值操作,這個過程就是典型的過程思維模式,沒有體現出一點的 OO 思想,賦值完之後,使用 Repository(倉儲)進行持久話,傳送訊息的這個業務邏輯體現在哪?如果硬要說體現的話,那就是:messageRepository.Add(message) 這段程式碼了,想想當時無知的認為,傳送訊息的業務邏輯體現就是持久化資料庫,還真是可笑。
在這篇博文發表後,很多園友也都意識到了這個問題,什麼問題?主要是以下兩個:
- Domain Model(領域模型):領域模型到底該怎麼設計?你會看到,MessageManager 專案中的 User 和 Message 領域模型是非常貧血的,沒有包含任何的業務邏輯,現在網上很多關於 DDD 示例專案多數也存在這種情況,當然專案本身沒有業務,只是簡單的“CURD”操作,但是如果是一些大型專案的複雜業務邏輯,該怎麼去實現?或者說,領域模 型完成什麼樣的業務邏輯?什麼才是真正的業務邏輯?這個問題很重要,後續探討。
- Application(應用層):應用層作為協調服務層,當遇到複雜性的業務邏輯時,到底如何實現,而不使其變成 BLL(業務邏輯層)?認清本質很重要,後續探討。
簡而言之就是:領域模型太貧血;應用層變成了業務邏輯層。意識到問題,那就找問題所在,經過一番探查,把 Repository 作為了重點懷疑物件,為什麼?主要是我當時以為 Repository 的職責有問題,也就有了下面的這篇博文《一縷陽光:DDD(領域驅動設計)應對具體業務場景,如何聚焦 Domain Model(領域模型)?》,在這篇博文中,關於上面原因的分析,主要講到了以下兩個節點的內容:
雖然博文中也講到了領域模型的重新設計,但是設計之後還是一坨屎,這邊就不拿出來誤導大家了。回到上面的問題,關於 Repository 的職責問題,我當時是這樣分析的:
Repository 應用在應用層,這樣就致使應用層和基礎層(我把資料持久化放在基礎層了)通訊,忽略了最重要的領域層,領域層在其中起到的作用最多也就是傳遞一個非常貧血的領域模型,然後通過 Repository 進行“CRUD”,這樣的結果是,應用層不變成所謂的 BLL(常說的業務邏輯層)才怪,另外,因為業務邏輯都放在應用層了,領域模型也變得更加貧血。
乍一看,上面的分析還真沒什麼問題(看來我還是蠻會忽悠人的,嘿嘿),Repository 服務於領域,所以就必須把 Repository 的呼叫放在領域層中,領域模型又不能直接和 Repository 通訊,所以我後來就把 Domain Service(領域服務)加了進來,讓領域服務和 Repository 進行協調,然後應用層和就和領域服務通訊了,然後的然後。。。
有朋友看到這,會覺得沒錯啊,就是這樣啊(如果你也這樣認為,那我就去幹傳銷了),先不討論對錯,我們看下領域服務究竟實現的是個什麼東西?領域模型變成了什麼?應用層又變成了什麼?
領域服務程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
/** * author:xishuai * address:https://www.github.com/yuezhongxin/MessageManager **/ using MessageManager.Domain.DomainModel; using MessageManager.Domain.Repositories; using System.Collections.Generic; namespace MessageManager.Domain.DomainService { /// <summary> /// Message領域服務實現 /// </summary> public class MessageDomainService : IMessageDomainService { #region Private Fields private readonly IMessageRepository messageRepository; private readonly IUserRepository userRepository; #endregion #region Ctor public MessageDomainService(IMessageRepository messageRepository, IUserRepository userRepository) { this.messageRepository = messageRepository; this.userRepository = userRepository; } #endregion #region IMessageDomainService Members public bool DeleteMessage(Message message) { messageRepository.Remove(message); return messageRepository.Context.Commit(); } public bool SendMessage(Message message) { message.LoadUserName(userRepository.GetUser(new User { Name = message.FromUserName }) , userRepository.GetUser(new User { Name = message.ToUserName })); messageRepository.Add(message); return messageRepository.Context.Commit(); } public Message ShowMessage(string id, User currentUser) { Message message = messageRepository.GetByKey(id); message.ReadMessage(userRepository.GetUser(new User { Name = currentUser.Name })); messageRepository.Update(message); messageRepository.Context.Commit(); return message; } public IEnumerable<Message> GetMessagesBySendUser(User user) { User userResult = userRepository.GetUser(user); return messageRepository.GetMessagesBySendUser(userResult); } public IEnumerable<Message> GetMessagesByReceiveUser(User user) { User userResult = userRepository.GetUser(user); return messageRepository.GetMessagesByReceiveUser(userResult); } public int GetNoReadCount(User user) { User userResult = userRepository.GetUser(user); return messageRepository.GetNoReadCount(userResult); } #endregion } } |
應用層程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
/** * author:xishuai * address:https://www.github.com/yuezhongxin/MessageManager **/ using AutoMapper; using MessageManager.Application.DTO; using MessageManager.Domain.DomainModel; using MessageManager.Domain.DomainService; using System.Collections.Generic; namespace MessageManager.Application.Implementation { /// <summary> /// Message管理應用層介面實現 /// </summary> public class MessageServiceImpl : ApplicationService, IMessageService { #region Private Fields private readonly IMessageDomainService messageService; #endregion #region Ctor /// <summary> /// 初始化一個<c>MessageServiceImpl</c>型別的例項。 /// </summary> /// <param name="messageRepository">“訊息”服務例項。</param> public MessageServiceImpl(IMessageDomainService messageService) { this.messageService = messageService; } #endregion #region IMessageService Members /// <summary> /// 通過傳送方獲取訊息列表 /// </summary> /// <param name="userDTO">傳送方</param> /// <returns>訊息列表</returns> public IEnumerable<MessageDTO> GetMessagesBySendUser(UserDTO sendUserDTO) { //User user = userRepository.GetUserByName(sendUserDTO.Name); var messages = messageService.GetMessagesBySendUser(Mapper.Map<UserDTO, User>(sendUserDTO)); if (messages == null) return null; var ret = new List<MessageDTO>(); foreach (var message in messages) { ret.Add(Mapper.Map<Message, MessageDTO>(message)); } return ret; } /// <summary> /// 通過接受方獲取訊息列表 /// </summary> /// <param name="userDTO">接受方</param> /// <returns>訊息列表</returns> public IEnumerable<MessageDTO> GetMessagesByReceiveUser(UserDTO receiveUserDTO) { //User user = userRepository.GetUserByName(receiveUserDTO.Name); var messages = messageService.GetMessagesByReceiveUser(Mapper.Map<UserDTO, User>(receiveUserDTO)); if (messages == null) return null; var ret = new List<MessageDTO>(); foreach (var message in messages) { ret.Add(Mapper.Map<Message, MessageDTO>(message)); } return ret; } /// <summary> /// 刪除訊息 /// </summary> /// <param name="messageDTO"></param> /// <returns></returns> public bool DeleteMessage(MessageDTO messageDTO) { return messageService.DeleteMessage(Mapper.Map<MessageDTO, Message>(messageDTO)); } /// <summary> /// 傳送訊息 /// </summary> /// <param name="messageDTO"></param> /// <returns></returns> public bool SendMessage(MessageDTO messageDTO) { return messageService.SendMessage(Mapper.Map<MessageDTO, Message>(messageDTO)); } /// <summary> /// 檢視訊息 /// </summary> /// <param name="ID"></param> /// <returns></returns> public MessageDTO ShowMessage(string id, UserDTO currentUserDTO) { Message message = messageService.ShowMessage(id, Mapper.Map<UserDTO, User>(currentUserDTO)); return Mapper.Map<Message, MessageDTO>(message); } /// <summary> /// 獲取未讀訊息數 /// </summary> /// <param name="user"></param> /// <returns></returns> public int GetNoReadCount(UserDTO userDTO) { return messageService.GetNoReadCount(Mapper.Map<UserDTO, User>(userDTO)); } #endregion } } |
領域模型程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
/** * author:xishaui * address:https://www.github.com/yuezhongxin/MessageManager **/ using System; namespace MessageManager.Domain.DomainModel { public class Message : IAggregateRoot { #region 構造方法 public Message() { this.ID = Guid.NewGuid().ToString(); } #endregion #region 實體成員 public string FromUserID { get; set; } public string FromUserName { get; set; } public string ToUserID { get; set; } public string ToUserName { get; set; } public string Title { get; set; } public string Content { get; set; } public DateTime SendTime { get; set; } public bool IsRead { get; set; } public virtual User FromUser { get; set; } public virtual User ToUser { get; set; } #endregion #region 業務邏輯 /// <summary> /// 閱讀訊息 /// </summary> /// <param name="CurrentUser"></param> public void ReadMessage(User currentUser) { if (!this.IsRead && currentUser.ID.Equals(ToUserID)) { this.IsRead = true; } } /// <summary> /// 載入使用者 /// </summary> /// <param name="sendUser"></param> /// <param name="receiveUser"></param> public void LoadUserName(User sendUser, User receiveUser) { this.FromUserID = sendUser.ID; this.ToUserID = receiveUser.ID; } #endregion #region IEntity成員 /// <summary> /// 獲取或設定當前實體物件的全域性唯一標識。 /// </summary> public string ID { get; set; } #endregion } } |
其實上面程式碼,如果是 DDD 大神來看的話,他只要看領域模型中的程式碼就行了,因為領域驅動設計的核心就是領域模型,那領域模型變成了什麼?只是新增了 ReadMessage 和 LoadUserName 兩個不是業務邏輯的業務邏輯方法(因為只有他們兩個,如果把他們兩個去掉,就變回原來的貧血模型了,所以,你懂的),領域服務中的 SendMessage 方法變的和原來的應用層程式碼一樣,要說變化的話,只是把 Application 單詞變成了 Domain Service 這個單詞,其他無任何變化,應用層的程式碼也就變成了下面這樣:
1 2 3 4 5 6 7 8 9 |
/// <summary> /// 傳送訊息 /// </summary> /// <param name="messageDTO"></param> /// <returns></returns> public bool SendMessage(MessageDTO messageDTO) { return messageService.SendMessage(Mapper.Map<MessageDTO, Message>(messageDTO)); } |
在領域驅動設計中,應用層的定義是很薄的一層,可以看到,上面的應用層程式碼也未免太薄了吧,為什麼?因為原來它的工作讓領域服務做了,導致現在變成了一個呼叫外殼(也就是可有可無的東西,沒有任何意義)。
》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》分割線《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《
為什麼會有分割線?因為在應對具體業務場景中,上面做的操作都是無用功,為什麼?因為設計的領域模型中什麼東西都沒有(指的是業務邏輯),沒有任何東西的領域模型,還是真正的領域驅動設計嗎?關於這個問題,傻子都知道,當然我也不傻,嘿嘿。
認識到這個根本問題後,下面就拋開一切外在因素,比如領域服務、倉儲、應用層、表現層等等,這些統統不管,只做領域模型的設計,讓真正的設計焦點集中在領域模型上,然後再針對領域模型做單元測試。
對,就是這麼簡單,至少聽起來的確簡單,事實真是這樣嗎?我卻不這樣認為,因為就是這個簡單的問題,我為此痛苦了兩三天的時間,說誇張點就是:吃不下飯,睡不著覺,在這個過程中,領域模型的一行程式碼我也沒有寫,不是不想寫,而是不知道如何寫?這是最最痛苦的,真是體會到了才知道。寫不出來怎麼辦?我就找遍網上所有關於領域模型設計的資料(大部分都是英文,只能很痛苦的看),還有《領域驅動設計》和《企業應用架構模式》這兩本書,希望能從中找到些靈感(就像畫畫,難點就在如何畫第一筆),並不是模仿,這個也模仿不來,因為每個業務場景都不相同,遺憾的是沒有找到任何的靈感,唯一找到的線索就是 OO 設計(大家都知道,我卻不知道,因為蒙了)。
既然是要物件導向,那就分析一下物件,主要包含兩個:使用者和訊息。物件擁有自身的屬性、狀態和行為,傳送訊息是使用者的一種行為,所以傳送訊息這個操作應該放在使用者中,那現在訊息只有自身的一些屬性值,因為在物件導向中,它是固定的,只有通過使用者來呼叫它,使用者領域模型程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
/** * author:xishaui * address:https://www.github.com/yuezhongxin/MessageManager **/ using MessageManager.Domain.Demo.V1.Event; using System; using System.Collections.Generic; namespace MessageManager.Domain.Demo.V1 { public class User { public User() { this.ID = Guid.NewGuid().ToString(); } public string ID { get; set; } public string Name { get; set; } public virtual ICollection<Message> SendMessages { get; set; } public virtual ICollection<Message> ReceiveMessages { get; set; } public void SendMessage(Message message) { User toUser = GetUser(message.ToUser); if (toUser == null) { throw new NotImplementedException(); } message.FromUser = this; message.ToUser = toUser; this.SendMessages.Add(message); toUser.ReceiveMessages.Add(message); DomainEvents.Raise(new MessageEvent() { DoMessage = message }); /// } } } |
按照物件導向設計,訊息是使用者的附屬物件,只有使用者存在,訊息才有意義,一個使用者物件擁有多個訊息的物件集合,那怎麼體現出傳送訊息這個動作呢?答案就是:this.SendMessages.Add(message) 這段程式碼,表示往使用者物件的訊息集合填充訊息物件,這樣就會相對於使用者物件來說,這條訊息的傳送動作就完成了。先不考慮這樣設計的合理或者不合理,我們看下訊息模型中的程式碼,就會發現裡面只有一些欄位屬性,沒有任何的操作,還有就是如果我們要新增訊息的其他動作,比如查詢,刪除等等,按照上面的分析,我們就會在使用者物件中新增這些操作,因為這些動作都是使用者所具有的,合理嗎?至少聽起來就不合理。
身處這個迷霧森林,才知道它的恐怖之處,不斷的迷失自我,以致最後可能連自己都不相信,並懷疑自己。
找回自我
在迷霧森林之中,如何找回自我?而不迷失,沒有確切的答案,我只能尋覓那一縷陽光一步一步的往前行。。。
首先,Repository,和你說聲抱歉,非常抱歉,讓你蒙冤,是我誤會你了,因為我對業務邏輯的不理解,以致做出錯誤的做法。
回到短訊息系統-MessageManager,需要注意的是,我們做的是訊息系統,一切的一切都應該圍繞 Message 領域模型展開,在這個系統中,最重要的就是傳送訊息這個業務邏輯,什麼叫發訊息?不要被上面的物件導向所迷惑,只考慮發訊息這個具體的業務,我們來分析一下:比如在現實生活中,我們要給女朋友寫信,首先我們要寫信的內容,寫完之後,要寫一下女朋友的地址資訊及名字,這個寫信才算完成,郵遞員郵遞並不在這個業務邏輯之內了,因為這封信我寫上收件人之後,這封信相對於我來說就已經發出了,後面只不過是收件人收不收得到的問題了(即使我寫好,沒有寄出去)。也就是說郵遞員郵遞這個工作過程相當於資料的持久化,寫信的這個過程就是郵遞(發訊息的業務邏輯),just it。
理解上面的內容很重要,然後我們再來看 Message 這個領域模型,建立這個物件的時候,就說明我們已經把訊息的內容寫好了,也就是必要的東西,比如:訊息標題、訊息內容、傳送人、傳送時間等等,用程式碼實現就是在 Message 領域模型中的建構函式傳遞必要值,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
/** * author:xishuai * address:https://www.github.com/yuezhongxin/MessageManager **/ using System; namespace MessageManager.Domain.DomainModel { public class Message : IAggregateRoot { public Message(string title, string content, User sendUser) { if (title.Equals("") || content.Equals("") || sendUser == null) { throw new ArgumentNullException(); } this.ID = Guid.NewGuid().ToString(); this.Title = title; this.Content = content; this.SendTime = DateTime.Now; this.State = MessageState.NoRead; this.SendUser = sendUser; } public string ID { get; set; } public string Title { get; set; } public string Content { get; set; } public DateTime SendTime { get; set; } public MessageState State { get; set; } public virtual User SendUser { get; set; } public virtual User ReceiveUser { get; set; } public bool Send(User receiveUser) { if (receiveUser == null) { throw new ArgumentNullException(); } this.ReceiveUser = receiveUser; return true; ///to do... } } } |
在例項 Message 領域模型之前要對必要值進行判斷,傳送訊息的關鍵程式碼就是:ReceiveUser = receiveUser,表示為這條訊息“貼上”收件人的標籤,指示這條訊息的傳送動作已經完成,當然在這個 Send 業務方法中可能還會有其他業務邏輯的加入,其實傳送訊息就是這樣,在 Message 這個領域模型中,沒有什麼資料庫的概念,只是描述這個業務功能,僅此而已。
在領域驅動設計的過程中,你會忘記資料庫的存在,使用介面注入,我們可以想怎麼操作就怎麼操作,資料庫只是業務場景中資料的儲存的一種方式,這個工作應該是你做完所有的業務設計之後執行,如果想進行單元測試,使用 IRepository 介面,我們甚至可以虛擬一切想要的物件(是物件,不是資料值)。
我們再來看下應用層的實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
/** * author:xishuai * address:https://www.github.com/yuezhongxin/MessageManager **/ using MessageManager.Domain.DomainModel; using MessageManager.Domain.Repositories; namespace MessageManager.Application.Implementation { /// <summary> /// Message管理應用層介面實現 /// </summary> public class MessageServiceImpl : ApplicationService, IMessageService { #region Private Fields private readonly IMessageRepository messageRepository; private readonly IUserRepository userRepository; #endregion #region Ctor /// <summary> /// 初始化一個<c>MessageServiceImpl</c>型別的例項。 /// </summary> /// <param name="context">用來初始化<c>MessageServiceImpl</c>型別的倉儲上下文例項。</param> /// <param name="messageRepository">“訊息”倉儲例項。</param> /// <param name="userRepository">“使用者”倉儲例項。</param> public MessageServiceImpl(IRepositoryContext context, IMessageRepository messageRepository, IUserRepository userRepository) : base(context) { this.messageRepository = messageRepository; this.userRepository = userRepository; } #endregion #region IMessageService Members /// <summary> /// 傳送訊息 /// </summary> /// <param name="title">訊息標題</param> /// <param name="content">訊息內容</param> /// <param name="sendLoginUserName">傳送人-登陸名</param> /// <param name="receiveDisplayUserName">接受人-接收人</param> /// <returns></returns> public bool SendMessage(string title, string content, string sendLoginUserName, string receiveDisplayUserName) { User sendUser = userRepository.GetUserByLoginName(sendLoginUserName); if (sendUser == null) { return false; } Message message = new Message(title, content, sendUser); User receiveUser = userRepository.GetUserByDisplayName(receiveDisplayUserName); if (receiveUser == null) { return false; } if (message.Send(receiveUser)) { return true; //messageRepository.Add(message); //return messageRepository.Context.Commit(); } else { return false; } } #endregion } } |
你會發現應用層中的 SendMessage 方法,所做的工作流程是多麼的行雲流水,一步一步的協調(不要誤讀為業務邏輯,我之前就是這樣),完美的完成這個傳送訊息的請求。應用層的核心是什麼?答案就是協調,什麼意思?就是說 UI 發出一個請求(比如發訊息),然後應用層接到這這個請求之後,進行一些協調處理(比如取想要的使用者值,完成傳送,以及傳送之後的流程-發郵件等等),完成這個工作流程,而並不是這個業務邏輯,業務邏輯是發訊息。
在程式碼註釋的地方,完成的是訊息的持久化操作,當然我們也可以不完成這個操作,因為業務邏輯是業務邏輯,持久化是持久化,並沒有半毛錢關係,我們描述的只是業務場景,僅此而已。
開源地址
- GitHub 開源地址:https://github.com/yuezhongxin/MessageManager
- ASP.NET MVC 釋出地址:http://www.xishuaiblog.com:8081/
- ASP.NET WebAPI 釋出地址:http://www.xishuaiblog.com:8082/api/Message/GetMessagesBySendUser/小菜
後記
以上只是一個簡單業務場景用例,就讓我在迷霧森林中迷失自我這麼久,到現在只是看到了那一縷陽光而已。DDD 是我們共同的語言,寫這篇博文的目的就是,希望園友們也可以看到希望,不要再像我一樣,迷失自我。
DDD(領域驅動設計)的過程,從上面一系列的問題更加證明是個迭代過程,一次一次的否決自己,然後再找回自己,反反覆覆,複復反反,才能成就真正的自己。可能幾天或者幾周後,看現在的這篇博文就像一坨屎一樣,但是沒關係,因為我又離真相更進了一步。
我喜歡這個挑戰,我也會堅持的完成它,誰叫我熱愛它呢,just so so。
貼一下,當時尋找的領域模型設計資料,僅作參考:
- https://github.com/sliedig/Employing-the-Domain-Model-Pattern
- http://msdn.microsoft.com/zh-cn/magazine/ee236415.aspx
- http://www.cnblogs.com/1-2-3/category/109191.html
- http://www.cnblogs.com/yimlin/archive/2006/06/15/426929.html
- http://www.blogjava.net/AndersLin/archive/2006/10/09/74187.html
- http://www.blogjava.net/AndersLin/archive/2006/08/25/65875.html