向領域驅動設計前進: 如何使用DDD實現從單體到微服務遷移? -Kevin Mas Ruiz

banq發表於2019-12-03

如果您的公司建立在單體monolith之上。由於您的業務知識在內部傳播,因此這種單體monolith可能是您的最佳資產,但是由於多年的技術債務和團隊在相互溝通的情況下發布程式碼,這些是髒的。

單體程式緩慢,不透明,容易出錯,未經測試。釋出新程式碼時開發人員和sysops團隊都開始擔心,因此最終會建立和定義繁重的流程以及漫長的釋出週期和漫長的手動測試過程。這是因為我們需要安全地釋出新版本,我們不能中斷生產,因為恢復或回滾很困難。

但是,單體仍然存在,可以為您帶來大部分收入,但也會影響團隊的表現。您如何改善主要收入來源並優化團隊以實現長期可預測性和業務發展?這是DDD派上用場的地方。

但是,在使用DDD之前,我們需要了解為什麼單體程式仍在工作併為大量流量提供服務。因為單體本身不是一個錯誤的根源,問題出在耦合造成大泥球。

單體非常便宜且用途廣泛。單體架構能夠長期存在的原因是,單體架構中的決策在中期是可恢復的。因為資料和程式碼在一個地方,所以重構更簡單(可以使用您最喜歡的IDE來完成),並且資料傳輸便宜。例如,讓我們從以下用例開始:

我們是像Amazon這樣的線上購物平臺,並且我們出售圖書。在產品的第一個迭代期間,我們不會驗證倉庫中書籍的庫存,因為我們沒有收到那麼多的採購訂單,因此我們可以手動修復損壞的訂單。我們最終得到以下架構圖。

向領域驅動設計前進: 如何使用DDD實現從單體到微服務遷移? -Kevin Mas Ruiz幾個月後,我們的業務開始增長,每分鐘開始有幾筆訂單,黑色星期五和聖誕節期間的訂單量達到頂峰。由於我們的書籍缺貨,我們無法處理越來越多的訂單中斷。我們決定實現一個StockService,該服務將在結帳過程中驗證我們要購買的書籍是否仍有庫存。

向領域驅動設計前進: 如何使用DDD實現從單體到微服務遷移? -Kevin Mas Ruiz

如您所見,新增新服務和業務規則非常便宜:只需新增一些新類和對其他服務的依賴關係就足夠了。我們沒有做出艱難的決定,我們只是遵循單體中已經存在的模式。我們可以這樣做是因為:

  • 單體移動資料很便宜
  • 單體中的決策僅限於單個過程
  • 單體具有明確且通用的模式
  • 可以使用IDE的幫助來重構單體結構

因此,我們正在做的事情是向前推進,而不是做出複雜的設計決策並提供新功能,從而增加了技術負擔。這使小型團隊可以快速迭代產品,但是,隨著團隊數量的增長,這是一個問題。原因是因為不同的團隊將需要來自不同服務的資料和邏輯來滿足使用者需求。

向領域驅動設計前進: 如何使用DDD實現從單體到微服務遷移? -Kevin Mas Ruiz

如您所見,在UserService上,團隊A和團隊C之間存在重疊,因為他們倆都需要來自其使用者的資料以保證其功能。面對這種情況的方法有3種,在下表中分為三類:求職,協作和效果。

所有權                       合作                       影響
其中一個團隊擁有UserService    當其他團隊需要功能時,詢問所有者團隊    拖延團隊共享工作積壓的速度
其中一個團隊擁有UserService    當其他團隊需要功能時,進行公關        減慢編寫PR的團隊的速度,因為需要其他團隊來審查功能
共享所有權                需要進行日常交流和協作才能實現新功能    減慢團隊,因為他們有一個積壓的共享

因為沒有解決此問題的簡單方法,所以解決方案是拆分整體。要了解在同一程式碼中擁有不同團隊的複雜性,只需參考使兩個執行緒在記憶體中使用同一組數百個變數的複雜性即可。

因此,經過幾個月或幾年的工作,我們將這種單體整體分成了微服務。我見過的最常見的分割整體的方法是定義資料邊界的策略。例如,所有與使用者相關的資料將最終出現在UserService中,StockService中的庫存資訊等等。

這種方法的問題在於:

  • 它可能看起來像域驅動設計,但事實並非如此,因為它基於資料,而不是業務知識。
  • 它可能看起來像微服務架構,但事實並非如此,因為服務之間的耦合度很高,因此服務和團隊都不是自治的。

