5

Spring Modulith能成熟实现模块化了吗? - Foojay

 1 year ago
source link: https://www.jdon.com/64157.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Spring Modulith能成熟实现模块化了吗?


设计微服务的主要原因之一是它们强制执行强大的模块边界
然而,微服务的缺点是如此之大,就像砍掉你的右手来学习用左手写字一样;有更多易于管理(并且痛苦更少!)的方法来实现相同的结果。
即使自微服务热潮开始以来,一些冷静的头脑也占了上风。
特别是, Spring 框架的开发人员Oliver Drotbohm长期以来一直是模块化替代方案的支持者:
这个想法是保持一个整体,但围绕模块进行设计。

许多人涌向微服务,因为他们处理的应用程序类似于意大利面条拼盘。
如果他们的应用程序设计得更好,微服务的吸引力就不会那么强大。

为什么要模块化?
模块化是一种减少变更对代码库影响的方法。这与设计(大型)船舶的方式非常相似。
当水不断渗入船内时,船通常会因为阿基米德推力的减小而沉没。为避免一次漏水沉船,它基于多个隔间设计。如果发生泄漏,水只会包含在一个隔间中。虽然这个方案并不理想,但它可以防止船沉没,使其能够重新路由到最近的港口,在那里人们可以修理它。
模块化的工作原理类似:它在部分代码周围设置了边界。这样,更改的影响仅限于该部分,不会超出其边界。

在 Java 中,这些部分称为包。与船舶的相似之处到此为止,因为包必须协同工作才能达到预期的结果。
包不能“滴水不漏”。Java 语言提供了跨包边界工作的可见性修饰符。有趣的是,最著名的一个public,允许完全交叉包。
设计遵循最小特权原则的边界需要不断努力。很可能在项目的初始开发压力或维护期间随着时间的推移,努力会失败,边界会衰减。
我们需要一种更先进的方式来强制执行边界。

模块,无处不在的模块
在 Java 的悠久历史中,“模块”一直是一种强制边界的解决方案。问题是,即使在今天,关于什么是模块的定义也有很多。

OSGI始于 2000 年,旨在提供可以在运行时安全部署和取消部署的版本化组件。它保留了 JAR 部署单元,但在其清单中添加了元数据。OSGi 很强大,但是开发 OSGi包(模块的名称)很复杂。
开发人员付出了更高的开发成本,而运维团队则享受了部署带来的收益,DevOps 尚未诞生;它并没有使 OSGi 像它本来应该的那样流行。

与此同时,Java 的架构师也在寻找将 JDK 模块化的途径:与 OSGI 相比,该方法要简单得多,因为它避免了部署和版本控制问题。
Java 9 中引入的 Java 模块将自身限制为以下数据:名称、公共 API 以及对其他模块的依赖性。

Java 模块适用于 JDK,但由于先有鸡还是先有蛋的问题,适用于应用程序的效果就差很多了。为了对应用程序有所帮​​助,开发人员必须将库模块化——而不是自动依赖模块。但只有当有足够多的应用程序开发人员使用它时,库开发人员才会这样做。当前,20 个公共库中只有一半是模块化的。

在构建方面,我需要引用 Maven 模块。它们允许将一个代码拆分到多个项目中。

强化边界的暂定方法
如上所述,微服务在开发和部署期间提供了最终的边界。在大多数情况下,它们是矫枉过正的。另一方面,不可否认的是,项目会随着时间的推移而腐烂。即使是最漂亮的、重视模块化的项目,如果没有持续的关注,也必然会变得一团糟。

我们需要规则来执行边界,并且需要像测试一样对待它们:当测试失败时,人们必须修复它们。同样地,当一个人破坏了一个规则时,他也必须修复它。
ArchUnit是一个创建和执行规则的工具。人们配置规则并将其作为测试进行验证。不幸的是,配置是很耗时的,而且必须不断维护以提供价值。
下面是一个遵循六边形架构原则的示例应用程序的片段:

HexagonalArchitecture.boundedContext("io.reflectoring.buckpal.account")
                     .withDomainLayer("domain")
                     .withAdaptersLayer("adapter")
                     .incoming("in.web")
                     .outgoing("out.persistence")
                     .and()
                         .withApplicationLayer("application")
                         .services("service")
                         .incomingPorts("port.in")
                         .outgoingPorts("port.out")
                     .and()
                         .withConfiguration("configuration")
                         .check(new ClassFileImporter()
                         .importPackages("io.reflectoring.buckpal.."));

请注意,HexagonalArchitecture类是ArchUnit API上定制的DSL立面。

总的来说,ArchUnit聊胜于无,但也只是聊胜于无。它的主要优点是通过测试实现自动化。如果架构规则能够被自动推断出来,它就会有明显的改善。这就是Spring Modulith项目背后的想法。

Spring Modulith
Spring Modulith 是 Oliver Drotbohm 的Moduliths 项目(尾随 S)的继承者。它同时使用 ArchUnit 和jMolecules。在撰写本文时,它是实验性的。
Spring Modulith 允许:

  • 记录项目包之间的关系
  • 限制某些关系
  • 在测试期间测试限制

它要求一个人的应用程序使用 Spring 框架:它利用后者对前者的理解,通过DI组装获得。
默认情况下,Modulith 模块是一个与SpringBootApplication-annotated 类位于同一级别的包:

|_ ch.frankel.blog
    |_ DummyApplication       // 1
        |_ packagex           // 2
        |  |_ subpackagex     // 3
        |_ packagey           // 2
        |_ packagez           // 2
          |_ subpackagez      // 3
  1. 模块化模块

默认情况下,一个模块可以访问任何其他模块的内容但不能访问
Spring Modulith 提供基于 PlantUML 生成基于文本的图表,带有 UML 或C4(默认)皮肤:

var modules = ApplicationModules.of(DummyApplication.class);
new Documenter(modules).writeModulesAsPlantUml();

要在模块访问常规包时中断构建,请verify()在测试中调用该方法:

var modules = ApplicationModules.of(DummyApplication.class).verify();

一个可供玩耍的样本
我创建了一个样本应用程序sample app 供大家使用:它模拟了一个在线商店的主页。这个主页是用Thymeleaf在服务器端生成的,并显示目录项目和新闻提要。后者也可以通过HTTP API访问,供客户端调用(我懒得编码)。物品的显示是有价格的,因此需要一个定价服务。

每个功能--页面、目录、新闻源和定价--都在一个包里,它被看作是一个Spring模块。Spring Modulith的记录功能产生了以下内容:

让我们检查一下定价功能的设计:

目前的设计有两个问题:

  • PricingRepository可以在模块外访问
  • 泄漏PricingServiceJPAPricing实体

我们将通过封装不应公开的类型来修复设计。我们将Pricing和PricingRepositorytypes 移动到模块的internal子文件夹中pricing:

如果我们调用该verify()方法,它会抛出并中断构建,因为Pricing无法从pricing模块外部访问:
Module 'home' depends on non-exposed type ch.frankel.blog.pricing.internal.Pricing within module 'pricing'!
让我们通过以下更改来解决违规问题:

结论
通过尝试示例应用程序,我确实喜欢 Spring Modulith。
我可以看到两个突出的用例:记录现有应用程序和保持设计“干净”。后者避免了应用程序随时间的“腐烂”效应。这样,我们可以保持设计的预期并避免意大利面条效应。
锦上添花:当我们需要将一个 或多个功能切入他们的部署单元时,这很棒。这将是一个非常直接的举措,不会浪费时间来理清依赖关系。Spring Modulith 提供了一个巨大的好处:将每个有影响力的架构决策推迟到最后一刻。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK