領域驅動設計,或者叫DDD,是一種軟件設計方法,它的目標是研發更好的軟件。工程師通過在連續設計過程中與領域專家緊密合作來實現這一目標。

Eric Evans 創造了領域驅動設計並寫了一本書《領域驅動設計: 軟件核心複雜性應對之道》。他的主要目標是在開發過程中使用模型和業務語言並遵循特定的技術模式。總之,這些步驟會幫助你處理複雜的軟件而不會陷入困境。

在這篇帖子中,我會進一步揭祕領域驅動設計方法。首先,我會分享一個故事,講述我對領域驅動設計的認知過程。然後我會詳細介紹過程的建模部分。最後,我會概述技術要素。

我是如何認識領域驅動設計

坦白說,當我第一次聽到“DDD”這個詞時,我會笑着禮貌的點點頭。對於我來說,它很神祕,而且直到我對我撰寫的TDD帖子發表了些禮貌的評論之前,我並沒有太多考慮它。評論者堅持認爲領域驅動設計是必經之路。

在TDD的那篇帖子中,我認爲測試驅動開發應該真正是“測試驅動的設計”。當然,緊急設計具有價值。但其它方法也有價值。因此,作爲一個思維開放、好奇的人,我得了解更多有關領域驅動設計的知識。我閱讀了Eric Evans的書並且馬上意識到它對怎麼解決設計問題的價值。

設計問題

在軟件中,存在許多設計問題。當然,存在很多工具來解決這些問題。一方面,我們有設計模式。另一方面,我們有建模。領域驅動設計使用這些工具並引入了一些自身的概念來處理複雜性。

然而,複雜性又有哪些問題?

複雜性的麻煩

我們憑直覺知道什麼是複雜性。但是,它可能與每個人和每個環境有關。一個問題可能對一個人來說很簡單,但對另一個人來說卻很複雜。

爲了量化複雜性,讓我們考慮一下這樣一個事物,它包含三個以上相互關聯的組件以及至少五個需要正式定義的業務規則,這些規則必須在“必填字段”之外。可能問題涉及一些計算,數據轉換,多屏顯示和一個外部數據源。而且,談到外部性,軟件通常是互連繫統的複雜網絡的一部分。那麼它的複雜程度如何?這要看情況。對於一個開發人員來說,這個系統很複雜。但是對於另一位開發人員來說可能是小菜一碟。

領域驅動設計主要涉及如何處理軟件開發中的複雜性。這是它的價值所在。當問題很簡單時,緊急設計很有用。但是當你面臨複雜性時,需要一些周到的考慮。當存在複雜性時,無論如何它都會存在。您可以遷移複雜性,但不能消除它。當您增加更多複雜性時,真正的麻煩就來了。另一方面,您可以控制複雜性。域驅動設計在“有界上下文”中控制複雜性。

有界上下文包含兩件事

有界上下文包含兩件事:複雜性和術語。

首先,有界上下文意味着包含複雜性。換句話說,我們可以爲某些特定的複雜業務邏輯創建有界上下文。有界上下文通過適配器連接到其他上下文。而且,這些適配器可以防止變化。當上下文中包含的那些複雜性發生變化時,我們系統的其餘部分也不必更改。這就是爲什麼我們應該使用適配器在上下文之間進行“阻抗匹配”。

另一方面,有限上下文是關於在上下文中定義術語的。例如,風險管理部門可能將僱員視爲“僱員”。但是人事部門可以稱他們爲“人員”。而財務部門對他們有自己的稱呼:“勞動力資源”。無論您是否喜歡這些術語,您都必須接受同一實體在不同上下文中有不同的名稱。

作爲服務行業,我們的IT人員應該花時間學習客戶的語言。與人事部門交談時,我們應使用“人員”一詞。但是在進行財務工作時,我們應該使用“勞動力資源”,即使這會使我們感到畏縮。畢竟它們只是對於我們來說只是詞語,但在特定上下文中,這些詞語具有意義。想象一下,如果非開發人員使用“單元測試”一詞來表示“功能測試”,你會感覺如何。如果他們堅持以這種方式濫用普遍存在的術語,你能認真對待嗎?這就是爲什麼我們需要直接與領域專家一起工作的原因,領域專家是從事業務領域工作或具有業務領域的深入知識的人。

與領域專家一起建模

領域驅動設計強調了與業務合作的重要性。在處理模型時尤其如此。模型是用於溝通和計劃的工具。它可以是簡單的模型,也可以是更復雜的格式,例如UML。最後,業務領域的領域專家和工程師都必須理解它。建模是溝通業務問題和遍歷解決方案的重要步驟。