而且,我們構建了一個分散式的單體,它無法輕鬆移動資料並且無法使用IDE進行重構,因此基礎架構成本也更高。那麼,我們如何確保不會出現這種情況呢?

我會做的最基本的建議是根據(業務領域)知識而非資料來劃分您的架構。公司如何構造知識完全取決於人員和他們所從事的業務,但是可以嘗試幾種廉價的探索模式。

要應用這些模式,我們需要在業務中將其視為業務平臺:我們沒有產品,我們有一套產品。這些產品是適用於角色的一組功能。例如,基於此模式,我們可以定義購物平臺,如下圖所示:

向領域驅動設計前進: 如何使用DDD實現從單體到微服務遷移? -Kevin Mas Ruiz每個產品的成功都應獨立衡量和發展。但是,正如您所注意到的,某些跨產品模組可能存在依賴性。例如,一鍵購買可能取決於庫存和使用者資訊,例如普通購買產品。我們如何確保那些依賴關係不會影響團隊績效並且我們不重複邏輯?

首先,我們需要將產品切成模組,以瞭解可能發生耦合的地方:

向領域驅動設計前進: 如何使用DDD實現從單體到微服務遷移? -Kevin Mas Ruiz

正如你所看到的,一鍵購買1 click purchase和採購Purchase需要來自同一來源的資訊。但是,如果我們更深入,我們將看到差異:

  • 會同時使用1 click purchase和standard purchase的購買者是否相同?
  • 我們需要的書籍資訊在兩個過程中是相同的嗎?
  • 兩種產品中庫存資訊是否以相同方式相關?
  • 兩種產品中的裝運資訊是否使用相同的方式?

如果是這些問題yes,那麼我們可能要製造的是同一產品的兩倍,因此至少其中一些最有可能no, they are different。讓我們仔細看看:

資料來源    一鍵購買                   採購
購買者   只有先買過其他書的人             大家
圖書    我們需要所有可能的資訊            我們需要所有可能的資訊
庫存    我們只需要知道我們是否有足夠的庫存    我們需要知道什麼時候庫存不足才能推動使用者購買
運輸    只送貨上門                    送貨上門和物流公司

在我們的案例中,只有書籍具有相同的特徵,它們不是行為而是資料。這種情況意味著我們的產品有一個有界的上下文,在這些上下文中,對使用者問題的瞭解和理解是不同的。這是有道理的,因為我們將知識連結到產品,產品連結到角色。

當我們在有界上下文之間共享資訊時,我們應該儘可能支援團隊績效。這意味著有時我們需要重複知識。這在其他系統中很常見:我們在浴室和廚房都有洗手池。

有多種方法可以跨有限的上下文共享資料,我個人更喜歡使用基於事件的體系結構(如SQS)或資料流傳輸平臺(如Kafka,進行狀態採購)進行資料流傳輸。您還可以使用更簡單的工具(例如資料庫檢視)共享資訊(如果您擁有分散式資料庫(例如Yugabyte或AWS RDS))。

即使這些模式看起來很浪費,也請考慮一下我們的身體如何運作。我們的身體總是在向我們的肌肉和器官輸送血液,以確保可得性和健康。現在考慮一下,在您的身體中,每當肌肉想要運動時,是否需要向您的心臟請求一些血液,因此您的心臟需要向您的肺部請求氧氣。現在每秒鐘重複一次。

但是,資訊需要來自其他有界上下文(例如,新買家的註冊流程),並且他們需要資訊的所有者。我們可以發泡,漂洗,重複和分裂更多的產品,直到我們擁有更小的模組,這些模組對於我們的團隊來說更容易處理。例如,下圖顯示了在假想的圖書購物平臺上的產品和依存關係:

向領域驅動設計前進: 如何使用DDD實現從單體到微服務遷移? -Kevin Mas Ruiz

如果我們發現大部分的相關資訊暴露給其他產品(有界上下文),我們可以抽象產品到一個更通用的上下文(一般用於角色,不用於業務),並公開一個更簡單的服務(例如UserService)。

因此,總而言之,我想分享一些我認為有用的觀點:

  • 在平臺中思考可以使我們更好地拆分業務。
  • 將產品連結到角色和有界上下文,可以使邊界明確。(banq注:產品=有界上下文)
  • State-sourcing和事件驅動的體系結構對於構建分散式和可用平臺至關重要。
  • 團隊不應共享程式碼,而應共享一個公共平臺。

 

相關文章