寫在前面
除了博文內容之外,和 netfocus 兄的討論,也可以讓你學到很多(至少我是這樣),不要錯過哦。
閱讀目錄:
毫無疑問,領域驅動設計的核心是領域模型,領域模型的核心是實現業務邏輯,也就是說,在應對具體的業務場景的時候,實現業務邏輯是領域驅動設計最重要的一環,在寫這篇博文之前,先總結下之前關于 DDD(領域驅動設計)的三篇博文:
-
我的“第一次”,就這樣沒了:DDD(領域驅動設計)理論結合實踐:偽領域驅動設計,只是用 .NET 實現的一個“空殼”,僅此而已。
-
一縷陽光:DDD(領域驅動設計)應對具體業務場景,如何聚焦 Domain Model(領域模型)?:只是聚焦領域模型(認清各個部分的職責,讓設計的焦點集中在領域模型中),文中關于領域模型的實現就是一個“渣”,僅此而已。
-
死去活來,而不變質:Domain Model(領域模型) 和 EntityFramework 如何正確進行對象關系映射?:走了個彎路,ORM 的映射關系及倉儲的實現,應該是在本篇內容之后探討,原因都是腳本驅動模式惹的禍,如果說腳本驅動模式是惡魔(特定的環境,也有好處,不能一概而論,這邊只是一個比喻),那領域驅動設計可以看作是天使,心里想的是天使,卻聽了惡魔的話,為什么?因為它在你心中已根深蒂固,僅此而已。
OO(面向對象)設計指導我們要面向(停頓一下)對象設計,也就是說你要去面向這個對象,去思考它的本質,說白了就是思考:它是從哪里來,到哪里去,生長軌跡,擁有的事物,自身的變化規律,現在的狀態等等方面,更深入一點就是站在哲學的角度去思考萬物的本質(請參考老子的道德經),咳咳,說大了。
但是,在現實生活中,我們是做軟件設計的程序員,需要做出東西給用戶用的,而不是像老子那樣逍遙的每天面對星空,去思考宇宙萬物(如果有興趣,你也可以試試,只要不被你的女朋友罵死)。也就是說我們要面對具體的業務場景,而并非只是單純的 OO,這就要求我們除了要 OO 之外,還要去探討業務邏輯的本質以及實現。
以下內容是本人掉的一個又一個深坑,拼死爬了上來,在 DDD 的道路上,友情提醒各位:前方有坑,園友們請小心謹慎。
迷霧森林
迷霧森林中,切勿迷失自我,不幸的是,我就這樣迷失了:
具體的業務場景還是短消息系統-MessageManager,存在 Message 和 User 兩個領域模型,業務邏輯:一個用戶給另一個用戶發送消息,就是這么簡單,可以看作是一個最簡單的業務邏輯,當然在發送消息這個過程中會有其他的業務邏輯,先不探討 Message 領域模型和 User 領域模型如何協調完成這個業務邏輯,我們先看以下,我在第一篇《我的“第一次”,就這樣沒了:DDD(領域驅動設計)理論結合實踐》博文中,關于領域模型的實現:
1 /** 2 * author:xishaui 3 * address:https://www.github.com/yuezhongxin/MessageManager 4 **/ 5 6 using System; 7 8 namespace MessageManager.Domain.DomainModel 9 { 10 public class Message : IAggregateRoot 11 { 12 #region 構造方法 13 public Message() 14 { 15 this.ID = Guid.NewGuid().ToString(); 16 } 17 #endregion 18 19 #region 實體成員 20 public string FromUserID { get; set; } 21 public string FromUserName { get; set; } 22 public string ToUserID { get; set; } 23 public string ToUserName { get; set; } 24 public string Title { get; set; } 25 public string Content { get; set; } 26 public DateTime SendTime { get; set; } 27 public bool IsRead { get; set; } 28 public virtual User FromUser { get; set; } 29 public virtual User ToUser { get; set; } 30 #endregion 31 32 #region IEntity成員 33 /// <summary> 34 /// 獲取或設置當前實體對象的全局唯一標識。 35 /// </summary> 36 public string ID { get; set; } 37 #endregion 38 } 39 }
Are you kidding me?不,你沒看錯,以上就是 Message 領域模型的實現代碼,User 領域模型的代碼我就不貼了,比這個還要簡單,只包含 ID 和 Name 兩個字段屬性,領域驅動設計主張的是充血模型,只包含字段屬性的領域模型是極其貧血的,像上面的 Message 領域模型,充血的領域模型實現的是業務邏輯。上面我們說的發送消息這個業務邏輯,在領域模型中為什么沒有體現?既然是基于領域驅動設計,那為什么我還要這樣設計呢?這是為什么呢?當時設計完之后,我也在思考這個問題,難道腦袋有問題?不可能吧?看下應用層的代碼就知道了:
1 /** 2 * author:xishuai 3 * address:https://www.github.com/yuezhongxin/MessageManager 4 **/ 5 6 using AutoMapper; 7 using MessageManager.Application.DTO; 8 using MessageManager.Domain.DomainModel; 9 using MessageManager.Domain.Repositories; 10 using System.Collections.Generic; 11 12 namespace MessageManager.Application.Implementation 13 { 14 /// <summary> 15 /// Message管理應用層接口實現 16 /// </summary> 17 public class MessageServiceImpl : ApplicationService, IMessageService 18 { 19 #region Private Fields 20 private readonly IMessageRepository messageRepository; 21 private readonly IUserRepository userRepository; 22 #endregion 23 24 #region Ctor 25 /// <summary> 26 /// 初始化一個<c>MessageServiceImpl</c>類型的實例。 27 /// </summary> 28 /// <param name="context">用來初始化<c>MessageServiceImpl</c>類型的倉儲上下文實例。</param> 29 /// <param name="messageRepository">“消息”倉儲實例。</param> 30 /// <param name="userRepository">“用戶”倉儲實例。</param> 31 public MessageServiceImpl(IRepositoryContext context, 32 IMessageRepository messageRepository, 33 IUserRepository userRepository) 34 : base(context) 35 { 36 this.messageRepository = messageRepository; 37 this.userRepository = userRepository; 38 } 39 #endregion 40 41 #region IMessageService Members 42 /// <summary> 43 /// 通過發送方獲取消息列表 44 /// </summary> 45 /// <param name="userDTO">發送方</param> 46 /// <returns>消息列表</returns> 47 public IEnumerable<MessageDTO> GetMessagesBySendUser(UserDTO sendUserDTO) 48 { 49 //User user = userRepository.GetUserByName(sendUserDTO.Name); 50 var messages = messageRepository.GetMessagesBySendUser(Mapper.Map<UserDTO, User>(sendUserDTO)); 51 if (messages == null) 52 return null; 53 var ret = new List<MessageDTO>(); 54 foreach (var message in messages) 55 { 56 ret.Add(Mapper.Map<Message, MessageDTO>(message)); 57 } 58 return ret; 59 } 60 /// <summary> 61 /// 通過接受方獲取消息列表 62 /// </summary> 63 /// <param name="userDTO">接受方</param> 64 /// <returns>消息列表</returns> 65 public IEnumerable<MessageDTO> GetMessagesByReceiveUser(UserDTO receiveUserDTO) 66 { 67 //User user = userRepository.GetUserByName(receiveUserDTO.Name); 68 var messages = messageRepository.GetMessagesByReceiveUser(Mapper.Map<UserDTO, User>(receiveUserDTO)); 69 if (messages == null) 70 return null; 71 var ret = new List<MessageDTO>(); 72 foreach (var message in messages) 73 { 74 ret.Add(Mapper.Map<Message, MessageDTO>(message)); 75 } 76 return ret; 77 } 78 /// <summary> 79 /// 刪除消息 80 /// </summary> 81 /// <param name="messageDTO"></param> 82 /// <returns></returns> 83 public bool DeleteMessage(MessageDTO messageDTO) 84 { 85 messageRepository.Remove(Mapper.Map<MessageDTO, Message>(messageDTO)); 86 return messageRepository.Context.Commit(); 87 } 88 /// <summary> 89 /// 發送消息 90 /// </summary> 91 /// <param name="messageDTO"></param> 92 /// <returns></returns> 93 public bool SendMessage(MessageDTO messageDTO) 94 { 95 Message message = Mapper.Map<MessageDTO, Message>(messageDTO); 96 message.FromUserID = userRepository.GetUserByName(messageDTO.FromUserName).ID; 97 message.ToUserID = userRepository.GetUserByName(messageDTO.ToUserName).ID; 98 messageRepository.Add(message); 99 return messageRepository.Context.Commit(); 100 } 101 /// <summary> 102 /// 查看消息 103 /// </summary> 104 /// <param name="ID"></param> 105 /// <returns></returns> 106 public MessageDTO ShowMessage(string ID, string isRead) 107 { 108 Message message = messageRepository.GetByKey(ID); 109 if (isRead == "1") 110 { 111 message.IsRead = true; 112 messageRepository.Update(message); 113 messageRepository.Context.Commit(); 114 } 115 return Mapper.Map<Message, MessageDTO>(message); 116 } 117 #endregion 118 } 119 }
可以看到應用層的代碼真是不忍直視,撇開其他操作,我們看下 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 * author:xishuai 3 * address:https://www.github.com/yuezhongxin/MessageManager 4 **/ 5 6 using MessageManager.Domain.DomainModel; 7 using MessageManager.Domain.Repositories; 8 using System.Collections.Generic; 9 10 namespace MessageManager.Domain.DomainService 11 { 12 /// <summary> 13 /// Message領域服務實現 14 /// </summary> 15 public class MessageDomainService : IMessageDomainService 16 { 17 #region Private Fields 18 private readonly IMessageRepository messageRepository; 19 private readonly IUserRepository userRepository; 20 #endregion 21 22 #region Ctor 23 public MessageDomainService(IMessageRepository messageRepository, IUserRepository userRepository) 24 { 25 this.messageRepository = messageRepository; 26 this.userRepository = userRepository; 27 } 28 #endregion 29 30 #region IMessageDomainService Members 31 public bool DeleteMessage(Message message) 32 { 33 messageRepository.Remove(message); 34 return messageRepository.Context.Commit(); 35 } 36 public bool SendMessage(Message message) 37 { 38 message.LoadUserName(userRepository.GetUser(new User { Name = message.FromUserName }) 39 , userRepository.GetUser(new User { Name = message.ToUserName })); 40 messageRepository.Add(message); 41 return messageRepository.Context.Commit(); 42 } 43 public Message ShowMessage(string id, User currentUser) 44 { 45 Message message = messageRepository.GetByKey(id); 46 message.ReadMessage(userRepository.GetUser(new User { Name = currentUser.Name })); 47 messageRepository.Update(message); 48 messageRepository.Context.Commit(); 49 return message; 50 } 51 public IEnumerable<Message> GetMessagesBySendUser(User user) 52 { 53 User userResult = userRepository.GetUser(user); 54 return messageRepository.GetMessagesBySendUser(userResult); 55 } 56 public IEnumerable<Message> GetMessagesByReceiveUser(User user) 57 { 58 User userResult = userRepository.GetUser(user); 59 return messageRepository.GetMessagesByReceiveUser(userResult); 60 } 61 public int GetNoReadCount(User user) 62 { 63 User userResult = userRepository.GetUser(user); 64 return messageRepository.GetNoReadCount(userResult); 65 } 66 #endregion 67 } 68 }
應用層代碼:

1 /** 2 * author:xishuai 3 * address:https://www.github.com/yuezhongxin/MessageManager 4 **/ 5 6 using AutoMapper; 7 using MessageManager.Application.DTO; 8 using MessageManager.Domain.DomainModel; 9 using MessageManager.Domain.DomainService; 10 using System.Collections.Generic; 11 12 namespace MessageManager.Application.Implementation 13 { 14 /// <summary> 15 /// Message管理應用層接口實現 16 /// </summary> 17 public class MessageServiceImpl : ApplicationService, IMessageService 18 { 19 #region Private Fields 20 private readonly IMessageDomainService messageService; 21 #endregion 22 23 #region Ctor 24 /// <summary> 25 /// 初始化一個<c>MessageServiceImpl</c>類型的實例。 26 /// </summary> 27 /// <param name="messageRepository">“消息”服務實例。</param> 28 public MessageServiceImpl(IMessageDomainService messageService) 29 { 30 this.messageService = messageService; 31 } 32 #endregion 33 34 #region IMessageService Members 35 /// <summary> 36 /// 通過發送方獲取消息列表 37 /// </summary> 38 /// <param name="userDTO">發送方</param> 39 /// <returns>消息列表</returns> 40 public IEnumerable<MessageDTO> GetMessagesBySendUser(UserDTO sendUserDTO) 41 { 42 //User user = userRepository.GetUserByName(sendUserDTO.Name); 43 var messages = messageService.GetMessagesBySendUser(Mapper.Map<UserDTO, User>(sendUserDTO)); 44 if (messages == null) 45 return null; 46 var ret = new List<MessageDTO>(); 47 foreach (var message in messages) 48 { 49 ret.Add(Mapper.Map<Message, MessageDTO>(message)); 50 } 51 return ret; 52 } 53 /// <summary> 54 /// 通過接受方獲取消息列表 55 /// </summary> 56 /// <param name="userDTO">接受方</param> 57 /// <returns>消息列表</returns> 58 public IEnumerable<MessageDTO> GetMessagesByReceiveUser(UserDTO receiveUserDTO) 59 { 60 //User user = userRepository.GetUserByName(receiveUserDTO.Name); 61 var messages = messageService.GetMessagesByReceiveUser(Mapper.Map<UserDTO, User>(receiveUserDTO)); 62 if (messages == null) 63 return null; 64 var ret = new List<MessageDTO>(); 65 foreach (var message in messages) 66 { 67 ret.Add(Mapper.Map<Message, MessageDTO>(message)); 68 } 69 return ret; 70 } 71 /// <summary> 72 /// 刪除消息 73 /// </summary> 74 /// <param name="messageDTO"></param> 75 /// <returns></returns> 76 public bool DeleteMessage(MessageDTO messageDTO) 77 { 78 return messageService.DeleteMessage(Mapper.Map<MessageDTO, Message>(messageDTO)); 79 } 80 /// <summary> 81 /// 發送消息 82 /// </summary> 83 /// <param name="messageDTO"></param> 84 /// <returns></returns> 85 public bool SendMessage(MessageDTO messageDTO) 86 { 87 return messageService.SendMessage(Mapper.Map<MessageDTO, Message>(messageDTO)); 88 } 89 /// <summary> 90 /// 查看消息 91 /// </summary> 92 /// <param name="ID"></param> 93 /// <returns></returns> 94 public MessageDTO ShowMessage(string id, UserDTO currentUserDTO) 95 { 96 Message message = messageService.ShowMessage(id, Mapper.Map<UserDTO, User>(currentUserDTO)); 97 return Mapper.Map<Message, MessageDTO>(message); 98 } 99 /// <summary> 100 /// 獲取未讀消息數 101 /// </summary> 102 /// <param name="user"></param> 103 /// <returns></returns> 104 public int GetNoReadCount(UserDTO userDTO) 105 { 106 return messageService.GetNoReadCount(Mapper.Map<UserDTO, User>(userDTO)); 107 } 108 #endregion 109 } 110 }
領域模型代碼:

1 /** 2 * author:xishaui 3 * address:https://www.github.com/yuezhongxin/MessageManager 4 **/ 5 6 using System; 7 8 namespace MessageManager.Domain.DomainModel 9 { 10 public class Message : IAggregateRoot 11 { 12 #region 構造方法 13 public Message() 14 { 15 this.ID = Guid.NewGuid().ToString(); 16 } 17 #endregion 18 19 #region 實體成員 20 public string FromUserID { get; set; } 21 public string FromUserName { get; set; } 22 public string ToUserID { get; set; } 23 public string ToUserName { get; set; } 24 public string Title { get; set; } 25 public string Content { get; set; } 26 public DateTime SendTime { get; set; } 27 public bool IsRead { get; set; } 28 public virtual User FromUser { get; set; } 29 public virtual User ToUser { get; set; } 30 #endregion 31 32 #region 業務邏輯 33 /// <summary> 34 /// 閱讀消息 35 /// </summary> 36 /// <param name="CurrentUser"></param> 37 public void ReadMessage(User currentUser) 38 { 39 if (!this.IsRead && currentUser.ID.Equals(ToUserID)) 40 { 41 this.IsRead = true; 42 } 43 } 44 /// <summary> 45 /// 加載用戶 46 /// </summary> 47 /// <param name="sendUser"></param> 48 /// <param name="receiveUser"></param> 49 public void LoadUserName(User sendUser, User receiveUser) 50 { 51 this.FromUserID = sendUser.ID; 52 this.ToUserID = receiveUser.ID; 53 } 54 #endregion 55 56 #region IEntity成員 57 /// <summary> 58 /// 獲取或設置當前實體對象的全局唯一標識。 59 /// </summary> 60 public string ID { get; set; } 61 #endregion 62 } 63 }
其實上面代碼,如果是 DDD 大神來看的話,他只要看領域模型中的代碼就行了,因為領域驅動設計的核心就是領域模型,那領域模型變成了什么?只是添加了 ReadMessage 和 LoadUserName 兩個不是業務邏輯的業務邏輯方法(因為只有他們兩個,如果把他們兩個去掉,就變回原來的貧血模型了,所以,你懂的),領域服務中的 SendMessage 方法變的和原來的應用層代碼一樣,要說變化的話,只是把 Application 單詞變成了 Domain Service 這個單詞,其他無任何變化,應用層的代碼也就變成了下面這樣:
1 /// <summary> 2 /// 發送消息 3 /// </summary> 4 /// <param name="messageDTO"></param> 5 /// <returns></returns> 6 public bool SendMessage(MessageDTO messageDTO) 7 { 8 return messageService.SendMessage(Mapper.Map<MessageDTO, Message>(messageDTO)); 9 }
在領域驅動設計中,應用層的定義是很薄的一層,可以看到,上面的應用層代碼也未免太薄了吧,為什么?因為原來它的工作讓領域服務做了,導致現在變成了一個調用外殼(也就是可有可無的東西,沒有任何意義)。
》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》分割線《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《《
為什么會有分割線?因為在應對具體業務場景中,上面做的操作都是無用功,為什么?因為設計的領域模型中什么東西都沒有(指的是業務邏輯),沒有任何東西的領域模型,還是真正的領域驅動設計嗎?關于這個問題,傻子都知道,當然我也不傻,嘿嘿。
認識到這個根本問題后,下面就拋開一切外在因素,比如領域服務、倉儲、應用層、表現層等等,這些統統不管,只做領域模型的設計,讓真正的設計焦點集中在領域模型上,然后再針對領域模型做單元測試。
對,就是這么簡單,至少聽起來的確簡單,事實真是這樣嗎?我卻不這樣認為,因為就是這個簡單的問題,我為此痛苦了兩三天的時間,說夸張點就是:吃不下飯,睡不著覺,在這個過程中,領域模型的一行代碼我也沒有寫,不是不想寫,而是不知道如何寫?這是最最痛苦的,真是體會到了才知道。寫不出來怎么辦?我就找遍網上所有關于領域模型設計的資料(大部分都是英文,只能很痛苦的看),還有《領域驅動設計》和《企業應用架構模式》這兩本書,希望能從中找到些靈感(就像畫畫,難點就在如何畫第一筆),并不是模仿,這個也模仿不來,因為每個業務場景都不相同,遺憾的是沒有找到任何的靈感,唯一找到的線索就是 OO 設計(大家都知道,我卻不知道,因為蒙了)。
既然是要面向對象,那就分析一下對象,主要包含兩個:用戶和消息。對象擁有自身的屬性、狀態和行為,發送消息是用戶的一種行為,所以發送消息這個操作應該放在用戶中,那現在消息只有自身的一些屬性值,因為在面向對象中,它是固定的,只有通過用戶來調用它,用戶領域模型代碼如下:
1 /** 2 * author:xishaui 3 * address:https://www.github.com/yuezhongxin/MessageManager 4 **/ 5 6 using MessageManager.Domain.Demo.V1.Event; 7 using System; 8 using System.Collections.Generic; 9 10 namespace MessageManager.Domain.Demo.V1 11 { 12 public class User 13 { 14 public User() 15 { 16 this.ID = Guid.NewGuid().ToString(); 17 } 18 19 public string ID { get; set; } 20 public string Name { get; set; } 21 public virtual ICollection<Message> SendMessages { get; set; } 22 public virtual ICollection<Message> ReceiveMessages { get; set; } 23 24 public void SendMessage(Message message) 25 { 26 User toUser = GetUser(message.ToUser); 27 if (toUser == null) 28 { 29 throw new NotImplementedException(); 30 } 31 message.FromUser = this; 32 message.ToUser = toUser; 33 this.SendMessages.Add(message); 34 toUser.ReceiveMessages.Add(message); 35 DomainEvents.Raise(new MessageEvent() { DoMessage = message }); 36 /// 37 } 38 } 39 }
按照面向對象設計,消息是用戶的附屬對象,只有用戶存在,消息才有意義,一個用戶對象擁有多個消息的對象集合,那怎么體現出發送消息這個動作呢?答案就是:this.SendMessages.Add(message) 這段代碼,表示往用戶對象的消息集合填充消息對象,這樣就會相對于用戶對象來說,這條消息的發送動作就完成了。先不考慮這樣設計的合理或者不合理,我們看下消息模型中的代碼,就會發現里面只有一些字段屬性,沒有任何的操作,還有就是如果我們要添加消息的其他動作,比如查找,刪除等等,按照上面的分析,我們就會在用戶對象中添加這些操作,因為這些動作都是用戶所具有的,合理嗎?至少聽起來就不合理。
身處這個迷霧森林,才知道它的恐怖之處,不斷的迷失自我,以致最后可能連自己都不相信,并懷疑自己。
找回自我
在迷霧森林之中,如何找回自我?而不迷失,沒有確切的答案,我只能尋覓那一縷陽光一步一步的往前行。。。
首先,Repository,和你說聲抱歉,非常抱歉,讓你蒙冤,是我誤會你了,因為我對業務邏輯的不理解,以致做出錯誤的做法。
回到短消息系統-MessageManager,需要注意的是,我們做的是消息系統,一切的一切都應該圍繞 Message 領域模型展開,在這個系統中,最重要的就是發送消息這個業務邏輯,什么叫發消息?不要被上面的面向對象所迷惑,只考慮發消息這個具體的業務,我們來分析一下:比如在現實生活中,我們要給女朋友寫信,首先我們要寫信的內容,寫完之后,要寫一下女朋友的地址信息及名字,這個寫信才算完成,郵遞員郵遞并不在這個業務邏輯之內了,因為這封信我寫上收件人之后,這封信相對于我來說就已經發出了,后面只不過是收件人收不收得到的問題了(即使我寫好,沒有寄出去)。也就是說郵遞員郵遞這個工作過程相當于數據的持久化,寫信的這個過程就是郵遞(發消息的業務邏輯),just it。
理解上面的內容很重要,然后我們再來看 Message 這個領域模型,創建這個對象的時候,就說明我們已經把消息的內容寫好了,也就是必要的東西,比如:消息標題、消息內容、發送人、發送時間等等,用代碼實現就是在 Message 領域模型中的構造函數傳遞必要值,代碼如下:
1 /** 2 * author:xishuai 3 * address:https://www.github.com/yuezhongxin/MessageManager 4 **/ 5 6 using System; 7 8 namespace MessageManager.Domain.DomainModel 9 { 10 public class Message : IAggregateRoot 11 { 12 public Message(string title, string content, User sendUser) 13 { 14 if (title.Equals("") || content.Equals("") || sendUser == null) 15 { 16 throw new ArgumentNullException(); 17 } 18 this.ID = Guid.NewGuid().ToString(); 19 this.Title = title; 20 this.Content = content; 21 this.SendTime = DateTime.Now; 22 this.State = MessageState.NoRead; 23 this.SendUser = sendUser; 24 } 25 public string ID { get; set; } 26 public string Title { get; set; } 27 public string Content { get; set; } 28 public DateTime SendTime { get; set; } 29 public MessageState State { get; set; } 30 public virtual User SendUser { get; set; } 31 public virtual User ReceiveUser { get; set; } 32 33 public bool Send(User receiveUser) 34 { 35 if (receiveUser == null) 36 { 37 throw new ArgumentNullException(); 38 } 39 this.ReceiveUser = receiveUser; 40 return true; 41 ///to do... 42 } 43 } 44 }
在實例 Message 領域模型之前要對必要值進行判斷,發送消息的關鍵代碼就是:ReceiveUser = receiveUser,表示為這條消息“貼上”收件人的標簽,指示這條消息的發送動作已經完成,當然在這個 Send 業務方法中可能還會有其他業務邏輯的加入,其實發送消息就是這樣,在 Message 這個領域模型中,沒有什么數據庫的概念,只是描述這個業務功能,僅此而已。
在領域驅動設計的過程中,你會忘記數據庫的存在,使用接口注入,我們可以想怎么操作就怎么操作,數據庫只是業務場景中數據的存儲的一種方式,這個工作應該是你做完所有的業務設計之后執行,如果想進行單元測試,使用 IRepository 接口,我們甚至可以虛擬一切想要的對象(是對象,不是數據值)。
我們再來看下應用層的實現:
1 /** 2 * author:xishuai 3 * address:https://www.github.com/yuezhongxin/MessageManager 4 **/ 5 6 using MessageManager.Domain.DomainModel; 7 using MessageManager.Domain.Repositories; 8 9 namespace MessageManager.Application.Implementation 10 { 11 /// <summary> 12 /// Message管理應用層接口實現 13 /// </summary> 14 public class MessageServiceImpl : ApplicationService, IMessageService 15 { 16 #region Private Fields 17 private readonly IMessageRepository messageRepository; 18 private readonly IUserRepository userRepository; 19 #endregion 20 21 #region Ctor 22 /// <summary> 23 /// 初始化一個<c>MessageServiceImpl</c>類型的實例。 24 /// </summary> 25 /// <param name="context">用來初始化<c>MessageServiceImpl</c>類型的倉儲上下文實例。</param> 26 /// <param name="messageRepository">“消息”倉儲實例。</param> 27 /// <param name="userRepository">“用戶”倉儲實例。</param> 28 public MessageServiceImpl(IRepositoryContext context, 29 IMessageRepository messageRepository, 30 IUserRepository userRepository) 31 : base(context) 32 { 33 this.messageRepository = messageRepository; 34 this.userRepository = userRepository; 35 } 36 #endregion 37 38 #region IMessageService Members 39 /// <summary> 40 /// 發送消息 41 /// </summary> 42 /// <param name="title">消息標題</param> 43 /// <param name="content">消息內容</param> 44 /// <param name="sendLoginUserName">發送人-登陸名</param> 45 /// <param name="receiveDisplayUserName">接受人-接收人</param> 46 /// <returns></returns> 47 public bool SendMessage(string title, string content, string sendLoginUserName, string receiveDisplayUserName) 48 { 49 User sendUser = userRepository.GetUserByLoginName(sendLoginUserName); 50 if (sendUser == null) 51 { 52 return false; 53 } 54 Message message = new Message(title, content, sendUser); 55 User receiveUser = userRepository.GetUserByDisplayName(receiveDisplayUserName); 56 if (receiveUser == null) 57 { 58 return false; 59 } 60 if (message.Send(receiveUser)) 61 { 62 return true; 63 //messageRepository.Add(message); 64 //return messageRepository.Context.Commit(); 65 } 66 else 67 { 68 return false; 69 } 70 } 71 #endregion 72 } 73 }
你會發現應用層中的 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
文章列表