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


前言

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

這個分層架構是工做中項目正在使用的分層架構,使用了一段時間發現受益不淺,因此整理好我對該分層架構的一些理解分享給你們,我對於該分層架構還處於學習階段理解有誤的地方請指出。本次會以一個案例來講明各個分層的做用以及他們之間的調用關係,還有本次的重點不在於DDD,由於這個我還未能徹底理解,固然避免不了中間會涉及DDD的一些概念。github

DDD 簡單介紹

DDD 什麼?爲何使用 DDD數據庫

關於這個問題有興趣的能夠自行百度,我相信網絡上已經有大量的文章來講明這幾個問題。我目前的理解是「業務」,是爲了應對如今複雜和多變的業務,是一種開發理念。網絡

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

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

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

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

接着你接到一個新的需求就是:須要增長用戶修改信息的記錄。學習

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

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

好了以後又經歷了幾波需求,你的代碼也在不斷的增長和變化,有一天你接收新的項目或者離開了,那麼接收你項目的人徹底不清楚這裏的業務狀況。由於 Update 方法並無直接的反應出裏的業務狀況,代碼目的不明確。代碼變得難以維護。

那麼在 DDD 裏這些應該怎麼作呢?

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

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

  3. 對應的通知管理員的代碼也應該放入 DomainService 中,DomainService 應該儘可能簡單通常只作一件事情。

分層架構圖

DDD 流程圖

下面是關於 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 因此有錯的地方請指出,望多多包涵。

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

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

我認爲無論使用什麼技術,咱們的目的都是提供業務價值。

相關文章
相關標籤/搜索