Spring Modulith:我們已經達到模塊化成熟度了嗎?

qaseven 發佈 2022-11-19T14:09:52.205181+00:00

每日分享最新,最流行的軟體開發知識與最新行業趨勢,希望大家能夠一鍵三連,多多支持,跪求關注,點讚,留言。

每日分享最新,最流行的軟體開發知識與最新行業趨勢,希望大家能夠一鍵三連,多多支持,跪求關注,點讚,留言。

微服務強制執行強大的模塊邊界,但微服務的缺點很大。在這篇文章中,學習可管理的方法來實現相同的結果。

設計微服務的主要原因之一是它們強制執行強大的模塊邊界。然而,微服務的缺點是如此之大,就像砍掉你的右手來學習用左手寫字一樣;有更多易於管理(並且痛苦更少!)的方法來實現相同的結果。

即使自微服務熱潮開始以來,一些冷靜的頭腦也占了上風。特別是, Spring 框架的開發人員Oliver Drotbohm長期以來一直是模塊化替代方案的支持者。這個想法是保持一個整體,但圍繞模塊進行設計。

許多人湧向微服務,因為他們處理的應用程式類似於義大利麵條拼盤。如果他們的應用程式設計得更好,微服務的吸引力就不會那麼強大。

為什麼要模塊化?

模塊化是一種減少變更對代碼庫影響的方法。這與設計(大型)船舶的方式非常相似。

當水不斷滲入船內時,船通常會因為阿基米德推力的減小而沉沒。為避免一次漏水沉船,它圍繞多個水密隔間設計。如果發生泄漏,它會包含在一個隔間中。雖然它並不理想,但它可以防止船沉沒,使其能夠重新路由到最近的港口,在那裡人們可以修理它。

模塊化的工作原理類似:它在部分代碼周圍設置了邊界。這樣,更改的影響僅限於該部分,不會超出其邊界。

在 Java 中,這些部分稱為包。與船舶的相似之處到此為止,因為包必須協同工作才能達到預期的結果。包不能「滴水不漏」。Java 語言提供了跨包邊界工作的可見性修飾符。有趣的是,最著名的一個public,允許完全交叉包。

設計遵循最小特權原則的邊界需要不斷努力。很可能在項目的初始開發壓力或維護期間隨著時間的推移,努力會滑落,邊界會衰減。

我們需要一種更先進的方式來強制執行邊界。

模塊,模塊無處不在

在 Java 的悠久歷史中,「模塊」一直是一種強制邊界的解決方案。問題是,即使在今天,關於什麼是模塊的定義也有很多。

OSGi始於 2000 年,旨在提供可以在運行時安全部署和取消部署的版本化組件。它保留了 JAR 部署單元,但在其清單中添加了元數據。OSGi 很強大,但是開發 OSGi(模塊的名稱)很複雜。開發人員付出了更高的開發成本,而運維團隊則享受了部署帶來的收益。DevOps 尚未誕生;它並沒有使 OSGi 像它本來應該的那樣流行。

與此同時,Java 的架構師也在尋找將 JDK 模塊化的途徑。與 OSGI 相比,該方法要簡單得多,因為它避免了部署和版本控制問題。Java 9 中引入的 Java 模塊將自身限制為以下數據:名稱、公共 API 以及對其他模塊的依賴性。

Java 模塊適用於 JDK,但由於先有雞還是先有蛋的問題,適用於應用程式的效果就差很多了。為了對應用程式有所幫助,開發人員必須將庫模塊化——而不是依賴自動模塊。但只有當有足夠多的應用程式開發人員使用它時,庫開發人員才會這樣做。上次查的時候,20個常用庫中只有一半是模塊化的。

在構建方面,我需要引用 Maven 模塊。它們允許將一個代碼拆分到多個項目中。

JVM 上還有其他模塊系統,但這三個是最知名的。

執行邊界的嘗試性方法

如上所述,微服務在開發和部署過程中提供了最終邊界。在大多數情況下,它們都是矯枉過正的。另一方面,不可否認的是項目會隨著時間的推移而腐爛。即使是最精美的、重視模塊化的,如果不經常小心,也註定會變得一團糟。

我們需要規則來強制執行邊界,並且需要像測試一樣對待它們:當測試失敗時,必須修復它們。同樣,當一個人違反規則時,就必須改正它。ArchUnit是一種創建和執行規則的工具。一個配置規則並將它們驗證為測試。不幸的是,配置非常耗時,必須不斷維護才能提供價值。以下是遵循六邊形架構原則的示例應用程式的片段:

HexagonalArchitecture.boundedContext("io.reflectoring.buckpal.account")
.withDomainLayer("domain")
.withAdaptersLayer("adapter")
.incoming("in.web")
.outgoing("out.persistence")
.and()
.withApplicationLayer("application")
.services("service")
.incomingPorts("port.in")
.outgoingPorts("port.out")
.and()
.withConfiguration("configuration")
.check(new ClassFileImporter()
.importPackages("io.reflectoring.buckpal.."));

請注意,HexagonalArchitecture該類是基於 ArchUnit API 的定製 DSL 門面。

總的來說,ArchUnit 總比沒有好,但只是勉強如此。它的主要好處是通過測試實現自動化。如果可以自動推斷架構規則,將會有顯著改善。這就是 Spring Modulith 項目背後的想法。

彈簧模塊

Spring Modulith 是 Oliver Drotbohm 的Moduliths 項目(尾隨 S)的繼承者。它同時使用 ArchUnit 和jMolecules。在撰寫本文時,它是實驗性的。

Spring Modulith 允許:

  • 記錄項目包之間的關係
  • 限制某些關係
  • 在測試期間測試限制

它要求一個人的應用程式使用 Spring 框架:它利用後者對前者的理解,通過DI組裝獲得。

默認情況下,Modulith 模塊是一個與SpringBootApplication-annotated 類位於同一級別的包。

|_ ch.frankel.blog
|_ DummyApplication // 1
|_ packagex // 2
| |_ subpackagex // 3
|_ packagey // 2
|_ packagez // 2
|_ subpackagez // 3

  • #1:應用類
  • #2:模塊化模塊
  • #3:不是模塊

默認情況下,一個模塊可以訪問任何其他模塊的內容,但不能訪問其他模塊的子包。

C4

var modules = ApplicationModules.of(DummyApplication.class);
new Documenter(modules).writeModulesAsPlantUml();

要在模塊訪問常規包時中斷構建,請verify()在測試中調用該方法。

var modules = ApplicationModules.of(DummyApplication.class).verify();

一個可以玩的樣本

我創建了一個示例應用程式來玩:它模擬在線商店的主頁。主頁是使用 Thymeleaf 在伺服器端生成的,並顯示目錄項和新聞源。後者也可以通過客戶端調用的 HTTP API 訪問(我懶得寫代碼)。項目顯示有價格,因此需要定價服務。

每個功能 - 頁面、目錄、新聞源和定價 - 都位於一個包中,該包被視為一個 Spring 模塊。Spring Modulith 的文檔功能生成以下內容:

讓我們檢查一下定價功能的設計:

目前的設計有兩個問題:

  • PricingRepository可以在模塊外訪問
  • 泄漏PricingServiceJPAPricing實體

我們將通過封裝不應公開的類型來修復設計。我們將Pricing和PricingRepositorytypes 移動到模塊的internal子文件夾中pricing:

如果我們調用該verify()方法,它會拋出並中斷構建,因為Pricing無法從pricing模塊外部訪問:

純文本1個模塊「home」依賴於模塊「定價」中的非公開類型 ch.frankel.blog.pricing.internal.Pricing!


讓我們通過以下更改來解決違規問題:

結論

通過嘗試示例應用程式,我確實喜歡 Spring Modulith。

我可以看到兩個突出的用例:記錄現有應用程式和保持設計「乾淨」。後者避免了應用程式隨時間的「腐爛」效應。這樣,我們可以保持設計的預期並避免義大利麵條效應。

錦上添花:當我們需要將一個或多個功能切入他們的部署單元時,這很棒。這將是一個非常直接的舉措,不會浪費時間來理清依賴關係。Spring Modulith 提供了一個巨大的好處:將每個有影響力的架構決策推遲到最後一刻

感謝 Oliver Drotbohm 的審閱。

您可以在 GitHub 上找到原始碼。

走得更遠

  • 介紹 Spring 模塊
  • 快速開始
  • 參考文檔
關鍵字: