領域驅動設計 ——一種將概念模型化的方式

原文發佈於:http://www.gufeng.tech/  穀風的我的主頁算法

1.引子

      2004年Eric Evans 發表了一本書:《Domain-Driven Design: Tackling Complexity in the Heart of Software》(中文名:《領域驅動設計:軟件核心複雜性應對之道》),在這本書中做者提出了領域驅動設計(DDD)的概念,到如今已經10多年的時間了。數據庫

1.1 面向對象與面嚮對象語言

      面向對象思想已經存在至關長的歷史了(相對於軟件的歷史),我而們使用的語言,不少也都是面向對象的,可是咱們使用面向對象的語言就必定能寫出來面向對象的程序嗎?顯然是不可能的。業務邏輯代碼的堆積、缺少良好設計的系統或模塊亦或是功能,這樣是不能保證代碼的複用性、擴展性的。緩存

1.2  領域模型

      領域驅動設計的出現,就是爲了解決這一問題的。領域驅動設計是以創建正確的領域模型爲核心,以構建清晰的分層架構基礎,從而使面向對象的開發進入到了一個新的階段。架構

      領域驅動設計的前提是有一種可以在領域專家(業務專家)、設計人員、開發人員(爲何會有開發人員,咱們會在後面介紹緣由)三類參與者通用的溝通語言,在三類參與者的不斷交流、溝通中發現領域概念(業務概念),再將概念固化成模型,最後由領域模型驅動設計並實現。ide

      說到這裏,看上去領域模型並無什麼特別的地方,與咱們平常分析的方式沒什麼大的區別,咱們首先來簡單介紹寫領域模型的兩個特色:編碼

      1)業務邏輯集中在領域對象(類)上;架構設計

      2)每一個領域對象是完整和獨立的,並具備本身的屬性和行爲。翻譯

      在接下來的內容中,咱們一塊兒來了解下如何實現領域驅動設計以及領域驅動設計的優勢。設計

2.領域驅動設計

      領域驅動設計涵蓋了領域模型、領域語言、架構設計、實現幾部份內容,下面咱們逐一瞭解一下。對象

2.1 領域模型

      關於什麼是領域模型以及領域模型的特色,在前面內容中咱們有了總體的瞭解,接下來咱們就領域模型自己進行一下簡單的瞭解。

2.1.1 抽象模型

      領域模型是某個邊界內的領域的一個抽象,是客觀世界的模型,首先它使有邊界的,清晰的邊界是領域模型抽象是否完整的一個重要衡量指標。在該領域模型內,咱們只關心領域內的內容。

      領域模型只是實際業務的一種反映,與具體實現技術無關。能夠說領域模型創建的成功與否,直接關係到最終的實現、使用等等。領域模型確保任參與人在任什麼時候間看到的內容都是同樣的,瞭解了模型,就能知道實現的步驟。

      領域模型對於提升軟件的維護性、複用性以及業務可理解性等方面都有很好的幫助。領域模型貫穿整個分析、設計、開發過程,前面提到的三類參與者使用一種你們都能理解的語言進行溝通,確保全部人對模型的理解是一致的,這樣最終開發出來的結果和最初的設計才能最大程度的吻合。

      要創建一個好的領域模型並不簡單,甚至多是一路坎坷,須要領域專家、設計人員、開發人員通力配合、深刻交流、共享信息和知識。最後,領域模型要經過文檔或圖形方式展示出來(推薦使用圖形分解總體結構,配以文字說明)。設計足夠好的領域模型,確定是符合業務需求的,同時也可以快速響應需求變化。

      與領域模型緊密相關的還有另一組概念:聚合、聚合根。下面咱們來簡單瞭解下這兩個概念。

      聚合:經過定義對象間的隸屬關係和邊界來實現領域模型的內聚,

      聚合根:聚合內的某個實體,外部調用聚合時,必須從聚合根開始調用,不能繞過。

      關於聚合的一些特色:

      1)每一個聚合有一個根和邊界;

      2)內部對象可互相引用,可是外部對象訪問聚合時,必須從聚合根開始;

      3)除根外,其它對象在聚合內保持惟一便可;

      4)聚合內部對象能夠保持對其它聚合根的引用;

      5)刪除聚合根時,必須同時刪除其它聚合內對象。

      全部具備獨立含義而且可以被單獨訪問的內容是聚合。

