引入 Spring Modulith

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

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

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

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

示例

讓我們來看一個具體示例。假設我們需要開發一個電子商務應用程式,我們從兩個邏輯模組開始。一個訂單模組處理訂單,一個庫存模組跟蹤我們銷售的產品的庫存。本文的主要關注點是訂單完成後需要更新庫存的用例。我們的專案結構可能看起來像這樣( 表示公共型別,- 表示私有型別)

□ 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 test 註解類似,開發人員可以透過在整合測試上使用 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 社群所有即將舉行的活動。

檢視全部