領域驅動設計簡介

概述

當今的企業應用程序無疑是複雜的,並且依靠某些專門技術(持久性、AJAX、WEB服務器等)來完成其工作。作為開發人員,我們傾向於專註於這些技術細節是可以理解的,但事實就是,不能解決業務需求的系統對任何人都沒有用,無論它的外觀多麼漂亮或其基礎架構的如何牛逼。

領域驅動設計

(DDD)的哲學是關於將我們的注意力放在應用程序的核心,專註於業務領域固有複雜性本身。我們還將核心域(對於業務而言是唯一的)與支持子域(本質上通常是通用的,例如金錢和時間)區分開來,並將我們更多設計工作適當地放在核心上。領域驅動設計由一組模式組成,這些模式用於從領域模型開始構建企業應用程序。在你的軟件職業生涯中,你可能已經遇到了許多這樣的想法,尤其是如果你是一位使用OO語言經驗豐富的開發人員。將他們一起應用將使您能夠構建真正滿足業務需求的系統。
下圖是要展現的模式和模式間關係的總圖。

代碼和模型

藉助DDD,我們正在尋求創建問題域的模型。持久性,用戶界面和消息傳遞的內容可能會在以後出現,這是需要了解的領域。從模型中去除在設計中使用的術語和所賦予的基本職責後,代碼就成了模型的表達式,所以對代碼的一個變更就可能稱為對模型的變更。
這個影響會涉及到項目的實現中。為了緊密捆綁起實現和模型,通常需要支持建模範型的軟件開發工具和語言,例如面向對象編程。面向對象編程非常適合對模型的實現,因為它們基於同一個范型。面向對象編程提供了對象的類和類之間的關聯關係、對象實例、
以及對象實例之間的消息通信。面向對象編程語言讓建立模型對象、對象關係與它們的編程副本之間的直接映射成為可能。過程化語言提供了有限的模型驅動設計的支持。這樣的語言不能提供實現模型關鍵組件所必須的構建能力。

這是DDD模式的第一個:模型驅動設計。這意味着能夠將模型中的概念(理想情況下完全按字面意義)映射到設計/代碼的概念。模型的改變意味着代碼的改變。更改代碼意味着模型已更改。DDD並不要求你使用面向對象對域進行建模-例如,我們可以使用規則引擎來構建模型-但鑒於主要的企業編程語言是基於OO的,因此大多數模型的本質上都是OO。畢竟,OO是基於建模範例的。模型的概念將表示為類的接口,職責將表示類成員。

模型上下文

每當我們討論模型時,它總是在一定範圍內。通常可以從使用該系統的最終用戶集合中推斷出此上下文。因此,我們有一個部署到交易員的前台交易系統,或一個超市收銀員使用的銷售點系統。這些用戶以特定的方式與模型的概念相關,並且模型的術語對這些用戶有意義,但對於上下文之外的任何其他人則不一定。不是試圖保持一個遲早要四分五裂的大模型,我們應該做的是有意識地將大模型分解成數個較小的部分。只要遵守相綁定的契約,整合得好的小模型會越來越有獨立性。每個模型都應該有一個清晰的邊界,模型之間的關係也應該被精確地定義。DDD將此稱為有界上下文(BC)。每個領域模型僅存在於一個BC中,而BC恰好包含一個領域模型。

我必須承認,當我第一次讀到BC時,我看不出要點:如果BC與領域模型同構,為什麼要引入一個新術語?如果只有最終用戶與BC進行交互,那麼也許不需要這個術語。但是不同的系統(BC)也彼此交互,發送文件,傳遞消息,調用API等.如果我們知道有兩個BC相互交互,則我們必須注意在一個概念之間進行傳換。域或其他域。
在模型周圍放置明確的邊界還意味着我們可以開始討論這些BC之間的關係。實際上,DDD標識了BC之間的一整套關係,以便我們可以合理化當我們需要將不同的BC鏈接在一起應該採取的的措施:

  • 已發佈的語言:交互的BC商定一種共同的語言(例如,企業服務總線上的一堆XML模式),通過它們可以彼此交互。
  • 開放的主機服務:BC指定任何其他BC可以使用其他服務協議(例如Restful
    Web服務);
  • 共享內核:兩個BC使用通用的代碼內核(例如,庫)作為通用的通用語言,但其他方式則以自己的特定方式執行;
  • 客戶-供應商:一個BC使用另一個服務的服務,並且是另一個BC的利益相關者(客戶)。因此,它可以影響該BC提供的服務;
  • 順從者:一個BC使用另一個服務,但不是該另一個BC的利益相關者。因此,它使用原樣(符合)該BC提供的協議或API;
  • 防崩潰層:一個BC使用另一方的服務,而不是利益相關者,但其目的是引入一組適配器將一個BC依賴的BC的變化所產生的影響降至最低,即反腐層。
    可以看到,當我們在列表中單擊時,兩個BC之間的合作水平逐漸降低。使用已發佈的語言,我們從BC開始建立它們可以交互的通用標準。他們都不擁有這種語言,而是由他們所居住的企業擁有(甚至可能是行業標準)。使用開放主機,我們仍然做的不錯;BC提供了作為運行時服務的功能,供任何其他BC調用,但隨着服務的發展,它將(可能)保持向後兼容性。

