領域驅動設計(DDD)是一種基於模型驅動的軟件設計方式。它以領域爲核心,分析領域中的問題,經過創建一個領域模型來有效的解決領域中的核心的複雜問題。領域驅動設計提出了一套核心構造塊(如聚合、實體、值對象、領域服務、領域工廠、倉儲、領域事件,等),這些構造塊是對面向對象領域建模的一些核心最佳實踐的濃縮。這些構造塊可使得咱們的設計更加標準、有序。html
什麼是領域:領域本質上能夠理解爲就是一個問題域,只要是同一個領域,那問題域就相同。任何一個系統都會屬於某個特定的領域,好比論壇是一個領域,只要你想作一個論壇,那這個論壇的核心業務是肯定的,好比都有用戶發帖、回帖等核心基本功能。好比電商平臺、普通電商系統,這種都屬於網上電商領域,只要是這個領域的系統,那都有商品瀏覽、購物車、下單、減庫存、付款交易等核心環節。因此,同一個領域的系統都具備相同的核心業務,由於他們要解決的問題的本質是相似的git
什麼是驅動:領域驅動領域模型設計,領域模型驅動代碼實現。這個就和咱們傳統的數據庫驅動開發的思路造成對比了。DDD中,咱們老是以領域爲邊界,分析領域中的核心問題(核心關注點),而後設計對應的領域模型,再經過領域模型驅動代碼實現。而像數據庫設計、持久化技術等這些都不是DDD的核心,而是外圍的東西。github
什麼是設計:DDD中的設計主要指領域模型的設計。DDD是一種基於模型驅動開發的軟件開發思想,強調領域模型是整個系統的核心,領域模型也是整個系統的核心價值所在。每個領域,都有一個對應的領域模型,領域模型可以很好的幫咱們解決複雜的業務問題。數據庫
理解領域:假設你如今打算作一個電商平臺,可是你對這個領域沒什麼瞭解,那你必定得先去了解下該領域內主流的電商平臺,好比淘寶、天貓、京東、亞馬遜等。這個瞭解的過程就是你沉澱領域知識的過程。雖然咱們明確了要作一個什麼樣的系統,該系統主要解決什麼問題,可是就這樣咱們還沒法開始進行實際的需求分析和模型設計,咱們還必須將咱們的問題進行拆分,需求進行細化。要知道一個系統到底該作成什麼樣子,到底哪些是核心業務關注點,只能靠沉澱領域內的各類知識,別無他法。。api
拆分領域:有時一個領域每每太複雜,涉及到的領域概念、業務規則、交互流程太多,致使咱們沒辦法直接針對這個大的領域進行領域建模。因此,咱們須要將領域進行拆分,本質上就是把大問題拆分爲小問題,而後各個擊破的思路。而後既然把一個大的領域劃分爲了多個小的領域(子域),那最關鍵的就是要理清每一個子域的邊界;而後要搞清楚哪些子域是核心子域,哪些是非核心子域,哪些是公共支撐子域;而後,還要思考子域之間的聯繫是什麼。拿經典的電商系統來分析,一般一個電商系統都會包含好幾個大塊,好比:跨域
上面這些中心看起來很天然,由於你們對電子商務的這個領域都已經很是熟悉了,因此都沒什麼疑問,好像很天然的樣子。之因此咱們以爲子域劃分很簡單,是由於咱們對整個大領域很是瞭解了。若是咱們遇到一個冷門的領域,就沒辦法這麼容易的去劃分子域了。這就須要咱們先去努力理解領域內的知識。子域劃分沒有什麼技巧,這個工做沒有任何訣竅可使用。當咱們對整個領域有必定的熟悉了,瞭解了領域內的相關業務的本質和關係,咱們就天然而然的能劃分出合理的子域了。不過並非全部的系統都須要劃分子域的,有些系統只是解決一個小問題,這個問題不復雜,可能只有一兩個核心概念。因此,這種系統徹底不須要再劃分子域。緩存
細化子域:了領域裏的知識,也對領域進行了子域劃分。但這樣還不夠,憑這些咱們還沒法進行後續的領域模型設。還必須再進一步細化每一個子域,進一步明確每一個子域的核心關注點,即需求細化,們須要細化的方面有如下幾點:安全
從上面這4個方面,咱們從領域概念、業務規則、交互場景、業務流程等維度梳理了咱們到底要什麼,整理了整個系統應該具有的功能。這個工做是一個很是具備創造性和有難度的工做。咱們一方面會主觀的定義咱們想要什麼;另外一方面,咱們還會思考咱們要的東西的合理性。架構
關於領域概念的梳理,我以爲能夠採用四色原型分析法,這個分析法經過系統的方法,將概念劃分爲不一樣的種類,爲不一樣種類的概念標註不一樣的顏色。而後將這些概念有機的組合起來,從而讓咱們能夠清晰的分析出概念和概念之間的關係。有興趣的同窗能夠在網上搜索下四色原型。框架
領域:用戶會把軟件程序應用於某個主體區域,這個區域就是軟件的領域。簡單來講,就認爲是公司的某塊業務好了。若是領域比較大,能夠將其拆分爲多個子域,子域包含核心域和支撐子域,核心域顧名思義,是最重要的子域,咱們應該把關注點集中在它上面;其他的子域都是支撐子域。支撐子域裏有一類特殊的用於解決通用問題的子域,稱爲通用子域,例如用戶和權限等。不過這些都是相對而言的,對於消費方來講,他的支撐子域有可能就是你的核心域。個別子域可能會有交集,稱爲共享內核,目的是減小重複,可是仍保持兩個獨立的上下文。因爲不一樣子域的開發團隊可能會同時修改共享內核,因此須要當心並注意溝通。
限界上下文: 通用語言裏,同一個名詞在不一樣的場景裏不必定有相同的意思。好比用戶,在推薦好友(可能關注年齡、性別、地域)或是瀏覽商品(可能關注喜愛、歷史購買記錄)的時候有着不一樣的含義。所謂的不一樣的場景,其實就是不一樣的限界上下文。不一樣的限界上下文之間,經過上下文映射圖來進行交互。上下文映射圖其實就是一個簡單的框圖,表示限界上下文之間的的映射關係,這張圖就是一個簡單的例子 U表示上游被依賴方,D表示下游依賴方。因爲上下游的限界上下文模型不一樣,實現時,能夠用RPC、Restful、消息機制等集成方式。下游須要防腐層來將上游的返回內容翻譯爲下游的領域模型。
關於領域、領域模型、限界上下文的關係:
關於領域、子領域、核心子域、通用子域,以及共享內核的理解:
實體:實體是有標識的,兩個擁有相同屬性的實體不是相等的,除非它們的標識相等;而不一樣實體的標識不能相等。實體有生命週期,實體從被建立後可能會被持久化到數據庫,而後某個時候又會被取出來
例如:某人下了兩個相同的訂單,裏面都購買了相同的商品。這兩個訂單就是有標識(訂單號)的兩個實體,雖然內容相同,但它們是兩個不一樣的實體。實體做爲領域模型的主體,須要擁有本身的方法,方法名來自於通用語言。經過這些方法來保證本身始終是一致的狀態,而非被調用者set來set去。例如:people.runTo(x, y)
,而非people.setX(x);people.setY(y);
值對象:值對象只用於描述或度量一個東西。值對象沒有任何標識,只要兩個值對象的屬性相等,那麼它們就是相等的。值對象是不可變的,若是要改變值對象的內容,那就從新建立一個值對象。值對象沒有生命週期,由於它只是值而已。例如:金額(含數值和貨幣單位),顏色(含rgb值)等
聚合及聚合根:聚合表示一組領域對象(包括實體和值對象),用來表述一個完整的領域概念,而每一個聚合都有一個根實體,這個根實體又叫作聚合根。舉個簡單的例子,一個電腦包含硬盤、CPU、內存條等,這一個組合就是一個聚合,而電腦就是這個組合的聚合根。。聚合根是聚合所表述的領域概念的主體,外部對象須要訪問聚合內的實體時,只能經過聚合根進行訪問,而不能直接訪問。關於聚合的劃分學問仍是挺大的,須要在實踐中慢慢積累。同一個實體,在不一樣的聚合中,它多是聚合根,也可能不是,須要根據實際的業務決定。
聚合根,實體,值對象區別和聯繫:
聚合有如下一些特色:
如何識別聚合?
這個須要從業務的角度深刻分析哪些對象它們的關係是內聚的,即咱們會把他們當作是一個總體來考慮的;而後這些對象咱們就能夠把它們放在一個聚合內。所謂關係是內聚的,是指這些對象之間必須保持一個固定規則,固定規則是指在數據變化時必須保持不變的一致性規則。當咱們在修改一個聚合時,咱們必須在事務級別確保整個聚合內的全部對象知足這個固定規則。做爲一條建議,聚合儘可能不要太大,不然即使可以作到在事務級別保持聚合的業務規則完整性,也可能會帶來必定的性能問題。有分析報告顯示,一般在大部分領域模型中,有70%的聚合一般只有一個實體,即聚合根,該實體內部沒有包含其餘實體,只包含一些值對象;另外30%的聚合中,基本上也只包含兩到三個實體。這意味着大部分的聚合都只是一個實體,該實體同時也是聚合根。
聚合設計的原則:
如何識別聚合根?
若是一個聚合只有一個實體,那麼這個實體就是聚合根;若是有多個實體,那麼咱們能夠思考聚合內哪一個對象有獨立存在的意義而且能夠和外部直接進行交互。
聚合根、實體、值對象對象之間如何創建關聯?
聚合根到聚合根:經過ID關聯;聚合根到其內部的實體,直接對象引用;聚合根到值對象,直接對象引用;
實體對其餘對象的引用規則:1)能引用其所屬聚合內的聚合根、實體、值對象;2)能引用外部聚合根,但推薦以ID的方式關聯,另外也能夠關聯某個外部聚合內的實體,但必須是ID關聯,不然就出現同一個實體的引用被兩個聚合根持有,這是不容許的,一個實體的引用只能被其所屬的聚合根持有;
值對象對其餘對象的引用規則:只需確保值對象是隻讀的便可,推薦值對象的全部屬性都儘可能是值對象;
例子分析:帖子與回覆的模型
不 變性分析:帖子和回覆之間有不變性規則嗎?彷佛咱們只知道一點是確定的,那就是帖子和回覆之間的關係,1:N的關係;除了這個以外,咱們看不到任何其餘的 不變性規則。那麼這個1:N的對象關係是一種不變性規則嗎?不是!首先,一個帖子能夠沒有任何回覆,帖子也不對它的回覆有任何規則約束,它甚至都不知道自 己有多少個回覆;再次,發表了一個回覆和帖子也沒有任何關係;其次,發表回覆對帖子沒有任何改變;從業務場景的角度去分析,咱們有發表帖子的場景,有發表 回覆的場景。當在發表回覆的時候,是以回覆爲主體的,帖子只是這個回覆裏所包含的必要信息,用於說明這個回覆是對哪一個帖子的回覆。這些都說明帖子和回覆之 間找不出任何不變性約束的規則;由於帖子和回覆都有各自獨立的業務場景的須要,因此能夠很容易理解它們都是獨立的聚合根;那也很容易知道該如何創建他們之 間的關聯了,可是咱們要儘可能減小關聯,因此只保留回覆對帖子的關聯便可;帖子沒有任何須要去保存一個回覆的ID的列表;那麼你可能會說,當我刪除一個帖子 後,回覆應該是沒有存在的意義的呀?不對,不是沒有存在的意義,而是刪除了帖子後致使了回覆對帖子的關聯信息的缺失,致使數據不一致。這是由於帖子和回覆 之間有一種必然的聯繫(1:N),回覆必定會有一個對應的帖子;可是回覆有其本身的生命週期,不該該隨着帖子的刪除而級聯刪除。這種狀況下,若是你刪除了 帖子,就致使回覆也成爲了一條無效的數據;因此,咱們絕對不容許刪除任何聚合根,由於一旦你刪除了聚合根,那就意味着與該聚合根相關的其餘任何聚合根都會 有外鍵引用缺失的問題,會致使整個領域模型數據的不一致;因此,永遠都不要刪除聚合根;
領域服務:領域模型主張富領域模式,也就是說把領域邏輯儘可能寫在領域實體裏面,也就是常說的「充血模式」,而對於業務邏輯,最好是以服務的形式提供。至於領域邏輯和業務邏輯的界定,這個要根據實際狀況來定。若是通用語言裏面出現了名詞,那通常就是實體或值對象;若是裏面出現了動詞,那一般就意味着領域服務。例如:支付,這是一個比較明顯的業務操做。另外,若是有什麼操做會讓實體變得臃腫,也可使用領域服務來解決。可是不能把全部的東西都堆到領域服務裏,過分使用領域服務會致使貧血對象的產生。良好的領域服務具備如下三個特徵:
工廠:工廠是生命週期的開始階段,它能夠用來建立複雜的對象或是一整個聚合。複雜對象的建立是領域層的職責,但它並不屬於被建立的對象自身的職責。實體和值對象的工廠不太同樣,由於值對象是不可變的,因此須要工廠一次性建立一個完整的值對象出來。而實體工廠則能夠選擇建立以後再補充一些細節。工廠的做用是將建立對象的細節隱藏起來。客戶傳遞給工廠一些簡單的參數,而後工廠能夠在內部建立出一個複雜的領域對象而後返回給客戶。領域模型中其餘元素都不適合作這個事情,因此須要引入這個新的模式,工廠。工廠在建立一個複雜的領域對象時,一般會知道該知足什麼業務規則(它知道先怎樣實例化一個對象,而後在對這個對象作哪些初始化操做,這些知識就是建立對象的細節),若是傳遞進來的參數符合建立對象的業務規則,則能夠順利建立相應的對象;可是若是因爲參數無效等緣由不能建立出指望的對象時,應該拋出一個異常,以確保不會建立出一個錯誤的對象。固然咱們也並不老是須要經過工廠來建立對象,事實上大部分狀況下領域對象的建立都不會太複雜,因此咱們只須要簡單的使用構造函數建立對象就能夠了。隱藏建立對象的好處是顯而易見的,這樣能夠不會讓領域層的業務邏輯泄露到應用層,同時也減輕了應用層的負擔,它只須要簡單的調用領域工廠建立出指望的對象便可
資源庫:資源庫是生命週期的結束,它封裝了基礎設施以提供查詢和持久化聚合的操做。這樣可以讓咱們始終聚焦於模型,而把對象的存儲和訪問都委託給資源庫來完成。以訂單和訂單明細的聚合爲例,由於必定是經過訂單這個聚合根來獲取訂單明細,因此能夠有訂單的資源庫,可是不能有訂單明細的資源庫。也就是說,只有聚合才擁有資源庫。須要注意的是,資源庫並非數據庫的封裝,而是領域層與基礎設施之間的橋樑。DDD關心的是領域內的模型,而並不是是數據庫的操做。理想的資源庫對客戶(而非開發者)隱藏了內部的工做細節,委託基礎設施層來幹那些髒活,到關係型數據庫、NOSQL、甚至內存裏讀取和存儲數據。
領域驅動設計的經典分層架構:
用戶界面/展示層:負責向用戶展示信息以及解釋用戶命令。更細的方面來說就是:
應用層:很薄的一層,定義軟件要完成的全部任務。對外爲展示層提供各類應用功能(包括查詢或命令),對內調用領域層(領域對象或領域服務)完成各類業務邏輯,應用層不包含業務邏輯。
領域層:負責表達業務概念,業務狀態信息以及業務規則,領域模型處於這一層,是業務軟件的核心
基礎設施層:本層爲其餘層提供通用的技術能力;提供了層間的通訊;爲領域層實現持久化機制;總之,基礎設施層能夠經過架構和框架來支持其餘層的技術需求;
業務初期,咱們的功能大都很是簡單,普通的CRUD就能知足,此時系統是清晰的,使用三層結構開發方式,對象只是數據的載體,沒有行爲。以數據爲中心,以數據庫ER設計做驅動。三層架構在這種開發模式下,能夠理解爲是對數據移動、處理和實現的過程。
但在業務邏輯複雜了,業務邏輯、狀態會散落到在大量方法中,本來的代碼意圖會漸漸不明確,而DDD將數據和行爲封裝在一塊兒,並與現實世界中的業務對象相映射。各種具有明確的職責劃分,將領域邏輯分散到領域對象中,解決了代碼高耦合低聚合的問題
DDD試圖解決的是軟件的複雜性問題,若是軟件比較複雜,或者是預期會很複雜,或者是你不知道,那麼均可以開始考慮DDD。不然,因爲維繫領域模型須要實現大量的封裝和隔離,DDD會帶來較大的成本。可是,DDD並非一個笨重的開發過程,它可以和敏捷開發很好地結合起來,另外,DDD也傾向於「測試先行,逐步改進」。
咱們建立微服務時,須要建立一個高內聚、低耦合的微服務。而DDD中的限界上下文則完美匹配微服務要求,能夠將該限界上下文理解爲一個微服務進程。他們的具體關係以下圖:
需求:舉辦一個比賽,有兩個隊參加,比賽在某個時間開始,只能開始一次,比賽結束後,統計積分
做爲用戶,但願看到:參加比賽的隊伍名稱,比賽開始時間,比賽結束時間,比賽結束後的分數。
這是貧血失血模型,對象只有屬性,沒有本身的行爲方法,有的只有setter/getter方法而已。
本文只是粗淺介紹了下DDD,demo來源於http://www.jdon.com/44815
文章大部分摘自湯雪華的連載博客共23篇,涵蓋了DDD的方方面面,如想深刻研究,請參考:http://www.cnblogs.com/netfocus/category/361987.html
其他參考以下:https://tech.meituan.com/DDD%20in%20practice.html