【系統架構】領域驅動DDD(Domain-Driven Design)- 軟件核心複雜性應對之道

前言php

     領域驅動設計是一個開放的設計方法體系,目的是對軟件所涉及到的領域進行建模,以應對系統規模過大時引發的軟件複雜性的問題,本文將介紹領域驅動的相關概念。前端

 

一.軟件複雜度的根源mysql

1.業務複雜度(軟件的規模)

  軟件的需求決定了系統的規模。當需求呈現線性增加的趨勢時,爲了實現這些功能,軟件規模也會以近似的速度增加。因爲需求不可能作到徹底獨立,致使出現相互影響相互依賴的關係,修改一處就會牽一髮而動全身。就好似城市的一條道路由於施工須要臨時關閉,此路不通,通行的車輛只能改道繞行,這又致使了其餘本來已經飽和的道路,由於涌入更多車輛,超出道路的負載從而變得更加擁堵,這種擁堵現象又會順勢向這些道路的其餘分叉道路蔓延,造成一種輻射效應的擁堵現象。程序員

 2.技術複雜度(軟件的結構)web

  結構之因此變得複雜,在多數狀況下仍是由於系統的質量屬性決定的。例如,咱們須要知足高性能、高併發的需求,就須要考慮在系統中引入緩存、並行處理、CDN、異步消息以及支持分區的可伸縮結構。假若咱們須要支持對海量數據的高效分析,就得考慮這些海量數據該如何分佈存儲,並如何有效地利用各個節點的內存與 CPU 資源執行運算。sql

從系統結構的視角看,單體架構必定比微服務架構更簡單。thinkphp

3.人爲的因素數據庫

  不存在一致性、不存在風格、也沒有統一的概念可以將不一樣的部分組織在一塊兒。缺乏必要的註釋,沒有字段說明和數據字典, 意大利麪條式的代碼,缺少統一編碼風格,致使錯綜複雜和不可維護的程序。設計模式

 4.需求引發的軟件複雜度api

  需求分爲業務需求與質量屬性需求,於是需求引發的複雜度能夠分爲兩個方面:技術複雜度業務複雜度

  技術複雜度來自需求的質量屬性,諸如安全、高性能、高併發、高可用性等需求,爲軟件設計帶來了極大的挑戰,讓人痛苦的是這些因素彼此之間可能又互相矛盾、互相影響。例如,系統安全性要求對訪問進行控制,不管是增長防火牆,仍是對傳遞的消息進行加密,又或者對訪問請求進行認證和受權等,都須要爲整個系統架構添加額外的間接層,這不可避免會對訪問的低延遲產生影響,拖慢了系統的總體性能。又例如,爲了知足系統的高併發訪問,咱們須要對應用服務進行物理分解,經過橫向增長更多的機器來分散訪問負載;同時,還能夠將一個同步的訪問請求拆分爲多級步驟的異步請求,再經過引入消息中間件對這些請求進行整合和分散處理。這種分離一方面增長了系統架構的複雜性,另外一方面也由於引入了更多的資源,使得系統的高可用面臨挑戰,並增長了維護數據一致性的難度。

  業務複雜度對應了客戶的業務需求,於是這種複雜度每每會隨着需求規模的增大而增長。因爲需求不可能作到徹底獨立,一旦規模擴大到必定程度,不只產生了功能數量的增長,還會由於功能互相之間的依賴與影響使得這種複雜度產生疊加,進而影響到整個系統的質量屬性,好比系統的可維護性與可擴展性。在考慮系統的業務需求時,還會由於溝通不順暢、客戶需求不清晰等多種局外因素而帶來的需求變動和修改。若是不能很好地控制這種變動,則可能會由於屢次修改而致使業務邏輯糾纏不清,系統可能開始慢慢腐爛而變得不可維護,最終造成一種如 Brian Foote 和 Joseph Yoder 所說的「大泥球」系統。

  以電商系統的促銷規則爲例。針對不一樣類型的顧客與產品,商家會提供不一樣的促銷力度;促銷的形式多種多樣,包括贈送積分、紅包、優惠券、禮品;促銷的週期須要支持定製,既能夠是特定的日期,如雙十一促銷,也能夠是節假日的固定促銷模式。若是咱們在設計時沒有充分考慮促銷規則的複雜度,並處理好促銷規則與商品、顧客、賣家與支付乃至於物流、倉儲之間的關係,開發過程則會變得踉踉蹌蹌、舉步維艱。

