近年来微服务架构成为了主流架构,大量复杂应用都在从单体架构向微服务架构进行迁移,似乎微服务成了复杂应用的唯一解。但是采用微服务架构也大大增加了系统架构的复杂性,反对滥用微服务的声音也是越来越高。Spring Modulith
,是一个新的 Spring 项目,它帮助开发者在代码中表达逻辑上的应用模块,并构建结构良好、与领域模型一致的 Spring Boot 应用程序。
官方简介
https://spring.io/blog/2022/10/21/introducing-spring-modulith
简单来说,在微服务架构中,不同子系统间的代码天然之间就是相互隔离的,只能通过公开暴露的API进行访问,以此达到解耦。而在单体应用中,代码之间相互可以访问,很容易耦合在一起从而变得难以维护。Spring Modulith
提供了一系列的工具,可以帮助我们更好的规划代码的结构,检测不合理的代码依赖,从而建设更加稳定的单体程序。
依赖的引入
Spring Modulith
针对不同的场景提供了一系列的工具包,建议通过spring-modulith-bom
来进行版本控制。同时需要注意1.X的版本需要使用Springboot3,Springboot2的版本需要自行查阅官方文档
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-bom</artifactId>
<version>1.1.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-starter-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
简单的例子
假设我们的项目中有两个模块,order
和inventory
,我们的代码目录如下
Example
└─ src/main/java
├─ example
| └─ Application.java
├─ example.inventory
| ├─ InventoryManagement.java(public)
| └─ SomethingInventoryInternal.java(private)
├─ example.order
| └─ OrderManagement.java(public)
└─ example.order.internal
└─ SomethingOrderInternal.java (public)
InventoryManagement
和OrderManagement
为各自模块对外暴露的接口,InventoryManagement依赖OrderManagement。SomethingOrderInternal
为order模块的内部处理逻辑,所以将其放在internal
包下,但是由于其要在OrderManagement中使用,所以无奈将其改为public。
@Component
public class InventoryManagement {
OrderManagement orderManagement;
//Spring Modulith仅支持构造器注入
public InventoryManagement(OrderManagement orderManagement) {
this.orderManagement = orderManagement;
}
}
然后我们使用Spring Modulith
来检测
class ModularityTests {
ApplicationModules modules = ApplicationModules.of(Application.class);
@Test
void verifiesModularStructure() {
modules.verify();
}
}
运行之后,测试通过,没有任何问题。
做一点改变
这时候有一个开发人员,不想通过OrderManagement来间接使用SomethingOrderInternal中的功能,想要一步到位直接使用SomethingOrderInternal,他将代码做了如下修改
@Component
public class InventoryManagement {
SomethingOrderInternal internal;
public InventoryManagement(SomethingOrderInternal orderManagement) {
this.internal = orderManagement;
}
}
这时我们再执行测试的时候就会有如下错误:
org.springframework.modulith.core.Violations: - Module 'inventory' depends on non-exposed type example.order.internal.SomethingOrderInternal within module 'order'!
InventoryManagement declares constructor InventoryManagement(SomethingOrderInternal) in (InventoryManagement.java:0)
- Module 'inventory' depends on non-exposed type example.order.internal.SomethingOrderInternal within module 'order'!
通过错误信息我们可以看到,inventory默认情况下只允许依赖最外层的order,不允许依赖任何内部的组件,即使这是一个public组件。
指定依赖模块
默认情况Spring Modulith
认定一级目录下为对外暴露的公共接口,模块之间可以相互依赖,如果我们想要限制依赖情况,可以通过@ApplicationModule
注解来实现。
下面假设我们新多了一个模块other
Example
└─ src/main/java
├─ example
| └─ Application.java
├─ example.inventory
| ├─ InventoryManagement.java(public)
| └─ SomethingInventoryInternal.java(private)
├─ example.order
| └─ OrderManagement.java(public)
└─ example.order.internal
| └─ SomethingOrderInternal.java (public)
├─ example.other
| └─ OtherManagement.java(public)
默认情况下我们可以在inventory中直接依赖OtherManagement
,不会有任何问题
@Component
public class InventoryManagement {
OtherManagement otherManagement;
public InventoryManagement(OtherManagement otherManagement) {
this.otherManagement = otherManagement;
}
}
但是后来经过业务、架构上的梳理,inventory仅能依赖order,我们可以在inventory下增加一个package-info.java
@org.springframework.modulith.ApplicationModule(
allowedDependencies = "order"
)
package example.inventory;
这时再依赖other,我们就会有如下报错
org.springframework.modulith.core.Violations: - Module 'inventory' depends on module 'other' via example.inventory.InventoryManagement -> example.other.OtherManagement. Allowed targets: order.
- Module 'inventory' depends on module 'other' via example.inventory.InventoryManagement -> example.other.OtherManagement. Allowed targets: order.
- Module 'inventory' depends on module 'other' via example.inventory.InventoryManagement -> example.other.OtherManagement. Allowed targets: order.
Named Interfaces
从前一小节可知,默认情况Spring Modulith
只允许我们依赖最外层的接口,如果我们确实需要依赖一个内部组件,那该怎么办?比方说order模块做了如下调整,将对外的api都放到了example.order.spi
这个目录下,inventory现在需要依赖SomeSpiInterface
Example
└─ src/main/java
├─ example
| └─ Application.java
├─ …
├─ example.order
| └─ OrderManagement.java
├─ example.order.spi
| ├— package-info.java
| └─ SomeSpiInterface.java
└─ example.order.internal
└─ SomethingOrderInternal.java
我们可以在spi的包下新增加一个package-info.java
@org.springframework.modulith.NamedInterface("spi")
package example.order.spi;
同时也在inventory下增加一个package-info
@org.springframework.modulith.ApplicationModule(
allowedDependencies = "order::spi"
)
package example.inventory;
@Component
public class InventoryManagement {
SomeSpiInterface spiInterface;
public InventoryManagement(SomeSpiInterface spiInterface) {
this.spiInterface = spiInterface;
}
}
这样,inventory就可以正常访问order中的spi
测试代码地址:
https://gitee.com/xiiiao/modulith-example