Quarkus云原生微服务开发实战
上QQ阅读APP看书,第一时间看更新

2.3 源代码组织

对于微服务架构的云原生应用来说,由于多个独立的微服务的存在,源代码仓库的管理方式可以有不同的策略。在选择源代码仓库的策略时,需要权衡两个因素。

第一个因素是微服务代码的独立性。如果每个微服务有各自完全独立的源代码仓库,可以实现最大程度的隔离。在进行持续集成和部署时,只需要处理微服务自身的代码,因此构建和部署的速度会更快。开发人员也只需要关注微服务自身的代码,更容易理解。

另外一个因素是如何处理微服务之间的共享代码。不同微服务之间不可避免地会存在一些需要共享的代码,比如,实用工具类和公共的实体模型等。有些共享代码可能仅在部分微服务中共用,比如,微服务A对外开放API,使用该API的微服务B可以直接使用A中定义的API模型中的对象来发送请求和处理响应。

一般来说,一共有如下三种方案可供选择。

1 单一源代码仓库

第一种方案是使用单一的源代码仓库管理所有微服务的全部代码。以Maven来说,所有的共享代码和微服务都作为一个Maven项目的子模块。在这种方案中,代码共享的方式非常简单,只需要在微服务的模块中添加对其他模块的Maven依赖即可。当所做的修改涉及多个微服务时,可以在一个代码提交中包含对所有相关的微服务所做的修改。这保证了构建版本的稳定性,很容易追踪不同构建版本之间的代码变化。这种方案的优势在于简单易懂,适合于较小或中等规模的微服务架构的应用。本书的实战应用采用这种方案。

这种方案的不足之处在于任何代码的提交都会触发整个应用的构建流程。在大部分情况下,一个代码提交只会影响单个微服务,但是会触发整个应用的持续集成和部署流程,使得其他没有被修改的微服务也会被构建和部署。由于需要构建整个代码仓库并运行自动化测试,整个流程的运行时间会较长。

在组织项目中模块的结构时,可以采用扁平化结构或层次结构。扁平化结构把所有模块组织在一个层次,如下面的目录结构所示,其中的前缀lib表示共享库,service表示微服务。

层次结构把不同的模块按照类型组织在一起。在下面的目录结构中,目录lib下是共享的模块,目录service下是不同的微服务各自的子目录。

2 独立源代码仓库

第二种方案是共享代码和每个微服务使用各自独立的源代码仓库。以Maven来说,每个源代码仓库都有对应的Maven项目。每个项目有独立的构建和部署流程。构建的工件被发布到私有的Maven仓库。不同项目之间通过Maven仓库来共享代码。单个微服务依赖的是构建完成的共享代码的JAR文件。

这种方案的优势在于每个微服务可以独立构建和部署,从而提升了构建和部署的速度。如果一个改动只涉及单个微服务,那么只有该微服务需要被重新构建和部署,不会影响其他微服务。这种方案的不足之处在于共享代码的管理变得更加复杂。在修改了共享代码之后,需要等待该项目的构建完成并发布到Maven仓库,才能在引用它的其他项目中看到这个改动。如果一个改动涉及多个微服务,这个改动需要在不同的源代码仓库上进行多次代码提交来共同完成。这使得追踪代码改动的历史记录变得比较困难。

3 使用Git子模块

第三种方案是前两种方案的一种折中方案。这种方案与第二种方案有很大的相似之处。共享代码和微服务有各自独立的源代码仓库。不同之处在于共享代码以Git子模块(Submodule)的形式添加到使用它的微服务的Git仓库中。在微服务构建时,共享代码以源代码的形式参与到构建过程之中,直接成为微服务产生的JAR包的一部分,不再需要使用Maven仓库来共享。

这种方案的好处是避免了通过Maven仓库来共享代码,不过也增加了代码管理的复杂度。根据Git子模块的使用方式,对于当前微服务项目的每个Git子模块,在Git仓库中都有一个文件来包含子模块的提交号(commit id),类似于一个指针。当希望更新微服务使用的子模块的代码时,需要修改对应的文件来指向子模块中新的代码提交。这会在当前微服务的Git仓库中产生一个新的代码提交,触发新的构建过程。

图2-3展示了Git子模块的用法。图片右侧是共享代码的Git仓库,其中的每个圆圈表示一个代码提交。图片左侧是微服务的Git仓库,其中的白色圆圈表示正常的代码提交,而灰色圆圈表示的代码提交的作用是修改所引用的共享代码的提交号。

当单个微服务的代码变得复杂时,第二和第三种方案的优势会更明显。第三种方案由于需要使用Git子模块,管理起来会比较麻烦。如果能够避免在微服务之间共享代码,那么每个微服务的代码仓库可以完全独立,彼此互不影响。微服务之间一般共享的是开放API的接口对象。如果不希望共享代码,可以应用领域驱动设计的思想,在消费开放API的微服务上添加防腐化层。

·图2-3 Git子模块