微服務架構設計基礎之領域驅動設計

来源:https://www.cnblogs.com/heishao/archive/2019/01/21/10297817.html
-Advertisement-
Play Games

DDD早於微服務「出道」十年,這兩個「忘年交」的軟體設計哲學是如何相愛相殺的? 背景 微服務現在可以說是軟體研發領域無人不提的話題,然而業界流行的對比多數都是所謂的Monolithic(單體應用),而大量的系統在十幾年前都已經是以SOA(面向服務架構)為基礎的分散式系統了,那麼微服務作為新的架構標準 ...


DDD早於微服務「出道」十年,這兩個「忘年交」的軟體設計哲學是如何相愛相殺的?

背景

微服務現在可以說是軟體研發領域無人不提的話題,然而業界流行的對比多數都是所謂的Monolithic(單體應用),而大量的系統在十幾年前都已經是以SOA(面向服務架構)為基礎的分散式系統了,那麼微服務作為新的架構標準與SOA有什麼差異點呢?其本質區別在於設計原理,微服務是去中心化設計,SOA是「集成」形成中心設計;

另外,筆者認為以下幾點並不是微服務和SOA的區別點:

  • CI/CD:持續集成、持續部署本身與敏捷、DevOps是交織在一起的,CI\CD更傾向於軟體工程的領域,與微服務無關;

  • 基於容器還是虛擬機:Docker、虛擬機、物理機等是物理介質的一種實現方式,與微服務無關;
  • 微服務周邊生態:比如日誌平臺、調用鏈系統?更多的是研發本身對於效率提高的自驅力,而與使用何種架構方式無關;
  • 通訊協議:微服務的推薦通訊協議是RESTful,而傳統的SOA是SOAP。不過基於輕量級的RPC框架Dubbo、Thrift、gRPC來實現微服務也很多;在Spring Cloud中也有Feign框架將標準RESTful轉為代碼的API這種仿RPC的行為,這些通訊協議不是區分微服務架構和SOA架構的核心差別;

    當然,軟體工程(DevOps)、基礎設施(容器化)、軟體開發模式(敏捷開發)的變革有利的推進了微服務架構的大行其道。而微服務架構是一種架構風格、架構理念,其中的「微」更體現了它的精髓在切分。在實際微服務的落地過程中證明,如果切分是錯誤的,你得不到微服務承諾的「低耦合、自治、易維護」之類的優勢,並且還會比單體架構擁有更多的麻煩。那麼如何切分呢?其實並不是一些新的方法論,而是都提出很多年的架構設計方法,也稱它們為微服務設計基礎或架構模型:領域驅動設計和立方體模型。

領域驅動設計

2004年,Eric Evans 發表了Domain Driven Design(領域驅動設計,DDD)。領域驅動設計已經問世十幾年,從Eric Evans出版的著作「領域驅動設計」一書中對領域驅動做了開創性的理論闡述,在軟體設計領域中,DDD可以稱得上是步入暮年時期了。遺憾的是,國外軟體圈享有盛譽並行之有效的設計方法學,國內大多數的技術人員卻並不瞭解,也未曾運用到項目實踐中。直到行業內吹起微服務的熱風,人們似乎才重新發現了領域驅動設計的價值,並不是微服務拯救了領域驅動設計,是因為領域驅動設計一直在頑強的成長,其設計開放的設計方法體系,雖然從來不曾在國內大行其道,但卻發揮著巨大的價值。錶面上看確實是因為微服務,領域驅動設計才又開始出現在大眾視野里。

領域驅動設計的意義

當然,領域驅動設計並非「銀彈」,不是能解決所有疑難雜症的「靈丹妙藥」,學習並應用它的意義在於:

  • 一套完整的模型驅動的軟體設計方法,用於簡化軟體項目的複雜度,它能帶給你從戰略設計到戰術設計的規範過程,使得你的設計思路能夠更加清晰,設計過程更加規範;
  • 一種思維方式和概念,可以應用在處理複雜業務的軟體項目中,加快項目的交付速度;
  • 一組提煉出來的原則和模式,可以幫助開發者開發優雅的軟體系統、促進開發者對架構與模型的精心打磨,尤其善於處理系統架構的演進設計、有助於提高團隊成員的面向對象設計能力與架構設計能力;
  • 領域驅動設計與微服務架構天生匹配,無論是在新項目中設計微服務架構,還是將系統從單體架構演進到微服務設計,都可以遵循領域驅動設計的架構原則。
    當然,領域驅動能給我們帶來很多收穫,但如果你是屬於以下幾種情況的某種,那麼你確實不需要學習領域驅動設計了:

  • 如果你是獨當一面的架構師,並能設計出優雅的軟體架構
  • 如果你是高效編碼的程式員,並只想踏踏實實的寫代碼
  • 如果你是前端的設計人員,並奉行「用戶體驗至上」的理念
  • 如果你負責的軟體系統並不複雜,二三人便可輕鬆維護

    DDD的關鍵概念

一個軟體系統的誕生,一定是為瞭解決我們遇到的某個問題。比如一家企業的一直採用線下銷售產品,耗費大量的財力和物力,希望可以線上上銷售自己的產品,用於實現線上銷售銷售產品的目的,那麼就誕生了一個電商系統。通常最初設立的目標或要解決的問題就是一個軟體項目的出發點,明確我們要做什麼。比如一個電商、一個論壇、一個支付平臺等。

下文將從領域、問題域、領域模型、設計、驅動這幾個詞語的含義和聯繫的角度去闡述DDD是如何融入到軟體開發的。要理解什麼是領域驅動設計,首先要理解什麼是領域,什麼是設計,什麼是驅動,什麼驅動什麼。

什麼是領域/子領域(Domain/Subdomain)

領域是與某個特定問題相關的知識和行為。比如支付平臺就屬於特定的領域,只要是這個領域,都會有賬戶、會記、收款、付款、風控等核心環節。所以,同一個領域的系統都具有相同的核心業務,他們要解決的問題的本質是一致的。一個領域本質上可以理解為就是一個問題域,只要是同一個領域,那問題域就相同。所以,只要我們確定了系統所屬的領域,那這個系統的核心業務,即要解決的關鍵問題、問題的範圍邊界就基本確定了。

在日常開發中,我們通常會將一個大型的軟體系統拆分成若幹個子系統。這種劃分有可能是基於架構方面的考慮,也有可能是基於基礎設施的。在DDD中,我們對系統的劃分是基於領域(基於業務)的。比如上文提到支付平臺是一個領域,而賬戶、會記、收款、付款等則為子領域。一個領域由眾多子領域聚集而形成。

當然,問題隨之而來:

  • 哪些概念應該建模在哪些子系統裡面?
  • 有時可能會發現一個領域概念建模在子系統A中是可以的,而建模在子系統B中也合情合理。
  • 各個子系統之間的應該如何集成?
  • 有人可能會說,這不簡單得就像客戶端調用服務端那麼簡單嗎?問題在於,兩個系統之間的集成涉及到基礎設施和不同領域概念在兩個系統之間的翻譯,稍不註意,這些概念就會對我們精心創建好的領域模型造成污染。

DDD中,有標準方法解決上述問題,就是限界上下文(Bounded Context)和上下文映射圖。在一個領域/子域中,我們會創建一個概念上的領域邊界,在這個邊界中,任何領域對象都只表示特定於該邊界內部的確切含義。這樣的邊界便稱為限界上下文。限界上下文和領域具有一對一的關係。從物理層面講,一個限界上下文最終可以是一個Jar/War文件,甚至可以是一個Package中的所有對象。但是,技術本身並不是用來界分限界上下文。

上圖引自《實現領域驅動設計》。通常情況下,一個領域有且只有一個核心問題,我們稱之為該領域的「核心域」。在核心域、通用子域、支撐子域梳理的同時,會定義出子域中的「限界上下文」及其關係,用它來闡述子域之間的關係。界限上下文可以簡單理解成一個子系統或組件模塊。

什麼是設計(Design)

DDD中的設計主要指領域模型的設計。DDD是一種基於模型驅動開發的軟體開發思想,強調領域模型是整個系統的核心,領域模型也是整個平臺的核心價值。每一個領域都有一個對應的領域模型,領域模型能夠很好的解決負責的業務問題。所以領域模型的設計和架構設計同等重要。

什麼是驅動(Driven)

DDD中,總是以領域為邊界,分析領域中的核心問題(核心關註點)。然後設計對應的領域模型,通過領域模型驅動代碼的實現。而資料庫設計、持久化技術這些都不是DDD的核心,屬於外圍的東西。與資料庫驅動開發的思路形成對比,驅動中需要記住兩個原則:

  • 領域驅動領域模型設計
  • 領域模型驅動代碼實現
    領域驅動設計的最大價值是讓我們告別從面向過程式的思想(天馬星空,想到哪寫到哪)轉化為基於系統化的模型驅動思維。我們腦補一下軟體開發中的常規心路歷程:

  • 1、設計表結構
  • 2、寫代碼(代碼寫的很冗餘,不夠抽象)
  • 3、維護代碼(適應業務變化)
  • 4、遇到困難(數據結構設計不合理、代碼到處冗餘、改BUG引入新BUG、新人看代碼和無字天書一般)
  • 5、愈發難以維護,開始重構(理論上在老基礎上改的技術債務堪比重新開發)
  • 6、重構完成,新系統上線(相容歷史數據、數據遷移、新老系統並行,等等出發點考慮,其實本質上只是做了代碼重構)
  • 7、重覆執行3-6步......

    DDD的分層架構

四層架構

Eric Evans在《領域驅動設計-軟體核心複雜性應對之道》這本書中提出了傳統的四層架構模式,在後來演進過程中出現了五層架構和六層架構,,如下圖所示:

  • User Interface:用戶界面層/展示層,負責與用戶交互。包含顯示信息、解釋用戶命令等;
  • Application:應用層,用來協調用戶與各應用以及各應用之間的交互。不包含業務邏輯、不保存業務對象的狀態;
  • Domain:領域層/模型層,負責表達業務概念,業務狀態信息以及業務規則。包含領域模型、領域信息、業務對象的狀態。領域層是業務軟體的核心;
  • Infrastructure:基礎設施層,為其他各層提供技術能力。包括為應用層傳遞消息、為領域層提供持久化機制、為用戶界面層繪製屏幕組件等等。基礎設施層還能夠通過架構框架來支持四個層次間的交互模式。

六邊形架構

隨著後續的演進,出現了一種改進分層架構的方法,即Robert C. Martin提出的依賴倒置原則(Dependency Inversion Principle,DIP)。它通過改變不同層之間的依賴關係達到改進目的。

  • 高層模塊不應該依賴於底層模塊,兩者都應該依賴於抽象
  • 抽象不應該依賴於細節,細節應該依賴於抽象

根據該原則的定義,DDD分層架構中的低層組件應該依賴於高層組件提供的介面,即無論高層還是低層都依賴於抽象,整個分層架構好像被推平了,再向其中加入了一些對稱性,就出現了一種具有對稱性特征的六邊形架構風格。六邊形架構是Alistair Cockburn在2005年提出的,其本質是倡導不同的客戶通過「平等」的方式與系統交互,通過不斷的擴展適配器轉化成系統API所理解的參數來達到每種特定的輸出,而每種特定的輸出都有適配器完成相應的轉化功能。

聚合:

  • 一組具有內聚關係的相關對象的集合;
  • – 是一個修改數據的最小原子單元;
  • – 聚合通常使用id訪問;
  • 實體(Entity):表示具有生命周期並且會在其生命周期中發生改變的東西。含有VO、具有identity的特性,通常具有生命周期的概念 JPA tag @Entity;
  • 值對象(Value Object):表示起描述性作用的並且可以相互替換的概念。類似於pojo,不可變immutable,可在不同模型中傳遞,Spring tag @value;
  • 領域事件(Domain Event):所有的領域對象的跨聚合變更需要以事件方式進行通知和記錄,聚合內的酌情考慮;
  • 工廠(Factory):負責所有對象的生成和組裝;
  • 領域服務(Domain Service):純技術層面的服務,例如日誌,或者是跨聚合的編排服務,通常是Spring Component;
  • 資源層(Repository):類似於DAO層,Spring JPA, Hibernate之類 @CRUDRepository;
  • 防腐層:並非是系統間的消息傳遞機制,它的職責更具體的是指將某個模型或者契約中的概念對象及其行為轉換到另一個模型或者契約中;

貧血模型VS充血模型

讀完上面的兩種分層架構方式,可能很多人會有疑問,這些是什麼?為什麼我之前一直都沒聽到過這種分法?確實是這樣,DDD和麵向對象、設計模式等等理論有千絲萬縷的聯繫,如果不熟悉OOA、OOD,那麼DDD可能也會理解不了。因為我們大部分從開發生涯開始之初接觸的都是「Action層、Service層、Dao層、DB層」這樣的MVC分層理論。並且在21中設計模式中,「行為型」的設計模式,我們幾乎沒有什麼機會使用,導致這些問題的原因是J2EE經典分層的開發方式是「貧血模型」。

Martin Fowler(對,就是提出微服務的那位大牛)曾經提出了兩種開發方式,即:

  • 以「貧血模型」為基礎的「事務腳本」的開發方式
  • 以「充血模型」為基礎的「領域驅動」的開發方式

    貧血模型

貧血模型是指對象只用於在各層之間傳輸數據使用,只有數據欄位和Get/Set方法,沒有邏輯在對象中。而「事務腳本」可以理解為業務是由一條條增刪改查的SQL組織而成,是面向過程的編程。

充血模型是面向對象設計的本質,一個對象是擁有狀態和行為的。將大多數業務邏輯和持久化放在領域對象中,業務邏輯只是完成對業務邏輯的封裝、事務、許可權、校驗等的處理。

舉例,用戶管理模塊大概是這樣的兩種實現:

// 貧血模型下的實現

public class User{

private Integer id;
private String name;
...
// 省略get/set方法

}

public class UserManager{
public void save(User user){

    // 持久化操作....

}

}

// 保存用戶的操作可能是這樣
userManager.save(user);
// 充血模型下的實現
public class User{
private Integer id;
private String name;
...
// 省略get/set方法

public void save(){

    // 持久化操作....

}

}

// 保存用戶的操作可能是這樣
user.save();
Martin Fowler定義的「貧血模型」是反模式,面對簡單的小系統用事務腳本方式開發沒問題;稍微大一些的系統使用事務腳本方式會擴大維護成本,業務邏輯、各種狀態散佈在大量的函數中,哪怕就是要用戶對象中增加一個欄位,可能都會涉及到幾個類的調整......

希望領域對象能夠準確地表達出業務意圖,但是多數時候,我們所看到的卻是充滿getter和setter的領域對象,此時的領域對象已經不是領域對象了,反模式的貧血對象了。其實在貧血模型和充血模型模型之外,還有失血模型和脹血模型,但後者兩個基本是實際開發中不會去使用,因為走的是兩個極端。

總結

本文巨集觀角度介紹了領域驅動設計,那麼微服務和DDD是什麼關係呢?其實在2015年的一次演講中,DDD的提出者Eric Evans表達了對微服務技術的熱愛與支持,認為微服務是讓DDD落地的好工具。因為DDD和微服務其本質是降低軟體項目的複雜性,而DDD是一種設計理念/設計方法,DDD需要有強制性的原則做保障,否則不同的領域對象終究會混在一起。而微服務本身的一些限制,以及大家都能理解微服務的實施前提和首要條件,會在實現上給DDD增加了一些原則限制。DDD和微服務的不一定要同時使用落地,但是如果將DDD和微服務(兩個相差十歲的軟體設計方法)結合一起,那麼Martin Fowler和Eric Evans兩位佈道師是會很贊同的。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • confing/index.js 文件裡面的 useEslint 改成false就可以關閉代碼檢查了 不過為了代碼的規範性,不建議關閉! ...
  • 打開https://www.iconfont.cn/網址登錄後選擇你需要的圖標添加到購物車中 點擊下載代碼或者添加到項目後再下載代碼,再找到之前下載的的文件,拷貝到項目中 ...
  • 1. css 儘可能的放到head裡面,且避免css表達式 【@media 類似】 2. js 儘可能的放到</body>之前 <script>do something</script></body></html> 3. 儘可能的減少圖片的使用,併在實際需要使用圖片的地方在css定義好img的尺寸。 ...
  • 所有基礎課程鏈接: 1.JavaScript基礎視頻教程總結(001-010章) 2.JavaScript基礎視頻教程總結(011-020章) 3. JavaScript基礎視頻教程總結(021-030章) 4. JavaScript基礎視頻教程總結(031-040章) 5. JavaScript基 ...
  • 預覽鏈接:https://www.vanwee.cn/%E6%BB%9A%E5%8A%A8%E7%9B%91%E5%90%AC/ 使用方法: 在想要添加動畫的元素上添加class類名:vanwee 由於我只需要從下漸現向上移動的效果,所以從原博主轉載過來做了調整,大家有不明白的地方建議查看轉載來源博 ...
  • 創建 function 對象的兩種方法: 方式一(推薦) 方式二:var func2 = new Function("參數1", "參數n","函數體"); arguments 對象 獲得參數的個數 獲得參數的個數和值的總額 控制拋出異常 自執行函數 instanceof 用於判斷一個變數是否某個對 ...
  • 在jquery中,給元素綁定事件,本文一共介紹三種方法,運用案例,針對最常用的on()方法,進行事件綁定操作。 事件綁定方法: ①$(element).bind() 參數:{ “事件名稱1”:function( ){ } ,“事件名稱2”:function(){ },......} 例如給類名為on ...
  • 一、23種設計模式分類: 二、設計模式的六大原則: 1、開閉原則(Open Close Principle):對擴展開放,對修改關閉。在程式需要進行拓展的時候,不能去修改原有的代碼,實現一個熱插拔的效果。 2、里氏代換原則(Liskov Substitution Principle):任何基類可以出 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...