技術複雜度與業務複雜度並不是徹底獨立,兩者混合在一塊兒產生的化合做用更讓系統的複雜度變得不可預期,難以掌控

 

 

 二.控制軟件複雜度的原則

       1.保持結構的清晰與一致.

  2.分而治之、控制規模

  3.擁抱變化(變化對軟件系統帶來的影響能夠說是無解)

    除了在開發過程當中,咱們應儘量作到敏捷與快速迭代,以此來抵消變化帶來的影響;在架構設計層面,咱們還能夠分析哪些架構質量屬性與變化有關,這些質量屬性包括:

  • 可進化性(Evolvability):有句話這麼說的好的架構是進化來的,不是設計出來的。
  • 可擴展性(Extensibility)

 

 三.傳統開發設計的模式

  1.傳統的設計分層結構:

                            

 

  Model層

    包含數據對象,是service操縱的對象,model層中的對象被建模成業務對象,這些對象是對DB中表的映射,一個表對應一個model,表中的字段就對應成model對象的屬性,而後在加上get() / set()方法,可是並無包含這個對象的業務上的行爲,不知道它會作什麼,這樣就是一個很典型的貧血模式

  Dao層(數據訪問層,DTO對象:數據傳輸對象)

    Dao層主要是和數據庫打交道,作數據持久化的工做,也包括一些數據過濾,爲model層服務的,好比php裏面的mysqli和pdo。

  Service層

    公開一些接口給外部服務調用的,放置全部的服務類,它會調用Dao層去處理數據(獲取設置數據)。

  展示層(UI):

    前端的一些業務邏輯展示,使用各類UI框架,如Layzui,smarty,twing等模版js框架去渲染頁面。

     Controller層:

            層負責具體的業務模塊流程的控制,在這層調用能夠調用service層的接口來控制業務流程,也能夠訪問model層獲取數據。

  如今主流的php框架都是按照這樣的分層去設計和開發,thinkphp,laveral,ci。

 

   2.傳統的設計方式和開發框架及其問題

    1.因爲設計或者編碼的不當,核心業務邏輯容易散佈在各處

           因爲業務邏輯混散在各處,帶來的麻煩維護很困難,有可能在model層 service層作一些業務方面的東西,或者在action裏面寫一些業務相關的代碼,好比有些業務寫在展示層,那麼就要去改展示層裏面的代碼,寫在service層就要去改service的邏輯。當想要了解這裏業務邏輯的時候得看下上下文,翻閱不少類,須要追代碼各個文件去看才能大概的明白。

         2.過分耦合

           業務初期,咱們的功能大都很是簡單,普通的CRUD就能知足,此時系統是清晰的。隨着迭代的不斷演化,業務邏輯變得愈來愈複雜,咱們的系統也愈來愈冗雜。模塊彼此關聯,誰都很難說清模塊的具體功能意圖是啥。修改一個功能時,每每光回溯該功能須要的修改點就須要很長時間,更別提修改帶來的不可預知的影響面。

       下圖是一個常見的系統耦合病例。

   

        訂單服務接口中提供了查詢、建立訂單相關的接口,也提供了訂單評價、支付、保險的接口。同時咱們的表也是一個訂單大表,包含了很是多字段。在咱們維護代碼時,牽一髮而動全身,極可能只是想改下評價相關的功能,卻影響到了創單核心路徑。雖然咱們能夠經過測試保證功能完備性,但當咱們在訂單領域有大量需求同時並行開發時,改動重疊、惡性循環、疲於奔命修改各類問題。

       上述問題,歸根到底在於系統架構不清晰,劃分出來的模塊內聚度低、高耦合。

       問題:既然架構不清晰重構是否能夠解決這些問題?

                  能夠,可是並不能解決根本問題。通常重構都是經過在單獨的類及方法級別上作一系列小步重構來完成,封裝一些經常使用的操做,提煉出通用的代碼片斷。因此咱們能夠很容易重構出一個獨立的類來放某些通用的邏輯,可是你會發現你很難給它一個業務上的含義,只能給予一個技術維度描繪的含義。這會帶來什麼問題呢?新來的同事並不老是知道對通用邏輯的改動或獲取來自該類。顯然,制定項目規範並非好的方法,隨着業務的變化在不久的未來重構還會一直繼續下去。

   3.貧血模式,基於數據表的設計,數據驅動(Data-Driven),全部的開發都是圍繞數據表來進行的。

 

 

 四.貧血模型

貧血領域對象(Anemic Domain Object)是指僅用做數據的載體,而沒有行爲和動做的領域對象,只有get和set方法,或者包含少許的CRUD方法,全部的業務邏輯都不包含在內。

       貧血模型實際上是違背了oop模式,對象有什麼反應的就是屬性,對象會作什麼,反應在類裏面對應的就是方法,傳統開發中很明顯看不到能作什麼,不會有業務行爲,get / set方式只是外部獲取屬性值的載體而已。

       數據庫有什麼,模型纔會反應什麼,這樣迫使咱們先去設計數據庫,這就是傳統的開發思路,這就是基於數據庫表的設計,致使的問題就是表中會有一些重複的多餘的字段,在設計的時候也會依賴於的特定的數據庫(由於是先去設計數據庫),因此好的系統是不該該依賴於特定的數據庫,更不該該依賴於特定數據庫的存儲過程,存儲函數,觸發器等,作到數據庫無關性,當作到數據遷移等時候很方便很平滑。因此應該先去設計業務對象,就是對對象建模而後再去反向設計數據庫。

 缺點:

  1.溝通困難,開發人員和業務人員交流語言不統一,開發用技術語言和業務溝通,而業務人員不瞭解技術,就是交流障礙。

  2.業務邏輯不能重用,由於業務散在各個層,業務各個方法互相調用,你不知道調用哪一個方法,業務後期的查找維護也比較困難,業務邏輯也會和應用邏輯的混合,業務邏輯反應的是需求,應用邏輯是和系統相關,好比業務要查詢什麼數據,查詢的過程是業務,而如何展示在ui上是應用。

  3.傳統的開發是和特色的技術耦合的,若是想把業務脫離出來,去更換某種技術就很困難。

  4.適應將來的變化就頗有問題。

 優勢:

      這種貧血模型的傳統開發也是當前咱們最經常使用的方法,開發速度快,開發人員容易掌握。

 

