当前位置: 首页 > 科技观察

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

时间:2023-03-16 10:24:11 科技观察

这个月,我和我的同事们开源了一个内部架构治理平台:ArchGuard,我们进行了一系列遗留系统的迁移:从Maven到Gradle。原因是灵活的自定义任务,以及内置的增量构建。依赖库更新。该系统从微服务走向单体。为持续交付构建规范和相应的规范工具。结合GitHubAction、DockerHub等一系列DevOps开源基础设施,进行全自动化构建。...其中,最有趣的故事是:从微服务到单体架构。因为它是一种反主流的形态,或者说是一种反主流的技术架构。遗留微服务系统的挑战经过一系列内部会议,我们决定开源ArchGuard。然后,查看代码库,我们发现了一系列挑战:服务/模块太多。这个内部开发多年的系统,由多个微服务+代码库组成,知识匮乏。之前没有留下太多的开发文档,对当时做出的一系列技术决策也不是很理解,需要学习Git的历史。复杂的部署架构。也是一个工具,相对于Jenkins/Sonarqube的部署方式来说相对复杂一些。不一定是合理的服务分工。我们需要部署一系列服务,但只有扫描器(ArchScanner)需要弹性伸缩等特性。那么,我们重新思考一个合理的后端服务(微服务)的粒度应该是多少?那么,参考以往的总结,到底使用了什么样的微服务呢?而有多少个微服务比较合理呢?之前的一个结论类似在:微服务的数量不超过开发者的数量。满足康威定律。微服务与开发团队保持一致。两个披萨团队的原则——开发团队人数维持在3~12人。微服务仅由一个开发团队维护。高内聚,低耦合。单个服务较少依赖于其他服务。比如两个服务相互调用,耦合度比较高,可以认为是一个服务。收益大于成本。创建服务的开销是否超过作为单独服务的好处。您不一定需要微服务。考虑采用DDD(DomainDrivenDesign)分层架构进行划分,方便以后拆分成微服务。在这种情况下,几乎违反了上述一系列规则。于是,我又回到了上面的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似乎是重复的,或者是迁移中的代码。为什么单体更适合现在?回到多年前,MartinFowler写了那篇《Monolithic First》,意在告诉人们,当团队的微服务能力和技术还不够成熟时,你不应该采用微服务。这里的场景和上面的不一样。对于系统的最终形态,单体不一定适合这个系统,但是对于我们目前来说,单体是最适合的。原因如:单体部署架构决定应用架构。使用Docker,虽然Saas也更友好。然而,作为一个初出茅庐的开源项目,没有资金支持这种规模的SaaS服务。最终用户是开发人员。软件的使用者自己可能成为开发者,所以能启动一次就应该启动一次。开发者体验是重中之重。开源和面向开发者决定了ArchGuard是一个优先考虑开发者体验的系统。如果一个参与ArchGuard项目的开发者要在多个项目之间切换,体验很差。在开源社区中,单体一直是首选,比如Gradle、Spring等。首先是部署速度。设置速度。一般来说,作为一个开源应用/工具,软件工程的模式受其合作方式的限制。因此,常规的软件开发架构不一定适用,我们需要一些更好的模型。那么,我们还有哪些其他选择?我们的目标架构是一个整体吗?从某种意义上说,就目前而言,它是。但是,如果管理不善,它会变成一个大泥球。回顾一下,多仓库/多模块微服务系统与单体系统在物理形态上的主要区别在于,微服务使用进程间调用,而单体使用进程内调用。微服务最终会有多个产品包,而单个产品只有一个或多个插件包。所以,只要我们用类似的形式来构建一个单体应用,从部署形式上来说,就可以成为微服务架构。简单来说就是:在代码库中,包(包、服务)之间的调用使用HTTP调用,而不是函数调用。通过自定义构建脚本,在构建时拆分代码库,生成并部署多个服务工件。结果,系统处于临界状态。让人们根据自己的需要做出不同的选择。比如当它是SaaS的时候,它可以成为微服务的一种形式,当它作为单体部署时,它可以成为一个单一的状态。唯一的麻烦是开发人员需要对构建系统有很好的了解并设计足够的自动化测试设施。如何迁移?接下来,我们开始合并多个代码存储库,其中一些存储库保留了历史提交记录。主要是结合git-filer-repo来过滤和选择路径。全套构建配置。统一Application.properties等,使用相同的依赖版本。由于年龄不同的原因,选择的依赖版本也不同。在合并代码之前,有必要尝试统一。解决冲突。因为只是合并了src目录下的内容,所以如果包名有问题,比如冲突,需要重新设置。类似的问题,还有:Applicationduplication、Beanconflict、Serviceconflict。就迁移过程而言,它并不复杂,只是很耗时。还有其他选择吗?类似的场景,如果开发者有多个微服务,不考虑单机部署,Monorepo是更好的选择,将所有微服务项目的代码放在一个仓库中。毕竟,Google可以将所有代码存储库放在一起,我们为什么不能。当然,谷歌使用的技术原理是不同的。尽管如此,它还是有足够的理由。回过头来看,单人选手会不会更适合小团队的选择?一个大团队呢?