如何學習領域驅動設計? - Vladik Khononov

banq發表於2022-02-16

Vladik Khononov 是《學習領域驅動設計》一書的作者。在這一集中,我們深入討論了領域驅動設計 (DDD) 和 Vlad,首先分享了為什麼理解業務領域在軟體工程中至關重要,以及 DDD 如何幫助在領域專家和軟體工程師之間建立共同的理解。Vlad 隨後解釋了 DDD 中的兩個重要設計,即戰略和戰術設計,以及它們之間的關係。對於每個設計,Vlad 都涉及到一些重要的模式,例如有界上下文、上下文對映、子域、聚合、實體和值物件。最後,Vlad 就將 DDD 應用於棕地專案以及這些專案如何從一些 DDD 實踐中獲得最大收益提供了很好的建議。  
 

瞭解業務領域的重要性

  • 業務邏輯是軟體的核心是有原因的。這就是構建軟體的原因。
  • 你不是在構建一個軟體來展示你的資料庫有多快和可擴充套件,或者你的使用者介面有多性感。您正在構建用於解決特定業務問題的軟體。這個問題可以使某人更有效率,簡化業務流程。如今,許多企業都建立在軟體之上。那是他們的業務領域。
  • 軟體工程最大的謊言是“要做軟體工程師,你要寫程式碼,而我是個骨子裡的內向者”。我們不是為了寫程式碼而寫程式碼。我們正在編寫程式碼來解決某人的問題。這個人,他們是領域專家。他們是最瞭解我們正在建模並在程式碼中實現它的業務領域的人。為了有效地解決這個問題,我們必須與他們溝通,與他們交談。我們必須有效地與人們進行溝通和協作。所以軟體工程不僅僅是程式碼。
  • 這就是為什麼與領域專家的互動在實施軟體中起著關鍵作用。你必須確保你理解你正在解決的問題。這很關鍵。如果不先了解問題,就無法提供軟體解決方案。要麼是錯誤的解決方案,要麼是正確的解決方案,但對於不同的問題,兩者都是無用的。
  • 如果我們將這種方法命名為領域驅動設計,我更願意將其擴充套件一下,並說業務領域驅動的軟體設計。所以我們這裡有三個元件。
    • 首先,我們有一個必須掌握和理解的業務領域。
    • 其次,我們有軟體設計。這就是我們在這裡建造的。
    • 而我們中間有個“驅動”這個詞,用數學語言來說,就像軟體設計等同於業務領域的功能一樣。
  • 如果您沒有正確地獲得該業務領域,那麼您將獲得錯誤的軟體設計。您可能正在解決錯誤的問題,或者您可能正在嘗試解決正確的問題,但效率低下。
  • 其次,正如領域驅動設計指導我們的那樣,有很多設計決策是基於我們對業務領域的瞭解。
    • 這是戰略設計決策,比如我們將使用哪些粗粒度元件來實現我們的系統?他們將如何相互交流?我們如何將這項工作分配給軟體工程團隊?
    • 我們有戰術設計決策,例如,我們將使用什麼設計模式來實現每個元件?我們將如何實現這個業務邏輯?我們將如何協調元件不同部分的互動?這是我們的內部架構。當然,我們有高階架構。元件如何相互協作?
  • 如果您錯誤地掌握了業務領域知識,那麼這是做出一些錯誤決策的好機會,包括戰略和戰術決策。所以這就是為什麼我們必須確保在我們嘗試為它設計解決方案之前對問題領域有足夠的瞭解。

 