// 貧血模型下的實現
public class User{
    private $id;
    private $name;
    ...
    // 省略get/set方法

}

public class UserManagerService{
    public function save(User user){
        
        // 持久化操做....
    
    }
}

// 保存用戶的操做多是這樣
$userManagerService::getInstance()->save(user);

     個人業務邏輯都是寫在userManagerService中的,User只是個數據載體,沒有任何行爲。簡單的業務系統採用這種貧血模型和過程化設計是沒有問題的,但在業務邏輯複雜了,業務邏輯、狀態會散落到在大量方法中,本來的代碼意圖會漸漸不明確,咱們將這種狀況稱爲貧血模型或者是由貧血症引發的失憶症。

// 充血模型下的實現
public class User{
    private $id;
    private $name;
    ...
    // 省略get/set方法

//用戶信息保存 public function save(User user){ // 持久化操做.... } } // 保存用戶的操做多是這樣 $User::getInstance()->save(user);

      更好的是採用領域模型的開發方式,將數據和行爲封裝在一塊兒,並與現實世界中的業務對象相映射。各種具有明確的職責劃分,將領域邏輯分散到領域對象中。繼續舉咱們上述的例子,用戶保存信息就應當放到User類中。

 

五.爲何選擇DDD

       既然上述傳統開發和貧血模式有這些問題,那麼有沒有什麼方法來解決這些問題?

       解決思路

    1.思想理論:基於領域驅動設計(DDD能應對複雜性與快速變化)。

    2.技術實現:

          1).從技術維度實現分層:遵循分層架構模式,可以在每層關注本身的事情,好比領域層關注業務邏輯的事情,倉儲關注持久化數據的事情,應用服務層關注用例的事情,接口層關注暴露給前端的事情。經過‘開發主機服務’(REST服務是其中的一種)、消息模式、事件驅動 等架構風格實現.

          2).業務維度:經過將大系統劃分層多個上下文,關注點放在domain上,將業務領域限定在同一上下文中,可讓不一樣團隊和不一樣人只關注當前上下文的開發。下降上下文之間的依賴,業務核心與特定的技術隔離開來,不依賴任何一個技術框架。

          

 

 六.理解DDD概念

  DDD的全稱爲Domain-driven Design,即領域驅動設計。是一種思惟方式和概念,能夠應用在處理複雜業務的軟件項目中,加快項目的交付速度。下面我從領域、問題域、領域模型、設計、驅動這幾個詞語的含義和聯繫的角度去闡述DDD是如何融入到咱們平時的軟件開發初期階段的。要理解什麼是領域驅動設計,首先要理解什麼是領域,什麼是設計,還有驅動是什麼意思,什麼驅動什麼。

       1.什麼是領域

  領域表明的是某個範圍,假如如今要作一個系統,這個系統有一些要實現的功能。那麼這個系統確定屬於某個特定的領域,好比論壇是一個領域,只要你想作一個論壇,那這個論壇的核心業務是肯定的,好比都有用戶發帖、回帖等核心基本功能。好比電商系統,這種都屬於網上電商領域,只要是這個領域的系統,那都有商品瀏覽、購物車、下單、減庫存、付款交易,物流等核心環節,或者一個支付平臺等。因此,同一個領域的系統都具備相同的核心業務,由於他們要解決的問題的本質是相似的。

   所以,咱們能夠推斷出,一個領域本質上能夠理解爲就是一個問題域,只要是同一個領域,那問題域就相同。因此,只要咱們肯定了系統所屬的領域,那這個系統的核心業務,即要解決的關鍵問題、問題的範圍邊界就基本肯定了。一般咱們說,要成爲一個領域的專家,必需要在這個領域深刻研究不少年才行。由於只有你研究了不少年,你纔會遇到很是多的該領域的問題,同時你解決這個領域中的問題的經驗也很是豐富。領域專家的重要性對於設計良好的領域驅動設計是很重要的。在開發前理解領域知識是基礎,也很重要,由於一個系統要作成什麼樣,裏面包含哪些業務規則,核心業務關注點是什麼,就要求對這個領域內的一切業務相關的知識都很是瞭解,若是開發一個陌生的系統,好比航空管理軟件,讓一個只會開發電商的程序員去寫,是徹底不知道從哪開始下手。

      在平常開發中,咱們一般會將一個大型的軟件系統拆分紅若干個子系統。這種劃分有多是基於架構方面的考慮,也有多是基於基礎設施的。可是在DDD中,咱們對系統的劃分是基於領域的,也便是基於業務的,領域的劃分,一個大的領域能夠劃分紅多個小的領域,也就是子域。

      領域及子域的劃分是如何進行的,如何去限定的,這個就得須要限界上下文上下文映射圖,下面待會說。

    

  2.什麼是設計

   DDD中的設計主要指領域模型的設計。爲何是領域模型的設計而不是架構設計或其餘的什麼設計呢?由於DDD是一種基於模型驅動開發的軟件開發思想,強調領域模型是整個系統的核心,領域模型也是整個系統的核心價值所在。每個領域,都有一個對應的領域模型,領域模型可以很好的幫咱們解決複雜的業務問題。

從領域和代碼實現的角度來理解,領域模型綁定了領域和代碼實現,確保了最終的代碼實現就必定是解決了領域中的核心問題的。由於:1)領域驅動領域模型設計;2)領域模型驅動代碼實現。咱們只要保證領域模型的設計是正確的,就能肯定領域模型能夠解決領域中的核心問題;同理,咱們只要保證代碼實現是嚴格按照領域模型的意圖來落地的,那就能保證最後出來的代碼可以解決領域的核心問題的。這個思路,和傳統的分析、設計、編碼這幾個階段被割裂(而且每一個階段的產物也不一樣)的軟件開發方法學造成鮮明的對比。

 

   3.什麼是驅動

  上面其實已經提到了,就是:1)領域驅動領域模型設計;2)領域模型驅動代碼實現。這個就和咱們傳統的數據庫驅動開發的思路造成對比了。DDD中,咱們老是以領域爲邊界,分析領域中的核心問題(核心關注點),而後設計對應的領域模型,再經過領域模型驅動代碼實現。而像數據庫設計、持久化技術等這些都不是DDD的核心,而是外圍的東西。

  領域驅動設計第一步最關鍵就是應該儘可能先把領域模型想清楚,而後再開始動手編碼,這樣的系統後期纔會很好維護。可是,不少項目(尤爲是互聯網項目,爲了趕工)都是一開始模型沒想清楚,一上來就開始建表寫代碼,代碼寫的很是冗餘,徹底是過程是的思考方式,最後致使系統很是難以維護。並且更糟糕的是,前期的領域模型設計的很差,不夠抽象,若是你的系統會長期須要維護和適應業務變化,那後面你必定會遇到各類問題維護上的困難,好比數據結構設計不合理,代碼處處冗餘,改BUG處處引入新的BUG,新人對這種代碼上手困難等。而那時若是你再想重構模型,那要付出的代價會比一開始從新開發還要大,由於你還要考慮兼容歷史的數據,數據遷移,如何平滑發佈等各類頭疼的問題。

  因此經過創建領域模型來解決領域中的核心問題,這就是模型驅動的思想。

 

 

七.DDD核心組件

 

  1.通用語言

   造成統一的領域術語,尤爲是基於模型的語言概念,是溝通可以達成一致的前提。尤爲是開發人員與領域專家之間,他們掌握的知識存在巨大的差別,尤爲是專業性很強的業務,好比金融系統,醫療系統,它們的術語都很專業。而善於技術的開發人員關注於數據庫、通訊機制、集成方式與架構體系,而精通業務的領域專家對這些卻一竅不通,但他們在講解業務知識時,很是天然,這些對於開發人員來講,卻成了天書,這種交流就好似使用兩種不一樣語言的外國人在交談。

  使用統一語言能夠幫助咱們將參與討論的客戶、領域專家與開發團隊拉到同一個維度空間進行討論,若沒有達成這種一致性,那就是雞同鴨講,毫無溝通效率,相反還可能形成誤解。所以,在溝通需求時,團隊中的每一個人都應使用統一語言進行交流

  一旦肯定了統一語言,不管是與領域專家的討論,仍是最終的實現代碼,均可以經過使用相同的術語,清晰準確地定義領域知識。重要的是,當咱們創建了符合整個團隊皆認同的一套統一語言後,就能夠在此基礎上尋找正確的領域概念,爲創建領域模型提供重要參考。

 

  2.界限上下文(Bounded Contexts):

  界限上下文是DDD中的一個核心模式,這種模式是幫助咱們剝離開復雜的應用程序,將他們隔離開,造成不一樣的上下文邊界。不一樣的模塊有着不一樣的上下文,且能獨立調用,而各自的模塊能夠有本身的持久化的,互不干擾。