圖 2:有界上下文關係的頻譜

然而,當我們開始循規蹈矩時,我們只是生活在我們身邊;一個 BC 顯然是從屬於另一個的。如果我們必須與以百萬美元購買的總系統集成,那很可能就是我們所希望的情況。如果我們使用反腐敗層, 那麼我們通常會與遺留系統集成,但引入一個額外的一層來儘可能地將我們自己隔離開來。當然,這需要花錢來實施,但它降低了依賴性風險。反腐敗層也比重新實施該遺留系統便宜得多,這充其量會分散我們對核心領域的注意力,最壞的情況是以失敗告終。

DDD 建議我們繪製一個上下文映射來識別我們的 BC 以及我們依賴或依賴的那些,識別這些依賴的性質。圖 3 顯示了我過去 5 年左右一直在研究的系統的上下文映射。
image
圖 3:上下文映射示例

所有這些關於上下文映射和 BC 的討論有時被稱為戰略 DDD,這是有充分理由的。畢竟,當你想到它時,弄清楚 BC 之間的關係都是非常政治化的:我的系統將依賴哪些上游系統,我是否容易與它們集成,我是否對它們有影響力,我是否信任它們?下游也是如此:哪些系統將使用我的服務,我如何將我的功能公開為服務,他們是否對我有影響力?誤解這一點,您的應用程序很容易失敗。

層和六邊形

現在讓我們轉向內部,考慮我們自己的 BC(系統)的架構。從根本上說,DDD 只真正關心領域層,實際上,它並沒有對其他層有很多話要說:表示層、應用程序或基礎設施(或持久層)。但它確實希望它們存在。這就是分層架構模式。
image

圖 4:分層架構

當然,我們多年來一直在構建多層系統,但這並不意味着我們一定很擅長。確實,過去的一些主導技術 – 比如,EJB2,- 對域模型可以作為有意義的層存在的想法產生了積極的危害。所有的業務邏輯似乎都滲入了應用層或(甚至更糟的)表示層,留下了一組貧乏的領域類 [3] 作為數據持有者的空殼。這不是 DDD 的內容。

所以,絕對清楚,應用層不應該有任何域邏輯。相反,應用層負責諸如事務管理和安全性之類的事情。在某些體系結構中,它還可能負責確保從基礎結構/持久層檢索的域對象在與交互之前正確初始化(儘管我更喜歡基礎結構層來代替)。

當表示層在單獨的內存空間中運行時,應用層還充當表示層和域層之間的中介。表示層通常處理域對象或域對象(數據傳輸對象,或 DTO)的可序列化表示,通常每個「視圖」一個。如果這些被修改,則表示層將任何更改發送回應用層,應用層又確定已修改的域對象,從持久層加載它們,然後將更改轉發到這些域對象。

分層架構的一個缺點是它暗示了依賴關係的線性堆疊,從表示層一直到基礎設施層。但是,我們可能希望在表示層和基礎設施層中支持不同的實現。如果(我認為我們是!)我們想要測試我們的應用程序,那就肯定是這種情況:

  • 例如,FitNesse [4] 等工具允許我們從最終用戶的角度驗證系統的行為。但是這些工具一般不經過表示層,而是直接進入下一層,即應用層。所以從某種意義上說,FitNesse 充當了另一種觀看者的角色。
  • 同樣,我們很可能有多個持久性實現。我們的生產實現可能使用 RDBMS 或類似技術,但對於測試和原型設計,我們可能有一個輕量級實現(甚至可能在內存中),因此我們可以模擬持久性。
    我們可能還想區分「內部」和「外部」層之間的交互,其中內部我的意思是兩個層都完全在我們的系統(或 BC)內的交互,而外部交互則跨越 BC。
    因此,與其將我們的應用程序視為一組層,不如將其視為六邊形 [5],如圖 5 所示。我們最終用戶使用的查看器以及 FitNesse 測試使用內部客戶端API(或端口),而來自其他 BC 的調用(例如用於開放主機交互的 RESTful,或用於已發佈語言交互的 ESB 適配器調用)命中外部客戶端端口。對於後端基礎設施層,我們可以看到替代對象存儲實現的持久端口,此外,我們域層中的對象可以通過外部服務端口調用其他 BC。
    image

圖 5:六邊形架構

這種大規模的東西已經足夠了,讓我們多干實事,少扯虛的.