news 2026/5/24 11:57:14

Spring Modulith:构建模块化单体应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Modulith:构建模块化单体应用

从“大泥球”到“乐高积木”的架构演进之旅

传统的Spring Boot单体应用往往陷入这样的困境:订单服务直接调用库存仓储,用户工具类被订单模块随意引用,改一个运费计算可能要翻遍大半个代码库。依赖关系错综复杂,形成一个剪不断理还乱的网状结构。

这种“大泥球”带来的后果是沉重的:

  • 脆弱性:对用户模块内部逻辑的微小修改,可能导致订单处理流程意外崩溃,因为两者在代码层面缺乏隔离;

  • 认知负荷:新加入的开发者无法区分哪些类是模块的公开API,哪些是内部实现细节,很容易错误地依赖本应隐藏的内部组件;

  • 重构瘫痪:当系统最终需要拆分为微服务时,由于依赖关系过于混乱,剥离任何一个业务领域都几乎不可能。

为此,不少团队选择了微服务架构。然而,实践表明许多组织在未能充分评估其运维复杂性的情况下盲目拆分单体应用,最终构建了“分布式单体”——系统虽然在物理上是分布式的,但在逻辑上依然紧密耦合,同时引入了网络延迟、分布式事务难题以及高昂的运维成本。

企业需要的不是非此即彼的极端选择,而是一个“中间地带”

这正是Spring Modulith的诞生之地。它提供了一种介于传统单体与微服务之间的架构模式:模块化单体。既保留了单体应用一次部署、资源共享、内存调用高效的优势,又通过强制模块边界来防止跨模块的非法依赖。

一、从“分层孤岛”到“功能模块”的思维转变

1.1 传统分层架构的困境

许多Spring Boot应用仍然采用按技术层打包的方式:

bookstore/ ├── config/ ├── entities/ ├── exceptions/ ├── models/ ├── repositories/ ├── services/ └── web/

这种按技术分层的风格带来了几个问题:代码结构无法表达应用的实际功能,你看到的是“repositories”“services”“web”,而不是“catalog”“orders”“inventory”;业务领域被隐藏在技术文件夹背后。更糟糕的是,在这种布局下,类往往被设置为public以便被多个包调用,导致整个应用没有任何清晰的“公共API”概念,任何代码都可以依赖任何代码。

1.2 模块化单体的核心思想

Spring Modulith借鉴了领域驱动设计的理念,将每个模块视为一个“限界上下文”,内部独立维护领域模型和业务逻辑。它的核心思想是:将系统按业务领域拆分为模块,而不是按技术分层。模块之间通过同步调用或异步事件协作,并提供模块边界验证和自动生成文档的能力。

1.3 为什么不用JPMS?

一个常见的问题是:为何不直接使用Java 9的JPMS(Java Platform Module System)?JPMS的设计目标是模块化JDK本身,在这方面做得很好。但对于应用开发人员来说,JPMS要求每个模块都是单独的JAR,集成测试必须打包成单独模块,这带来了严重的技术开销。Spring Modulith采用更轻量级的方式,基于普通的Java包结构定义模块边界,既保持了简单性,又提供了足够的架构约束能力。

二、核心能力:Spring Modulith的四大支柱

2.1 强制模块边界:让架构约束“可执行”

Spring Modulith通过分析包结构来定义模块边界。默认情况下,应用程序主包(包含@SpringBootApplication主类的包)下的每个直接子包都被识别为独立的应用程序模块。

一个典型的包结构如下:

com.example.demo/ ├── DemoApplication.java # 主类 ├── order/ # 订单模块(应用模块) │ ├── OrderManagement.java # 公开API │ └── internal/ # 内部实现 │ ├── OrderServiceImpl.java │ └── OrderRepository.java ├── inventory/ # 库存模块 │ ├── InventoryManagement.java │ └── internal/ │ └── InventoryServiceImpl.java └── customer/ # 客户模块 ├── CustomerController.java └── internal/ └── CustomerService.java

Spring Modulith通过静态分析与运行时验证双重机制,管理并约束模块之间的依赖关系:

  • 只能通过API调用其他模块:模块之间只能通过对方的公开API进行交互;

  • 禁止访问内部实现:直接访问其他模块internal包的代码会被阻止;

  • 依赖关系图必须是有向无环图:不允许存在循环依赖。

要验证模块结构是否符合约定,只需编写一个简单的测试:

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

如果inventory模块直接调用了order.internal中的类,测试会以明确的错误消息失败。