請注意,這種類型的建模不是數據建模。這也不是數據庫設計。數據建模和數據庫設計是定義低級細節的練習。這些通常與業務無關。同樣,這種類型的建模也不是對象建模。可見,對象模型是實現細節,其使用方式與數據庫設計相同。

領域驅動設計中的建模是關於創建一個用於理解業務的模型。它可能有點抽象,但必須是一個共同的願景。目的是建立共同的理解,並促進業務與工程之間關於業務問題的雙向交流。只有到那時,我們才真正準備好進入技術細節。

技術部分

一旦對領域有了足夠的瞭解,就該進行項目剩餘部分了。Evans強調變化的重要性。隨着i項目實施的進行,新的理解不斷發展。解決某些問題可能有更好的方法。通常,您需要返回到建模北門。這是一件好事,因爲對模型的那些迭代將進一步改善設計!

除了改進設計之外,在領域驅動的設計中還有一些技術建議。Evans提出了許多模式,這些模式使軟件對變化的適應性更強。我們已經討論了兩種模式-適配器模式和有界上下文。其他還包括包括工廠模式和存儲庫模式。還有其他一些,但是由於它們是最常見的,所以我將重點關注這兩個。讓我們先看一下工廠模式。

工廠模式

當我們需要創建一些新對象時,應該使用工廠模式。我們不希望每個消費者都關心構造特定類型或子類型的對象的細節。那麼我們該怎麼辦?我們將責任委託給工廠。當然,我們可以直接調用構造函數,但是最好不要在構造函數中執行太多操作。複雜對象創建的解決方案是工廠!(順便說一句,工廠對於包含有關返回哪種特定類型對象非常有用。)

工廠可以是基類上的靜態方法,也可以是自己的靜態方法。消費者必須使用工廠來構建對象。我們可以通過使消費者無法訪問該類的構造函數來指定此行爲。獲取新Foo的唯一方法是調用Foo類的Build(... params)方法。然後,你將使用Foo對象來做一些事情:

Foo aFoo = Foo.build(fooData);

aFoo.reportHours(7);

aFoo.setManager(managerData);

Integer aFooId = aFoo.save();

此代碼調用工廠方法來構建Foo。然後,它使用該對象報告工作時間並設置管理員。最後,它保存對象並獲取生成的對象ID。以後,我們可以使用ID從保存的數據中重新操作Foo對象。

關聯使用存儲庫

存儲庫是持久性機制的抽象。一些常用的持久化機制包括數據庫、文件和內存。一般存儲庫是代表了任何可以保存和獲取對象數據的事物。所有這些都發生在對象或工廠內部。在這種情況下,領域類依賴存儲庫。

一個領域類代表了一個現實世界中的概念,它也叫領域對象。這些領域類是應用程序中需要使用的。那麼我們怎麼在領域對象中關聯使用存儲庫。

幸運的是,我們可以使用工廠進行控制。下面是例子:

java

Foo aFoo = Foo.Get(aFooId);

//---//

public class Foo

{

    private Foo(FooData data)

    {

       ...

    }

    public static Foo Build(FooData data)

    {

       return new Foo(data);

    }

    public static Foo Get(int id)

    {

       var data = ServiceLocator.GetRepository<Foo>().Get(id);

       if(!data) throw new NoFooFoundException(id);

       return new Foo(data);

    }

}

您可以看到Foo的Get方法使用ServiceLocator來獲取存儲庫。這是一個實現方式,但是你也可以通過依賴注入來達到相同的目的。

你必須記住,領域驅動設計已經問世超過十年。從那開始,我們的工具不斷發展。即使如此,這裏還有很多重要概念。一個非常好的收穫是我們把相關職責放到一塊代碼,形成了Foo類。這是一個非常重要的面向對象的法則,但是它常常被遺忘。當缺少這些模式時,代碼庫變得更加難以維護。

最後,領域驅動設計與組織相關

最後,所有術語和技術實踐加在一起就是一件事--組織。Evans的書的副標題取名“軟件核心複雜性應對之道”是有原因。複雜軟件項目需要特別注意才能不斷向前推進。不然,項目可能陷入泥潭。DDD的概念和實踐是管理複雜性的一種深思熟慮的方法。當然,還有其他優點,但是這一優點值得您注意!如果您還沒有讀過這本書,我強烈建議你去讀讀它。

原文鏈接: Domain-Driven Design Demystified (翻譯:吳偉略)

相關文章