DDD實戰進階第一波:開發通常業務的大健康行業直銷系統(概述)前端
近年來,關於如何開發基於業務的軟件系統與產品一直是軟件行業的一個重要內容。對於架構師與軟件開發人員來講,開發此類系統頭痛的問題大概是如下幾個方面:數據庫
1.如何將需求準確的轉爲軟件的設計?
2.系統的架構與代碼如何有效的體現咱們的設計?
3.如何將領域邏輯與技術分離?
4.如何可以讓團隊人員的開發可以專一與業務,而不是技術自己?
5.如何交付高質量的軟件,如何在出現問題時可以快速定位到代碼?
6.如何快速響應需求的變動?
7.如何可以有一個框架或思想限定,讓開發人員遵循一個約束,有節奏感的開發?json
爲了解決以上問題,軟件行業提出了一個成熟的思想(或叫方法論):領域驅動設計(DDD)。經過DDD,咱們可以很好的對需求應對到設計,可以讓開發聚焦業務自己,可以讓代碼體現咱們設計,可以讓團隊在一個框架內有節奏的開發。後端
有些開發人員或架構師也許瞭解過DDD,但總時認爲很難落地,本系列文章就是經過一個大健康行業的直銷系統實戰案例,讓你們瞭解如何可以基於DDD的思想和框架,開發一個業務系統。微信
本系列文章將達到如下幾個目的:架構
1.熟悉DDD基本概念。
2.基於DDD基本概念構建一個輕量級的框架。
3.基於DDD設計與開發一個實際的直銷系統的後端。
4.可以觸類旁通,開發其餘的業務系統或產品。
5.本系列文章不涉及高性能、大併發的系統開發。併發
本系列文章須要你具有的技術基礎:app
1.熟悉C#。
2.熟悉http://Asp.net 或http://Asp.net Core。
3.熟悉EF或EF Core。
4.有必定的系統開發經驗。框架
本系列文章大致的內容安排:高併發
1.直銷系統概述。
2.DDD基本概念、開發支持DDD基本概念的輕量級框架。
3.開發直銷系統的產品上下文。
4.開發直銷系統的經銷商上下文。
5.開發直銷系統的訂單上下文。
6.簡單瞭解對前端的其餘支持、前端的開發。
直銷系統需求(裁剪後,便於你們理解DDD應用便可)概述:
一.產品管理
1.產品上架,產品分爲主產品與多個子產品規格。
2.子產品用於經銷商購買。
3.子產品有價格與相應的PV(PV用於經銷商購買產品後累加,用於覈算經銷商獎金)。
二.經銷商發展
1.子經銷商由上級經銷商介紹並註冊。
2.根據註冊時的電子幣肯定子經銷商的會員級別。
3.被註冊的子經銷商屬於一個介紹層級。
4.一個經銷商最多介紹兩個子經銷商。
三.產品下單
1.經銷商經過電子幣購買產品。
2.肯定郵寄地址。
3.其餘功能與普通電商相似。
搭建支持DDD的輕量級框架
要實現軟件設計、軟件開發在一個統一的思想、統一的節奏下進行,就應該有一個輕量級的框架對開發過程與代碼編寫作必定的約束。雖然DDD是一個軟件開發的方法,而不是具體的技術或框架,但擁有一個輕量級的框架仍然是必要的,爲了開發一個支持DDD的框架,首先須要理解DDD的基本概念和核心的組件。
一.什麼是領域驅動設計(DDD)
首先要知道DDD是一種開發理念,核心是維護一個反應領域概念的模型(領域模型是軟件最核心的部分,反應了軟件的業務本質),而後經過大量模式來指導模型設計與開發。
DDD的通常過程是:首先經過軟件需求規格說明書或原型生成一個領域模型(類、類的屬性、類與類之間的關係);而後根據模式(應該如何分層?、領域邏輯寫在哪?與持久化如何交互?如何協調多對象領域邏輯?如何實現邏輯與數據存儲解耦等)指導來實現代碼模型。
二.爲何使用DDD
DDD能應對複雜性與快速變化:
1.從技術維度實現分層:可以在每層關注本身的事情,好比領域層關注業務邏輯的事情,倉儲關注持久化數據的事情,應用服務層關注用例的事情,接口層關注暴露給前端的事情。
2.業務維度:經過將大系統劃分層多個上下文,可讓不一樣團隊和不一樣人只關注當前上下文的開發。
3.時間維度:經過敏捷式迭代快速驗證,快速修正。
三.DDD核心組件
1.界限上下文:
首先要將大系統劃分層多個界限上下文,好比大健康行業直銷系統能夠劃分爲產品、經銷商、訂單等幾個界限上下文,每一個界限上下文有本身的領域邏輯、數據持久化、用例、接口等。每一個界限上下文根據特色,具體實現方式又不一樣,好比有些界限上下文基本沒有業務邏輯,就是增刪改查,則可使用CRUD最簡單的模式;有些界限上線文有必定的業務邏輯,但對高併發、高性能沒要求,則可使用經典DDD模式;有些界限上下文有必定的業務邏輯,並且有高性能要求,則可使CQRS模式。
2.實體:
有業務生命週期,採用業務標識符進行跟蹤。好比一個訂單就是實體,訂單有生命週期的,並且有一個訂單號惟一的標識它本身,若是兩個訂單全部屬性值所有相同,但訂單號不一樣,也是不一樣的實體。
3.值對象:
無業務生命週期,無業務標識符,一般用於模式實體。好比訂單的收貨地址、訂單支付的金額等就是值對象。
4.服務:
無狀態,有行爲,一般就是一個用例來協調多個領域邏輯完成功能。
5.聚合:
一般將多個實體和值對象組合到一個聚合中來表達一個完整的概念,好比訂單實體、訂單明細實體、訂單金額值對象就表明一個完整的訂單概念,並且生命週期是相同的,而且須要統一持久化到數據庫中。
6.聚合根:
將聚合中表達總概念的實體作成聚合根,好比訂單實體就是聚合根,對聚合中全部實體的狀態變動必須通過聚合根,由於聚合根協調了整個聚合的邏輯,保證一致性。固然其餘實體能夠被外部直接臨時查詢調用。
7.服務:
協調聚合之間的業務邏輯,而且完成用例。
8.倉儲:
用於對聚合進行持久化,一般爲每一個聚合根配備一個倉儲便可。倉儲可以很好的解耦領域邏輯與數據庫。
9.工廠:
用於建立複雜的領域對象,可以將領域對象複雜的建立過程保護起來。
瞭解了DDD的好處與基本的核心組件後,咱們先不急着進入支持DDD思想的輕量級框架開發,也不急於直銷系統需求分析和具體代碼實現,咱們還少一塊,那就是經典DDD的架構,只有瞭解了經典DDD的架構,你才能知道具體在哪層要實現哪些功能,編寫哪些代碼,具體在開發DDD的輕量級框架與具體模塊代碼實現時,才能作到有的放矢。
在這裏須要說明的是,咱們的大健康行業直銷系統有必定的業務複雜性,沒有高併發、高性能的需求,因此不管是經銷商上下文、產品上下文仍是訂單上下文的具體實現,咱們都將遵循經典DDD架構,而不是CRUD簡單方式或CQRS DDD架構的方式。
傳統三層架構以及問題:
問題:
1.過度注重數據訪問層,而不重視領域。
2.業務邏輯直接與數據訪問層耦合,與領域爲核心的DDD思想背道而馳。
3.沒有一系列的模式與方法論指導這種分層架構的開發約束。
經典DDD架構:
1.基礎結構層:整個產品或系統的底層支撐
a.經常使用工具、支撐功能:這個.net core項目至少要實現如下的功能:Json配置文件的讀取、WebApi返回給前端的基本格式對象的定義、Json序列化與反序列化、加密功能、依賴注入框架的二次封裝等。
b.支持DDD框架:這個.net core 項目至少要實現如下的功能:聚合根接口定義、實體接口定義、值對象接口定義、倉儲接口定義、倉儲接口的EF Core頂層實現(工做單元模式)。
c.聚合根倉儲實現:這個.net core項目嚴格來說其實不屬於基礎結構層部分,只是因爲習慣,把它放到基礎結構層這個解決方案文件夾中。它實際上是引用了領域層的領域對象,而且 從領域層對應的聚合根倉儲接口中繼承,而後實現領域對象持久化到數據庫,這樣,倉儲實現是依賴衣領對象,領域對象與領域邏輯就不須要依賴倉儲。領域模型纔是系統真正的核心。
2.領域層:界限上下文的領域邏輯
a.首先要實現這個界限上下文的領域對象的POCO模型。
b.而後針對這個界限上下文的全部領域對象,創建每一個領域對象本身的業務邏輯,注意的是,領域對象的業務邏輯最好不與倉儲直接發生交互,就算領域邏輯要臨時查詢數據庫也不要這樣。
c.定義該界限上下文聚合根的倉儲接口,這個接口表明的是聚合根與持久化打交道的基礎約束,具體實現仍是在基礎結構層的聚合根倉儲中實現,這樣就實現瞭解耦。把聚合根倉儲接口定義在領域層的意義是能夠爲領域層的調用方-應用服務層的用例提供對聚合持久化支持。
d.定義該界限上下文的EF Core上下文接口並實現,這樣就經過映射關係,EF Core就能夠處理領域對象與數據庫表之間的映射了。
3.應用服務層:界限上下文的用例
a.某個上下文的應用服務層的某個用例,經過調用領域對象的領域邏輯,完成相關領域邏輯的實現。
b.領域邏輯完成後,應用服務層用例調用領域層的聚合根的倉儲接口的方法,完成領域對象的預持久化。(應用服務經過基礎結構層的依賴注入框架與Json配置文件找到聚合根倉儲接口對應的實現)
c.應用服務層用例而後調用基礎結構層的EF Core倉儲接口的工做單元方式,完成真正的持久化。(應用服務經過基礎接口層的依賴注入框架與Json配置文件找到頂層倉儲接口對應的工做單元實現)
d.用例返回給接口層須要的前端所需的json對象格式。
4.接口層:很是薄的一層
a.只須要調用應用服務層用例
b.向前端返回所需的json對象格式
從上述架構特色能夠看出,聚合根的倉儲與領域邏輯徹底解耦,是經過應用服務層的用例將他們協調起來完成功能。
咱們講了經典DDD架構對比傳統三層架構的優點,以及經典DDD架構每一層的職責後,下面將介紹基礎結構層中支持DDD的輕量級框架的主要代碼。
這裏須要說明的是,DDD輕量級框架可以體現DDD的思想便可,不必作得很重,你也能夠根據理解,本身實現支持DDD的框架。
1.實體、聚合根與值對象的頂層體現
實體頂層定義:
public interface IEntity
{ string Code { get; set; } Guid Id { get; set; } }
Id是一個將來存儲到數據庫表中的技術主鍵,Code是領域對象的惟一業務標識符。你也能夠擴展這個接口,定義兩個實體比較接口(將來實現就是比較兩個實體若是Code一致,則表明兩個實體相等)。
聚合根頂層定義:
public interface IAggregationRoot:IEntity
{ }
聚合根接口就是從實體接口繼承,只是將來的用法能夠在倉儲中定義持久化時的領域對象必須從這個接口或繼承了這個接口的抽象類繼承下來的。
值對象頂層定義:
public interface IValueObject
{ Guid Id { get; set; } }
值對象接口只須要保留一個技術主鍵便可,它沒有業務標識符。在數據庫中,值對象可能做爲單獨表存儲,也能夠做爲實體的一部分存儲。你也能夠擴展這個接口,定義兩個值對象比較接口(將來實現就是比較兩個值對象若是全部屬性值一致,則表明兩個值對象相等)。
工做單元頂層定義:
public interface IUnitOfWork
{ void Commit(); }
工做單元接口就定義了一個提交方法,在具體實現時,其實就是對應的EF Core的整個聚合的事務提交方法。
倉儲接口頂層定義:
public interface IRepository:IUnitOfWork,IDisposable
{ }
倉儲接口從工做單元接口與資源釋放接口繼承,爲將來的數據訪問框架和可替換性提供頂層約束。
EF Core頂層倉儲持久化實現:
public class EFCoreRepository : IRepository
{ private readonly DbContext context; public EFCoreRepository(DbContext context) { this.context = context; } public void Commit() { try { context.SaveChanges(); } catch(Exception error) { throw error; } } public void Dispose() { context.Dispose(); } }
從上述代碼中能夠看到,主要實現了倉儲接口的Commit方法,其實就是使用了EF Core的DbContext數據訪問上下文類的SaveChanges()事務提交方法,應用服務層的用例就能夠獲取到某個聚合根的當前狀態,而後調用倉儲接口的Commit方法,實現了整個聚合全部對象的一次性事務提交。
2.經常使用工具類的實現
咱們還應該定義另外一個項目,這個項目是整個系統都須要使用到的工具,其中至少應該包括http://Asp.net Core Json配置文件的讀,Json序列化與反序列化,加密,依賴注入,返回給前端的對象格式定義等,這裏先列出幾個須要的,其餘的在後面具體案例中在補充。
http://Asp.net Core Json配置文件讀取:
Json配置文件會存儲咱們的一些配置信息,好比數據庫鏈接字符串,微信AppId與AppSecure等,因此須要有功能支持Json配置文件的Key到Value的讀取
public class AppSetting
{ private static IConfigurationSection appsections = null; public static void SetAppSetting(IConfigurationSection section) { appsections = section; } public static string GetAppSetting(string key) { string str = ""; if (appsections.GetSection(key) != null) { str = appsections.GetSection(key).Value; } return str; } }
返回前端的對象格式定義:
咱們的應用服務層將返回WebApi接口必定的數據格式,WebApi接口也會將這個數據返回給前端,前端拿到後就會作相應的處理。
public class ResultEntity<T>
{ public bool IsSuccess { get; set; } public string Msg { get; set; } public T Data { get; set; } public int ErrorCode { get; set; } public int Count { get; set; } }
public class BaseAppSrv
{ protected ResultEntity<T> GetResultEntity<T>(T vobj,string msg="未成功獲取到對象",int errorcode = 0) { var ueresult = new ResultEntity<T>(); var issuccess = true; if(vobj is int && Convert.ToInt32(vobj) <= 0) { issuccess = false; } else if(vobj is bool && !Convert.ToBoolean(vobj)) { issuccess = false; } else if(vobj is string && string.IsNullOrEmpty(Convert.ToString(vobj))) { issuccess = false; } if (!issuccess) { ueresult.Msg = msg; ueresult.ErrorCode = 200; } ueresult.IsSuccess = issuccess; ueresult.Data = vobj; return ueresult; } }
將來全部的用例都將從BaseAppSrv繼承,最終返回的格式都是ResultEntity<T>。
好了,基本的框架搭建好了,下一章就能夠直接進入案例,看案例中如何經過DDD思想進行設計,並經過經典DDD架構與DDD輕量級框架進行實際業務系統的代碼編寫。