前言
基於 DDD 傳統分層架構實現。 項目 github地址:https://github.com/WuMortal/DDDSample
這個分層架構是工作中項目正在使用的分層架構,使用了一段時間發現受益匪淺,所以整理好我對該分層架構的一些理解分享給大家,我對於該分層架構還處於學習階段理解有誤的地方請指出。本次會以一個案例來說明各個分層的作用以及他們之間的調用關系,還有本次的重點不在於DDD
,因為這個我還未能完全理解,當然避免不了中間會涉及DDD
的一些概念。
DDD 簡單介紹
DDD
什么?為什么使用 DDD
?
關於這個問題有興趣的可以自行百度,我相信網絡上已經有大量的文章來說明這幾個問題。我目前的理解是“業務”,是為了應對現在復雜和多變的業務,是一種開發理念。
這里我就以一個小故事描述吧,有一天你接到任務要實現一個修改用戶的功能,非常簡單。使用傳統三層架構我們會怎么寫?
-
先在
DAL
層添加UserDAL
然后實現一個Update(UserEntity user)
方法 -
接着在
BLL
中添加一個UserBLL
在實現一個Update(string email,string pwd ...)
方法。 -
UI
層在調用,OK 完成任務下班回家。
接着你接到一個新的需求就是:需要增加用戶修改信息的記錄。
你立馬在 BLL
的 Update
的方法里增加的用戶修改信息的操作記錄,完成需求。
過了一段時間又來了一個需求:用戶改了信息需要通知到管理員,並且用戶每天只能修改 3 次信息。
好了之后又經歷了幾波需求,你的代碼也在不斷的增加和變化,有一天你接收新的項目或者離開了,那么接收你項目的人完全不清楚這里的業務情況。因為 Update
方法並沒有直接的反應出里的業務情況,代碼目的不明確。代碼變得難以維護。
那么在 DDD
里這些應該怎么做呢?
-
首先在方法的命名上做出更改既然業務是修改信息那么命名應該是
Modify(string email,string pwd ...)
-
將用戶修改信息的記錄代碼放在
DomainService
(領域服務) 中,當然這里的類、方法命名要直接的反應出業務情況,如:RecordUserModifyDomainService
。 -
對應的通知管理員的代碼也應該放入
DomainService
中,DomainService
應該盡量簡單一般只做一件事情。
分層架構圖
下面是關於 DDD 分層的一些描述,摘抄至之前看過的一片文章。
Presentation 為表示層,負責向用戶顯示信息和解釋用戶命令。這里指的用戶可以是另一個計算機系統,不一定是使用用戶界面的人。
Application 為應用層,定義軟件要完成的任務,並且指揮表達領域概念的對象來解決問題。這一層所負責的工作對業務來說意義重大,也是與其它系統的應用層進行交互的必要渠道。應用層要盡量簡單,不包含業務規則或者知識,而只為下一層中的領域對象協調任務,分配工作,使它們互相協作。它沒有反映業務情況的狀態,但是卻可以具有另外一種狀態,為用戶或程序顯示某個任務的進度。
Domain 為領域層(或模型層),負責表達業務概念,業務狀態信息以及業務規則。盡管保存業務狀態的技術細節是由基礎設施層實現的,但是反映業務情況的狀態是由本層控制並且使用的。領域層是業務軟件的核心,領域模型位於這一層。
Infrastructure 層為基礎實施層,向其他層提供通用的技術能力:為應用層傳遞消息,為領域層提供持久化機制,為用戶界面層繪制屏幕組件,等等。基礎設施層還能夠通過架構框架來支持四個層次間的交互模式。
說明
如上圖每個層中其實對應着具體的項,下面將對每個項進行說明。
-
Domain
層分為:Domain
、DomainService
和IDomainService
。- 首先
Domain
中包含有Entity
和IRepository
,Entity
是你的實體一般對於數據庫表但是在某些情況下你也可以冗余一些字段。IRepository
倉儲的方法的定義,該層不會有具體的實現。 DomainService
和IDomainService
,IDomainService
只是負責表達業務的概念,DomainService
里才是具體業務邏輯代碼。在這一層的代碼命名上需要注意,我們的命名一般要能直接描述出該代碼業務的功能。這里可以參考DDD
的幾個概念:通用語言、領域。
- 首先
-
Infrastructure
層分為:Repository
和CrossCutting
:Repository
里面就是Domain
里IRepository
的具體實現。項目中 RepositoryExtensions.cs 是一個擴展類,將所有的倉儲注入容器中,方便我們在項目中使用DI
(依賴注入)。CrossCutting
主要是提供一些各個層通用的東西,如一些枚舉、擴展方法、工具類等等。
-
Application
層分為:Application
和ApplicationContract
。ApplicationContract
里主要包含DTO
、ViewModel
、IXXXService
。DTO
是數據傳輸對象,主要負責給展現層提供展示數據,DTO
里應該只有值類型存在,當然根據具體情況也可存在其他的DTO
。ViewModel
用於展現層傳入的模型,簡單的說DTO
輸出,ViewModel
輸入。IXXXService
就是應用層的方法定義。Application
里面主要是用於 實現ApplicationContract
里的IXXXService
,還有Entity
和DTO
的映射也屬於該層的工作。ApplicationExtensions.cs 擴展方法是用於實現DI
。
-
Presentation
層里目前只有一個 WebAPI。展現層的代碼一般有:對傳入模型的校驗。
案例
本次以一個用戶注冊的流程為案例,來簡單說明如何使用該分層架構進行項目開發。
- 首先在
Domain
中建一個 UserEntity,有 Id、Mobile、Name、Age、RegisterDateTime 屬性。接着建立 IUserRepository,編寫需要定義的方法,這里我定義了一個 GetByMobile(string mobile) 方法。
1 [Table(Name = "User")] 2 public class UserEntity 3 { 4 [Column(IsIdentity = true)] public Guid Id { get; set; } 5 6 public string Mobile { get; set; } 7 8 public string Name { get; set; } 9 10 public int Age { get; set; } 11 12 public DateTime RegisterDateTime { get; set; } = DateTime.Now; 13 } 14 15 public interface IUserRepository : IBasicRepository<UserEntity, Guid> { Task GetByMobileAsync(string mobile); }
IBasicRepository 是使用了 FreeSql,你們可以自己實現。
- 然后在 Repository 中建 UserRepository 類,該類繼承 IUserRepository 並且實現該接口的所有方法。
public class UserRepository : GuidRepository, IUserRepository { public UserRepository(IFreeSql freeSql) : base(freeSql) { } #region Implementation of IUserRepository public async Task<UserEntity> GetByMobileAsync(string mobile) { return await this.Where(u => u.Mobile == mobile).FirstAsync(); } #endregion }
- 倉儲基本好了后就是
Application
,首先需要在 ApplicationContract 中建 UsesDTO,根據業務情況你也可以建 UserSimpleDTO 、UserDetailDTO。DTO
里包含你需要返回的數據,我這里有 Id、Name、Mobile、Age、ProfilePhotoSrc(頭像地址根據 Id 拼接,這里我用 imgage/Id.png 的格式)。
public class UserDTO { public Guid Id { get; set; } public string Name { get; set; } public string Mobile { get; set; } public int Age { get; set; } public string ProfilePhotoSrc { get; set; } }
- 添加好 UserDTO 后,然后添加 IUserService.cs 接口,接着在 Application 的 Service 中添加對應的 UserService,並且 UserService 繼承 IUserService。
public interface IUserService { /// /// 用戶注冊 /// ///用戶名 ///手機號 ///年齡 /// Task Register(string userName, string mobile, int age); List<UserDTO> GetList(); } public class UserService : IUserService { readonly IUserRepository _userRepository; public UserService(IUserRepository userRepository) { _userRepository = userRepository; } #region Implementation of IUserService /// <summary> /// 用戶注冊 /// </summary> /// <param name="userName">用戶名</param> /// <param name="mobile">手機</param> /// <param name="age">年齡</param> /// <returns></returns> public async Task<bool> Register(string userName, string mobile, int age) { var userEnity = await _userRepository.GetByMobileAsync(mobile); if (userEnity != null) { return false; } var addUserEntity = new UserEntity { Id = Guid.NewGuid(), Age = age, Name = userName, Mobile = mobile }; return await _userRepository.InsertAsync(addUserEntity) != null; } public List<UserDTO> GetList() { return _userRepository.Select .ToList().ToDTOList(); } #endregion }
-
UserServcie 是對應展現層的控制器 UserController ---> IUserService。
-
最后展現層的 WebAPI 只需要注入 IUserService,就可以開心的使用了。
[HttpPost] public async Task Post() { var second = DateTime.Now.Second.ToString("00"); bool isSuccess = await _userService.Register("Wigor", $"188888888{second}", 22); return Ok(isSuccess); }
就這樣這個簡單的案例就完成了,你可以參考着上面 說明 對比着去看看,當然這里有一些東西並沒有體現,如 DomainServie,如果按照 DDD 來說還有 值對象、聚合、通用語言……,對於「通用語言」的話其實上面的小故事就體現出了一點。
結語
就 DDD 而言我這里還有很多東西都沒有交代,今后有時間的話會慢慢的寫出來。還有我也是在學習 DDD 所以有錯的地方請指出,望多多包涵。
在使用這套分層架構的時候碰到了許多問題,這里還要感謝老大的指導,為我解答疑問。
最后附上《實現領域驅動設計》中的一句話:
我認為不管使用什么技術,我們的目的都是提供業務價值。