Spring Modulith(一)

近年来微服务架构成为了主流架构,大量复杂应用都在从单体架构向微服务架构进行迁移,似乎微服务成了复杂应用的唯一解。但是采用微服务架构也大大增加了系统架构的复杂性,反对滥用微服务的声音也是越来越高。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>

简单的例子

假设我们的项目中有两个模块,orderinventory,我们的代码目录如下

 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)

InventoryManagementOrderManagement为各自模块对外暴露的接口,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

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值