Spring Modulith 1.3 的新特性是什么?

工程 | Oliver Drotbohm | 2024 年 11 月 22 日 | ...

经过半年的开发,Spring Modulith 1.3 GA 已发布。它包含了大量新功能、改进,以及最重要的是社区贡献。让我带你了解其中一些最有趣的内容。

基线升级

像往常一样,Spring Modulith 的新次要版本升级到了 Spring Boot 和 Spring Framework 的最新版本,分别为 3.46.2。话虽如此,我们仍然与它们的上一代版本兼容,这样您就可以选择性地升级到 Spring Modulith 1.3,而无需升级到最新的 Boot 和 Framework 版本。

应用程序模块模型

嵌套应用程序模块

Spring Modulith 的核心抽象在内部进行了重大改进,以支持一些很酷的新功能。应用程序模块现在可以包含嵌套模块,这些嵌套模块对其同级模块是隐藏的。这为您的应用程序结构增加了另一个层次。嵌套应用程序模块是通过在根据定义的检测策略发现的模块的基础包内嵌套的包上使用 Spring Modulith 的 @ApplicationModule(或 jMolecules 的 @Module)来声明的。

🍃 Example
└─ 🖿 src/main/java
   |
   ├─ 📦 example
   |  └─ 🟢 Application.java
   |
   |  -> Inventory
   |
   ├─ 📦 example.inventory
   |  ├─ 🟢 InventoryManagement.java
   |  └─ 🔴 SomethingInventoryInternal.java
   ├─ 📦 example.inventory.internal
   |  └─ 🔴 SomethingInventoryInternal.java
   |
   |  -> Inventory > Nested
   |
   ├─ 📦 example.inventory.nested
   |  ├─ ☕ package-info.java // @ApplicationModule
   |  └─ 🟠 NestedApi.java
   ├─ 📦 example.inventory.nested.internal
   |  └─ 🔴 NestedInternal.java
   |
   |  -> Order
   |
   └─ 📦 example.order
      ├─ 🟢 OrderManagement.java
      └─ 🔴 SomethingOrderInternal.java

在此示例中,inventory 是一个应用程序模块,如参考文档中所述。nested 包上的 @ApplicationModule 注解使其也成为一个嵌套应用程序模块。在这种安排下,适用以下访问规则:

  • Nested 中的代码只能从 Inventory 或嵌套在 Inventory 内的同级应用程序模块暴露的任何类型中访问。
  • Nested 模块中的任何代码都可以访问父模块中的代码,甚至是内部代码。也就是说,NestedApiNestedInternal 都可以访问 inventory.internal.SomethingInventoryInternal
  • 嵌套模块中的代码也可以访问顶级应用程序模块暴露的类型。

nested(或任何子包)中的任何代码都可以访问 OrderManagement

外部应用程序模块

Spring Modulith 的默认应用程序模块检测策略假定所有应用程序模块都位于同一个包命名空间中。到目前为止,向排列中添加其他包只能通过预先知道要包含的包并在 @Modulithic(additionalPackages = "…" 中进行配置来实现。在 1.3 版本中,我们引入了一个 SPI ApplicationModuleSourceFactory,可以通过 META-INF/spring.factories 进行注册,并用于声明要扫描的根包、显式的应用程序模块基础包,以及可选地自定义用于声明的根包的应用程序模块检测策略。

package example;

public class CustomApplicationModuleSourceFactory implements ApplicationModuleSourceFactory {

  @Override
  public List<String> getRootPackages() {
    return List.of("com.acme.toscan");
  }

  @Override
  public ApplicationModuleDetectionStrategy getApplicationModuleDetectionStrategy() {
    return ApplicationModuleDetectionStrategy.explicitlyAnnotated();
  }

  @Override
  public List<String> getModuleBasePackages() {
    return List.of("com.acme.module");
  }
}

上述示例将使用 com.acme.toscan 来检测其中显式声明的模块,并从 com.acme.module 创建一个应用程序模块。从这些包名返回的内容随后将通过 ApplicationModuleDetectionStrategy 中暴露的相应 getApplicationModuleSource(…) 变体转换为 ApplicationModuleSources。

集成测试

优化集成测试的执行一直是 Spring Modulith 的核心功能。可以使用 @ApplicationModuleTest 注解单独或以不同程度的协作方式引导应用程序模块。

在 1.3 版本中,我们通过 JUnit Jupiter 扩展扩展了该支持,该扩展将分析对应用程序所做的更改,并且在测试运行时仅选择确实需要执行的集成测试来执行。默认情况下,我们会考虑未提交的更改以及当前分支跟踪分支之上的提交。为了优化 CI 系统上的测试执行,可以为该扩展提供一个提交哈希(通常是最新成功构建提交的哈希),作为计算需要考虑的更改文件的参考。

Some console output showing tests being skipped that would test functionality not affected by the current changes made to the repository

如您所见,未受项目更改影响的测试会被跳过。非常感谢贡献此功能的Lukas DohmenDavid Bilge

集成测试支持领域的另一个虽小但有用的补充是 @ApplicationModuleTest 现在支持通过其新引入的 module 属性显式定义要执行的模块。这允许将集成测试放在应用程序模块的包空间之外,这也是 Lukas 贡献的功能。

事件发布注册表

应用程序事件发布模式

Spring Modulith 1.3 在其事件发布支持方面提供了大量新功能。首先,我们提供了两种新的完成模式,可通过 spring.modulith.events.completion-mode 属性进行配置。第一个新完成模式(delete)会立即删除发布,而不是设置完成日期。这意味着注册表中的条目数量保持较少,从而在发布大量事件的系统中降低了性能开销。

完成后立即删除发布带来的缺点是无法访问它们,例如出于统计目的。这就是我们还添加了一个归档(archive)模式的原因,该模式维护一个专门的发布归档,已完成的发布将被移到该归档。这在事件发布上提供了相同的性能改进,但会单独保留已完成的发布。

按标识符完成事件发布

到目前为止,事件发布是通过装饰事务性应用程序监听器来完成的。这意味着完成代码只能访问所消费的原始应用程序事件和监听器的标识符,而无法访问单个发布的标识符。

在 Spring Modulith 1.3 中,事件发布注册表跟踪当前正在进行的事件发布,因此我们能够通过引用发布的标识符来发出发布完成指令。这使得各个后端存储实现能够显著提高完成操作的性能,因为索引可以更有效地被使用。

支持更多数据库和关系型数据库模式

事件发布注册表的关系型数据库后端现在支持另外三种数据库:MariaDB、Oracle DB 和 Microsoft SQL Server。此外,对于支持模式的数据库,可以定义一个用于 DDL 执行和通用操作的模式。

最后,出于一致性原因,spring.modulith.republish-outstanding-events-on-restart 已被弃用,取而代之的是新引入的 spring.modulith.events.republish-outstanding-events-on-restart

事件外部化

Spring Modulith 支持将应用程序事件轻松外部化到其他系统,这也带来了不少新功能。现在,发送出去的消息可以关联头部信息。为了实现这一点,EventExternalizingConfiguration 现在暴露了各种 headers(…) 方法,允许注册针对一般事件或特定事件类型的自定义提取器。

@Configuration
class MyConfiguration {

  @Bean
  EventExternalizingConfiguration config(SomeBean bean) {

    return EventExternalizingConfiguration.defaults(…)
        .headers(event -> Map.of("signature", bean.signMessage(event)))
        .build();
  }
}

为了动态计算外部化事件的路由目标,@Externalized 现在支持使用 SpEL 表达式来定义,这补充了之前已支持的动态路由键。

我们的好朋友 Josh Long 贡献了一个新的外部化适配器,用于将事件写入 Spring 的 MessageChannel 抽象。Spring Integration 的消息通道实现了该接口,因此您现在可以在需要时将应用程序事件无缝地馈送到这些通道中。

对 Amazon SQS 和 SNS 的事件外部化支持已移至当前版本的 Spring Cloud for AWS 中(感谢 Maciej Walkowiak 的贡献)。因此,我们正式弃用我们自己的实现,转而使用他们的实现,这些实现是 Spring Modulith 原生实现的副本,并且应该可以直接替换。我们计划在明年即将发布的 2.0 版本中移除我们的artifact。

文档支持

我们的文档支持也带来了一些有趣的功能。首先,感谢 Cora Iberkleid,默认的文档生成过程会生成一个聚合文档,其中包含创建的所有单个artifact,便于预览。每当您使用 DocumenterwriteDocumentation(…) 变体之一时,就会生成该文档。或者,在您选择性地触发了单个图表和应用程序模块 Canvas 的生成之后,显式调用 writeAggregatingDocument(…)

Canvas 方便地布局了构成应用程序模块表面的所有代码片段。我们一直通过集成第三方库和相当复杂的构建设置,为 Canvas 提供了包含类型和方法(针对事件监听器)上存在的 Javadoc 的选项。在 1.3 版本中,我们现在提供了一个注解处理器(位于 spring-modulith-apt 中),它可以自动将您的代码库的 Javadoc 提取到可重用的 JSON 文档中,并且 Canvas 生成已更新为如果存在该文档则透明地使用它。现在 spring-modulith-starter-core 会自动包含它。

第三方集成

最后,为了方便起见,如果 classpath 中存在 jMolecules,我们现在会自动触发其提供的基于 ArchUnit 的架构验证,例如针对六边形、洋葱或分层架构的验证。

订阅 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您飞速提升。

了解更多

获得支持

Tanzu Spring 通过一项简单的订阅提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看全部