在大型的應用程序中,不一樣的人對不一樣的的東西可能取相同的名字,這跟咱們程序的類同樣,爲什麼咱們要在外面放一個namespace在外面,其實也是造成一個邊界。程序內部也是如此。例如,售樓部內的員工把商品房認爲是產品(Product);可是在工程部,可能他們把刷灰和修理管道的服務叫作產品,爲了消除這些歧義,能夠定義一個邊界,分離開兩種狀況,以避免在系統內產生混淆。每一個界限上下文根據特色,具體實現方式又不一樣,好比有些界限上下文基本沒有業務邏輯,就是增刪改查,則可使用CRUD最簡單的模式;有些界限上線文有必定的業務邏輯,但對高併發、高性能沒要求,則可使用經典DDD模式。有些界限上下文有必定的業務邏輯,並且有高性能要求,則可使CQRS模式(命令查詢職責分離(Command Query Responsibility Segregation,簡稱CQRS))

        

               上面這張圖展現了界限上下文

 

  3.實體:

        有業務生命週期,採用業務標識符進行跟蹤。好比一個訂單就是實體,訂單有生命週期的,並且有一個訂單號惟一的標識它本身,若是兩個訂單全部屬性值所有相同,但訂單號不一樣,也是不一樣的實體。

    實體之間的關係:

      1)關係越多,耦合越大。

      2)找出整個業務期間的都依賴的關係,某些關係只是在對象建立的時候有意義,好比建立訂單的時候會查詢一下商品價格信息。

      3)儘量的簡化關係,避免雙向依賴關係。

  4.值對象:

    無業務生命週期,無業務標識符,一般用於描述實體。好比訂單的收貨地址、訂單支付的金額等就是值對象。

    根據上下文的不一樣,一個值對象在一個界限上下文中上值對象,到了另外一個界限上下文環境中會是實體,就是在不一樣的領域裏屬性是不同的,具體還得根據上下文來看。

    值對象是不可變(只讀),這樣線程安全,能夠處處傳遞。

    值對象最大的好處在於增長了代碼複用。

  5.領域服務:

    無狀態,有行爲,服務自己也是對象,但它卻沒有屬性(只有行爲),所以說是無狀態,一般負責協調多個領域對象的操做來完成一些功能。好比嘗試如何將信息轉化爲領域模型,但並不是全部的點咱們都能用Model來涵蓋。對象應當有屬性,狀態和行爲,但有時領域中有一些行爲是沒法映射到具體的對象中的,咱們也不能強行將其放入在某一個模型對象中,而將其單獨做爲一個方法又沒有地方,此時就須要服務。協調聚合之間的業務邏輯,而且完成用例,表示某種能力。

  6.聚合:

    聚合是一組相關的對象,它經過定義對象之間清晰的所屬關係和邊界來實現領域模型的內聚,並避免了錯綜複雜的難以維護的對象關係網的造成,咱們把聚合看做是一個修改數據的單元,目的將這些對象做爲一個單元(是業務的一個最小單元,持久化最小單元),每一個聚合都有一個邊界和一個根,邊界定義了聚合裏應該包含什麼,根是聚合中惟一能夠被外部飲用的元素,好比說不能直接繞過訂單實體去訪問訂單項,但在聚合邊界內部,能夠互相引用。聚合根具備全局惟一標識,聚合根由倉儲負責持久化其生命週期,而實體只有在聚合內部有惟一局部標識,由聚合根負責其生命週期持久化。

    一般將多個實體和值對象組合到一個聚合中來表達一個完整的概念,好比訂單實體、訂單明細實體、訂單金額值對象就表明一個完整的訂單概念,並且生命週期是相同的,而且須要統一持久化到數據庫中。

  7.聚合根:

    將聚合中表達總概念的實體作成聚合根,好比訂單實體就是聚合根,對聚合中全部實體的狀態變動必須通過聚合根,由於聚合根協調了整個聚合的邏輯,保證一致性。固然其餘實體能夠被外部直接臨時查詢調用。

  8.倉儲:

    用於對聚合進行持久化,一般爲每一個聚合根配備一個倉儲便可。倉儲可以很好的解耦領域邏輯與數據庫。

  9.工廠

    用於建立複雜的領域對象,可以將領域對象複雜的建立過程保護起來,能夠建立實體,值對象。在大型系統中,實體和聚合一般是很複雜的,這就致使了很難去經過構造器來建立對象,工廠就決解了這個問題,其實就是一種封裝,隱藏了複雜的建立細節。

    10.上下文映射圖

     若是咱們將限界上下文理解爲是對工做邊界的控制,則上下文之間的協做實則就是團隊之間的協做,高效的團隊協做應遵循「各司其職、權責分明」的原則。從組織層面看,須要預防一個團隊的「權力膨脹」,致使團隊的「勢力範圍」擴大到整個組織。從團隊層面,又須要避免本身的權力遭遇壓縮,致使本身的話語權愈來愈小,這中間就存在一個平衡問題。映射到領域驅動設計的術語,就是要在知足合理分配職責的前提下,謹慎地確保每一個限界上下文的粒度。職責的合理分配,能夠更好地知足團隊的自組織或者說自治,但不可能作到「萬事不求人」,全靠本身來作。若是什麼事情都由這一個團隊完成,這個團隊也就成爲無所不能的「上帝」團隊了。上下文映射展示了一種組織動態能力(Organizational Dynamic),它能夠幫助咱們識別出有礙項目進展的一些管理問題。」這也是我爲什麼要在識別上下文的過程當中引入項目經理這個角色的緣由所在,由於在團隊協做層面,限界上下文與項目管理息息相關。

  領域驅動設計根據團隊協做的方式與緊密程度,定義了五種團隊協做模式:

    1)合做關係(Partnership):兩個上下文緊密合做的關係,互相聯繫緊密。

    2)共享內核(Shared Kernel):兩個上下文依賴部分共享的模型。

    3)客戶方-供應方開發(Customer-Supplier Development):正常狀況下,這是團隊合做中最爲常見的合做模式,體現的是上游(供應方)與下游(客戶方)的合做關係。這種合做須要兩個團隊共同協商。

    4)遵奉者(Conformist):下游限界上下文對上游限界上下文模型的追隨,作出遵奉模型決策的前提是須要明確這兩個上下文的統一語言是否存在一致性,由於限界上下文的邊界自己就是爲了維護這種一致性而存在的。

    5)分離方式(Separate Ways):在典型的電商網站中,支付上下文與商品上下文之間就沒有任何關係,兩者是「分離方式」的體現。

 

