上下文對映關係中如何解耦特定和通用的領域? - Nick Tune

banq發表於2019-08-02

您正在構建一個新系統,並且您的團隊的兩名成員各自提出了用於傳送通知的兩種架構,哪一個是正確?如何選擇?

  • 第一個開發人員提出的是推送模型:有界上下文應指示通知上下文傳送通知。專門的通知上下文應該只是遵循來自其他上下文的命令,並在指示時傳送通知。

  • 第二個開發人員不喜歡推送模型,並提出了一個精心設計的解決方案:當有界上下文引發事件時,通知上下文應該監聽這些引發的事件並確定何時傳送通知。

您將如何構建解決方案?更重要的是,您如何在團隊中解決此決定?您將如何設計支援短期目標和長期演變的最有效架構?

擁有團隊中的DDD技能是一個主要優勢。能夠分析您的域以瞭解核心,支援和通用功能,使您能夠進行合理的工程權衡。

讓我們以DDD的角度進一步探索這個場景。

領域能力分析

選項1(推送命令)的引數是泛型(通知)不應該依賴於特定的有界上下文。如果事件在許多域中是通用的,那麼邏輯上如果它與某個特定領域中的事件進行耦合是錯誤的。

除了抽象推理之外,這個啟發式警告我們是什麼?我們需要注意哪些工程後果?

我們希望避免兩種情況:

  1. 避免特定於域的邏輯洩漏到通用上下文中,從而導致共同變化
  2. 由於與特定領域的有界上下文過於緊密耦合,無法使用現成的解決方案重用或替換通用上下文

耦合領域和通用責任

使用選項1,域特定API和域通用API之間不存在共同變化。如果需要新通知,則僅更改特定於領域的上下文。但是選項2的情況並非如此。

使用選項2(精心設計),如果引入需要通知的新事件,則特定於域的API將需要釋出新事件,並且通知服務將需要訂閱該事件併傳送通知。這在感覺上不對 : 領域的知識隱藏在通用上下文中。

隔離通用功能

如果通知服務是真正的通用服務並在許多團隊甚至組織中重用,則需要了解數百個領域事件。有這麼多團隊取決於通知上下文,它肯定會成為一個瓶頸,減少了它在整個組織中重用的可能性。

另一項設計反饋是,我們無法用現成的解決方案取代通用上下文。如果它在許多領域真正具有通用性,但我們無法用具有更多功能的現成解決方案取代它,並且執行成本更低,設計就會給出錯誤的反饋。

那麼從特定命令到通用命令是最佳實踐嗎?

所有證據都表明選項1是正確的:特定於領域的上下文應該將命令傳送到領域域通用上下文以將它們與領域邏輯分離。但是,我們的分析很淺薄。我們需要進一步分析這個領域,讓我們有信心做出良好的工程選擇。

更深入的領域分析

更仔細地觀察選項1,每個需要傳送通知的特定於域的上下文都承擔了額外的責任。它需要知道何時傳送通知以及要傳送的通知型別。

將通知邏輯分散在所有有界上下文中是否明智?除了糾結的程式碼,如果通知接近變化,這可能意味著跨多個團隊進行大規模協調。

如果每個團隊採用他們自己的通知方法,那麼風險或不一致以及重複/浪費也會增加。共享庫是可能的解決辦法,但並不能解決所有問題,也會帶來妥協。

只要難以確定職責責任是否屬於某種情況,請進一步放大並分解責任。尋找可以分解的子責任 - 也許有一個隱藏的領域概念。當一個概念不能完全適合單個有界背景時,進一步分析它以確定子責任。也許有一個隱藏的域概念可以產生一個乾淨分割槽的模型。

放棄傳送通知的有爭議的責任,是否會有一個缺失的概念?也許有第三個概念將特定領域與領域通用相關聯,以提供更優雅的模型。

上下文對映器

將特定領域與領域通用進行解耦的模式是領域上下文對映器(Domain Mapper Contexts),當這個上下文承擔此角色時,它會偵聽特定的領域事件並將它們對映到傳送到通用上下文的命令。

