再談領域驅動設計

richiezhang發表於2021-01-03

再談領域驅動設計

作為一個領域驅動設計的實踐者,我切實感受到了領域驅動為軟體開發帶來的好處,同時在實踐領域驅動的過程中也感受到了困難,這種困難體現在工程實踐的方方面面,例如什麼是領域驅動的最佳設計?如何把書本上的設計靈活的應用在自己的專案上?如何跟團隊成員就設計達成一致?

本文嘗試從領域驅動設計的目的出發,試圖通過簡單的描述來說明領域驅動設計的思想。

為什麼需要領域驅動設計

作為一個軟體開發者,多數人以為自己的職責就是編寫程式碼,然而軟體開發不是工廠流水線,如果所有的軟體開發者不停的開發新功能而不關心設計,那麼軟體開發過程將會變得越來越複雜,關於這一點大家應該都有不同程度的感受。
軟體開發工程師的工作是通過軟體來解決問題,編寫程式碼只是其中的一部分工作,設計和交流同樣重要。而領域驅動設計就是一個讓軟體開發工程師交流和共享領域知識的途徑。

共享領域知識的重要性

作為一個問題的解決者,能否理解和認識問題的前因後果至關重要。很明顯,如果你只看到了問題的表面,或者對事實有曲解,你顯然不會找到一個有效的解決方案。對於開發者,如果你編寫的程式碼只是你的理解,而不是領域專家的理解,你如何保證線上產品的質量?

那麼如何保證開發者編寫的程式碼就是領域專家的想法?最簡單的辦法就是讓領域專家來編寫程式碼,但是這種方案可遇不可求,還有沒有別的辦法呢?

如果領域專家,開發團隊以及程式碼能夠共享一個模型,這將有效減少不同利益相關者的溝通及交流,並且會確保所有人都在解決同一個問題。

這個想法要求開發者能夠把程式碼設計為一個反映業務的模型,而這正是領域驅動設計的核心思想。

通過業務事件來理解問題域

為了在領域專家和開發者之間建立一個共享模型,收集需求並理解業務是第一步。收集需求和理解業務的方式多種多樣,而事件風暴經常被用來達到這一目的。業務邏輯可以看做是一系列狀態的轉換過程,而這些過程轉換又被稱為領域事件。比如“訂單已提交”就是一個領域事件,如果把這個領域事件看做是訂單業務的開始,通過梳理”訂單已支付”以及”訂單已出庫”等後續的領域事件,就可以理解整個訂單業務。此時對業務的理解被稱為“問題域”。

把問題域劃分為問題子域

通過事件風暴,開發團隊和領域專家已經對整個”問題域”有了理解,但是現在著手解決“問題域”還有點早。當我們在面對一個大的問題時,自然而然會想到先將大的問題劃分成若干個小問題,然後再考慮各個擊破。接下來的一步就是把大的問題域劃分為若干個小的問題域。我們有一個網上商城的問題域,能不能把它分割為更小的問題域?

答案肯定的,我們把網上商城的問題分為:“訂單”,“銷售”,“市場”,“財務”,“採購”等若干個小問題域,再針對小的問題域分而治之。小的問題域在領域驅動設計中被稱為“問題子域”。

使用限界上下文建立解決方案

理解了問題域並劃分為問題子域並不意味著你就能建立出一個好的方案,你無法針對問題子域的所有資訊設計出一個解決方案,你的解決方案只會專注於那些有助於解決該問題子域的資訊,對於不相關的資訊則會人為的遮蔽掉。

為什麼叫限界?

在現實世界中,領域的邊界很模糊,但是要設計一個好的解決方案,我們需要對問題子域加上一個邊界,將不重要的資訊排除在邊界外。讓解決方案專心解決重點問題。

為什麼叫上下文?

每個上下文都代表著該解決方案的專業知識。在同一個上下文裡,我們共享統一的語言和一致的設計。
通過界限上下文人為將問題子域限制在有限的界限內,你才可以著手建立解決方案。

建立統一語言

團隊之間共享的術語和詞彙被稱為統一語言。統一語言用來定義業務領域的共享模型,當然可以用在專案的任何地方,包括需求分析和設計,最重要的是統一語言還需要出現在程式碼中。另外,統一語言在不同的界限上下文中往往不能夠通用,例如在“認證上下文”中提到“使用者”,在“機票訂單上下文”中叫做“乘客”。

領域建模

