1. 程式人生 > >DDD「領域驅動設計」分層架構初探

DDD「領域驅動設計」分層架構初探

項目開發 div time present 通過 離開 中項 posit tostring


前言

基於 DDD 傳統分層架構實現。 項目 github地址:https://github.com/WuMortal/DDDSample

這個分層架構是工作中項目正在使用的分層架構,使用了一段時間發現受益匪淺,所以整理好我對該分層架構的一些理解分享給大家,我對於該分層架構還處於學習階段理解有誤的地方請指出。本次會以一個案例來說明各個分層的作用以及他們之間的調用關系,還有本次的重點不在於DDD,因為這個我還未能完全理解,當然避免不了中間會涉及DDD的一些概念。

DDD 簡單介紹

DDD 什麽?為什麽使用 DDD

關於這個問題有興趣的可以自行百度,我相信網絡上已經有大量的文章來說明這幾個問題。我目前的理解是“業務”,是為了應對現在復雜和多變的業務,是一種開發理念。

這裏我就以一個小故事描述吧,有一天你接到任務要實現一個修改用戶的功能,非常簡單。使用傳統三層架構我們會怎麽寫?

  1. 先在 DAL 層添加 UserDAL 然後實現一個 Update(UserEntity user) 方法

  2. 接著在 BLL 中添加一個 UserBLL 在實現一個 Update(string email,string pwd ...) 方法。

  3. UI 層在調用,OK 完成任務下班回家。

接著你接到一個新的需求就是:需要增加用戶修改信息的記錄。

你立馬在 BLLUpdate 的方法裏增加的用戶修改信息的操作記錄,完成需求。

過了一段時間又來了一個需求:用戶改了信息需要通知到管理員,並且用戶每天只能修改 3 次信息。

好了之後又經歷了幾波需求,你的代碼也在不斷的增加和變化,有一天你接收新的項目或者離開了,那麽接收你項目的人完全不清楚這裏的業務情況。因為 Update 方法並沒有直接的反應出裏的業務情況,代碼目的不明確。代碼變得難以維護。

那麽在 DDD 裏這些應該怎麽做呢?

  1. 首先在方法的命名上做出更改既然業務是修改信息那麽命名應該是 Modify(string email,string pwd ...)

  2. 將用戶修改信息的記錄代碼放在 DomainService(領域服務) 中,當然這裏的類、方法命名要直接的反應出業務情況,如:RecordUserModifyDomainService

  3. 對應的通知管理員的代碼也應該放入 DomainService

    中,DomainService 應該盡量簡單一般只做一件事情。

分層架構圖

技術分享圖片

下面是關於 DDD 分層的一些描述,摘抄至之前看過的一片文章。

  • Presentation 為表示層,負責向用戶顯示信息和解釋用戶命令。這裏指的用戶可以是另一個計算機系統,不一定是使用用戶界面的人。

  • Application 為應用層,定義軟件要完成的任務,並且指揮表達領域概念的對象來解決問題。這一層所負責的工作對業務來說意義重大,也是與其它系統的應用層進行交互的必要渠道。應用層要盡量簡單,不包含業務規則或者知識,而只為下一層中的領域對象協調任務,分配工作,使它們互相協作。它沒有反映業務情況的狀態,但是卻可以具有另外一種狀態,為用戶或程序顯示某個任務的進度。

  • Domain 為領域層(或模型層),負責表達業務概念,業務狀態信息以及業務規則。盡管保存業務狀態的技術細節是由基礎設施層實現的,但是反映業務情況的狀態是由本層控制並且使用的。領域層是業務軟件的核心,領域模型位於這一層。

  • Infrastructure 層為基礎實施層,向其他層提供通用的技術能力:為應用層傳遞消息,為領域層提供持久化機制,為用戶界面層繪制屏幕組件,等等。基礎設施層還能夠通過架構框架來支持四個層次間的交互模式。

說明

如上圖每個層中其實對應著具體的項,下面將對每個項進行說明。

  1. Domain 層分為:DomainDomainServiceIDomainService

    • 首先 Domain 中包含有 EntityIRepositoryEntity 是你的實體一般對於數據庫表但是在某些情況下你也可以冗余一些字段。IRepository 倉儲的方法的定義,該層不會有具體的實現。
    • DomainServiceIDomainServiceIDomainService 只是負責表達業務的概念,DomainService 裏才是具體業務邏輯代碼。在這一層的代碼命名上需要註意,我們的命名一般要能直接描述出該代碼業務的功能。這裏可以參考 DDD 的幾個概念:通用語言、領域。
  2. Infrastructure 層分為:RepositoryCrossCutting

    • Repository 裏面就是 DomainIRepository 的具體實現。項目中 RepositoryExtensions.cs 是一個擴展類,將所有的倉儲註入容器中,方便我們在項目中使用 DI(依賴註入)。
    • CrossCutting 主要是提供一些各個層通用的東西,如一些枚舉、擴展方法、工具類等等。
  3. Application 層分為:ApplicationApplicationContract

    • ApplicationContract 裏主要包含 DTOViewModelIXXXServiceDTO 是數據傳輸對象,主要負責給展現層提供展示數據,DTO 裏應該只有值類型存在,當然根據具體情況也可存在其他的 DTOViewModel 用於展現層傳入的模型,簡單的說 DTO 輸出,ViewModel 輸入。IXXXService 就是應用層的方法定義。
    • Application 裏面主要是用於 實現 ApplicationContract 裏的 IXXXService,還有 EntityDTO 的映射也屬於該層的工作。ApplicationExtensions.cs 擴展方法是用於實現 DI
  4. Presentation 層裏目前只有一個 WebAPI。展現層的代碼一般有:對傳入模型的校驗。

案例

本次以一個用戶註冊的流程為案例,來簡單說明如何使用該分層架構進行項目開發。

  1. 首先在 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,你們可以自己實現。

  1. 然後在 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
}

  1. 倉儲基本好了後就是 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; }
}

  1. 添加好 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
}

  1. UserServcie 是對應展現層的控制器 UserController ---> IUserService。

  2. 最後展現層的 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 所以有錯的地方請指出,望多多包涵。

在使用這套分層架構的時候碰到了許多問題,這裏還要感謝老大的指導,為我解答疑問。

最後附上《實現領域驅動設計》中的一句話:

我認為不管使用什麽技術,我們的目的都是提供業務價值。

DDD「領域驅動設計」分層架構初探