前言:以前一直在搭建項目架構的代碼,有點偏離咱們的主題(DDD)了,這篇咱們繼續來聊聊DDD裏面另外一個比較重要的知識點:領域服務。關於領域服務的使用,書中也介紹得比較晦澀,在此就根據博主本身的理解談談這個知識點的使用。html
DDD領域驅動設計初探系列文章:前端
在《領域驅動設計:軟件核心複雜性應對之道.Eric.Eva》這本書中,做者這樣定義領域服務:有些重要的領域操做,不適合歸到實體(Entity)和值對象(Value Object)的類別中,這些操做從本質上講是有些活動或動做,而不是事物,當領域中的某個重要過程或轉換操做不屬於實體或值對象的天然職責時,應該在模型中添加一個做爲獨立接口的操做,並將其申明爲Service。程序員
Service是做爲接口提供的一種操做,它在模型中是獨立的,與實體和值對象不一樣的是,它強調的是與其餘對象的關係,只定義可以爲客戶作什麼。通常狀況下,對於那些放在哪一個實體和值對象裏面都不合適,而且它更加偏向的是實體和實體之間的關係,這種場景下,咱們就須要使用領域Service。看到這裏,有人就鬱悶了,既然領域服務的做用是處理實體與實體之間的關係,那麼爲何不把它放在應用層裏面去呢,應用層的做用不就是協調任務的嗎?的確,理論上來說,放在應用層也可以解決,但博主的理解是,DDD的設計原則之一是儘可能豐滿領域模型,主張充血的領域模型,也就是說和領域模型相關的領域邏輯最好是方法領域層,很顯然,處理實體和實體之間的關係通常來講領域邏輯,因此最好仍是放在領域層。固然,這個也並不絕對,只是博主本身的理解。架構
咱們仍是來舉個例子說明,好比咱們權限管理裏面,若是須要一個功能,將指定的用戶賦予制定的權限,好比接口這樣定義app
void AssignPower(TB_USERS oUser, TB_ROLE oRole)
按照咱們前面聚合的劃分,這個接口是應該放在用戶這個聚合裏面仍是角色聚合裏面呢?很顯然,感受都不合適,這個接口包含兩個聚合的實體,因此像這種狀況,能夠考慮用領域服務去解決。若是你非要說,方法不這樣設計,放在應用層裏面也是能夠的。我只能說,呵呵,別鬧。函數
還記得咱們介紹倉儲的時候咱們領域層裏面的Services文件夾麼?下面,咱們就根據給指定的用戶賦予制定的權限的功能一步一步寫一個領域服務功能。post
這裏須要提出一個問題,對於博主在C#進階系列——DDD領域驅動設計初探(一):聚合這篇裏面聚合的劃分,如今想來存在不合理的地方,上次說了應該劃分爲4個聚合 ,但是沒有考慮到用戶和角色是多對多的關係,因此須要將用戶角色表TB_USERROLE單獨劃分爲一個聚合,因此總共是5個聚合,這也就是前面說的對於DDD裏面聚合的劃分是比較考量程序員經驗的,對於聚合劃分有誤形成的誤解表示抱歉。我須要在相應的地方作下修改。測試
領域實體要繼承聚合根基類:ui
public partial class TB_USERROLE:AggregateRoot { }
新建倉儲接口和倉儲實現spa
public interface IUserRoleRepository : IRepository<TB_USERROLE> { }
[Export(typeof(IUserRoleRepository))] public class UserRoleRepository : EFBaseRepository<TB_USERROLE>, IUserRoleRepository { }
在領域層的Services文件夾創建服務的接口和實現
public interface IPowerManagerDomainService { void AssignPower(TB_USERS oUser, TB_ROLE oRole); }
[Export(typeof(IPowerManagerDomainService))] public class PowerManagerDomainService:IPowerManagerDomainService { private IUserRepository _userRepository = null; private IRoleRepository _roleRepository = null; private IUserRoleRepository _userroleRepository = null; [ImportingConstructor] public PowerManagerDomainService(IUserRoleRepository oUserRoleRepository) { _userroleRepository = oUserRoleRepository; } public void AssignPower(TB_USERS oUser, TB_ROLE oRole) { if (oUser == null || oRole == null) { return; } var oUserRole = _userroleRepository.Find(x => x.USER_ID == oUser.USER_ID && x.ROLE_ID == oRole.ROLE_ID).FirstOrDefault(); if (oUserRole == null) { oUserRole = new TB_USERROLE(); oUserRole.ROLE_ID = oRole.ROLE_ID; oUserRole.USER_ID = oUser.USER_ID; oUserRole.ID = Guid.NewGuid().ToString(); _userroleRepository.Insert(oUserRole); } } }
在領域服務的實現類PowerManagerDomainService裏面,咱們使用了MEF的[ImportingConstructor]導入構造函數特性,使用這個特性的前提是構造函數裏面的參數類型IUserRoleRepository必須標註了導出Export,由前面咱們看到IUserRoleRepository的實現類是標記了導出的,因此經過該特性就能夠順利將用戶角色的倉儲實現類的實例導入進來。
在應用層裏面咱們來寫測試代碼:
class Program { [Import(AllowDefault=false,AllowRecomposition=true,RequiredCreationPolicy=CreationPolicy.Any,Source=ImportSource.Any)] public IPowerManagerDomainService powerDomainService { get; set; }
static void Main(string[] args) { var oProgram = new Program(); Regisgter.regisgter().ComposeParts(oProgram); var oUser = new TB_USERS() { USER_ID = "04acd48a819447d388b20dffb15f672e" }; var oRole = new TB_ROLE() { ROLE_ID = "cccc" }; oProgram.powerDomainService.AssignPower(oUser, oRole); Console.ReadKey(); } }
這裏須要說明一點:雖然領域服務的實現類PowerManagerDomainService定義了一個有參的構造函數,若是不適用IOC注入的方式,咱們經過new這個對象的時候須要傳入倉儲對象,但因爲使用了構造函數的導入,參數經過構造函數的導入而傳進去了,因此在應用層使用的時候也不用關心構造函數參數的問題了。這一點博主也是糾結了半天,後臺調試程序才知道。是否是這樣的,咱們來看看:
咱們在有參的構造函數裏面打一個斷點來看看
構造函數的參數就是這樣傳進去的。方法的執行都是很簡單的邏輯。
(1)MEF對於有參構造函數的導入要使用[ImportingConstructor]特性,參數類型要可導入。
(2)因爲領域服務的接口和實現都是放在領域層裏面,並且領域服務裏面調用了倉儲的實現邏輯,那麼這樣看領域層又和倉儲的實現揉到一塊兒了,這樣是否又會形成領域層的不純潔了呢?答案是不會,咱們看領域服務實現類PowerManagerDomainService裏面的代碼可知,裏面的邏輯都是經過倉儲的接口對象IUserRoleRepository _userroleRepository去處理的,也就是說領域服務裏面並無調用具體的倉儲實現,仍是和倉儲的接口在打交道,倉儲的實現是在運行的時候經過MEF動態注入進去的。因此這樣設計依然能夠保持領域層的純潔,不會依賴於具體的倉儲實現。
(3)在上面的例子中,既然只用到IUserRoleRepository這一個倉儲接口,那麼這個功能是否能夠直接放在用戶角色的倉儲實現裏面而不用使用領域服務呢?剛開始博主也有這種疑惑,但是後來想一想仍是不行,由於對於權限模塊,UI前端是不會和像DTO_TB_USERROLE這種對象打交道的,由於它裏面只有用戶ID和角色ID,對於UI來講沒有實際的意義。因此UI裏面只會傳遞用戶和角色這種對象,而這兩種對象隸屬於不一樣的聚合,這種狀況下最好仍是使用領域服務了。固然你也能夠將TB_USERROLE、TB_USERS、TB_ROLE這3個劃分爲一個聚合,把TB_USERROLE做爲聚合根,另外兩個當作實體,而後利用TB_USERROLE的導航屬性來處理TB_USERS和TB_ROLE,這種作法理論上也行,可是博主以爲像導航屬性這種東西不少項目考慮到效率問題可能會關閉掉,因此使用起來也有必定的風險。仍是那句話,視狀況而定,若是你的項目利用倉儲基本能搞定需求,或者說你不想又引入一個什麼領域服務的概念,你也能夠遵守你的設計,這個都沒有問題,畢竟最好的架構是適合項目的架構!
源碼下載。有興趣看看!