領域分區:如何在微服務和單體之間找到健康的平衡
深入瞭解適合大多數中小型公司的架構模式:領域分區。
只要工程師一直在編寫代碼,就一直在討論構建一組系統的最佳方法。兩種最常見的模式是單體和微服務。它們都有其優點和缺點,但是否有其他選擇可以尋求兩者之間的平衡?我相信是這樣——域分區服務。
什麼是域分區?
在我們深入研究域分區是什麼之前,快速介紹微服務和單體的標準架構很重要。
通常,微服務架構鼓勵創建相對較小的服務,每個服務都有自己的存儲庫,所有這些都以某種方式通過網絡進行通信——例如 HTTP 或通過消息代理。在頻譜的另一端,單體應用通常是一個大型服務,全部位於單個存儲庫中,在內存中調用系統不同部分之間的通信。
那麼,域分區如何適應?從本質上講,它們是兩者之間的中間地帶。您最終會得到一組中型服務,而不是大量小型服務或單個單體服務,這些服務具有不同且明確定義的部分 - 分區。
讓我們通過一個示例來看看使用我們的三種架構模式是如何實現的。在進行了假設域建模練習之後,我們定義了一個新域 — 購買域。它處理四個功能領域:付款、運輸、退貨和促銷。
我們已經有許多其他域已經實施,例如,目錄和客戶域。
不要太擔心這些域是否有意義,它們在某種程度上是爲了說明這種模式。下面的架構也是如此,它們在依賴項方面絕對沒有經過深思熟慮,但只是爲了說明要點。
單體 monoliths
在單體應用中,這很容易——所有與購買域相關的代碼都將進入單體應用。至少我們希望每個子域都具有命名空間,但這可能是大多數單體應用所能達到的。
代碼庫的不同部分之間通常沒有明確的界限,這會產生問題,包括整個單體的級聯故障,以及模糊的所有權和關於誰擁有什麼的冗長討論。
微服務
在微服務架構中,會有很多選擇,但很可能你最終會得到四個新服務。您可能決定將退貨和運輸結合起來,可能只會產生三項新服務,但無論哪種方式,我們最終都會提供許多新服務。
服務之間需要某種形式的通信,例如在付款後,需要請求發貨。
領域分區
通過域分區,我們最終會在兩者之間取得平衡。將創建一項新服務,在該服務中將有四個分區——每個子域一個分區。下面有更多技術層面的實現細節,所以我不會在這裏介紹。
架構中的其他服務將通過 HTTP 或消息代理以類似於微服務架構的方式與購買服務進行通信,而分區本身通常通過內存調用進行通信。沒有什麼可以阻止通過消息代理進行通信,但是,如果有充分的理由這樣做(例如,減少直接耦合,更快地處理 API 調用)
另一個重要方面是公開單個 API,通常通過 API 網關。這並不意味着每個分區不能有自己的內部 API,有自己的控制器,但外部服務會將它們視爲該域的單個、統一的 API。
域分區帶來什麼價值?
我在上面提到了這種架構的一些好處,但值得更詳細地討論它們。
- 架構複雜性
如果您曾經使用過微服務,您可能知道試圖瞭解所有部分如何組合在一起的痛苦。當出現問題時,這尤其成問題:問題或錯誤究竟在哪裏?
當您進入異步工作流時,問題會加劇,尤其是在可觀察性較差的情況下。例如,什麼服務消耗事件 X?
此外,新加入者經常被所有不同的服務所淹沒,並試圖瞭解它們是如何組合在一起的。通常,有一些圖表可以嘗試幫助解決這種情況,但它們通常已經過時或無法提供全貌。
另一方面,域分區服務仍然存在一些複雜性,因爲我們仍在處理分佈式架構中的許多服務。因此,與單體相比,它在架構上更加複雜。
- 開發時間
根據我的經驗,當您考慮廣泛的場景時,域分區服務架構模式可以縮短開發時間。
我認爲可以公平地說單體應用的開發時間是三者中最慢的,因爲找出需要進行更改的位置以及瞭解更改的影響範圍可能很棘手——尤其是需要做什麼進行測試以確保更改後沒有任何損壞?
如果更改特別小且範圍非常好(例如,針對服務),那麼微服務將略微超出域分區架構。
但是,如果您需要修改兩個或更多服務,那麼這將比域分區架構花費更多的時間。這就是爲什麼,根據我的經驗,域分區架構在開發時間方面通常是最快的,因爲更改經常跨越分區。在微服務架構中,這意味着修改兩個不同的服務。
我要指出的最後一點是,單體應用通常具有龐大而緩慢的測試套件,這使得像 TDD 這樣的實踐難以遵循,而且通常很難在本地運行大部分測試套件。
微服務和域分區服務都不會遇到這個問題,但是它們的測試套件通常很快——我每天工作的服務的測試套件有四個域分區,在本地運行只需一分鐘多一點。最慢的單個規範在幾秒鐘內運行,而我處理的單個規範可能需要超過 5 分鐘的整體應用程序。
- 部署
單體應用通常在三種模式中部署最慢,這通常是因爲它們必須處理整個架構的所有問題——通常包括部署額外的雲服務、編譯資產等。
相反,微服務應該快速部署,因爲它們應該很小,並且因爲它們涵蓋了非常具體的職責,需要部署的步驟很少,至少與單體相比是這樣。
雖然域分區服務比微服務大,但它們的部署時間通常不會與微服務架構大不相同。唯一的區別是通過分區有比傳統微服務更多的代碼——這在大多數情況下不會顯着影響部署時間。
也有例外,例如如果某個分區需要一些其他分區不需要的額外部署步驟,但總的來說,域分區服務和微服務之間的部署時間在我的經驗中可以忽略不計。
- 成本
工程師經常忘記的一個指標是運行服務的成本。考慮到當今大多數公司都在使用容器,我們將從這個角度來解決它。
如果您不處理大量吞吐量(讓我們現實一點,大多數公司不必處理大量吞吐量),Monolith 實際上相對具有成本效益。這是因爲您不需要部署多組服務,相反,您只需要爲單體應用部署足夠的容器來處理其所有工作負載。
然而,基於微服務的架構很快就會變得昂貴。每個微服務至少需要 2-3 個容器來確保高可用性,因此如果您有 10 個服務,那麼至少需要 20-30 個容器用於生產,並且在考慮臨時環境時可能會增加一倍。這只是開始,當您考慮還需要其他資源(例如負載平衡器)時。
與其他類別類似,域分區服務位於中間的某個位置。您可能需要比單體應用更多的容器,但沒有微服務那麼多,因爲與微服務架構相比,我們的服務數量減少了。
概述
有許多領域和技術可以嘗試分析任何架構,我已經涵蓋了上面的主要領域。
這種架構的一個重要優點也值得強調,它能夠相對輕鬆地將分區提取到自己的服務中。您需要或想要這樣做的原因可能有很多,例如:
-
您可能分區不正確,需要將一個分區移動到另一項服務。
-
服務中的一個分區可能具有隨着您的架構的發展而出現的特定需求。例如,一個分區需要處理比給定服務中的其他分區多得多的吞吐量。在這種情況下,提取該分區並擴大該服務的容器數量可能是有意義的,而不是擴大現有服務(這可能更昂貴)。
總的來說,我相信域分區服務是大多數中小型公司(絕大多數行業)的絕佳選擇。它將領域置於架構的中心,使工程師能夠快速完成他們的最佳工作,並且對於大多數用例和團隊規模而言相對而言可擴展。
您如何決定如何分區您的域?
這種架構與其他架構的主要區別在於它的分區,因此確定如何分區至關重要。
對於每一項需要大量添加或更改功能的重要工作,這是一個考慮該工作在何處以及如何適應的機會。因此,其基本的領域驅動設計 得到了實踐。
一旦進行了域建模練習,新功能應該去哪裏通常就會變得很明顯。以下是一些需要考慮的事項,按此順序:
-
這個新功能是否屬於現有分區?
-
這個新功能是否屬於現有域?(很可能已經有支持它的服務)
如果其中任何一個的答案是肯定的,那麼您可能已經找到了放置新功能的位置。如果兩者的答案是否定的,您可能需要創建一個新服務!
還有一些其他考慮因素,例如新功能是否具有定製要求(例如,新的雲服務,或者必須處理與任何現有服務 / 分區相比的大量吞吐量),這可能會影響您的決定。如果有令人信服的理由,那麼決定爲一個域提供兩個服務而不是一個服務是完全合理的。
域分區的實現
這部分值得單獨寫一篇文章,在未來,我計劃更詳細地充實這一部分。現在,我想專注於架構的高級視圖,但是,我認爲該架構有一個非常重要的構造對其成功至關重要。
在域分區之間實施清晰而嚴格的邊界至關重要。您如何執行此操作將因語言而異,例如在 Java 中,每個分區可以爲每個分區公開少量(最好是一個)公共類——其餘的應該是包私有的,因此這些類在外部無法訪問那個包裹。這種可見性配置然後爲您提供了一個分區。
這確保了分區之間的清晰接口,並允許在不影響其他依賴的分區的情況下重構分區的內部。此外,如果需要將一個分區提取到另一個服務,任何依賴分區都可以輕鬆遷移以調用新服務而不是內存調用。
最後,API 網關將所有內容整合在一起。這意味着服務之間的集成點較少,每個都有自己的身份驗證要求和配置。您最終仍會得到相同的端點(例如 POST /payments 和 GET /promotions),但它們會從單個服務公開。
概括
我希望你覺得這篇文章有幫助。我從 Shopify 解構單體應用的旅程、Uber 減少微服務數量的旅程、關於微服務問題的大量演講和研討會,以及我自己實現域分區的經驗中汲取靈感。
這兩篇文章都值得一讀,因爲它們正在解決類似的問題,儘管方式略有不同。Shopify 正在採用模塊化單體方法,而 Uber 正在採用一種他們稱之爲面向領域的微服務架構的方法。
在 Uber 的案例中,他們仍然維護着大量的微服務,但它們被分組在一個 API 網關後面——類似於這裏概述的方法,但有細微的不同,因爲我們在大多數情況下將其分組爲單個服務。優步可能出於其可擴展性要求而做出此選擇,但大多數公司並沒有這樣的擔憂。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/FFh1_AiFoXRlZN8PJD3NWw