2.2 即时文档:永不失效的架构图

软件开发中一个永恒的难题是:文档,尤其是架构图,几乎总是在编写完成的那一刻就开始过时。代码在不断演进,但手动维护的图表却常常被遗忘,最终沦为不可信的废纸。

Spring Modulith的Documenter功能彻底解决了这个问题。它能直接扫描代码结构,将模块间的真实依赖关系自动生成为标准的可视化图表:

  • 组件图:通过writeModulesAsPlantUml()方法,自动生成清晰展示所有模块及其依赖关系的架构图;

  • 应用模块画布:为每个模块生成详尽的说明书,清晰列出模块的入口点、核心领域对象、对外承诺的契约以及依赖的外部信号。

文档与代码实现100%同步,因为唯一真相来源就是代码本身。

2.3 精准测试:模块化集成测试

传统的@SpringBootTest在大型应用中是一个效率杀手。每次运行测试,它都会启动完整的Spring应用上下文,加载成百上千个Bean,导致测试过程极其缓慢。

@ApplicationModuleTest注解是Spring Modulith为集成测试量身打造的利器:

@ApplicationModuleTest class InventoryModuleTests { @Test void testInventoryLogic() { // 只启动inventory模块所需的Spring上下文 // 隔离其他无关模块,测试速度极快 } }

在默认的STANDALONE模式下,它只会启动当前测试用例所在模块所需的Spring上下文。如果需要测试模块间的交互,可以通过设置启动模式为DIRECT_DEPENDENCIES(加载直接依赖的模块)或ALL_DEPENDENCIES(加载整个依赖树)。

Spring Modulith 2.1进一步增强了测试能力,PublishedEventsScenario现在从整个应用程序捕获事件,而非仅限于线程绑定,使得来自独立线程池(如outbox集成使用)的事件也能被测试感知。

2.4 运行时观测:洞察模块交互

Spring Modulith提供了运行时观测能力,能够自动为模块发布的应用程序事件创建Micrometer计数器。开发者可以实时监控模块间的交互行为,及时发现异常依赖。

三、模块间通信:同步与异步的双向选择

3.1 同步通信:通过公开API

模块之间可以通过显式声明的公开API进行同步调用。以一个配送服务应用为例:

// customer模块公开的API(订单Controller依赖) @RestController @RequestMapping("/api/customer") public class CustomerController { private final PriceCalculator priceCalculator; // calculator模块的API private final ShipmentService shipmentService; // shipment模块的API // 通过其他模块的公开Bean进行同步调用 }

同步通信保持了调用的即时性和类型安全,适用于业务逻辑紧密关联的场景。

3.2 异步通信:事件驱动

Spring Modulith鼓励使用Spring Framework的应用事件作为模块间异步交互的主要方式。它通过事件发布注册中心对事件进行了增强,该注册中心通过持久化事件确保了事件的可靠交付——即便整个应用发生了崩溃,或者只有一个模块接收到了事件,注册中心依然能够确保事件正常交付。

Spring Modulith 2.1进一步支持了基于Outbox模式的事件外部化,支持多实例、保序的消息发布,为模块化单体向分布式系统的演进提供了坚实的基础。

3.3 命名接口:显式声明API边界

@NamedInterface注解允许开发者显式声明哪些包是模块的公开API,哪些是内部实现:

@ApplicationModule( displayName = "订单管理模块", allowedDependencies = {"inventory", "payment"} ) package com.example.demo.order; import org.springframework.modulith.ApplicationModule;

这种显式的声明不仅让模块边界一目了然,也使得IDE和测试工具能够提供更精准的验证。

四、实战:构建一个模块化电商应用

4.1 环境准备

pom.xml中添加依赖:

<dependencyManagement> <dependency> <groupId>org.springframework.modulith</groupId> <artifactId>spring-modulith-bom</artifactId> <version>${spring-modulith.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencyManagement> <dependency> <groupId>org.springframework.modulith</groupId> <artifactId>spring-modulith-starter-core</artifactId> </dependency> <!-- 可选:运行时观测 --> <dependency> <groupId>org.springframework.modulith</groupId> <artifactId>spring-modulith-starter-insight</artifactId> </dependency>

4.2 定义模块边界

按照业务领域划分模块,而非技术分层。每个模块内部可以继续采用六边形架构组织代码:

com.example.ecommerce/ ├── DemoApplication.java ├── order/ # 订单模块 │ ├── application/ │ │ └── OrderApplicationService.java │ ├── domain/ │ │ ├── Order.java # 聚合根 │ │ └── OrderItem.java │ ├── infrastructure/ │ │ └── JpaOrderRepository.java │ └── interfaces/ │ └── OrderController.java ├── inventory/ # 库存模块 │ ├── application/ │ │ └── InventoryApplicationService.java │ ├── domain/ │ │ └── ProductStock.java │ └── infrastructure/ │ └── JpaStockRepository.java └── payment/ # 支付模块 └── ... # 类似的内部结构

4.3 编写验证测试

package com.example.ecommerce; import org.junit.jupiter.api.Test; import org.springframework.modulith.core.ApplicationModules; import org.springframework.modulith.docs.Documenter; class ModularityTests { ApplicationModules modules = ApplicationModules.of(DemoApplication.class); @Test void verifyModularity() { modules.verify(); // 验证模块边界,任何违规都会导致测试失败 } @Test void createModuleDocumentation() { new Documenter(modules) .writeModulesAsPlantUml() // 生成PlantUML架构图 .writeModuleCanvases(); // 生成每个模块的详细说明文档 } }

4.4 模块间事件通信

// 订单模块发布事件 @Service public class OrderApplicationService { private final ApplicationEventPublisher events; public void completeOrder(OrderId orderId) { // 订单完成逻辑... events.publishEvent(new OrderCompletedEvent(orderId, userId, totalAmount)); } } // 库存模块监听事件 @Component class InventoryEventListener { @EventListener @Async public void handleOrderCompleted(OrderCompletedEvent event) { // 预留库存 - 跨模块异步解耦 inventoryService.reserve(event.getOrderId()); } }

4.5 IntelliJ IDEA中的Spring Modulith支持

IntelliJ IDEA Ultimate为Spring Modulith提供了出色的工具支持。添加依赖后,IDE会自动识别模块边界:在Project工具窗口中,顶层模块以绿色锁标记,内部组件以红色锁标记。IDE提供了一系列检查和快速修复操作,帮助你保持应用结构符合Spring Modulith架构原则,这些问题的严重程度默认为错误级别。

五、架构演进:从模块化单体到微服务

Spring Modulith最令人欣赏的设计之一是渐进式演进。它支持从单体平滑过渡到微服务,避免一次性不可逆的大决策。

5.1 演进路径

第一阶段:模块化单体

  • 使用Spring Modulith划分模块边界,基于包结构定义模块;

  • 模块间通过同步API或异步事件协作;

  • 单个部署单元,零运维成本,保留单体的全部优势。

第二阶段:独立部署模块

  • 当单体性能出现瓶颈时,将高频模块拆分为独立微服务;

  • 模块已通过事件方式解耦,且有清晰的API边界,拆分成本极低;

  • 模块间通信方式无需改变,只是从进程内调用变为HTTP/RPC调用。

第三阶段:完整微服务治理

  • 引入服务发现、API网关、熔断降级等基础设施;

  • 按需扩展核心域服务,享受微服务的弹性伸缩优势。

Spring Modulith的Outbox模式支持为这种演进提供了坚实的底层基础——当模块最终被拆分为独立微服务时,现有的异步事件机制可以平滑升级为消息队列。

六、核心要点总结

维度核心要点
定位单体与微服务之间的“中间地带”,兼顾单体的简便性与微服务的模块化
模块定义主包下的直接子包 = 应用模块,internal子包自动视为内部实现
边界验证ApplicationModules.of().verify()将架构约束转化为可执行的测试
文档生成Documenter自动生成PlantUML组件图和模块API画布,文档永不失效
集成测试@ApplicationModuleTest仅加载当前模块上下文,测试速度极快
模块通信同步通过公开API,异步通过领域事件 + Event Publication Registry
观测性自动为模块API和事件处理创建Micrometer指标
演进能力从模块化单体平滑演进到微服务,无需推翻重来

七、实战避坑指南

① 模块不要划分得过于精细:模块数量过多会引入不必要的依赖管理开销。一般建议每个模块对应一个明确的业务领域,不要为“可能”的复用而过度拆分。

② 不要将跨模块调用“偷渡”进internal包internal包是模块的最后一道防线。一旦有外部模块直接访问internal中的类,模块边界就失去了意义。

③ 谨慎处理循环依赖:循环依赖的模块往往意味着职责划分不够清晰。遇到这种情况,请重新审视两个模块的边界是否合理,必要时引入新的领域服务作为协调层。

④ 不要过早拆分微服务:Spring Modulith的设计初衷就是在业务早期保持模块化单体的低成本优势。只有当模块边界真正稳定、性能瓶颈明确时,才值得考虑物理拆分。

⑤ 合理利用命名接口:不要把所有公开类都当作API。通过@NamedInterface显式声明哪些包是对外公开的,可以减少模块间的耦合度。

写在最后:架构不是目的,解耦才是

Spring Modulith是一个“固执己见”的工具包,它就像Spring Boot对应用的技术安排有自己的看法一样,对如何从功能上构建应用程序并允许其各个逻辑部分相互交互有自己的实现。

它的真正价值在于:

  • 将架构约束转化为可执行的代码,让模块边界不再是写在文档上的空话,而是可以被测试验证的硬约束;

  • 让团队在业务早期专注于业务本身,而不是陷入微服务的运维泥潭;

  • 为系统的长期演进铺平道路,让从单体到微服务的过渡变得渐进、可控、低成本。

正如项目负责人Oliver Drotbohm所强调的,团队不应仅仅因为技术平台支持某种架构就匆忙采用,而应让用户感受到同等水准的支持,无论他们选择何种架构。

Spring Modulith正是这条理性道路上的指南针。它不试图让你相信单体胜于微服务,也不试图让你相信微服务胜于单体。它的使命很纯粹:在你需要单体的时候,帮你把单体做干净;在你需要微服务的时候,让你从干净的单体出发

📌 参考资源:
*- Spring Modulith官方文档*
*- IntelliJ IDEA Spring Modulith支持*
*- Spring Modulith GitHub仓库*
*- Spring Modulith示例应用*

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/24 11:57:03

RePKG实战指南:解密Wallpaper Engine资源处理的高效解决方案

RePKG实战指南&#xff1a;解密Wallpaper Engine资源处理的高效解决方案 【免费下载链接】repkg Wallpaper engine PKG extractor/TEX to image converter 项目地址: https://gitcode.com/gh_mirrors/re/repkg 你是否曾经遇到过这样的困境&#xff1f;下载了精美的Wallp…

作者头像 李华
网站建设 2026/5/24 11:55:06

CML估计器:融合机器学习与工具变量,稳健估计因果效应

1. 项目概述与核心问题 在实证研究的工具箱里&#xff0c;因果推断一直是个让人又爱又恨的领域。爱的是&#xff0c;它直指我们最关心的“如果…那么…”问题&#xff1b;恨的是&#xff0c;现实数据里无处不在的内生性、混杂变量和选择偏差&#xff0c;让干净的因果识别变得异…

作者头像 李华
网站建设 2026/5/24 11:53:41

构建多Agent系统时利用Taotoken作为统一的模型调度中间层

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 构建多Agent系统时利用Taotoken作为统一的模型调度中间层 在多智能体&#xff08;Multi-Agent&#xff09;系统的设计与开发中&…

作者头像 李华
网站建设 2026/5/24 11:51:44

Armv8-R内存一致性模型解析与Cortex-R82实践

1. Cortex-R82/R82AE内存一致性解析&#xff1a;架构师视角的深度指南 在实时计算领域&#xff0c;内存一致性模型直接影响着多核系统的确定性和性能表现。作为Armv8-R架构的旗舰处理器&#xff0c;Cortex-R82/R82AE集群通过精细的内存属性控制机制&#xff0c;为汽车电子、工业…

作者头像 李华
网站建设 2026/5/24 11:49:15

【提问艺术】“元提示词”:让大模型自己帮你写出完美的测试Prompt

写在前面:一个深夜调参的真实故事 凌晨两点,团队的新人小张还在对着一份1500字的Prompt反复修改。这是他第三十一次微调措辞——把“请详细分析”改成“请从多角度深入分析”,又把“深入”改回“详细”。代码生成的准确率卡在85%,上不去,也下不来。 第二天早会,资深架构…

作者头像 李华
网站建设 2026/5/24 11:49:13

如何快速重置JetBrains IDE试用期?ide-eval-resetter终极指南

如何快速重置JetBrains IDE试用期&#xff1f;ide-eval-resetter终极指南 【免费下载链接】ide-eval-resetter 项目地址: https://gitcode.com/gh_mirrors/id/ide-eval-resetter 还在为JetBrains IDE试用期到期而烦恼吗&#xff1f;ide-eval-resetter就是你的救星&…

作者头像 李华