DDD - 快速 hold 住業務的利器

以前在小廠,由於業務場景與組織架構很小, 生產關係與鏈路都比較簡單,業務的複雜度也相對較小。但在所謂的大廠(生活所迫),業務場景較多且要支持業務的快速擴展,組織架構龐大,上下游的生產關係與鏈路巨複雜,所以研發在很大程度上都是在解決業務的複雜性的問題,一個如同 “大泥團” 般的項目,會給業務項目 / 團隊協作 / 技術架構都會帶來極高的複雜性,同時也帶來了各種各樣的問題,這些都讓一個業務新人面對要接手的業務或服務一下子懵逼的(恭喜你,已經成功入坑了!),所以要想讓一個新人快速上手業務並有產出,需要一個強大的指導工具與方法論的指引。

戰略:運用資源實現目標的全局性長久性的綱領規劃;

戰術:運用資源實現目標的局部性短期性規劃和實現方法;

戰略和戰術的區別是博弈的整體與局部、長與短、抽象與具體的區別.

一個資深老手肯定是同時具有好的戰略規劃能力與戰術執行能力,說白了就是既要能看得遠分得清,也要能腳踏實地認真幹。對於這樣的能力要求,DDD 就成爲了一門必修課程。

1. DDD 定義

領域驅動設計 (DDD) 是一種軟件設計方法, 提供了戰略和戰術的上的建模工具,戰略部分用於理解、梳理業務,找到核心業務,更好地劃分領域 / 系統 / 服務; 戰術部分用於落地到代碼上,用代碼來清晰地表示業務,代碼如何分層、如何設計都有一套成熟的指導方案.

戰略設計: 戰略設計工具可幫助團隊作出最有競爭力的軟件設計選擇和業務整合決策,並讓團隊在這些具有核心競爭力的軟件模型中獲取最大的利益。使用限界上下文的戰略設計模式來分離領域模型,並在其中發展出一套領域模型的通用語言,如領域術語。

戰術設計: 戰術實施工具可幫助團隊設計出實用的軟件,對業務的運作方式進行精準的建模。將若干實體和值對象以恰當的大小聚集在一起,也叫做的聚合模式。

2.DDD 作用

DDD 可以幫助提高設計的有效性,並用來實現高價值的軟件,也用來解決軟件設計上的複雜度 / 擴展 / 開發成本等問題,在目前日益競爭的市場環境下持續交付出最有效的軟件設計與實現。採用 DDD 帶來的可能改變:

提高項目的成功率;

提升業務的競爭力,正確地對業務需求建模;

能比較輕鬆地對軟件架構進行迭代演進並擴展.

3. 限界上下文

限界上下文是語義和語境上的邊界,意味着邊界內的每個軟件模型的組件都有特定的含義並處理特定的事務,限界上下文中的這些組件有特定但上下文語境和語義理據。其中可以理解爲限界上下文是一個問題空間以及這個問題的解決方案空間。

問題空間: 給定項目的約束條件下進行高級戰略分析與設計的各個步驟的地方。可以使用簡單的圖表來展示討論中高級的項目驅動因素,並記錄關鍵目標與風險。

解決方案空間: 真正實施解決方案的地方,這些解決方案在問題空間討論中被識別爲核心域,當限界上下文被當作組織的關鍵戰略舉措進行開發時,即被稱爲核心域。主要通過源代碼和測試代碼來實現限界上下文中的解決方案,也會在解決方案空間中編寫代碼,來支撐與其他限界上下文之間的集成。

通用語言:  團隊在限界上下文中發展了一種語言用於表達其邊界內的軟件模型,這一語言在限界上下文中開發軟件模型的每個成員使用。該語言就是軟件模型團隊交流時使用的語言,而軟件模型的源代碼就是這種語言的書面表達方式,強烈建議定義領域術語,比如某個英文單詞就特定代表某個模型並寫上該單詞的備註解釋,讓不同地域、不同團隊成員都可以準確理解專用語言的含義,該語言可用在數據庫、系統設計、設計文檔等等方面。

