7

回到单体架构:一个开源项目的重构

 2 years ago
source link: https://www.phodal.com/blog/migration-microservices-to-monolith/
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

Posted by: Phodal Huang Feb. 27, 2022, 8:38 p.m.

这个月,我和我的同事们开源了一个内部的架构治理平台:ArchGuard,我们进行了一系列的遗留系统的迁移工作:

  • 从 Maven 到 Gradle。原因是灵活的自定义 task,还有自带的增量构建等。
  • 依赖库的更新。
  • 系统从微服务到单体。
  • 构建规范和对应的规范工具化
  • 持续交付。结合 GitHub Action、Docker Hub 等一系列的 DevOps 开源基础设施,进行全自动化的构建。

其中,最有意思的一个故事莫过于:从微服务到单体架构。因为,它是一种反主流的形式,又或者是反主流的技术架构。

遗留微服务系统的挑战

在我们经过了一系列的内部会议之后,决定了将 ArchGuard 开源。随后,看到了代码库,我们发现了一系列的挑战:

  • 过多的服务/模块。这个在内部开发多年的系统,由多个微服务组成 + 代码库组成,
  • 知识缺少沉淀。先前并没有留下太多的开发文档,不了解当时做的一系列技术决策,需要从 Git 历史中汲取。
  • 复杂的部署架构。同样是工具,对比于 Jenkins/Sonarqube 的部署方式,相对较为复杂。
  • 不一定合理的服务划分。我们需要部署一系列的服务,但是只有扫描器(Arch Scanner)才需要弹性伸缩这样的特性。

于是呢,我们重新思考合理的后端服务(微服务)的颗粒度应该是怎样的?所以,参考于过去总结的什么用微服务?以及有多少个微服务更合理?先前的一个结论,类似于:

  1. 微服务的数量不超过开发人员的数量。
  2. 满足康威定律。微服务与开发团队对齐。
  3. 两个比萨团队原则  —— 开发团队的人员数量维护在 3 \~ 12 人。一个微服务只由一个开发团队维护。
  4. 高内聚,低耦合。单个服务与其它服务依赖少。如:两个服务存在相互调用,耦合度相对较高,可以考虑合为一个服务。
  5. 收益大于开销。创建服务的开销是否超过了独立成服务的好处。
  6. 你不一定需要微服务。考虑采用 DDD (领域驱动设计)分层架构来划分,以方便未来拆分为微服务。

在这个场景之下,几乎违反了上面的一系列规则。所以,我就回到了上述的 6 中去,采用 DDD 的分层架构模式。每个资源/聚合/服务在各自的包下管理(common 除外):

├── Application.kt
├── clazz
├── code
├── common
├── config
├── evaluation_bak
├── evolution
├── method
├── metrics
├── module
├── packages
├── qualitygate
├── report
├── report_bak
├── scanner
├── scanner2
└── system_info

由于是合并的代码,所以代码中除在于 _bak 还有 scanner2 这样看似重复,又或者是迁移中的代码。

为什么单体更适合当前?

再回到多年以前, Martin Fowler 写了那篇《Monolithic First》,意在告诉人们在团队微服务能力和技术不够成熟的时候,你不应该采用微服务。这里的场景和上述的这个场景并不是一样的。对于系统的最终形态来说,单体并不一定适合这个系统,但是当于当前的我们来说,单体是最合适的。原因诸如于:

  • 单体部署架构决定应用架构。使用 Docker,尽管 Saas 也是更友好的。但是,作为一个刚起步的开源项目,并不会资金来支撑这种规模的 SaaS 服务。
  • 最终用户是开发者。软件的使用者本身又可能成为开发者,所以能一次启动就应该一次启动。
  • 开发者体验优先。开源与面向开发者决定了 ArchGuard 是一个开发者体验优先的系统。如果一个参与到 ArchGuard 项目的开发者,要在多个项目中切换, 那么这中体验是非常差的。在开源社区里,一直都是单体优先,如 Gradle、Spring 等。
  • 首次部署速度。
  • setup 速度。

总体来说,作为一个开源应用/工具,软件工程的模式受限于其合作模式。所以,常规的软件开发架构,并不一定适用,我们需要一些更好的模式。

那么,我们还有别的选择吗?

我们的目标架构是单体吗?

从某种意义上,就当前来说,它是的。但是,如果管理有所不善的话,它会变成一个大泥球架构。回顾一下,一个多仓库/多模块的微服务系统,它与一个单体系统在物理形态上的主要区别在于:

  • 微服务使用的是进程间调用,单体是进程内调用。
  • 微服务最终有多个制品包,而单体只有一个或者是插件化的一带多。

所以,只要我们用相似的形态来构建一个单体应用,那么它在部署形态上就可以变成是微服务架构。简单来说,就是:

  • 代码库内,包(package、service)间的调用使用 HTTP 调用,而不是函数调用。
  • 通过自定义的构建脚本,在构建时拆分代码库,生成多个服务制品,并进行部署。

从结果来说,便是将系统放置在一种临界状态。以让人们根据自己的需要,做出不同的选择。如在 SaaS 化的时候,这就可以变成微服务的形态,单体部署时,则可以变成单体的状态。唯一麻烦的是,需要开发者对于构建系统有足够的了解,并设计好充足的自动化测试设施。

如何迁移 ?

接着,我们就开始合并多个代码仓库,其中的一些

  1. 保留历史提交记录的合并。主要是结合 git-filer-repo 来进行过滤和选择路径。
  2. 构建配置的全集。对 Application.properties 等进行统一。
  3. 使用相同的依赖版本。由于不同的年代的原因,所以选择的依赖版本也有所不同,需要尝试先统一,才能合并代码。
  4. 解决冲突。因为,只合并了 src 目录下的内容,如果包名有问题,如冲突了,需要重置。类似的问题,还有:Application 重复、Bean 冲突、Service 冲突。

就迁移过程来说,它并不复杂,就是耗时。

还有其它选项吗?

相似的场景,如果一个开发人员多个微服务,并且在不考虑单机部署的情况下,Monorepo 是一个更好的选择,把所有微服务项目的代码放在一个仓库里。

毕竟,Google 都可以把所有的代码仓库放一起,我们又有什么不可以的。当然了,Google 使用的技术原理是不一样的。不过,它能提供一个足够强壮的理由。

回过头来看,对于小的团队来说,单体会不会是更合适的选择?那么大的团队呢?


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK