微服务不是问题,无能才是!


微服务不是问题,认知能力才是关键,无法意识到"认知负荷"存在的人,是无能的人,是组织无能

微服务本身并不是问题,对于较小的产品,单体架构也不一定更适合。

无能
软件工程领域的炒作令人难以置信。微服务是当前的热门话题(尽管我认为人们现在已经准备好迎接下一个趋势)。

事实上,我喜欢我们如此迅速地采用新技术。这让我们的职业充满趣味。如果我不喜欢探索,我就不会在完全不同的技术领域拥有如此多的经验,也不会成为一个差劲得多的程序员(很可能在我的职业道路起步时使用的还是那个低劣的俄罗斯 PHP 框架)。

然而,采用一种新的(开创性的)技术意味着我们从前辈变成了后辈。是的,我们会应用迄今为止学到的知识和技术,但有时这些知识和技术并不能很好地迁移。

还记得 Scala 开始流行的时候,人们发现其中的代码要么是过于 "聪明 "的数学胡言乱语,要么就是与 Java 代码不同的语法。

公司通常会认为,如果雇用 "聪明人",他们就能把事情弄明白。
事实并非如此(另见下文的 "聪明人情结")。聪明并不能保证你能解决任何问题。
经验才可能是最宝贵的财富(经验本身也需要高质量,这也是难得的财富),而我们在采用新技术时却放弃了经验。我们甘愿让自己变得无能。
当然,有些人在这方面知识渊博。但是,他们能够教育和带动其他人正确利用技术吗?没有。

实例:Apache Kafka 和事件驱动系统
如今,"Apache Kafka "在谷歌上的搜索量激增。

很多公司,尤其是初创公司都在使用它(只需在 LinkedIn 或一些招聘网站上搜索即可)。

当我把它称为事件总线时,我遇到的很多人都会感到愤怒(脸上露出居高临下的怜悯表情)。
不,这不是事件总线,这是一个事件流和存储平台
所以说,这是一个不会删除消息的事件总线?
不,你不明白,让我再解释一遍

事实上,很多公司甚至没有将它用于事件流,而是将它用作一个消息队列,如果有人搞砸了,他们就可以重播。

在我目前所在的公司,它甚至与 Avro 架构一起使用,作为使用 protobuff 的 GRPC 的替代方案。这有什么意义?还不如使用 HTTP。

现在,推荐它的人通常(但并不总是)都有一个相当有说服力的理由,而且听起来他们确实明白它为什么有用,以及如何有效地使用它。
问题是,即便如此,他们雇佣或管理的人也不会理解:Kafka 才诞生 10 年前,我从哪里获得使用它的经验呢?

如果一个人一辈子都在使用锤子和钉子,而你给他螺丝和螺丝刀,他只会说 "哦,这锤子真烂,但工作就是工作",然后用锤子柄把螺丝砸进木头里。如果你想让他们正确使用锤子,那就对他们的教育进行投资,或者至少手把手地教他们,直到他们熟练为止。
人们会这样做吗?
除非是一个很小的工程团队,每个人都紧密合作(即便如此,也不一定会这样)。
别忘了,有些团队的技术负责人也不知道自己在做什么。

我其实很喜欢基于 kafka 的架构。对我来说,它就像一个更好的 AWS SNS(如果我不是设置和维护它的人)。但是,如果我在招聘职位中有Kafka技能要求,那么很有可能这家公司用错了它,所以它会成为这家公司的麻烦,需要从外部招聘专家。

躺平
学着我拿锤子和螺丝刀的样子,你可能会问,为什么不自己找点时间多学点东西呢?毕竟,你可以每天花一两个小时读点东西,还能拿工资(而且对公司还是有用的)。

嗯,因为大多数人都不屑于提高自己。

是的,程序员应该不断学习,人们在 Reddit 上对新手说的那些废话都是废话。但事实是,大多数人并不学习。

还有一类人。他们是优秀的工程师(有时甚至是杰出的工程师),知道自己在做什么,但根本不关心如何做正确的事,如何引导他人做正确的事。在他们看来,有了任务,解决了问题,就能拿到钱。没有额外的报酬。公司可能会在地狱里燃烧,他们不在乎。我见过几个这样的人。他们在闲聊时非常有见地,但在会议上却非常安静和无聊。

