Spring Modulith 簡介

工程 | Oliver Drotbohm | 2022 年 10 月 21 日 | ...

在設計軟體系統時,架構師和開發人員有很多架構選項可供選擇。基於微服務的系統在過去幾年中已變得無處不在。然而,單體式模組化系統的概念最近也重新流行起來。無論最終選擇哪種架構風格,組成整個系統的各個應用程式都需要其結構能夠不斷發展,並能夠跟隨業務需求的變化。

傳統上,應用程式框架透過提供與技術概念對齊的抽象,例如 Spring Framework 的刻板印象註解 (@Controller@Service@Repository 等),來提供結構指導。然而,將重點轉移到 使程式碼結構與領域對齊已被證明可以產生結構更好的應用程式,這些應用程式最終更容易理解和維護。到目前為止,Spring 團隊已經提供了關於我們建議如何構建您的 Spring Boot 應用程式的口頭和書面指導。我們決定我們可以做得更多。

Spring Modulith 是一個新的、實驗性的 Spring 專案,它支援開發人員在程式碼中表達這些邏輯應用程式模組,並構建結構良好、與領域對齊的 Spring Boot 應用程式。

一個範例

讓我們看看 一個具體的例子。假設我們需要開發一個電子商務應用程式,我們從兩個邏輯模組開始。order 模組處理訂單處理,而 inventory 模組追蹤我們銷售產品的庫存。我們這篇文章的主要重點是,一旦訂單完成,就需要更新庫存。我們的專案結構看起來會像這樣( 表示公共類型,- 表示私有類型)

□ Example
└─ □ src/main/java
   ├─ □ example
   │  └─ ○ Application.java
   │
   ├─ □ example.inventory
   │  ├─ ○ InventoryManagement.java
   │  └─ - InventoryInternal.java
   │
   ├─ □ example.order
   │  └─ ○ OrderManagement.java
   └─ □ example.order.internal
      └─ ○ OrderInternal.java

這種安排從通常的骨架開始,一個包含 Spring Boot 應用程式類別的基本套件。我們的兩個業務模組反映在直接子套件中:inventoryorder。庫存使用相當簡單的安排。它只包含一個套件。因此,我們可以使用 Java 可見性修飾符來隱藏其他模組中的程式碼對內部元件的存取,例如 InventoryInternal,因為 Java 編譯器限制對非公共類型的存取。

相反,order 套件包含一個子套件,該子套件公開了一個 Spring bean,在我們的例子中,需要公開,因為 OrderManagement 引用了它。不幸的是,這種類型安排排除了編譯器作為防止非法存取 OrderInternal 的助手,因為在普通的 Java 中,套件不是分層的。子套件不會隱藏在父套件中。然而,Spring Modulith 建立了應用程式模組的概念,預設情況下,它由一個 API 套件(直接位於應用程式主套件下的套件,在我們的例子中是 inventoryorder)和可選的巢狀套件(order.internal)組成。後者被認為是內部的,並且位於這些模組中的程式碼無法被其他模組存取。可以調整這個應用程式模組模型以適合您的喜好,但讓我們在這篇文章中堅持使用這個預設安排。

驗證模組化結構

為了驗證應用程式的結構以及我們的程式碼是否符合我們定義的結構,我們可以建立一個測試案例,建立一個 ApplicationModules 實例

class ModularityTests {

  @Test
  void verifyModularity() {
    ApplicationModules.of(Application.class).verify();
  }
}

假設 InventoryManagement 引入了對 OrderInternal 的依賴,該測試將會失敗,並顯示以下錯誤訊息,從而中斷建置

\- Module 'inventory' depends on non-exposed type ….internal.OrderInternal within module 'order'!
InventoryManagement declares constructor InventoryManagement(InventoryInternal, OrderInternal) in (InventoryManagement.java:0)

初始步驟 (ApplicationModules.of(…)) 檢查應用程式結構,應用模組慣例,並分析每個應用程式模組的哪些部分屬於其提供的介面。由於 OrderInternal 不位於應用程式模組的 API 套件中,因此從 inventory 模組對它的引用被視為無效,因此,在下一步中(呼叫 ….verify())會將其報告出來。

驗證以及對應用程式模組模型的基本分析是透過使用 ArchUnit 實現的。它將拒絕應用程式模組之間的循環依賴,存取被視為內部的類型(根據上面的定義),並且可以選擇僅允許引用透過在應用程式模組的 package-info.java 上使用 @ApplicationModule(allowedDependencies = …) 明確允許列入白名單的模組。有關如何定義應用程式模組邊界以及它們之間允許的依賴關係的更多信息,請參閱參考文檔

應用程式模組整合測試

能夠建立應用程式結構的模型對於整合測試也很有幫助。與 Spring Boot 的 slice 測試註解類似,開發人員可以使用 Spring Modulith 的 @ApplicationModuleTest 在整合測試中指示他們只想包含特定應用程式模組的元件和配置。這有助於隔離整合測試,使其免受其他模組中測試的變更和潛在失敗的影響。整合測試類別看起來會像這樣

package example.order;

@ApplicationModuleTest
class OrderIntegrationTests {

  // Test methods go here
}

與使用 @SpringBootTest 執行的測試案例類似,@ApplicationModuleTest 找到使用 @SpringBootApplication 註解的應用程式主類別。然後,它初始化應用程式模組模型,找到測試類別所在的模組,並預設為僅引導該模組。如果您執行此類別,並將 org.springframework.modulith.test 的日誌層級提高到 DEBUG,您將看到如下所示的輸出

… - Bootstrapping @ApplicationModuleTest for example.order in mode STANDALONE (class example.Application)…
…
… - ## example.order ##
… - > Logical name: order
… - > Base package: example.order
… - > Direct module dependencies: none
… - > Spring beans:
… -   + ….OrderManagement
… -   + ….internal.OrderInternal
…
… - Re-configuring auto-configuration and entity scan packages to: example.order.

測試執行報告引導哪個模組、其邏輯結構,以及它最終如何更改 Spring Boot 引導以僅包含該模組的基本套件。可以 調整它以明確包含其他應用程式模組,或引導整個模組樹。

使用事件進行模組間互動

將整合測試的重點轉移到應用程式模組通常會揭示它們的輸出依賴關係,通常透過引用位於其他模組中的 Spring bean 建立。雖然可以模擬這些依賴關係(透過使用 @MockBean)來滿足測試執行,但更好的方法通常是用已發佈的應用程式事件替換跨模組 bean 依賴關係,並使用先前明確呼叫的元件來使用該事件。

我們的範例已經以這種首選方式排列,因為它在呼叫 OrderManagement.complete(…) 期間發佈了 OrderCompleted 事件。 Spring Modulith 的 PublishedEvents 抽象允許測試整合測試案例是否導致發佈特定的應用程式事件

@ApplicationModuleTest
@RequiredArgsConstructor
class OrderIntegrationTests {

  private final OrderManagement orders;

  @Test
  void publishesOrderCompletion(PublishedEvents events) {

    var reference = new Order();

    orders.complete(reference);

    // Find all OrderCompleted events referring to our reference order
    var matchingMapped = events.ofType(OrderCompleted.class)
        .matchingMapped(OrderCompleted::getOrderId, reference.getId()::equals);

    assertThat(matchingMapped).hasSize(1);
  }
}

用於結構良好的 Spring Boot 應用程式的工具箱

Spring Modulith 提供慣例和 API,用於在您的 Spring Boot 應用程式中宣告和驗證邏輯模組。除了上述功能外,第一個版本還有更多功能可以幫助開發人員建構其應用程式

您可以在 其參考文檔 中找到有關該專案的更多信息,並查看 範例專案。儘管已經提供了廣泛的功能集,但這僅僅是旅程的開始。我們期待您對該專案的回饋和功能想法。另外,請務必在 Twitter 上關注我們,以獲取有關該專案的最新社群媒體更新。

關於 Moduliths

Spring Modulith (沒有尾隨的 "s") 是 Moduliths (帶有尾隨的 "s") 專案的延續,但使用 Spring Boot 3.0、Framework 6、Java 17 和 JakartaEE 9 作為基準。舊的 Moduliths 專案目前提供 1.3 版本,與 Spring Boot 2.7 相容,並將在相應的 Boot 版本維護期間保持維護。 我們利用過去兩年獲得的經驗,簡化了一些抽象概念,調整了一些預設值,並決定從更先進的基準開始。 有關如何遷移到 Spring Modulith 的更詳細指南,請參閱 Spring Modulith 參考文檔

獲取 Spring 電子報

與 Spring 電子報保持聯繫

訂閱

領先一步

VMware 提供培訓和認證,以加速您的進程。

了解更多

獲得支持

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二進制檔案。

了解更多

即將舉行的活動

查看 Spring 社群中所有即將舉行的活動。

查看全部