如何在不重构的情况下将单体拆分成微服务?


微服务在过去几年获得了很大的普及,并且对我作为全栈开发人员的工作产生了很大的影响。但这些年来,我从未对单体失去信心。微服务带来了很多额外的复杂性,在我所见的大多数情况下,这些复杂性并没有超过它们带来的价值。所以,我总是发现自己提倡和捍卫单一的方法。这引起了很多讨论。

对我来说,单体架构和微服务架构并没有什么不同。如果处理得当,单体应用由具有强大边界的模块组成。这给模块结构带来了高内聚和低耦合,使应用程序更易于维护和更改。它还允许多个团队在同一应用程序上工作,而不会互相绊倒。这是我多次听到的支持微服务的论点。

在设计应用程序时,我发现很难定义正确的边界。无论它们是用于设置模块还是微服务。微服务有一些常见的分解模式,我发现它们同样适用于单体应用。对于大多数情况,按业务能力进行分解是一个很好的起点。

例如,小型在线商店的基本结构可能如下所示:

模块化整体的想法绝对不是新的,但作为与微服务的对立而获得更多关注。像Sam Newman这样的专家写了很多关于这个主题的文章,新技术从这些想法中诞生。一个很好的例子是用于 Java 应用程序的Spring Modulith项目。对于我工作过的大多数公司来说,这种类型的单体应用已经绰绰有余了。

在我看来,微服务架构只是单体架构的分布式变体。分布式系统既不简单也不便宜,所以选择它一定有很好的理由。更好的应用程序结构或多团队支持对我来说不算是好的论据。重要的是与运维相关的原因,如可伸缩性、可靠性和可部署性。

如果模块边界足够强大,将模块化单体重构为微服务应该相当容易。模块可以一个接一个独立重构。

在对我们的小型在线商店进行全面重构后,应用程序可能看起来像这样:

有许多方法(模式)来组织一个微服务架构。没有一个放之四海而皆准的解决方案,所以正确的选择总是取决于环境。例子中使用的API网关模式对于解耦服务、解决安全问题和其他问题都很好。它适合我作为大多数情况下的一个伟大的起点。

从单体开始,逐步重构为微服务,听起来是一个很好的解决方案,但根据我的经验,并不是非常实用。将一个模块重构为一个服务仍然需要大量的努力。设置一个新的项目,端点,请求,操作等等。一旦应用程序的列车运行,当客户不断要求新功能时,很难找到资源把它放在另一条轨道上。当等待的时间过长时,事情就开始分崩离析,气氛迅速转变。

这让我开始思考。在理想的世界里,一个单体可以过渡到微服务而不需要任何重构:
定义什么在哪里运行应该是一个配置问题,而且可以随时改变。
我喜欢这个想法,但如何在不触及代码的情况下将模块转变为服务呢?

基于段Segment概念
在这个时候,我已经在研究一个可以帮助回答这个问题的概念:

作为一个全栈开发者,我花了相当多的时间来设置前端和后端之间的通信,这总感觉是一个很大的开销。
自动化两端通信是一个选项,将通信从代码中转移到运行时中。
通过这个解决方案,前端可以直接导入和调用后端组件。运行时拦截所有的后端导入,创建并提供一个远程实现,就像一个依赖性注入器。

使用这种解决方案进行模块间的通信解决了自动过渡到服务的问题。但运行时被限制在一个单一的前端和后端部分。因此,它已经被重构,以支持无限量的段,可以单独或作为一个组部署到前端或一个或多个后端。一个段包含来自一个或多个模块的一个或多个组件。它的内容是由配置定义的

每个模块都放在一个单独的段中。交付部分需要负载平衡(由于跟踪功能)并部署到多个服务器。其余部分被分组并一起部署到单个服务器。
或者,模块可以放置在单个段中,稍后移动到单独的段。因为段仅存在于配置中,所以将模块移动到另一个段对代码没有影响。这样,无需重构即可即时重新安排应用程序。

运行时已成为在 MIT 许可证下发布的开源项目。它的名字叫Jitar,是Just-In-Time-Architecture的缩写。由于全栈支持,它被实现为 Node.js 之上的一个层。所以它只适合 JavaScript 和 TypeScript 应用程序。尽管我认为同样的事情可以用 Java(也许还有 .NET?)来实现。

GitHub 存储库