核心域的識別是一個持續的精煉過程,把一堆混在在一起的組件分離,以某種形式提煉出最重要的內容,這種形式也將核心域更具價值,可以讓產品研發的資源更聚焦到最小化可行產品上,不斷獲取用戶反饋,並在這最小化可行產品上持續快速迭代,從而獲得一個穩定的核心產品。一個團隊應該在一個限界上下文中工作,每個限界上下文應該有一個獨立的源代碼倉庫,一個團隊可能工作在多個限界上下文中,但是多個團隊不應該在同一個限界上下文中共事,應該採用和分離通用語言同樣的方式,把不同的限界上下文的源代碼與數據庫模式隔離開,並且,將同一個限界上下文中的驗收測試、單元測試和主要源代碼存放在一起。

4. 架構

在限界上下文中不僅是一個領域模型,裏面還包括了合理的架構用來組織領域模型,輸出相關的領域能力。在限界上下文的很常見: 輸入適配器,例如用戶界面控制器、REST 終端節點和消息監聽器;編排用例和管理事務的應用服務;輸出適配器,如持久化管理和消息發送器。

目前經常可以看到的幾種架構風格:

事件驅動架構: 是一種以事件爲媒介,實現調用接口者和接口實現者之間的解耦,事件驅動則是調用者和被調用者互相不知道對方,兩者只和中間消息隊列耦合。

命令和查詢職責分離 (CQRS): 將讀取和寫入操作分成不同的模型,使用命令更新數據,並使用查詢來讀取數據,從不修改數據。命令應基於任務,而不以數據爲中心。命令可以放置在隊列上進行異步處理,而不是同步處理。

響應式架構和 Actor 模型: 是事件驅動的一種實現方式,特別擅長處理多個客戶端併發的向服務端請求服務的場景。如 Reactor 模式會解耦併發請求的服務並分發給對應的事件處理器來處理。目前,許多流行的開源框架都用到了 reactor 模式,如: netty、nio 等,還有一種如 Akka 的 actor 模型。

面向服務的的架構: 面向服務的體系結構,是一個組件模型,它將應用程序的不同功能單元通過這些服務之間定義良好的接口和契約聯繫起來。接口是採用中立的方式進行定義的,它應該獨立於實現服務的硬件平臺、操作系統和編程語言。這使得構建在各種這樣的系統中的服務可以以一種統一和通用的方式進行交互,目前比較常見的是 RPC 架構,包括了服務註冊中心,服務提供者、服務消費者三大組件。

具象狀態傳輸 (REST): 描述了一個架構樣式的互聯繫統。REST 約束條件作爲一個整體應用時,將生成一個簡單、可擴展、有效、安全、可靠的架構。由於它簡便、輕量級以及通過 HTTP 直接傳輸數據的特性。用於 web 服務和動態 Web 應用程序的多層架構可以實現可重用性、簡單性、可擴展性和組件可響應性的清晰分離。

5. 子域

DDD 項目中總會碰到很多限界上下文,這些限界上下文一定會有一個成爲核心域,而其他的限界上下文之中會存在許多不同的子域名。子域是整個業務領域的一部分,子域代表的實施一個單一的、有邏輯的模型,大多數的業務領域過於龐大和複雜,難以作爲整體來分析,因此我們只關心那些必須在單個項目中涉及的子域,子域可以用來有邏輯地拆分整個業務領域,通過 DDD 來創建子域,它將會被實現成一個清晰的限界上下文。

項目中有三種類型的子域:

核心域: 它是組織內一個唯一的、定義明確的領域模型,要對他進行較大的戰略投資,並在一個明確的限界上下文中投入大量的資源去構建通用語言,它是組織中最重要的項目,是業務的核心競爭力所在。

支撐子域: 這類場景下提倡 “定製開發”,能最大限度支撐核心域的發展爲第一要義,但對它的投入無論如何也達不到與和核心域相同的程度,也許會考慮使用外包的方式來定義限界上下文。

通用子域: 通用子域可以使用外包開發、採購或者內部團隊提供,但不會爲其分配與和核心域一樣的研發資源。

業務領域中的某些系統邊界可能是遺留系統,也許是由你的團隊構建的,也許是通過購買軟件許可的方式獲得的,此時子域就是目前的關注點。在整個遺留系統中可能充滿了多個錯綜複雜的模型,能準確地識別出每個子域以及子域的邊界,用子域來思考和討論此類系統有助於我們應對錯綜複雜模型的顯示。當使用這類工具時,我們可以明確那些對業務有價值、對項目更重要的子域,而降低其他子域可以降低到次要位置。當使用 DDD 時,限界上下文應該與子域一一對應 (1:1),如果存在一個限界上下文,那麼目標就是歸類出一個對應的子域模型。

6. 上下文映射

上下文映射的種類

合作關係存在與兩個或者多個團隊之間,每個團隊負責一個限界上下文。兩個團隊通過互相依賴的一套目標聯合起來形成合作關係,經常會面對同步日程和相互關聯的工作,還使用持續集成對保持集成工作協調一致的。保持長期的合作關係很有挑戰性,因此許多進入合作關係的團隊可能盡最大努力爲這種關係設置一個期限,只能在彼此發揮優勢時才維持合作關係。

共享內核: 兩個團隊之間共享着一個小規模但卻通用的模型,團隊必須要共享的模型元素達成一致,有可能它們當中只有一個小團隊會維護、構建以及測試共享模型的代碼。

客戶 - 供應商: 供應商位於上游,客戶位於下游,支配這種關係的是供應商,因爲它必須提供客戶需要的東西。客戶需要與供應商共同制定規劃來滿足這種預期,但最終卻由供應商來決定客戶獲得的是什麼,什麼時候獲得。

跟隨者: 關係存在於上游團隊和下游團隊之間,上游團隊沒有任何動機滿足下游團隊的具體需求。由於各種原因,下游團隊的也無法投入資源去翻譯上游模型的通用語言來適應自己的特定需求,因此只能順應上游的模型。防腐層就是下游團隊好用的工具之一,是最具有防禦性的上下文映射關係,下游團隊在其通用模型和位於它上游的通用語言 (模型) 之間創建了一個翻譯層次,隔離了下游模型和上游模型,並完成兩者之間的翻譯。

開放主機服務: 定義了一套協議或接口,讓限界上下文被當作一組服務訪問,該協議是開放的,所有需要與限界上下文進行集成的客戶端都可以輕鬆使用它,通過 API 提供出來服務並且都有的詳細的說明文檔。

已發佈語言: 是一種有着豐富文檔的信息交換語言,可以被許多消費方的限界上下文簡單地使用和翻譯。需要讀寫信息的消費者們可以把共享語言翻譯成自己的語言,最好的例子就是 SDK。

上下文映射集成方式

基於 SOAP 的 RPC:目前主流的 Dubbo/HSF 的框架都是典型的例子,基於 SOAP 的 RPC 的設計思路就是讓調用另一個系統的服務如同調用同一個本地過程或方法那樣簡單,SOAP 的請求需通過網絡傳輸才能抵達相關係統,成功執行後並返回結果。但這種集成方式缺乏健壯性,這就是爲什麼 RPC 框架中有很多容錯設計的原因。

RESTful HTTP: 注意力集中在上下文之間交換的資源,有 POST/GET/PUT/

DELETE 等操作,支持 REST 接口的服務端限界上下文應該提供開放主機服務和說明文檔。同樣這種方式也有可能因網絡或服務提供商故障,網絡延時等,錯誤或異常的全鏈路跟蹤與日誌也是很困難的。使用 REST 設計錯誤是直接把模型中的聚合暴露出來。

消息機制: 在使用消息機制進行集成時,很多工作都是通過客戶端限界上下文訂閱由它自己或另一個限界上下文發佈的領域事件來完成,使用消息機制可以消除阻塞調用,一個領域消息可被一個或多個訂閱方消費,常用的消息中間件一般可用於消峯 / 解耦,但也可能造成領域事件過多,消費過慢等情況發生。

7. 子域中的聚合設計

目前還需要對具體的子域進行具體的詳細設計 (戰術層面)。子域中有實體、值對象、聚合、聚合根等建模工具,使用了聚合設計的思路或工具來進行設計,其中一個聚合中是由一個或者多個實體組成,一個實體成爲聚合根,聚合的組成還包括了值對象。

實體: 一個實體模型就是一個獨立的事物,每個實體模型都擁有一個唯一的標識符,可以將它的個體和所有其他類型相同或者不同的實體區分開,並且每個實體都有一個生命週期,將實體與其他建模工具分開的主要因素是它的唯一性;

值對象: 值對象是對一個不變的概念整體所建立的模型。在這個模型中,它沒有唯一標識符,而是由類型封裝的屬性對比來決定相等性,一個值對象不是事物,而是被常常用來描述、量化或者測量一個實體;

聚合根: 每個聚合的根實體控制着所有聚集在其他的元素,根實體的名稱是聚合根概念上的名稱,每個聚合都會形成保證事務一致性的邊界。意味着在一個單獨的聚合中,在控制被提交給數據庫事務事,它的所有組成部分根據業務規則保持一致。一個聚合也要建立概念上的完整模型,事務一致性。

事務:如何隔離對聚合的修改,以及如何保證業務不變性 (即軟件必須遵守的原則) 在每一次業務操作中都保持一致。無論是通過原子級的數據庫事務還是其他方法來控制需求,聚合的狀態或者它通過事件溯源方法表現出的形式,必須安全和正確地進行轉移和維護;

聚合設計的一條普遍原則:只能在一次事務中修改一個聚合實例並提交,聚合的經驗法則:

在聚合邊界內保護業務規則不變性; 

聚合要設計得小巧;

只能通過標識符引用到其他聚合;

使用最終一致性更新其他聚合;

8. 敏捷項目中管理 DDD

DDD 不僅在軟件設計與開發中,也可以作用在敏捷項目管理中,爲了更好地提升業務與產品價值,在版本研發週期中的幾個節點上加入 DDD 的考慮因素,可以在產品版本迭代週期內不斷提升業務價值。

SWOT 分析法

swot 分析方法是根據項目 / 產品所擁有的資源,進一步分析項目 / 產品的優勢與劣勢以及企業外部環境的機會與威脅,進而選擇適當的戰略。SWOT 分析是一種綜合考慮內外部條件的各種因素,進行系統評價,從而選擇最佳經營戰略的方法。在每個版本的迭代週期中,在做需求分析 / 規劃 / 評審的時間點,利用 SWOT 分析法工具可以讓業務或產品的發展方向更加聚焦,將研發資源投放到真正該投放的方向。

優勢: 領先於對手的業務或項目特徵;

劣勢: 落後於對手的業務或項目特徵;

機會: 可以發揮項目優勢的要素;

威脅: 存在於環境中並可能給業務或項目帶來問題的因素.

任務識別與工作量估算

任務識別和工作量估算是做敏捷迭代很重要的一個步驟,時間代表了人力,代表了金錢,估算的準確度直接決定了版本發佈的時間偏差。這裏在平常的敏捷迭代的評估是基於用戶故事下的,粒度較粗,估算稍不準確,這裏使用了領域事件 / 命令 / 聚合 + 簡單複雜程度等領域驅動設計的概念作爲估算度量指標,使其更加準確,更加符合實際。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/srivB_zGZwiRtAK-5z5TQQ