拥有 "不给力\躺平 "的文化是一种病。它会毒害周围的一切。到了某个时候,每个人都不再试图改善现状。他们只是随波逐流。就像下水道里的大便一样(我的老师常这么说)。这就是为什么我总是建议初级员工每年至少换一次工作的原因之一。与其困在一个岗位上,不如从不同的地方学到零碎的知识。

这也适用于代码审查。还有决策。还有招聘。不称职的人雇佣其他不称职的人,而其他不称职的人又参与招聘。这是指数级的。我有时觉得,我们需要对此实施封锁并开发疫苗。

自作聪明情结
面对现实吧,工程师们,我们大多数人并不聪明。我们只是被误导相信自己很聪明。知道如何编写代码或打开终端使用 git grep 并不能让你变得聪明或有教养。充其量,它只能让你成为邻近的精通技术的人。

在不少国家,从事 STEM 的人甚至被认为比学习社会学的人更聪明(明白吗?)事实上,无论兴趣、爱好和工作如何,人都是白痴。成为一名工程师(即使是经验丰富的工程师)并不代表你和你的决定就聪明。

我之所以提出这个问题,是因为我认为,拥有 "自作聪明情结"(我们姑且称之为 "聪明人情结"),往往会让我们变得 "自作聪明":

  • 事情过于复杂。让我们在 AWS 上用 10 个不同的 Haskell 容器 Lambdas 构建一个 slack bot,它们通过 SNS 互相对话,并通过 Circle CI 持续部署,使用数据汇入 Athena。现在我们不必自己挑选站立协调人,每天早上 8 点就会自动完成!
  • 给同一件事取不同的名字。适配器?翻译器?门面?委托人?装饰器?桥接器?啊,你是说我们可以简单地称之为封装器的东西?是的,你可以说这里最重要的部分是上下文,但不如你先简单解释一下你要解决的问题,而不是用你那些花里胡哨的术语让自己听起来像个傻逼书呆子?下次你把基于 kafka 的系统称为 "事件驱动"(真的是这样吗?)
  • 看到不存在的问题。是的,我们需要大量扩展,让我们跟随 Twitter 的脚步,使用 Java 和 Scala,因为在我们这个只有几千名专业人士使用的小众应用中,将来肯定也会遇到同样的扩展问题。到那时,我们就会说 "神经病!我们已经为你准备好了"。其实,Java 太慢太老了,我们还是用 Elixir 吧。我们有一群 Ruby 开发人员,他们肯定知道如何用 Elixir 构建系统,反正它和 Ruby 差不多,只是速度更快、功能更强大。
  • 诸如此类,不一而足。我有点跑题了

不聪明完全没关系(我当然不聪明)。我们只需要避免愚蠢。我们需要谦虚,不要让自我蒙蔽了双眼。如果我们认识到自己并不聪明,我们就可以开始回归基本,保持简单。如果我们不够聪明,无法预测未来的问题,那就不要尝试。相反,让我们解决手头的问题,尽最大努力保持事情的简洁和灵活(参见双向门决策)。这才是明智的决定。

附注:巧合的是,在写这一部分的时候,我在 Reddit 上看到了一个关于软件工程文化中的毒性的问题(不幸的是,上下文被版主删除了)。其中提到的一点是试图成为房间里最聪明的人的文化。这种情况经常发生(我自己就犯过这样的罪)。

能力
那么,我们该如何驾驭这个被炒作所驱动的无能的软件工程世界呢?我希望我知道答案。不过,我会尝试提供一些想法。

SOLID 系统
我们都听说过 SOLID 原则。然而,我注意到人们几乎总是将其与 OOP 特别联系起来,而在谈到其他语言或系统设计时则完全将其排除在外。事实上,每当我在面向对象的语境之外提到 SOLID 时,你都不会相信我在会议上被纠正(即在我的位置上,笑)了多少次。实际上,如果你认真思考一下,每一条原则都适用于整个系统,就像适用于一个特定的代码库一样。

正如我们常说的那样,编程就是把一个问题拆分成一个个小问题,然后逐个解决。然而,编程不仅仅是为了解决问题。系统和组织也是如此(参见康威定律)。每个层次的系统(包括组织)都需要合理地分割成更小的单元。而在每个层面上,一个合理拆分的系统单元都将具备 SOLID 原则所定义的特征:

1、单一责任原则
一个单元应该有明确的职责,并始终对其负责。

  • 在代码层面,这意味着你的库/模块/类/函数/等等应该只有一个目的。
  • 在系统层面,这意味着微服务也是如此。不过,在实践中,只要服务是作为独立的应用程序接口提供的,那么服务是否具有多重职责并不重要。从根本上说,服务只是抽象层次之上的应用程序接口的基础结构单元/连接点。
  • 在组织层面,这意味着团队应该有明确的职责。在技术团队中,这意味着要负责特定的项目,最好是特定领域的项目(这一点很难实现)。

2、开放-封闭原则
单元必须对扩展开放,但对修改封闭。就我个人而言,这里还包括 "组成重于修改"。

  • 你的数据结构应该是向后兼容的(在代码和系统层面)。
  • 服务/应用程序接口应允许在其基础上进行构建,但不得进行任何修改。这与单一责任原则有关。例如:支付服务应只提供资金流动功能。与特定产品相关的业务逻辑应作为独立的松耦合应用程序接口存在,并利用原始支付服务。
  • 在组织层面,团队的角色不应随时间而改变。如果到了某个时候,团队的角色不再适用,就需要解散团队,并创建一个新的团队,明确新的目标。在技术团队中,这也与代码所有权密切相关。

3、利斯科夫替代原则
所有子类都应该可以替代父类。这听起来是 OOP 的特有原则。但并非如此。

  • 在代码层面,它意味着当你有两个单元实现了相同的接口时,你需要确保这两个单元在任何需要接口的情况下都是可用的。仅仅提供相同的结构/方法等并不总是足够的,在设计、实现和使用接口时要牢记这一点。
  • 在系统层面,这一点是完全一样的。我的意思是,API 的字面意思是 "应用程序编程接口"。
  • 在组织层面,团队不应依赖其他团队,而应依赖他们所做的工作。听起来很明显,但沟通过程有时非常低效。你通常如何接收其他团队的工作?他们会发送 JIRA 工作单吗?他们会先与你的经理或技术负责人沟通吗?如何确定优先级?最后期限/估计/更新是如何传达的?

4、接口隔离原则
单元不应依赖于它们不使用的其他单元。

  • 在代码层面,这基本上意味着使用 B 的 A 不应该因为 B 需要而直接使用 C。举个简单的例子:我不应该仅仅因为要获取特定用户而访问数据库凭据并与之建立连接。这应该在其他地方完成(具体位置和方式取决于具体的代码库)。
  • 在系统层面,情况也差不多。举例说明:TODO
  • 在组织层面,同样都是关于沟通。我不应该与不相关的人打交道,也不应该为了从另一个团队获得某些东西而跳来跳去。

5、依赖倒置原则
单元之间不应相互依赖,而应依赖于抽象(接口、契约)

  • 在代码层面,你基本上不想直接使用其他单元。但这并不总是切实可行,例如在 Ruby 中,这样做通常更方便,也没有什么坏处。例如,在 Java 这样的语言中,直接使用其他单元可能会导致笨拙/糟糕的测试。我想我会将这一原则改写为 "接口第一,实现第二";在这一点上很难制定具体的编码规则。
  • 在系统层面,服务之间的通信同样应该依赖于合约,因为内部服务往往会发生变化。有时,服务甚至会被替换。
  • 在组织层面也是如此。针对特定人员的合同。

单体 vs 服务 vs 代码
单一服务和多个服务有什么区别?调用。

考虑一下这两个相关的单元:

 

 module Authorisation
      def allowed?(user, action)
        check_database
      end
    end

    module Users
      def freeze(user)
        if !can_freeze_users?(current_user)
          return auth_error
        end

        do_it_mf
      end

      def can_freeze_users?(user)
        check_permission(user, :freeze_users)
      end
    end

很正常的单体代码,对吧?如果我告诉你这些单元不是来自同一个代码库呢?第一个模块实际上是一个独立的服务。有什么变化?

单体代码库和面向服务代码库的真正区别在于如何调用应用程序接口。

 

  # Monolithic
    Authorisation.allowed?(user, :freeze_users)

    # Service-oriented
    # makes HTTP/GRPC/etc request
    AuthorsationApiClient.allowed?(user.id, :freeze_users)

