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实现的。它将拒绝应用程序模块之间的循环依赖、对被认为是内部的类型(根据上面的定义)的访问,并且可以选择仅允许使用 @ApplicationModule(allowedDependencies = …) 在应用程序模块的 package-info.java 中显式允许列出的模块的引用。有关如何在链接中定义应用程序模块边界以及它们之间允许的依赖关系的更多信息,请参阅参考文档

应用程序模块集成测试

能够构建应用程序结构的模型对于集成测试也很有帮助。与 Spring Boot 的切片测试注解类似,开发人员可以使用 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 应用程序中的逻辑模块。除了上面描述的功能之外,第一个版本还有许多其他功能来帮助开发人员构建他们的应用程序

  • 支持更多高级包安排

  • 支持灵活选择要在集成测试运行中包含的一组应用程序模块。

  • 一个事务事件发布日志,允许开发人员在事务上下文中通过事件集成应用程序模块。

  • 从应用程序模块结构派生开发人员文档,包括 C4 和 UML 组件图以及应用程序模块画布(每个模块的表格高级描述)。

  • 应用程序模块级别的运行时可观察性

  • 一个时间流逝事件实现。

您可以在其参考文档中找到有关该项目的更多信息,并查看示例项目。尽管已经提供了广泛的功能集,但这仅仅是旅程的开始。我们期待您的反馈和项目的功能创意。此外,请务必关注我们的Twitter,以获取该项目的最新社交媒体更新。

关于模块石

Spring Modulith(无后缀“s”)是Moduliths(带后缀“s”)项目的延续,但使用Spring Boot 3.0、Framework 6、Java 17和Jakarta EE 9作为基线。旧的Moduliths项目当前版本为1.3,兼容Spring Boot 2.7,并将持续维护,直到相应的Boot版本停止维护。我们在过去两年中积累了经验,简化了一些抽象,调整了一些默认设置,并决定从一个更先进的基线开始。有关如何迁移到Spring Modulith的更详细指南,请参阅Spring Modulith的参考文档

获取Spring通讯

关注Spring通讯

订阅

抢先一步

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

了解更多

获取支持

Tanzu Spring在一个简单的订阅中提供对OpenJDK™、Spring和Apache Tomcat®的支持和二进制文件。

了解更多

即将举行的活动

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

查看全部