請注意這種模式的權衡取捨與最初兩種選擇的優缺點相比較。它提供了兩者的優點 - 可以自由地更改通知的傳送方式,而不會因為與通知相關的複雜性而混淆每個特定於域的上下文。

考慮這種情況:內部通知系統將由現成的解決方案取代。域對映器將所有命令路由到新的通知服務,而不會影響任何特定於域的上下文。

考慮一種情況:要新增新通知。註冊將在對映器中配置:當{domain event}觸發{notification}時,這樣{notification}通知變成領域通用功能 :許多領域利用電子郵件或推送通知,也可以使用Twitter上的通知首選項 。

為什麼稱為上下文Mapper?

這種模式的命名很重要。在一個域內發生的操作將對映到在另一個域中發生的操作(通用上下文在許多域中是通用的,因此部分在另一個域中)。

您可能與其他模式(如訊息傳遞翻譯器)有相似之處,但翻譯意味著某種等價; 翻譯的值是原始值的不同表示。使用對映器,情況並非如此。

對映器更像是一個監聽器,可以觀察域內發生的情況。它決定如何使用另一個域中的操作響應域中的事件。

領域上下文對映器閘道器

如果您決定使用SaaS解決方案替換自定義構建的通用上下文,則域對映器上下文將成為域上下文對映器閘道器(Gateway Domain Mapper Context)。它現在位於系統的邊緣並跨系統邊界進行通訊。它是資訊流入和流出系統的途徑。實現可能看起來相同,但是使用更精確的術語對於您更清楚地傳達架構非常有用。

域對映器的工程權衡

域上下文對映器多了一個附加對映層意味著現在涉及三個協作者。意味著更多可能失敗的發生可能。

也有共同變化。當引入新事件或現有事件發生更改時,擁有事件的特定於域的上下文和對映器上下文都需要更改。有一種廣泛使用的解決方案可以最大限度地降低這種耦合。

自助式的領域上下文對映器

經常出現的一種模式是自助服務有界上下文:扮演此角色的有界上下文允許消費者利用上下文的功能,而不會被擁有上下文的團隊阻礙。

第一種變體是通過源控制的編譯時機制,第二種是通過API呼叫的執行時機制。

在通知方案中,自助服務上下文可以提供DSL。擁有特定於域的上下文的團隊將建立包含配置檔案中的更改的拉取請求(甚至更好 - 編譯和測試的程式碼),使用DSL配置域事件之間的對映以及通知被髮送。

使用動態版本,將通過API呼叫或UI執行相應的配置。當動態配置成為可能並且域上沒有編譯時依賴性時,上下文將變為完全通用。這是一種常見的進化模式(從定製到商品)。

釋出的語言

在這種情況下要考慮的另一種DDD模式是已釋出的語言。觸發通知的任何事件都應構成已釋出語言的一部分。

已釋出的語言是有界上下文生成的訊息格式的合同或協議。確保需要通知的事件是已釋出語言的一部分意味著應該更加註意確保向後相容性並通知消費者未來的變化。

這裡顯示的每個模式都是有效的,並且可以在各種系統中成功使用。域對映器上下文是另一種具有明確權衡的模式,您可以使用它來探索替代建模可能性。

尋找超級使用者

在天真或膚淺的層面上建模域很容易。當聽到像“通知”這樣的單詞時,很容易陷入銜接名稱謬誤,假設因為一個單詞聽起來像一個單一的概念,它必須由我們系統中的單個有界上下文表示。

當我們使用DDD進行更深層次的分析時,我們希望將領域特定的和領域通用的概念分開,通常我們會發現有多種方法可以對域進行建模,包括具有多個有界上下文 - 從通用中描述特定領域知識 - 在一個超級環境中。

將領域特定與領域通用分離不是關於建立遵循古老DDD規則的漂亮抽象模型,而是關於將由於不同原因而一起變化的概念分離,以便我們可以設計權衡以允許系統更容易地進化。

為了獲得更深入的領域洞察力,我建議將DDD技能和DDD活動納入團隊的工作方式。

相關文章