有了界限上下文,讓解決方案聚焦在最有用的資訊裡,你才可以著手建立共享模型。
如何才能建立一個不錯的共享模型呢?
使用視覺化的圖示似乎是一個不錯的想法,但實際上畫出一個能夠表達所有領域知識的圖示並不是一個簡單的工作;如果你有資料庫開發相關的經驗,你可能會想到通過表和主外來鍵來表達領域知識,如果你有這樣的想法那你就錯了,在領域驅動設計中講究通過領域邏輯來驅動設計和開發工作,而不是通過資料庫模型來驅動開發。
在領域驅動設計中這一步叫做”領域建模“,你應該用程式碼建立一個反映領域知識的模型,這個模型跟領域專家口中的領域知識是一致的。領域模型是提供業務能力的核心部件,也是整個應用程式提供業務能力的核心。

領域建模中的其他概念

對於開發者而言領域建模至關重要,也是最考驗開發者功底的一個環節。一方面開發者需要抽象出一個跟領域專家口中一致的模型,另一方面開發者還需要通過程式碼將這個模型表達出來。你需要恰如其分的使用一些物件導向的技巧把領域知識抽象到一個程式碼模型中,在這個過程中你需要了解”值物件”,”實體“,”聚合根“等概念,在此不再細說。

領域模型的持久化

在領域建模以及之前的步驟中,我們都沒有提及資料庫,因為領域驅動設計的核心是用程式碼建立一個共享模型,而資料庫設計根本就不是領域驅動設計關心的內容。

但是終究我們還是要把領域模型的狀態持久化到資料庫中,有沒有辦法在不關心資料庫表結構的情況下,將已經建立好的領域模型持久化?主流ORM的Code First恰好匹配我們現在的處境,已經有一點為領域驅動設計而生的味道了。
但是即便是ORM的Code First也會對領域模型有侵入,你可能需要根據不同的ORM為模型加上一些註解或者配置之類的程式碼,這跟領域驅動設計其實是相互違背的,我們希望用程式碼建立一個純淨的領域模型,這個模型封裝著領域專家的領域知識,除此之外的程式碼都跟領域模型是無關。

領域事件及事件溯源

解決上面問題的思路是引入領域事件和事件溯源。領域模型在提供業務能力的過程,就是領域模型狀態發生變化的過程。一旦領域模型的狀態發生了變化,就會產生一個事件,這跟事件風暴中提到的業務事件是一致的,例如”使用者已下單“。訂單模型在提供”使用者已下單“的業務能力後發生了狀態變化。事件溯源的思路就是隻持久化領域事件,然後通過還原事件的方式將領域模型還原在最新的狀態。

通過採取事件溯源,就可以將領域模型持久化跟資料庫完全解耦。

微服務和領域驅動設計

我們通過領域驅動設計的思路來分析和發現問題域,通過分解把問題域劃分為問題子域,通過人為加限制的方式將問題子域轉換為限界上下文。而這個過程就是我們分解微服務的過程,一般來說每一個限界上下文都可以對映為一個微服務,但也不是絕對的,具體情況具體分析。

微服務的互動和整合

每一個微服務專注於解決對應的限界上下文中的問題,並不代表微服務之間沒有交流。單個微服務的領域模型在提供服務的過程中會產生領域事件,領域事件為基於事件驅動(Event based)的微服務整合提供了基礎,如果在微服務之間架設一條訊息匯流排(不同於ESB,ESB被認為是反模式)。不同的微服務將自己產生的領域事件廣播在訊息匯流排上,微服務之間通過訂閱自己感興趣的事件就能完成微服務的整合。

HATEOAS和領域模型

迄今為止我們已經建立了領域模型,建立了微服務,通過訊息和領域事件完成了微服務的整合。還需要把微服務的能力通過REST API展現出來,微服務在對外提供能力的過程就是領域模型狀態發生變化的過程,如果將領域模型理解為一個設計精良的狀態機也一點不為過。如果設法將領域模型在某個狀態下能夠提供的能力通過REST API的的返回結果表達出來,這就是HATEOAS的核心思想。REST API不但可以提供某種能力,還可以告訴消費者此時領域模型能夠提供的其他能力。

結束語

本文從需求分析到API設計,試圖描述領域驅動設計的過程及思想。同時也能看的出領域驅動設計並不是孤立存在的,它為解決開發團隊和業務人員之間溝通而生,進而驅動微服務劃分以及API的設計,領域驅動設計並不是遙不可及的方法論,每一個專業術語和思想都是為了解決基本的問題而定義,希望本篇部落格能夠帶你走入領域驅動設計。

相關文章