购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.2 通过库在代码库之间共享代码

我们先做一个设定,由于两个独立代码库之间存在大量重复代码,两个团队决定将通用代码抽取出来作为一个单独的库。我们会将授权服务的代码抽取到一个单独的代码库中。其中一个团队还需要创建新库的部署流程。最常见的场景是将库发布到一个外部的存储库管理器,譬如JFrog的制品仓库。图2.4对这一场景进行了说明。

一旦将通用代码存储到一个存储库管理器,服务就可以在构建时拉取该库,使用其中包含的类。这种方式使用同一个地方存储代码,解决了代码重复的问题。

图2.4 从存储库管理器获取某个通用库

消除重复代码带来的诸多益处中显而易见的是代码整体质量的提升。使用一个共享库存储通用逻辑有利于团队的协作,共同改进代码库。基于这种机制,某个缺陷修复后,其所在共享库的所有消费者都能直接受益,不再有重复的工作。现在,我们一起来看看这种方式有哪些缺点以及使用它时需要做怎样的取舍。

2.2.1 共享库的取舍与不足

一旦抽取新库,它就成为一个新的实体,有自己的编码风格、部署流程以及编码规范。在我们的语境里,“库”意味着对代码进行了打包操作(将其封装成.jar文件、.dll文件,或者Linux平台上的.so文件),它可以被多个项目使用。某个团队或者某个开发者若要负责新代码库的维护,则需要建立部署流程、验证项目代码的质量、开发新功能等。并且,这是一个持续不断的过程。

如果你决定采用共享库,就要定义一系列的规范与流程,包括编码规范、部署流程等。不过,只要创建过一次共享库,同样的规范与流程可以复用。添加第一个共享库的开销可能比较高,后续要低得多。

这种方式最显而易见的缺点之一是新创建的库需要使用与消费端一致的程序设计语言。举个例子,如果Payment和Person服务使用不同的程序设计语言开发,一个使用Python,另一个使用Java,那我们就不太可能采用共享库的方式解决代码重复问题。不过,实际项目中,这极少成为问题,因为服务通常都采用同一种程序设计语言或者同体系的程序设计语言(譬如基于JVM的语言)创建。当然,我们也可以用不同的技术创建服务生态。然而,这会极大地增加系统的复杂性。通常这意味着我们需要雇用熟稔各种技术栈的专家,他们能使用各式各样的工具,譬如基于不同技术栈的构建系统、包管理器等。你选择的程序设计语言决定了你要采用什么生态,语言与生态是紧密相关的。

开源贡献

JVM开源生态中有很多活跃的开源社区,它们开发、维护了各式各样的库。创建一个独立的库并将其开源之前,最好先调研开源社区中是否已经存在类似的库。当然,要适配自己的需求,你可能需要做一定的扩展。

如果开源社区中不存在类似的库,你也可以将自己的代码贡献到社区。通过向现有的开源项目贡献代码,更多的用户可以使用你的成果。而你将获得部署流程的支持以及免费的推广。这样一来,更多的人会知道你的库,并重用其中的代码。

很多时候,我们会用某种语言(譬如C语言)编写一个库,再将其封装到你选择的本地接口(譬如Java本地接口)语言中。然而,这种方式可能会带来问题,因为如此一来我们的代码需要经过另一层的间接调用。封装在原生接口内的代码在不同的操作系统之间可能是不兼容的,或者它的方法调用甚至比封装语言(譬如Java)的方法调用慢。基于这些考虑,接下来的讨论中,我们将专注于使用同一种技术栈的语言生态。

新创建的库需要在公司内部大力推广,只有这样,别的团队才能了解它,需要的时候才会使用它。否则就可能会出现混杂使用的情况,即有些团队用了新的库,另一些团队依旧还在使用冗余的代码。

利用存储库管理器是共享库的好办法,不过你需要为库文件维护一份文档。通常情况下,拥有良好测试的项目可以降低开发者为其贡献代码的难度。如果开发者可以使用你的测试套件方便地做一些实验,他们会更愿意使用你的库并为其贡献代码。另外,库的文档有时会由于欠维护而过期,这一点值得特别注意。因此定期更新文档非常重要。

同样,测试也需要及时维护,保持其与产品行为一致。这是帮助你在公司内部推广库的极好营销手段,可以让潜在用户对你的库的品质更有信心。当然,如果你选择了冗余代码的方式,那就需要在所有的地方测试那些重复的代码。这意味着你也需要有重复的测试代码。

不能因为测试覆盖率高就放弃维护库的文档。如果你希望靠查看测试代码了解如何使用一个新的库,可能困难重重,除非编写这些测试代码时就考虑了要将其作为文档提供给用户。测试需要覆盖各种使用库的方式,不仅局限在推荐的方式上。测试代码能回答某些问题,但是,和专用的帮助页面相比还有很大差距,帮助页面不仅提供了教学实例,还提供了帮助新手入门的内容。

2.2.2 创建共享库

创建共享库时,我们应该以极简为第一原则。如果你有第三方库的依赖,这是你要考虑的重中之重。假设我们的授权组件依赖于某个流行的Java库,譬如谷歌的Guava,并显式地声明了该依赖。Payment服务导入新的授权库时,由于依赖传递,它也会依赖谷歌的Guava库。到目前为止,一切都很顺利,直到Payment服务引入了另一个第三方库,新的库也对谷歌的Guava库有依赖,不过它依赖的是另一个版本的Guava库。图2.5展示了这种场景。

图2.5 Payment服务实现中的依赖传递

这种情况下,同一个库在Payment服务中会存在两个不同的版本。如果这两个库底层的大版本不一致,问题就会更加严重。这意味着它们甚至可能不是二进制兼容的。如果这两个库都存在于你的类路径中,你又没有做额外的配置,通常情况下你的构建工具(譬如Maven或者Gradle)会自动选择新版本的库。譬如可能出现这种情况,第三方库代码对27.0版本的Guava库中名为methodA的方法有依赖,而该方法在28.0版本的Guava库中被移除了。如果你没有在配置中明确指明使用哪个版本,构建工具就可能选择新版本的库。此时,就会发生类似MethodNotFound这样的异常。这是因为第三方库期待使用27.0版本的Guava库中的methodA()方法,而构建工具选择了28.0版本的Guava库,因此第三方库必须使用它。这就会导致上述问题的发生。这个问题很难解决,甚至可能让团队失去对你提取出来的库的信心。因此,你的库应该减少直接依赖。我们会在第9章和第12章更深入地讨论如何选择系统中的库。

在本节的场景里,我们假设新提取的库会同时被Payment和Person服务所使用。截至目前,没有固定的团队负责维护授权服务本身,因此两个团队都会参与到新的授权服务的开发工作中。这样一个库的开发工作需要在两个团队的成员间做一定的计划和协调。 JCDpq8wZddWS/0XtAlacL8PxEo/bBFElFS0jv3R+8C2+yZCxYQT19YWQ14h02rzs

点击中间区域
呼出菜单
上一章
目录
下一章
×