從我們作為業務開發主要的職責深入到DDD的本質是什麼?複雜度應處理?規範設計怎麼做?本文將全方位為大家解答。 ...
從我們作為業務開發主要的職責深入到DDD的本質是什麼?複雜度應處理?規範設計怎麼做?本文將全方位為大家解答。
一、作為業務開發,我們的主要的職責是什麼的
業務開發的職責
這裡摘錄一下文章中要點
技術一號位是負責使用技術能力解決業務問題,提供穩定可靠的技術支撐; 負責向業務各方提供各種必要的技術支撐,通過合理的數據分析為業務決策提供依據; 通過對技術領域的積累和發展,通過業務領域的理解和落地影響業務決策; 負責構建梯隊完整、能力全面、制度完善的技術團隊來支撐業務發展。
業務在實際開展中遇到的問題
-
業務啟動期:業務能力快速搭建 - 系統提供快速試錯的能力 -
業務發展期:業務能力擴展 - 系統需要支持原來越多的業務功能 -
業務平臺期:業務能力複製 - 系統需要支持原來越多的業務場景 -
業務衰退期:業務能力創新 - 系統提高生產力延長業務的生命周期
軟體複雜度
-
高性能
-
單機性能 -
集群性能
-
高可用
-
計算高可用 -
存儲高可用
-
可擴展性
-
低成本 -
安全 -
規模
-
業務規模 -
系統物理規模
二、DDD的本質是什麼
DDD實施給系統之後,我們依然需要關註系統其它的複雜度,這裡列舉一些示例措施:
容量規劃 架構設計 資料庫設計 緩存設計 框架選型 發佈方案 數據遷移、同步方案 分庫分表方案 回滾方案 高併發解決方案 一致性選型 性能壓測方案 監控報警方案
-
領域模型描述問題域的準確性
2、技術實現的複雜性
-
軟體的可擴展性較差 -
軟體變成面向過程 -
分層不合理 -
沒有規範
那DDD是如何處理上面提到的軟體複雜度的?
-
提供了一個領域劃分的方法:讓軟體系統產生邊界。 -
提供一個一系列的戰略模式:限界上下文的映射,分層架構等。 -
提供一個一系列的戰術模式:如何規劃領域層 內部
DDD不是什麼?
-
不光光只是一種編程方法 -
不光光只是一種架構風格 -
不具體指導如何具體建模
三、複雜度處理-領域模型描述問題域的準確性
合理性證明


什麼是問題域


如何實現問題域的分析
-
關聯模型與軟體實現; -
基於模型提取統一語言; -
開發富含知識的模型; -
精煉模型; -
頭腦風暴與試驗。
1 四色原型法
2 用例分析法
問題域的拆分
1 通用域: 非應用獨有的,多個應用都會有的功能。例如發送郵件,觸達等
2 核心域:和競爭對手區別開來的區域,或者是在市場上被賦予了競爭優勢的區域。
-
系統哪部分最難用 -
手動處理過程阻止了他們進行了根據創造性, 有附加值的工作 -
哪些修改能提高收益 -
哪些修改能提高運營效率
取哪些提示,取決於業務系統的性質。


-
賣家 + 會員身份:這兩者都是核心實體,網站可能為了讓促進會員能夠多參與拍賣可能提供了分層,或者積分等工功能。網站為了能讓賣家能夠更加提供更加有拍賣價值或者是轉化率高的品類可能為賣家提供了數據分析等業務功能。 -
名冊:這也就是核心實體,網站會對名冊提供一系列拍賣相關的功能,例如倒計時,一口價等,所以也需要形成一個領域。 -
拍賣:網站最核心的業務流程,核心域無疑。 -
爭議解決:買賣家的售後衝突解決流程向來很複雜,所以會獨立成為一個域無疑。
四、複雜度處理-進一步降低問題域的複雜度-限界上下文
限界上下文的誕生背景

限界上下文的本質

上下文映射

shared kernel 共用內核 shared kernel :通常是共用核心領域或者是一組通用子領域。 customer/supplier
客戶/供應商關係 customer/supplier:上下游關係。不同客戶需要協商來平衡,上游團隊需要有自動測試套件。 conformist
跟隨者模式 conformist:單方面跟隨模式。上游的設計質量較好,容易相容,可以採用嚴格遵循上游團隊的模型。 anticorruption layer
防腐層 anticorruption layer:防腐層、隔離層,使用 facade or adapter 等模式。可以減少其它系統變動對本系統的影響。 separate way
各行其道 separate way:聲明一個與其它上下文毫無關聯的 bounded context,使開發人員能夠在這個小範圍內找到簡單、專用的解決方案。 open host service
開放主機服務 open host service:開放子系統供其他系統訪問。其核心思想是開放出一個標準的各個領域都認可的協議,減輕各個領域實施ACL的負擔和成本。 published language
共用語言 published language:把一個良好文檔化、能夠表達領域信息的共用語言作為公共的通信媒介,必要時在其它信息與該語言之間進行轉換。
-
shared kernel:如果使用共用二方庫,誰來維護這個二方庫,如何防止在不同上下文使用不同kernal版本所帶來的問題。
如果一定能保證shared kernel的維護在一個團隊內,且所有使用shared kernel版本一定能保持一致, 那是可以使用的。
-
customer/supplier:我曾經因為匯率包升級而去重構一個應用,因為匯率包變更太大,且應用沒有防腐層,所以不論從開發還是測試都是非常痛苦的過程。 -
conformist:和customer/supplier類似, 但是在互聯網領域沒有靠譜的設計, 只有有人維護和沒有人維護的設計。conformist從長期來看其實就是customer/supplier。 -
Open Host Service 沒有任何一個領域保證自己的介面一定不會變,就算不會,其他領域的同學會相信嗎,他們會忍住不用ACL嗎?如果他們用ACL,OHS的意義何在? -
publish language 目前阿裡內部MTOP,TOP等協議正是使用這樣的協議。
另外限界上下文之間真的能夠隨便無規則無條件的互相依賴,互相調用嗎?在下麵的章節將會解釋論述。
五、複雜度處理 - 分層不合理
傳統的三層架構

六邊形架構


洋蔥架構

DDD 架構


CQRS



我們團隊裡面的架構實踐

1、依賴關係(除了依賴倒置)只能是從上當下;
2、同層之間永遠不能互相依賴;
六、複雜度 - 軟體變成一個大泥潭
實體
實體建模的註意點
-
現實意義標識符 -
人工生成的標識
-
自增 -
guid/uuid/ -
資料庫主鍵 -
自定義sequence
2、驗證和不變行
規模模式:
-
只是用id引用 -
value objects
什麼時候需要使用到值對象?
-
概念需要凸顯的時候。
public class WinningBid { ... public int Price { get; private set; } ... }
-
表述一個描述性的,但是沒有實體編號的概念的時候。
值對象的特征
-
無標識:他們只是標識對象的屬性。 -
基於屬性的相等性: 所有的屬性值相等即值對象相等 -
富含行為:值對象實現業務概念的抽象,其也有自己的行為 -
內聚:將不同的相關屬性組成一個概念整體,例如Money, 是由一個long 和一個currency組成的 -
不變性:值對象是不變的對象,如果需要改變屬性,那最好是建立一個新的對象並且進行值對象替換。如果一定是需要改變,那就需要考慮設置為值對象是否合理。
不變性是值對象非常重要的一個屬性,是可以保障值對象不會被"壞味道"代碼侵入的一個原則之一。 例如如果一個值對象引入了另外一個類實例, 另外一個值對象也引入了相同的類實例, 如果值對象允許改動,當一個值對象對這個類實例的內容進行修改,勢必會影響另外一個值對象。 所以最安全的方式還是通過對象替換的方式。
域服務
什麼時候用域服務
域服務應該包含什麼內容
應用服務與領域服務的區別:
應用服務里不要處理業務邏輯,只在領域服務里處理業務邏輯。(如何判斷某段邏輯是否是業務邏輯?) 領域服務掌握領域知識,而應用服務只是對領域服務的編排。 應用服務是領域服務的客戶方,也就是說應用服務會調用領域服務里的方法。 當領域中的某個操作過程不屬於實體或者值對象的職責時,需要將個操作放在領域服務中。而且確保領域服務是無狀態的(這句話很有意思,也就是說領域服務中不應該有任何記錄狀態的行為,在任何情況下調用這個服務,它都不會有副作用,也就是說它是個純記憶體操作)。 領域服務中包含的是業務邏輯,而應用服務關註的應該是安全和事務等非業務邏輯。 對事務的管理絕對不能放在領域服務層,事務管理需要放在應用服務層。因為和領域模型相關的操作的粒度都很細,無法用於事務管理。而且領域模型也不應該意識到事務的存在。 通常的可以放在應用服務中的邏輯有:參數驗證、錯誤處理、監控日誌、事務處理、認證與授權。
A ->B
A.accountDecrease(10);
工廠
-
只負責複雜邏輯對象的構建,讓構建邏輯中心化 -
減少外部對象對構建對象內部變數的理解 -
工廠方式不是在任何構建對象的時候使用,一定用在對象構建邏輯複雜,有子依賴或者是有invariant規則的場景。
Repository
-
uniqe ID 的生成 -
資料庫的操作 -
數據模型到領域模型的相互 -
橫跨多個數據模型構建出一個實體模型。
repository的反模式
-
定義出比較通用化的介面, e.g.
List<Customer> findBy(CusomterQuery query)
-
使用了延遲載入,延遲載入就是設計錯誤的標誌, 有可能說明我們聚合的邊界不催。 -
不要為了報表的訴求使用reponsitory, 領域的case和業務報告很不一樣,可能需要多個聚合的數據,這種情況可以考慮用一個離線的store去做,和其他的讀服務去做,不見得一定需要用領域的Repository模式。
領域事件
-
google guava - EventBus -
COLA框架 - 事件支持
聚合是什麼?


如何劃分聚合
聚合設計的原則
-
聚合是用來封裝真正的不變性,而不是簡單的將對象組合在一起; -
聚合應儘量設計的小; -
聚合之間的關聯通過ID,而不是對象引用; -
聚合內強一致性,聚合之間最終一致性;
什麼是聚合根
如何選出聚合根
1、聚合根一定至少有一個對應的datastore
2、聚合根一定更夠完全描述一組後者多組業務規則(invariant)。絕對不會存在一個業務規則需要多個聚合根聯合作用才能做判斷的。
七、規範設計
放對位置
-
程式架構目錄
貼好標簽
-
類名約定 -
方法名約定 -
錯誤碼約定 -
Domain Event約定 -
測試約定
下麵是我們小團隊參考了阿裡COLA框架建議的命名規範做出的開發規範:
程式架構組織
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|