領域驅動設計如何提供幫助

  • 我其實相信【軟體專案的高失敗率是可以解決的】。如果我們尋找這麼多軟體專案失敗的原因——如果您檢視針對該主題進行的研究,您會發現其中相當多的(如果不是全部)由於溝通問題或與其他相關的問題而失敗溝通。它可以是團隊成員之間的溝通,軟體工程師和領域專家之間的溝通,管理和工程團隊之間的溝通問題。
  • 領域驅動設計的核心原則是在專案的不同利益相關者之間建立共同的理解,以便他們可以用同一種語言談論和推理該軟體系統。在 DDD 術語中,它被稱為無處不在的語言。
  • 一旦我們能夠促進同一種語言的交流,而無需在知識到達目的地之前進行多次翻譯,一切都會變得更有效率。這可以解決我們大量的返工,很多錯誤的假設。最終,我相信它將帶來更多成功的軟體專案。
  • 一切都在變化,尤其是如果您正在為一家初創公司工作。不僅我們作為軟體工程師不知道如何以最有效的方式實現事物,而且業務人員也不知道他們將如何解決他們所關注的某個問題。
  • 他們知道,“嘿,我們想做點什麼。我們要怎麼做?我們將進行迭代。我們將測試一些解決方案,直到找到最最佳化的解決方案”。
  • 在那段時間裡,一切都會改變。對業務領域的理解將不斷髮展。不僅你會越來越多地瞭解它,而且那些商業人士,那些領域專家,他們也會學習。隨著他們獲得更多知識,這必須反映在軟體設計決策中。
  • 如果該元件業務領域發生變化,它必須反映在軟體設計中。可以有許多型別的更改,從描述業務領域的更有效方式、更好的術語、對業務流程的更好理解開始。
  • 而且,整個業務領域都可以改變。現在,一家公司從一個業務目標開始並在整個過程中改變它並不罕見,因為例如,前一個業務目標在財務上不可行。
  • 它必須反映在軟體設計中。未能對業務領域的變化做出反應會隨著時間的推移產生技術債務,最終,它會導致你承擔非常大的技術債務和一大堆泥漿。

 

DDD 戰略設計

  • 我認為戰略設計更為重要。因為您可以編寫非常成功的專案,同時僅納入戰略設計決策。但是,如果你只關注戰術設計決策,那麼這可能不會有好的結果,至少以我的經驗來看是這樣。
  • 首先,戰略設計是關於在軟體工程師和業務領域專家之間建立共同的理解。這是關於培養共同的語言和共同的理解。
    • 為此,您在交流中使用的該語言有一些要求。它的主要要求是每個術語應該有一個且只有一個含義。
    • 與軟體不同,人有時是如此不可預測。他們可以說一件事,但由於上下文不同,他們可以表達另一件事。在做出軟體設計決策時,我們不能允許這樣做。我們必須確保每條知識都是明確的。
    • 領域驅動設計說:“嘿,當你擁有如此無處不在的依賴於上下文的語言時,請在程式碼中明確地對其進行建模。定義該語言適用的上下文。它是有界上下文”。這是第一個戰略設計決策。
    • 它確保您擁有一個由有界上下文包含的模型,這很清楚。它的每一個術語只有一個含義。
    • 由於一些歷史問題和錯誤,(許多人)認為限界上下文必然是微服務。這不是真的。有界上下文是您最大的有效單體,因為它包含一個沒有衝突的模型。您可以進一步分解它。我們的想法是找到那些邊界,以確保您在其中擁有正確的模型,而不會發生衝突或衝突。
  • 其次,系統不會自行構建。為此,我們需要我們,軟體工程師。在任何組織中,可以有一個團隊或多個團隊從事同一個專案。這又會影響您設計這些有界上下文的方式,因為它們必須相互互動。
    • 領域驅動設計說:“嘿,這是你在設計這些元件時必須考慮的事情,它們將如何整合。”
    • 這是我們根據業務領域和團隊結構做出的第二個戰略設計決策。
    • 工程團隊的結構也可以改變。這應該會影響這些有界上下文相互整合的方式。

 

子域

  • 術語子域很難定義。這個想法是子域是一個業務構建塊。
  • 假設我們有兩家公司在同一個行業工作。所以他們將擁有相同的業務領域。但他們不是同一家公司。他們有不同的方式。他們在他們的市場上競爭。他們有不同的產品。因此,它們的不同之處在於它們的子域。
  • 子域就像拼圖一樣,完成了公司計劃如何在其業務領域取得成功的全貌。這些是您必須實施的活動,因為您的業務戰略,因為您希望如何將自己與競爭對手區分開來,或者因為您必須這樣做,因為這是法律,例如會計,您必須這樣做。
  • 子域作為起始啟發式,通常與組織單位、部門相關聯。但這是子域的粗粒度邊界。
  • 通常,當我們深入研究時,我們會發現更多,用軟體工程術語來說,是相互關聯的連貫用例集,它們要麼處理相同的資料,要麼實現相關的業務邏輯。我們能夠找到那些相互關聯的用例集,它們通常是子域的更優邊界。
  • 子域的全部目的不是描述組織的工作方式。為此,我們需要別的東西。這就是業務能力。這是一個更復雜的模型,因為它是分層的。有不同層次的業務能力。子域更簡單。我們這裡只有兩個層次。我們有業務域,在它之下是子域。
  • 所以我們必須專注於對我們正在構建的軟體系統真正重要的東西。我們必須決定我們找到的子域的邊界是否足以滿足我們的目的。
  • 根據域驅動設計,我們有三種型別的子域。
  • 第一個是核心子域。
    • 核心子域是公司如何將自己與競爭對手區分開來。這是你擁有的東西,但競爭對手沒有。至少你打算比他們賺更多的錢。這就是各種智慧財產權,比如在他們的組織中發明的專利或聰明的演算法。他們可能會提出一種新的方法來解決某些問題,或者他們只是最佳化現有的解決方案,從而以更便宜的方式更有效地解決它。
    • 這是您希望它留在您的公司中的東西,因為如果您的競爭對手擁有它,那是個壞訊息。這意味著您只是失去了一點競爭優勢。
  • 第二種型別的子域是通用子域。
    • 它們與核心相反。所以這裡沒有競爭優勢。這些是業內所有公司都使用的解決方案,也可能在其他行業中使用。這裡的一個很好的例子是加密演算法。
    • 所以這是一個通用的子域。解決了一些問題。你有問題,但你有一個現有的解決方案。你只需要繼續使用它。它可以購買現成的產品,採用開源解決方案。但無論如何,底線是您和您的競爭對手將使用相同的解決方案並且不會影響您的業務。
  • 第三種子域是支援子域,它們位於中間。
    • 同樣,與一般情況一樣,這裡沒有競爭優勢。但另一方面,作為核心子域,這是您必須在內部實現的。你必須在內部實施它,不是因為你想這樣做,而是因為你別無選擇。因為沒有可用的通用解決方案。
    • 在本質上,支援子域通常比核心子域簡單得多。這涉及一些簡單的實現。因為它很簡單,甚至可能有一個通用的解決方案,但您會選擇不使用它,而只是實現自己的解決方案,因為推出自己的解決方案比整合另一個解決方案更容易。實施比整合更容易。
  • 我的經驗法則是深入研究子域,然後決定是否應該進一步分解它。只有進一步分解才會產生不同型別的子域。

 

DDD 戰術設計

  • 戰略設計是關於你正在實施的。將是什麼元件?戰術設計是關於我們將如何實施它們。
  • 我們有不同的方式來實現元件和業務邏輯。這些模式是我們正在實現的一種子域的功能。
  • 如果它是通用的,那麼我們只是使用現有的解決方案。如果它是一個核心子域,我們需要一個設計模式來解決該核心子域的複雜性。如果這是一個支援子域,我們需要簡單的模式,否則,我們將過度設計。
  • 戰術設計決策涵蓋的另一個方面是我們將如何構建元件的整體工作,例如其內部元件。它將如何與資料庫一起工作?它將如何與使用者介面等一起工作?
  • 我們有架構模式,即分層架構,通常在支援子域或整合通用子域時更有用。我們有埠和介面卡,也叫六邊形架構,也叫洋蔥架構,也叫乾淨架構。
  • 我們有 CQRS 架構。您將使用哪種模式來設計架構和實現業務邏輯取決於正在使用的業務子域的型別。

 

