69

03 | 微服务反模式与缺陷:代码共享反模式

 5 years ago
source link: http://www.10tiao.com/html/136/201806/2651036803/1.html
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.

译者简介:ASCE1885, 《Android 高级进阶》作者。 本文首发于Source Code Chain开发者社区,欢迎使用我的专属邀请链接加入一起交流。

微服务被称为“无共享”架构,实际上,我更倾向于把它看作是一种“尽可能少共享”的架构,因为微服务之间不可避免的总会存在某种程度的代码共享。例如,与其使用一个负责身份验证和授权的安全服务,你可能会更倾向于将安全功能相关的代码封装为一个 JAR 文件,例如 security.jar,并提供给所有服务使用。如果安全性的保证是在服务内处理的,那么这种方式通常是一个很好的做法,因为它避免了每个请求都需要对安全服务发起远程调用,从而提高了系统的性能和可靠性。

然而,如果这种方式被滥用,那最终你将会碰到依赖的噩梦,如图 3-1 所示,其中每个服务都依赖于多个自定义的共享库。

这种级别的共享不仅打破了服务间的边界上下文,同时也引入了其他一些问题,包括总体可靠性,变更控制,可测试性和可部署性。

依赖过多

让我们回顾一下大多数面向对象软件应用的开发过程,不难发现共享带来的问题,特别是从单体应用架构迁移到微服务架构的时候。在大多数单体应用中,最重要的事情之一就是代码复用和共享。图 3-2 展示了在大多数单体应用架构中都会共享的两种构件(抽象类和公共的工具类)。

虽然创建抽象类和接口是大多数面向对象编程语言的常见实践,但当我们将单体应用中某个模块拆分为微服务时,这种实践会存在问题。自定义的共享类和工具类也存在类似的问题,例如共用的日期,字符串,计算等工具类。对于可能被潜在的数百个服务共享的代码你将作何处理呢?

微服务架构模式的主要目标之一就是尽可能的减少共享,这有助于保证每个服务的边界上下文,从而换来快速的测试和部署能力。使用微服务,所有这些都可以归结为变更控制和依赖关系。服务间依赖越多,想要隔离服务的变化就越困难,从而很难单独对一个服务进行测试和部署。服务间共享越多,依赖也就越多,从而导致了脆弱的系统,它难以测试和部署。

代码共享的技巧

避免这种反模式最好的方法就是服务间不共享代码,但这说起来容易,做起来难。正如我在本文开头实事求是的讲,总会有一些代码需要共享。那么这些代码要怎么放置呢?

图 3-3 展示了解决代码共享问题的四个基本技巧:共享工程,共享函数库,代码复制和服务合并。

共享工程的使用将会在共享工程中的公共源代码和每个服务工程之间形成编译期的绑定。虽然这使得软件开发和改动变得简单,但它是我最不喜欢的共享技巧,因为它在运行时会引入潜在的问题,使得应用程序变得不那么健壮。共享工程技巧的主要问题是沟通和控制,我们很难知道共享模块的变动及其原因,也很难控制我们服务是否需要特定的更改。想象一下,当你正准备发布你的微服务时,却发现有人对共享模块作了重大修改,从而迫使你不得不对服务代码作改动和重新测试,然后才能进行部署。

如果你想要共享代码,更好的方式是使用共享函数库(例如 .NET assembly 或者 JAR 文件)。但这种方式使得开发更加困难,因为任何对共享库中模块的改动,开发人员都必须首先打包函数库,然后重启服务,最后重新测试。然而共享库的优点是我们可以对库进行版本控制,从而更好的控制服务的部署和运行时的行为。如果共享库作了修改和版本化,那么服务的所有者可以决定何时合并该修改到服务中。

微服务架构中常见的第三种技巧是违反 DRY(Don't Repeat Yourself)原则,它在需要特定功能的所有服务中复制共享模块。虽然这种复制技术有风险,但它避免了依赖共享,并保留了服务的边界上下文。当复制的共享模块需要改动时,尤其是修复某个缺陷时,这种技巧就会出现问题。这种情况下,所有服务都要进行修改。因此这种技巧只对非常稳定的,几乎不会再改动的共享模块有用。

有时可能使用的第四种技巧是服务合并。假设两个或三个服务共享一部分相同的代码,而这些公共模块需要经常改动。由于这几个服务在公共模块改动时也都要跟着测试和部署,所以你可以把这些服务和公共模块整合到一个服务中,从而移除依赖的库。

关于共享库的一个建议是避免把所有共享的代码合并成单一的共享库,例如 common.jar。使用 common.jar 你很难知道你的服务是否需要集成共享代码以及何时使用。更好的方法是把共享库拆分成具有独立上下文的多个库。例如创建基于上下文的 security.jarpersistence.jardateutils.jar 等。这能够把不经常改变的代码和频繁改动的代码分离开,从而更容易认清每次更改的上下文,以及决定是否要立即把更改的内容集成到我们的服务中。



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK