天天看點

為什麼要使用DDD架構

什麼是DDD(Domain-Driven Design)

軟體開發不是一蹴而就的事情,我們不可能在不了解産品(或行業領域)的前提下進行軟體開發,在開發前,通常需要進行大量的業務知識梳理,而後到達軟體設計的層面,最後才是開發。而在業務知識梳理的過程中,我們必然會形成某個領域知識,根據領域知識來一步步驅動軟體設計,就是領域驅動設計的基本概念。領域驅動設計一般分為兩個階段:

  • 以一種領域專家、設計人員、開發人員都能了解的“通用語言”作為互相交流的工具,在不斷交流的過程中發現和挖出一些主要的領域概念,然後将這些概念設計成一個領域模型;
  • 由領域模型驅動軟體設計,用代碼來表現該領域模型。領域需求的最初細節,在功能層面通過領域專家的讨論得出。
在網際網路開發“小步快跑,疊代試錯”的大環境下,DDD似乎是一種比較“古老而緩慢”的思想。然而,由于網際網路公司也逐漸深入實體經濟,業務日益複雜,我們在開發中也越來越多地遇到傳統行業軟體開發中所面臨的問題。

過度耦合

業務初期,我們的功能大都非常簡單,普通的CRUD就能滿足,此時系統是清晰的。随着疊代的不斷演化,業務邏輯變得越來越複雜,我們的系統也越來越冗雜。子產品彼此關聯,誰都很難說清子產品的具體功能意圖是啥。修改一個功能時,往往光回溯該功能需要的修改點就需要很長時間,更别提修改帶來的不可預知的影響面。下圖是一個常見的系統耦合病例。

為什麼要使用DDD架構
訂單服務接口中提供了查詢、建立訂單相關的接口,也提供了訂單評價、支付、保險的接口。同時我們的表也是一個訂單大表,包含了非常多字段。在我們維護代碼時,牽一發而動全身,很可能隻是想改下評價相關的功能,卻影響到了創單核心路徑。雖然我們可以通過測試保證功能完備性,但當我們在訂單領域有大量需求同時并行開發時,改動重疊、惡性循環、疲于奔命修改各種問題。

上述問題,歸根到底在于系統架構不清晰,劃分出來的子產品内聚度低、高耦合。

有一種解決方案,按照演進式設計的理論,讓系統的設計随着系統實作的增長而增長。我們不需要作提前設計,就讓系統伴随業務成長而演進。這當然是可行的,靈活實踐中的重構、測試驅動設計及持續內建可以對付各種混亂問題。重構——保持行為不變的代碼改善清除了不協調的局部設計,測試驅動設計確定對系統的更改不會導緻系統丢失或破壞現有功能,持續內建則為團隊提供了同一代碼庫。

在這三種實踐中,重構是克服演進式設計中大雜燴問題的主力,通過在單獨的類及方法級别上做一系列小步重構來完成。我們可以很容易重構出一個獨立的類來放某些通用的邏輯,但是你會發現你很難給它一個業務上的含義,隻能給予一個技術次元描繪的含義。這會帶來什麼問題呢?新同學并不總是知道對通用邏輯的改動或擷取來自該類。顯然,制定項目規範并不是好的idea。我們又聞到了代碼即将腐敗的味道。

事實上,你可能意識到問題之所在。在解決現實問題時,我們會将問題映射到腦海中的概念模型,在模型中解決問題,再将解決方案轉換為實際的代碼。上述問題在于我們解決了設計到代碼之間的重構,但提煉出來的設計模型,并不具有實際的業務含義,這就導緻在開發新需求時,其他同學并不能很自然地将業務問題映射到該設計模型。設計似乎變成了重構者的自娛自樂,代碼繼續腐敗,重新重構……無休止的循環。

用DDD則可以很好地解決領域模型到設計模型的同步、演化,最後再将反映了領域的設計模型轉為實際的代碼。

注:模型是我們解決實際問題所抽象出來的概念模型,領域模型則表達與業務相關的事實;設計模型則描述了所要建構的系統。領域模型的開發方式,将資料和行為封裝在一起,并與現實世界中的業務對象相映射

貧血症和失憶症

貧血領域對象(Anemic Domain Object)是指僅用作資料載體,而沒有行為和動作的領域對象。【領域驅動要解決的一個重要的問題,就是解決對象的貧血問題】

如何知道你的模型是貧血的呢?可以看一下你代碼中是否有以下的幾個特征:

  • 有大量的XxxDO對象:這裡DO雖然有時候代表了Domain

    Object,但實際上僅僅是資料庫表結構的映射,裡面沒有包含(或包含了很少的)業務邏輯;

  • 服務和Controller裡有大量的業務邏輯:比如校驗邏輯、計算邏輯、格式轉化邏輯、對象關系邏輯、資料存儲邏輯等;
  • 大量的Utils工具類等。

缺陷

  • 無法保護模型對象的完整性和一緻性
  • 對象操作的可發現性極差
  • 代碼邏輯重複
  • 代碼的健壯性差
  • 強依賴底層實作

優點:

  • 開發“簡單”

軟體系統複雜性應對

解決複雜和大規模軟體的武器可以被粗略地歸為三類:抽象、分治和知識。

  • 分治把問題空間分割為規模更小且易于處理的若幹子問題。分割後的問題需要足夠小,以便一個人單槍匹馬就能夠解決他們;其次,必須考慮如何将分割後的各個部分裝配為整體。分割得越合理越易于了解,在裝配成整體時,所需跟蹤的細節也就越少。即更容易設計各部分的協作方式。評判什麼是分治得好,即高内聚低耦合。
  • 抽象使用抽象能夠精簡問題空間,而且問題越小越容易了解。舉個例子,從北京到上海出差,可以先了解為使用交通工具前往,但不需要一開始就想清楚到底是高鐵還是飛機,以及乘坐他們需要注意什麼。
  • 知識顧名思義,DDD可以認為是知識的一種。(DDD提供了這樣的知識手段,讓我們知道如何抽象出限界上下文以及如何去分治。)

與微服務架構相得益彰

微服務架構衆所周知,此處不做贅述。我們建立微服務時,需要建立一個高内聚、低耦合的微服務。而DDD中的限界上下文則完美比對微服務要求,可以将該限界上下文了解為一個微服務程序。

我們将架構設計活動精簡為以下三個層面:

  • 業務架構——根據業務需求設計業務子產品及其關系
  • 系統架構——設計系統和子系統的子產品
  • 技術架構——決定采用的技術及架構

一種基于springboot的DDD項目架構