聚合模式

  • 聚合是一個包含資料和所有行為以及影響資料的所有業務邏輯的物件。換句話說,可以更改聚合資料的所有內容都應該駐留在聚合邊界中。任何外部元件都不能更改其資料。
  • 通常,我們在實現一些複雜的業務邏輯時會使用這種模式。如果它很複雜,並且如果您允許外部元件繼續修改您的資料,那麼遲早會有一些業務規則影響這些資料。這些規則將被複制。複製業務邏輯通常不是一個好主意,但複製複雜的業務邏輯通常是一個糟糕的主意。這就是聚合模式應該阻止你做的事情。
  • 聚合不是資料庫中的平面記錄。相反,它是物件的層次結構。所有這些資料都可以駐留在同一個聚合中。
  • 下一個問題是我們在哪裡停止將事物推入聚合?我們在哪裡決定我們停止把東西放在哪裡?
  • 聚合不僅是資料和行為的邊界,也是事務的邊界。這意味著聚合資料中的所有更改都應在一個原子事務中提交。
  • 所以我們必須尋找更小的邊界,更有效的邊界。[Vaughn Vernon] 所說的,使用事務邊界作為選擇聚合邊界的啟發式方法。確保它只包含為實現駐留在其中的業務邏輯而必須高度一致的資料。所以無論什麼必須是強一致的,把它放在聚合中。其他所有可以最終一致的東西,都放在另一個聚合或另一個元件中。但總的來說,您應該只擁有必須高度一致的資料。
  • 你怎麼知道你是需要強烈一致還是最終一致?嘗試評估這兩條路徑。如果某件事最終是一致的,會發生什麼?你能正確實現所有這些業務規則和業務邏輯嗎?還是會導致資料損壞?當您以這種方式分析它時,通常很明顯您需要哪些資訊是高度一致的,以及您可以最終獲得哪些資訊是一致的。
  • 簡而言之,這是一個聚合。它包含資料、影響資料的行為以及所有資料周圍的事務邊界。
  • 因為所有行為都屬於聚合,所以我們必須公開一個允許我們執行它的公共介面。通常我們稱之為命令。所以我們正在對聚合執行命令。透過命令,我的意思是,不是您放入訊息匯流排或其他任何內容的訊息。不,這是說你有一個公共方法的冒犯方式。這應該是修改聚合內容的唯一方法。
  • 聚合的公共介面中非常重要的部分是領域事件。因為有時聚合可能非常大。您最好將它們保持小,但這取決於您的業務領域。其中可能會發生一些重要的商業事件。領域事件必須以通用語言、業務語言來表述。
  • 因此,一旦更改,聚合提交到資料庫,我們需要釋出該域事件,以便任何對其感興趣的元件都能夠訂閱,並執行與該域事件發生相關的事情。

 

實體模式

  • 當我們看到與聚合在同一級別討論的實體時,我們開始認為這是您可以選擇的兩種模式,這是不對的。聚合是一個實體。聚合是物件的層次結構。所以它是實體的層次結構。實體是聚合中的構建塊。
  • 我們需要該實​​體的主要原因是將其與值物件區分開來。所以值物件是這裡重要的東西,而不是實體。正如你所說,它們之間的區別是一個實體有一個ID。另一方面,值物件可以僅透過其值來標識。
  • 這種區分允許我們將這些值物件建模為程式碼中的不可變物件。作為實體,值物件不是獨立的。您不能單獨使用值。它必須描述一些東西。因此,它是位於聚合中的實體的屬性。
  • 這就是這三個核心模式的層次結構。聚合包含由值物件描述的實體。

 

為遺留系統實施 DDD

  • 不知何故,我們對領域驅動設計只能應用於新建專案有這種錯誤的理解,但這種情況非常罕見。我們的大部分工作是棕地專案。具有諷刺意味的是,棕地專案可以從一些 DDD 處理中受益最大。
  • 在棕地專案中實施領域驅動設計時,您需要的主要是耐心。你必須對一切保持耐心。
  • 在這些情況下,我通常建議從對映現有解決方案開始。所以你有這個想要發展的棕地專案,但在你開始考慮你想去哪裡之前,你必須確保你知道你的起點。所以你必須對映你擁有的那些有界上下文。
  • 現在,當然,這些不會是領域驅動設計的有界上下文。它們不一定包含連貫的模型。但是,這些都是您想要更改的元件。所以你必須找到它們,對映你擁有的有界上下文。您必須對映它們如何相互互動,這些整合模式。
  • 一旦你這樣做了,下一步就是開始學習業務領域。通常它是透過構建一種無處不在的語言來完成的。在現實生活中,這是具有挑戰性的,尤其是對於棕地專案。因為如果有一種語言在公司裡已經使用了一段時間,那麼改變它需要很多時間。
  • 在這種情況下,我建議以身作則,繼續與領域專家交談。確保你理解他們。確保您可以使用相同的語言進行交流。嘗試找出一些已經在程式碼中實現的關於業務領域的錯誤假設。一旦你開始培養共享語言,至少在你的程式碼中使用它,當你與人交談時。
  • 在組織中採用這種無處不在的語言需要時間。但是,再次以身作則。確保您製作的任何內容都使用無處不在的語言。與人交流時,訴諸無處不在的語言背後的原則。
  • 為什麼我們要確保每個術語只有一個含義?因為我們要消除假設。我們希望消除溝通中的不一致。因此,當您與人交談時,您可以將這些原則作為嘗試改變他們使用業務術語的方式的理由。

相關文章