2.1.2 領域通用語言

      設想一下,領域專家滿口的專業術語,設計人員滿口的設計理論,開發人員滿口的開發語言及算法,這樣的團隊怎麼溝通!固然能夠引入“翻譯”,可是“翻譯”的結果以及對結果的理解會形成多大程度上的信息丟失,誰也不肯定。

      基於以上的緣由,迫切須要一種你們都可以表達出來和理解的語言——這就是領域通用語言。領域通用語言是領域驅動設計的基礎和前提。在三類參與者的各類形式溝通中,都要使用領域通用語言,確保本身的信息可以被其餘人完整、快速的理解。

2.1.3 模型到實現

      假設咱們已經擁有了一個很是正確且嚴謹的模型,那麼是否能將這個模型直接轉換成代碼嗎?確定是不行的。因此要求咱們在領域建模和設計時,就要考慮最終的代碼實現,將領域模型與實現緊密關聯起來,這就是爲何要有開發人員參與的緣由。

      這樣的結構(開發人員參與模型創建、結構設計)有利於儘早發現那些不適合在軟件中實現的模型部分並要求修正,這樣也避免了在最後實現時發現問題、修正設計所帶來的巨大時間損失。同時,由於開發人員參與了模型設計,因此在編碼實現時,都會盡力保護模型不被破壞(由於這是你們共同努力的結果),同時當開發人員發現編碼實現有不知足模型或者不完善的地方,也會去完善它,進行代碼重構,這樣可以在很大程度上提高軟件的可靠性,也便於其餘人員在接手時能快速瞭解模型、掌握實現。

2.2 領域驅動設計的架構分層

      咱們先來看一張Eric Evans 在他的《Domain-Driven Design: Tackling Complexity in the Heart of Software》一書中提到的分層圖:

     關於這張圖可能都不陌生,可是每一層在領域驅動設計中的職責是什麼?完成什麼樣的功能?層與層之間的協做關係是什麼樣的?這些問題會在後面一一解釋。

2.2.1 分層

      1)用戶界面

      人機交互部分,沒有特殊內容。

      2)應用

       此應用非彼應用,這裏的應用只是很薄的一層,用於給User Interface提供功能接口,並調用Domain完成功能邏輯,看到這裏應該有了比較明確的認識了,Application不包括任何業務,只是將根據User Interface的須要提供接口,並完成對一個或者多個Domain的調用。

       本層包含了全部軟件系統要完成的任務,經過本層就能瞭解總體功能,User Interface僅僅是一種展示方式。

      3)領域

       這一層是整個系統的核心部分,包括了所有的業務邏輯、業務規則等所有業務相關內容。

      4)基礎設施

       這裏的基礎設施指的是基礎技術組件,包括消息通訊、持久化、緩存等等全部的基礎技術組件。

