本文最初发表于 Medium 博客,经原作者 Chris 授权,InfoQ 中文站翻译并分享。
本文的题目看似自相矛盾,实则不然。
我想讲两个故事。一个是不从微服务开始,一个是从微服务开始。我认为,通过观察事物的两面,我们将对微服务的实际好处有更多的了解。
闲话少叙,言归正题。
假设你正在一个大型电子商务平台上工作。和所有电子商务平台一样,都有一个产品列表页面,显示所有的产品。同时,也有一个处理购物车和结账流程的结账页面。
很久以前,这家公司的 CTO,John 读到了关于微服务方面的一些内容。他觉得微服务是个好主意。于是,他决定创建一个微服务来管理产品列表域和购物车域。
微服务架构允许 John 拥有一个产品清单团队和一个结账团队。他们有各自的业务目标、指标、KPI、SLA 等等。这两个团队都是跨职能的团队,负责产品列表面以及端到端的结账流程。
他们每一个人都可以随心所欲地做他们想做的任何事。举例来说,结账团队可以实施他们自己的 AB 测试,并在上面设置多个本地化和一个漂亮的节日主题。产品团队可以专注于通过建议相关项目来优化产品列表。他们可以把精力集中在自己的领域,而不必担心会对其他领域做出破坏性的改变,只需保持域间 API 通信的向后兼容性。
此外,我们还可以对系统进行扩展。假设我们周五有一个大型促销活动,客户将会看到更多的产品和促销活动。在需要一个大点的结账页面的同时,我们可能需要更大的产品列表后端。
完美听起来不错。生活美好!
第二年,CEO Jane 想要搞一个促销活动。Jane 也从一些研究中发现,如果在登陆页面和结账流程中展示相关的产品,能够显著提高销售额。
存在着特定的定价方案、相关性(消费者购买大量 IT 相关产品在 IT 产品上会获得更多折扣)和客户特权等级。这些应该由产品列表团队进行管理,因为他们已经进行了大量关于如何显示吸引人的产品的研究。此外,产品列表团队也知道如何用吸引人的方式来展示产品。他们很清楚什么颜色、字体、图像大小和边距能够产生最佳的转换率。
不过,改变将会很艰难。
因为结账流程和产品页面的前端和后端都属于不同的微服务,因此需要两个团队共享代码、设计、逻辑等。能够进行简单的复制粘贴,我们可以创建一个共享库,并且可以公开某种 API。
尽管它们在技术上都是可能的,但是在这种微服务架构中执行这种类型的改变需要比单体架构更加复杂。
那么如果我们需要迭代设计、数据和推广中的所有东西呢?每次迭代的每一个改变都必须反映在结账页面和产品列表页面上。假如我们做得不够好,我们就需要把所有的工作翻倍,或者在这两个团队之间一次又一次地将 API 重新设计。
使用微服务架构后,我们就可以在很大程度上使 服务内部的任何改变变得更容易,并使跨服务的改变更加困难。
因为人类并不擅长预测未来,所以我们永远不应该从微服务开始。我们不知道 6~12 个月后会有什么业务。不管我们怎样拆分微服务,它都有可能是错误的。
让我们永远不要从微服务开始。
让我们倒回去,看看事情的发展会有什么不同。
这家公司的 CTO,John 读到了关于微服务方面的内容。John 认为这微服务是个灾难性的想法。微服务在技术上所解决的所有问题都可以用单体来完成,那么为什么要把分布式部分加入到方程中呢?我们的大多数程序员都会受到分布式计算谬误的影响。
John 坚持使用单体架构。
John 有一个由 30 名开发者组成的团队。按照这个规模,John 是无法审查他那庞大的单体的每一个改变的。因此他偶尔会检查一下代码。
该公司走了了相反的方向。他们希望实现一个单一的“立即购买”(Buy Now!)按钮,它基本上是一个单步结账。
由于这明显位于产品列表页面,因此利益相关者要求产品列表团队来开发。其中一个开发者看了这个需求。他们认为,既然这个页面都是关于产品的,我们应该像这样在一个产品类中实现它。
class Product { public void CheckoutWithOneClick() { this.PaymentService.Pay(this.Price); }}
复制代码
在经验丰富的开发者看来,这是一种非常非常强烈的代码异味。不知何故,它通过了代码审查,被签入(check in)并成为单体的主要部分。
六个月之后,所有结账相关的事情都变得非常难以实现,而且漏洞百出。如果我们希望应用促销代码,我们需要记住在产品类别中实现了一键结账。如果我们希望为一个特殊类别的客户提供折扣,我们也需要记住这一点。在许多情况下,结账团队被要求实现一些复杂的结账方案,但这和这种一键按钮的效果并不理想。
特性开发、错误修复以及所有与结账流程相关的内容都是单独实施的。由于产品团队拥有 Product 类,而结账团队拥有 CheckoutService,每个团队对需求的解释各不相同,业务逻辑的实现也各不相同。一种方法是首先检查促销代码,另一种方法是检查客户特权,等等。
当 John 一旦看到这一切的时候,就太迟了。CheckoutWithOneClick 与常规结账流程之间存在许多代码重复和独立的逻辑分支。要重构这段代码需要大量的投资。
在某个时候,John 被解雇了,而 Jane 聘请了一名新的 CTO。
Jane 问的第一个问题是:为什么我们在结账流程中做任何事情都这么难?
新 CTO:嗯,一键结账和普通结账有不同的逻辑实现。这样就很难在结账流程中做任何事。
Jane :怎么会这样?
团队中的随机工程师:每个人都知道这件事。它由一位开发者开发,他决定为一键签入提供单独的逻辑。那太糟糕了,但我不知道 John 怎么能做得更好。他不可能检查每一段代码。
新 CTO:好吧, John 应该从微服务开始。这样的话,产品列表团队将不会想到这样的设计。他们会被结账团队的服务所有权所阻止。
嗯, John 处于一个奇怪的境地。如果他使用微服务,就会有厄运;但如果他不使用微服务,也会有厄运。
因为微服务在本质上讲是一种管理工具。
微服务可阻止某些类型的改变,并且使得某些类型的改变更加容易。
在第一个故事中,微服务架构阻止了所需的改变。在第二个故事中,由于缺乏微服务架构,导致了一系列不希望出现的改变。
这就是为什么 John 无论如何都注定要失败。问题不在于单体与微服务。而是问题在于架构选择与业务的增长不一致。
你可以争辩说,在第二个故事中, John 可以更加努力工作,确保所有的代码都经过了设计审查和整个审计等等。但这才是重点。这种管理方案的成本可能会很高,并在开发过程中会增加大量的开销。
微服务能够降低这一成本,因为它本身很难让设计混乱,所以我们不需要所有这些繁重的流程。但当我们真的想要把这个设计搞得乱七八糟的时候,好的,你已经从第一个故事里学到了。
最后,架构必须与业务增长保持一致。这就是我要讲的全部。
在使用微服务时,你会让某些增长变得更加容易,同时也会让某些类型的增长更加困难。
如果你有一个结账的微服务,结账团队就能更快地推出新功能。他们自己可以发布,能够使用自己喜欢的编程语言,不依赖其他团队,能够独立管理其服务的可扩展性。
这就是我认为的,微服务架构的核心优势。
微服务的每一个技术方面,例如独立扩展系统的某些部分或使用多语言实现,都可以用单体来解决。你可以使用一个构建,并将其配置为某些容器打开特定的端点,然后你就可以独立地扩展系统的某一部分了。
你可以在操作系统级别上使用跨进程通信,并管理二进制分布,这样就能用多语言实现了。也许延迟比网络更好。
单体不能解决的问题在于,当你希望拥有某种类型的管理结构,并且通过给予自主权和频繁的独立发布周期,从而激励某个领域的发展和创造力。这就是在单体架构中,真的非常难以做到的。
所以我想说的是微服务实施的决定性因素。它是否与你的组织想要发展的方式相一致?
由于类似地,微服务从本质上说是对管理问题的一种技术解决方案。这个问题确实存在,需要很好的解决方案。
如果你觉得这是个愚蠢的管理问题,那么我会邀请你来解决它。如果你能以可扩展和可重复的方式解决“如何使程序员有效协作?”这样一个简单的问题,我相信你有可能比杰夫·贝佐斯(Jeff Bezos)更富有。
亚马逊(Amazon)一直在做这方面的工作。谷歌、Facebook 也一直在做这个工作。现有的每一家巨无霸科技公司都已经在这方面工作了几十年,并提出了多个独立的服务产品(后来工业化成为微服务)。
如果你有更好的答案,那么请继续,让我和所有这些人知道。他们已经准备好投资了。
作者介绍:
Chris,专门从事编程的产品开发者。坚信人文主义。
原文链接:
领取专属 10元无门槛券
私享最新 技术干货