在面向服务的异步系统中,通信将不那么直接,具体实现方式将取决于上下文。您可能需要通过订阅某些更新日志,在本地保存一份用户权限副本。

回到无能的问题上,我喜欢面向服务架构的原因之一是,我宁愿拥有一些自己团队以外的人无法触及的小型服务,这样我们就能完全控制它们。我不信任别人,所以我宁愿不让他们碰我的东西。

当每个人都拥有一切时(这也是单体式代码库经常发生的情况),什么也做不了。
(banq注:每个人拥有代码的一切,他就是上帝了,不是人了,而且成为上帝掌握全局的人很少,几乎肯定没有,除非是恶魔。每个人都有认知局限,没有全能无所不知的,ChatGPT也有它不知道的,超过自身能力的认知就成为认知负担、认知负荷了)

另外,单体支持者经常说,编写得当的单体总是可以很容易地从其中取出一些东西。这话没错。然而,你的组织有多大可能有足够的能力编写出这样的单体呢?

思考开发者体验
我们总是谈论 UI/UX、顾客至上、逆向思维--许多公司都从亚马逊那里偷学了这些原则。但人们并不总是想到,客户并不总是企业客户。

当你编写代码或创建项目时,你的同事(和你自己)也会成为你的用户。因为他们会使用你刚写的代码。他们会阅读它,修改它。为你的用户考虑人体工程学。

测试系统/服务是否容易?本地运行是否方便?我们能否轻松发现并解决生产问题?我们需要文档吗?是否有解决具体问题的明确方法(现有代码应鼓励编写好的代码)。

KISS
保持简单。组织中的其他人把事情搞得过于复杂,并不意味着你也必须如此。当事情影响到系统层面时,你也许应该与你的技术主管谈谈,这样他们就能处理任何反弹。在与外人打交道时,他们是你最好的朋友(至少应该是)。

时刻保持常识。请学会识别单向/双向门决策并采取相应行动。

加倍努力和全身心投入并不可取
服务或单体似乎是个好主意,但这并不意味着你需要走极端。你总是可以混合使用。我认为,人们通常会采取 "要么全有,要么全无 "的立场。但这是不切实际的,而且往往很危险。我们需要时刻保持常识。此外,你可能已经注意到,许多初创企业突然决定转向服务。当他们这样做时,情况就会失控。最好的办法是循序渐进,并由有能力的人负责监督。

为 "微服务 "添加额外功能并不丢人。但要确保这些功能不会与现有功能纠缠在一起。如果需要,您可以稍后再将它们提取出来(剧透:YAGNI)。

所有权(边界感)
单一责任和开放封闭原则--拥有几项服务的所有权,不要让任何人碰它们。理想情况下,甚至自己都不要碰它们。所有变更/功能请求都必须通过特定的沟通渠道提交到你的积压工作中。如果有多个紧急请求,那就很难办了,让他们滚蛋吧。不,我们不在乎你的 KPI。我们有自己的 KPI。

如果你总是让他们把 "紧急 "的事情推给你,他们就会一直这样做。时不时表明立场。一味妥协只会拖慢今后的工作进度,公司也无法扩大规模(这样的例子比比皆是)。

在我目前的工作场所,所有权是我最大的不满。有 3 个不同的团队在开发我的软件仓库。这到底是怎么回事?最近我看了一眼代码,发现有些地方我都不认识了。有些改动甚至把我们团队搞得一团糟(幸好改动不大)。