2.2.2 幾種輔助模式

      1)實體(Entity)

      具備跨越系統的生命週期甚至能超越軟件系統的一系列的延續性和標識符的對象成爲實體。簡單說就是具備絕對惟一標識的對象。好比銀行帳戶的ID是惟一的標識,那麼一個銀行帳戶就是一個實體。實體擁有本身的屬性,管理本身的內部狀態並對外暴露行爲。

      2)值對象(Value Object)

      當咱們關心對象的惟一標識而只關心其屬性值的時候,這個對象就是一個值對象。對於值對象,理論上能夠被輕易的建立以丟掉。若是是可共享值對象,那應該確保它的值是不可變的。“值對象應該保持儘可能的簡單。當其餘當事人須要一個值對象時,能夠簡單地傳遞值,或者建立一個副本。”

      3)服務(Service)

      是否是全部的領域都能映射成對象呢?顯然是不可能的,那麼如何處理不可以映射成對象的領域呢?這時候就須要服務這個東西了。服務一般對應領域的動做,表明領域中得一些重要行爲,而這些行爲又不屬於任何一個實體或者值對象。這些行爲能夠定義爲服務對象。

      服務可能存在於領域層、基礎設施層等,因此要區分服務,不要濫用服務。

      服務對象不包含內部狀態,只有行爲,因此它提供的主要是行爲,做爲操做接口存在。咱們須要注意的是,不須要對每個操做建立服務。咱們一塊兒來一下服務的幾個特徵:

    (1)服務執行的操做涉及一個領域概念,這個領域概念一般不屬於一個實體或者值對象;

    (2)被執行的操做涉及到領域中的其餘的對象;

    (3)操做是無狀態的。

      4)模塊(Module)

      當模型巨大,難以總體討論時,須要把這個大得模型拆成幾個關聯的模塊。

      5)聚合&聚合根

      聚合是針對數據變化能夠考慮成一個單元的一組相關的對象。聚合使用邊界將內部和外部的對象劃分開來。每一個聚合有一個根,是一個實體,而且它是外部能夠訪問的惟一的對象。

      關於聚合與聚合根的內容,可參考2.1.1節。

       6)Factory(工廠)

      引入工廠模式,是由於領域模型自己的複雜性決定的,建立領域對象要遠遠比建立pojo對象複雜得多,尤爲是聚合會更加複雜。此時引入工廠模式,能夠將複雜的實現放在工廠內,外部調用工廠方法便可獲得相應領域對象,同時也隱藏了建立邏輯(主要是提供給Application和Infrastructure使用的)。

       7)Repository(倉儲、資源庫)

      倉儲最初的設計目的是用來管理內存中的對象,但咱們能夠擴展使用,對於須要持久化的領域對象,使用Repository將其持久化到數據庫(或其它持久化存儲)中,再次須要時,能夠經過Repository將對象從數據庫中恢復。一般狀況下,一個聚合對應一個倉儲。

      那麼對於那些不可以經過單一Repository查詢出來的結果(好比界面中須要展示的數據來源於多個Repository的狀況)咱們該怎麼辦呢?固然能夠經過調用多個Repository查詢出結果,但更好的方式是經過CQRS架構來實現,也就是說對於查詢可繞過Domain,直接由Application發起調用另外的架構或者層來實現。

       8)CQRS(Command Query Responsibility Segregation,命令查詢職責分離)

      從字面理解,就是命令和查詢要分離開,那麼什麼事命令呢?非查詢的操做即命令。結合領域驅動設計,咱們能夠理解成命令能夠經過領域驅動設計完成,查詢則可以使用簡單、直接的方式完成(如直接寫SQL)。

      因爲是分離的,因此兩部分能夠採用相同甚至徹底不一樣的架構來實現,由此引伸,是否是數據庫也能夠分開設計呢?固然是能夠的。

3.結束

      本文中咱們粗略的瞭解了領域驅動設計的一些基本概念、原則和一些所謂的“模式”,在實際使用或者叫“領域驅動設計落地”的過程當中,除了一些必須遵照的原則外,咱們能夠根據本身的業務特色、團隊優點進行裁剪。

      沒有任何一種語言是具備絕對優點的,一樣也沒有任何一種設計方法是絕對正確的。找準咱們本身的方向,找出適合咱們業務特色、團隊特色的方法,並對該方法進行落地裁剪,使之更具生命力、可以解決咱們的實際問題。

      最後,不要迷信、迷戀任何一種或幾種方法、模式,全部的方法都是人根據經驗總結出來,方法、模式能夠參考並綜合使用,最終達到擁有本身的方法、本身的模式,這樣才能更好的服務於本身的業務,創造技術體系。

相關文章
相關標籤/搜索