八.DDD系統的分層架構 

 

          

 

  分層就是將具備不一樣職責的組件分離開來,組成一套層內部高聚合,層與層之間低耦合的軟件系統,領域驅動設計的討論一樣也是創建在層模式的基礎上的,但與傳統的分層架構相比,它更注重領域架構技術架構的分離。

 

  領域驅動設計將軟件系統分爲四層:基礎結構層、領域層、應用層和表現層。與上述的三層相比,數據訪問層已經不在了,它被移到基礎設施層了,這些是屬於外圍的,不是核心。

     1.基礎結構層(Infrastructure Layer): 
      該層專爲其它各層提供技術框架支持。注意,這部份內容不會涉及任何業務方面的知識。數據訪問的內容,也被放在了該層當中,由於數據的讀寫是業務無關的,該層主要是幫助領域模型進行落地。
     2.領域層(Domain Layer)
      DDD的核心,包含了業務所涉及的 領域對象(實體、值對象)領域服務倉儲接口(倉儲接口就是和存儲DB打交道的)都位於此層,領域模型無關技術,具備高度的業務抽象性,它可以精確的描述領域中的知識體系,並維護了領域對象的狀態以及它們之間的關係。這部份內容的具體表現形式就是領域模型(Domain Model)。領域驅動設計提倡富領域模型,即儘可能將業務邏輯歸屬到領域對象上,實在沒法歸屬的部分則以領域服務的形式進行定義。領域層是能夠依賴接口的。
    領域層遵循的原則是,除非業務發生變化,不然其餘的變化均不會影響領域層,這些變化包括是否使用不一樣的框架,是否須要分頁,是否移動端訪問等等。
     3.應用層(Application Layer)
      該層不包含任何領域邏輯,但它會對任務進行協調,並能夠維護應用程序的狀態,所以,它更注重流程性的東西,包括分頁,提供openapi,提供webservice,在某些領域驅動設計的實踐中,也會將其稱爲「工做流層」。能夠處理與領域層無關的攔截性工做,好比日誌,事務等。
     4.表現層(Presentation Layer)
      面向用戶信息展示層,負責顯示和接受輸入,不包含業務中的邏輯。

    

    從上圖還能夠看到,表現層與應用層之間是經過數據傳輸對象(DTO)進行交互的,數據傳輸對象是沒有行爲的POCO對象,它的目的只是爲了對領域對象進行數據封裝,實現層與層之間的數據傳遞。爲什麼不能直接將領域對象用於數據傳遞?由於領域對象更注重領域,而DTO更注重數據。不只如此,因爲「富領域模型」的特色,這樣作會直接將領域對象的行爲暴露給表現層。

    領域層是業務的核心,全部的業務都是在領域層。應用層是在領域層之上,爲ui服務,它是響應ui的請求去領域層調用相應的服務,把結果返回給ui,這裏麪包括一些事務,分頁等和業務無關的,因此這些和業務無關都會放在應用層。基礎設施層是服務領域層的。

 

 

         架構風格

           針對DDD的架構設計,《實現領域驅動設計》書中提到了幾種架構風格:六邊形架構、REST架構、CQRS、事件驅動等。在實際使用中,落地的架構並不是是純粹其中的一種,而頗有可能戶將上述幾種架構風格結合起來實現。

       

    所謂的六邊形架構,實際上是分層架構的擴展,原來的分層架構一般是上下分層的,好比常見的MVC模式,上層是對外的服務接口,下層是對接存儲層或者是集成第三方服務,中層是業務邏輯層。咱們跳出分層的概念,會發現上面層和下面層其實都是端口+適配器的實現,上面層開放http/tcp端口,採用rest/soap/mq協議等對外提供服務,同時提供對應協議的適配器;下層也是端口+適配器,只不過應用程序這時候變成了調用者,第三方服務或者存儲層提供端口和服務,應用程序自己實現適配功能。

    領域驅動設計(Domain Driven Design)有一個官方的sample工程,名爲DDDSample,官網:http://dddsample.sourceforge.net/,該工程給出了一種實踐領域驅動設計的參考架構。下圖就是它的代碼結構。

 

      

       各個目錄含義:Infrastructure(基礎實施層),Domain(領域層),Application(應用層),Interfaces(表示層,也叫用戶界面層或是接口層),config(各類配置)

 

  領域驅動設計過程當中使用的模式

 

 

 九.領域驅動總結

 

  DDD與數據庫設計不一樣:

    1.領域驅動設計是一種面向對象的設計,先建模再去設計數據庫。

    2.領域驅動設計主要是基於現實業務中的模型,更加貼近真實業務,不只僅是一種技術的實現。

    3.領域驅動設計出來的產品---領域對象(Domain  Object),是一個充血模型,不但包含業務對象的屬性,也包含業務對象的方法和行爲,更加符合oo原則。

    4.領域驅動設計並不包含數據庫具體設計,而是和領域專家一塊兒,採用統一的語言分析領域對象的屬性,業務方法,以及領域之間的關係,併爲之建模。

    5.領域驅動設計能減小溝通的成本。

 

   DDD的特色:

    1.統一語言,業務 產品 技術交流都不會設計到具體的技術方面,主要是對核心業務的建模,不會先考慮數據表的設計,先考慮建模。

    2.專有的領域層,領域層除了業務以外不設計軟件架構,等底層技術。

    3.領域層代碼就是業務文檔,看到領域層代碼就能看到業務 的核心,就是從對象中不只僅看到屬性還能能夠看到業務。

   

   DDD的一些問題

    1.爲何DDD能夠應對複雜性?

      答:就是分而自治思想,好比說一個系統幾百張表,不可能一會兒弄清楚,可是能夠按業務,模塊去劃分,DDD裏面叫作界限上下文,和模塊(可是提出了更多概念,好比聚合,一個模塊有可能仍是很大。)相似,劃分紅一個個領域,而領域模型有清晰的邊界,同時DDD重構了設計模式 架構模式  它裏面也引入了ioc,工廠模式 策略模式 只是在更高層次上的應用。

    2.爲何能夠快速應對變化?

      當問題空間出現變化的時候,咱們能夠快速的找到領域模型。領域層是能夠很容易將業務模塊拿出來重用的。

 

       什麼時候考慮使用領域驅動設計?

    1.若是系統只是簡單的curd,沒有很複雜的業務邏輯,不須要領域驅動設計,反而回增長複雜性。

    2.若是你的應用多於用例場景,你的系統可能會逐漸成爲一個大泥球(混雜在一塊兒的上下文關係,邊界不清晰,代碼混亂)。若是你肯定你的系統將會更復雜,你應該使用領域驅動設計來處理這個複雜性。

      使用領域驅動的難點

              應用領域驅動設計並沒那麼簡單容易,這須要花費時間和精力去了解業務領域、術語、調查、和領域專家一塊兒合做去劃分如何去劃分領域,劃分好邊界並建模,去業務進行抽象,這也是DDD的最重要的地方。

 

 