这在不同层面上造成了许多不同的问题。

  • 动力。只有你的团队在做的事情,你才更有可能去管它。你希望保持整洁干净。这是你自己的事。当共享所有权时,这种想法就会消失。不,把服务说成是你的,但让其他人来做,这不是所有权。正如我在上文所说的(虽然我很确定是亚里士多德先说的),如果每个人都拥有一切,那么就不会有任何事情得到处理。
  • 缺乏沟通。如果没有适当的所有权,相关人员往往无法充分参与决策。他们的注意力也会因为参与许多事情(有时是无关紧要的事情)而减弱。
  • 沟通过度。没错。一些相关人员参与不够,但一些完全不相关的人却参与得更频繁。这就降低了会议的质量,并强化了对更多会议的需求。因此,缺乏沟通也会导致沟通过多。
  • 知识折旧速度。如果人们所从事的工作经常被不同的人改变,他们对系统的整体理解就会受到影响。你积累的所有知识都会加速退化。你今天所了解的知识明天就会失效。当你只需要了解系统中你自己的部分,而所有的变化都要经过你和你的团队时,情况就不是这样了。
  • 无法估算,期限紧迫。由于每个人都可以随处做出改变,因此管理层将最后期限和预期估算放在了一个团队的范围内。如果估算的日期过于遥远,他们就会让更多的人参与进来(两个女人可以在 4.5 个月内生下一个孩子)。这里的问题是,由于工作内容相同,人们会相互影响,因此会产生额外的障碍和不必要的对话。此外,由于知识的过时,团队甚至无法正确估计特定服务的工作时间。如果有了明确的所有权,你就可以联系相关团队,他们或多或少都会对自己的工作和潜在的障碍做出精确的估计。
  • 糟糕的代码就像滚雪球一样越滚越大。与动力有关。你是否曾发现自己不得不在一个满是低劣代码的文件中添加更多测试或额外函数,但却因为必须遵循相同的模式而无法正确编写自己的代码?没错。不拥有的东西会产生更大的雪球。人们并不关心,有时人们无法确认蹩脚的代码不是因为某种原因而蹩脚。这显然会拖慢未来产品功能的交付。此外,它还会使你更不可能过渡到你宝贵的 "适当的 "微服务,因此你最终会陷入半途而废的状态,你试图全力以赴,但却失败了。蹩脚的代码会导致更多蹩脚的代码。呈指数增长。
  • 人力资源成本。上述所有情况都会导致不得不雇用更多的人,以及更多的人离开(并不得不替换这些人)。组织得当的团队工作效率会高得多。如果不能对他们进行适当的管理,那么雇不雇优秀的开发人员都无所谓。

我现在意识到,由于这会对整个组织和产品产生蝴蝶效应/多米诺效应,我可以把这份清单写很久。所以我就不说了。

管理
我之前提到过,你可能会有才华横溢的建筑师为你工作,但如果没有人将他们的计划付诸实践,他们就会一文不值。

仅仅告诉人们 "我们现在要采用事件驱动 "是不够的。你需要确保他们理解这意味着什么,并真正遵循这一点。如何做到这一点?我也不知道。

我想也许更严格的等级制度会有所帮助:架构师管理团队领导,团队领导管理自己的团队并向架构师汇报。工程经理应该推动等级制度的实施。是的,听起来很严格。但我们是一个组织,而不是一群个人,我们需要高效地协同工作,而不是各自为政。

如果你不关心公司,那就关心你自己。大声说出来
老实说,我理解 "无所谓 "的态度。毕竟,事关的不是你的生意。但你肯定不想就这样过一天算一天。当然,你也不想为了某个想完成 KPI 的白痴经理而编写低劣的代码和完成低劣的任务。

不要担心公司的成功。想想你自己的舒适度。我们可以改变什么,让你感觉更好一些?找出这些问题并说出来。

是的,说出来并不容易。有时你想避免对抗。在这种情况下,找一个你可以倾诉的人。这个人不会避免对抗。他可以试着促成此事。这个人可能是你的经理。或者是团队领导。甚至是与你或你的团队无关的人。但你需要找人谈谈这件事。否则,一切都将保持不变。

就像伦敦地铁上常说的:看到它,说出来,解决掉。

结论
我知道这是一个残酷的标题,有时也是一篇残酷的文章。这就是生活。所有观点仅代表我个人。

我也觉得自己说得好像所有问题都来自愚蠢的工程师。其实不然。平均而言,工程师都很普通。因此,LinkedIn 上那些辞职的人都在说,和他们共事过的人有多聪明,这让我很反感。

不,我说的无能是指整个组织的无能

当无能的人领导组织时,就会造成最大的损害。低效的管理导致低效的员工。

软件工程很简单。这是一门简单的手艺,与锁匠没什么区别。它没有浪漫。

(banq注:微服务不是问题,认知能力才是关键,无法意识到认知负荷存在的人,是无能的人,是组织无能)