十.一些相關的擴展閱讀

  CQRS架構:

  核心思想是將應用程序的查詢部分和命令部分徹底分離,這兩部分能夠用徹底不一樣的模型和技術去實現。好比命令部分能夠經過領域驅動設計來實現;查詢部分能夠直接用最快的非面向對象的方式去實現,好比用SQL。這樣的思想有不少好處:

  1) 實現命令部分的領域模型不用常常爲了領域對象可能會被如何查詢而作一些折中處理;

  2) 因爲命令和查詢是徹底分離的,因此這兩部分能夠用不一樣的技術架構實現,包括數據庫設計均可以分開設計,每一部分能夠充分發揮其長處;

  3) 高性能,命令端由於沒有返回值,能夠像消息隊列同樣接受命令,放在隊列中,慢慢處理;處理完後,能夠經過異步的方式通知查詢端,這樣查詢端能夠作數據同步的處理。

  事件溯源(Event Sourcing):

  基於DDD的設計,對於聚合,不保存聚合的當前狀態,而是保存對象上所發生的每一個事件。當要重建一個聚合對象時,能夠經過回溯這些事件(即讓這些事件從新發生)來讓對象恢復到某個特定的狀態;由於有時一個聚合可能會發生不少事件,因此若是每次要在重建對象時都從頭回溯事件,會致使性能低下,因此咱們會在必定時候爲聚合建立一個快照。這樣,咱們就能夠基於某個快照開始建立聚合對象了。

  DCI架構:

   DCI架構強調,軟件應該真實的模擬現實生活中對象的交互方式,代碼應該準確樸實的反映用戶的心智模型。在DCI中有:數據模型、角色模型、以及上下文這三個概念。數據模型表示程序的結構,目前咱們所理解的DDD中的領域模型能夠很好的表示數據模型;角色模型表示數據如何交互,一個角色定義了某個「身份」所具備的交互行爲;上下文對應業務場景,用於實現業務用例,注意是業務用例而不是系統用例,業務用例只與業務相關;軟件運行時,根據用戶的操做,系統建立相應的場景,並把相關的數據對象做爲場景參與者傳遞給場景,而後場景知道該爲每一個對象賦予什麼角色,當對象被賦予某個角色後就真正成爲有交互能力的對象,而後與其餘對象進行交互;這個過程與現實生活中咱們所理解的對象是一致的;

  DCI的這種思想與DDD中的領域服務所作的事情是同樣的,但實現的角度有些不一樣。DDD中的領域服務被建立的出發點是當一些職責不太適合放在任何一個領域對象上時,這個職責每每對應領域中的某個活動或轉換過程,此時咱們應該考慮將其放在一個服務中。好比資金轉賬的例子,咱們應該提供一個資金轉賬的服務,用來對應領域中的資金轉賬這個領域概念。可是領域服務內部作的事情是協調多個領域對象完成一件事情。所以,在DDD中的領域服務在協調領域對象作事情時,領域對象每每是處於一個被動的地位,領域服務通知每一個對象要求其作本身能作的事情,這樣就好了。這個過程當中咱們彷佛看不到對象之間交互的意思,由於整個過程都是由領域服務以面向過程的思惟去實現了。而DCI則通用引入角色,賦予角色以交互能力,而後讓角色之間進行交互,從而可讓咱們看到對象與對象之間交互的過程。但前提是,對象之間確實是在交互。由於現實生活中並非全部的對象在作交互,好比有A、B、C三個對象,A通知B作事情,A通知C作事情,此時能夠認爲A和B,A和C之間是在交互,可是B和C之間沒有交互。因此咱們須要分清這種狀況。資金轉賬的例子,A至關於轉賬服務,B至關於賬號1,C至關於賬號2。所以,資金轉賬這個業務場景,用領域服務比較天然。有人認爲DCI能夠替換DDD中的領域服務,我持懷疑態度。

  四色原型分析模式:

  1) 時刻-時間段原型(Moment-Interval Archetype)

    表示在某個時刻或某一段時間內發生的某個活動。使用粉紅色表示,簡寫爲MI。

  2) 參與方-地點-物品原型(Part-Place-Thing Archetype)

    表示參與某個活動的人或物,地點則是活動的發生地。使用綠色表示。簡寫爲PPT。

  3) 描述原型(Description Archetype)

    表示對PPT的本質描述。它不是PPT的分類!Description是從PPT抽象出來的不變的共性的屬性的集合。使用藍色表示,簡寫爲DESC。

    舉個例子,有一我的叫張三,若是某個外星人問你張三是什麼?你會怎麼說?可能會說,張三是我的,可是外星人不知道「人」是什麼。而後你會怎麼辦?你就會說:張三是個由一個頭、兩隻手、兩隻腳,以及一個身體組成的客觀存在。雖然這時外星人仍然不知道人是什麼,但我已經能夠借用這個例子向你們說明什麼是「Description」了。在這個例子中,張三就是一個PPT,而「由一個頭、兩隻手、兩隻腳,以及一個身體組成的客觀存在」就是對張三的Description,頭、手、腳、身體則是人的本質的不變的共性的屬性的集合。但咱們人類比較聰明,很會抽象總結和命名,已經把這個Description用一個字來代替了,那就是「人」。因此就有所謂的張三是人的說法。

  4) 角色原型(Role Archetype)

    角色就是咱們平時所理解的「身份」。使用黃色表示,簡寫爲Role。爲何會有角色這個概念?由於有些活動,只容許具備特定角色(身份)的PPT(參與者)才能參與該活動。好比一我的只有具備教師的角色才能上課(一種活動);一我的只有是一個合法公民才能參與選舉和被選舉;可是有些活動也是不須要角色的,好比一我的不須要具有任何角色就能夠睡覺(一種活動)。固然,其實說人不須要角色就能睡覺也是錯誤的,錯在哪裏?由於咱們能夠這樣理解:一個客觀存在只要具備「人」的角色就能睡覺,其實這時候,咱們已經把DESC看成角色來看待了。因此,其實角色這個概念是很是廣的,不能用咱們平時所理解的狹義的「身份」來理解,由於「教師」、「合法公民」、「人」均可以被做爲角色來看待。所以,應該這樣說:任何一個活動,都須要具備必定角色的參與者才能參與。

  用一句話來歸納四色原型就是:一個什麼什麼樣的人或組織或物品以某種角色在某個時刻或某段時間內參與某個活動。 其中「什麼什麼樣的」就是DESC,「人或組織或物品」就是PPT,「角色」就是Role,而」某個時刻或某段時間內的某個活動"就是MI。 

  以上這些東西若是在學習了DDD以後再去學習會對DDD有更深刻的瞭解,但我以爲DDD相對比較基礎,若是咱們在已經瞭解了DDD的基礎之上再去學習這些東西會更加有效和容易掌握。

 

十一 .其餘的軟件開發模式    

  TDD:測試驅動開發(Test-Driven Development)

  BDD:行爲驅動開發(Behavior Driven Development)

  ATDD:驗收測試驅動開發(Acceptance Test Driven Development)

相關文章
相關標籤/搜索