微服务的灾难-拆分
在之前写事故驱动开发的时候,提到过,在企业中的项目进行开发时,只要是自己方便,一个人可以用拆分和收敛同时作为自己的标准。所以大家都是双标狗。
目前业界的微服务方法论一般也没有固定的套路,比如在 《Building Microservice》 一书中,作者也讲到了服务之间协作的时候,可以选择编排(orchestration)和协同(choreography)这两种方式来对服务进行架构。所以在拆分阶段,就没有什么硬性的标准了,每个公司可能风格都有差别,并且都可以阐述出自己的条条以支持自己的架构是“正确”的。
显然,这件事情没有绝对正确的解法。无论哪种拆分方式,都会遇到业务边界的问题。在大企业中,顶着“架构师”头衔的这些架构师们根本就不会管任何实现上的细节。相对较大的业务需求,一般也是一线的 RD 商量怎么进行实现上的拆分。想要达到合适的职责划分,需要多个合作方的所有人都靠谱才行。这个要求实在是有点强人所难。比如在目前的公司内进行了三年多的开发,就和各种类型的人都打过交道。
兢兢业业的人比较多,但也不乏一些完全不负责任的人。有的素质奇差,只会逢迎甩锅,自己模块的职责都搞不清楚。本来自己该做的兜底不做,让所有下游系统给他擦屁股。从应聘要求上来讲,程序员应该是一个注重逻辑能力,编码能力的职业。然而你总发现有些人是没有办法讲道理的(可能是早期的一些能力一般的员工?),在与这些人讨论技术实现时,会陷入无穷无尽的无意义循环。而他能说(逼)服(迫)所有其它人就范的法宝,就是在两个小时的会议中不断地复读自己的观点,而完全不听取任何别人的观点。一旦这样的人在你的某个系统边界上待着,那你所面临的也是持续的痛苦。并且不断地在自己的系统中进行妥协,做那些职责上跟你的系统完全没什么关系的东西。
除去人的问题,业务部门的大多数一线领导是需要有业务上的业绩的。这种业绩怎么来?一般都是揽各种各样的活儿,能揽多少就揽多少。
从设计原则上来讲,逻辑上相同或者类似的代码应该放在一个地方来实现。这个稍微学过一点 SOLID 中的 SRP 原则就应该知道。这样可以避免逻辑本身过于分散,好处是:“一个类(模块)只会因为一个理由而发生变化”,其实就是相同的需求,尽量能够控制在单模块内完成。当然了这是一种理想状态,确实有些情况下达不到这种完美的平衡状态。微服务场景下这种业务边界往往划分都非常糟糕。即使 Domain Driven Design 的观点讲述了再多的上下文划分技巧,你在实际工作中会发现没有多少人把这些思想、原则当回事,一线 leader 在乎的就是揽活儿而已。他们在划分模块的职责时只考虑这么几点:
- 这件事情有没有业务收益,我能不能掺一脚
- 如果没有收益,这件事能不能甩给别人,我就不去做了
把业务上的影响全刨去,才会考虑架构上的事情。有些大佬会讲,系统是演化出来,而不是设计出来的,而这些“演化论”的大佬也是不参与一线开发的。你再看看实际的情况,只靠演化,可能演化出合理的系统么?
不可能的,对人的要求实在太高。而且是对所有开发人员要求都高。大多数企业的业务系统,都缺乏较为顶层的设计,有人管这个叫“战略设计”。相对复杂的业务逻辑,在企业的系统中根本没法合理解耦,很多时候实现一套业务流程的代码会随意地散落在多个模块,在你把所有模块的代码都看过之前,你是没法确定哪部分逻辑在哪个模块里的。可以称之为薛定谔的业务逻辑。
这样在模块发生负责人离职或者工作交接时,所有 RD 都会进入非常痛苦的阶段,我只看自己的模块,根本没法理清全局的业务逻辑!对于产品来说也一样,所有逻辑的分布都具有不确定性,在哪里控制我需要去问研发,而研发还要再问其他的研发,其他的研发如果是刚接手,又要去看大量的代码才能确定到底是怎么回事。如果代码写的烂(对于大多数业务系统来说,如果可以去掉),那可能连看都看不懂。就随便胡诌应付过去好了。现实就是如此荒诞。
在所有服务都在单体的时代,我们可以在合适的时间,参考《重构》书里的观点对我们的模块进行重构。重构对系统本身的要求其实也不多,只要测试覆盖率足够,然后是强类型语言,大多数 IDE 对重构支持都很不错了。但演化到微服务的时代,原来很简单的重构就没那么简单了。在 GHOST_URL/disaster-of-microservice-ul/ 中我们提到,开放的 API、消息、数据库中的字段名根本没有办法进行重构,为什么没有办法,因为我们的模块都被切开了,原本在代码中的强联系变成了分布式系统中的弱联系,薛定谔的联系。如果我们想要实现和单体服务一样的重构功能要怎么办?根本实现不了。你至少需要下面这些设施支持才能完成这样的伟业:
- 所有其它模块对你都有集成测试,并且有统一的 API 平台管理所有你们之间的“联系”。
- 有全局所有模块的“同时重构”工具
- 上线时,能针对旧版和新版的流量自动进行识别,防止新流量访问到旧系统
这个显然不可能啊,目前业界提出的 API 版本管理,也只是缓解了这种情况,新功能我如果在新版 API 实现,把旧版 API deprecated 掉,这样就可以逼迫用户放弃对我原来版本的依赖,平滑迁移到新版 API 上来。但显然加上版本,也并没有从本质上来解决问题,API 的用户在没有迭代需求的前提下,因为依赖方进行了修改就不得不进行修改,这是额外的工作量。
你也看到了,拆分给我们带来的并不全是好事,当前中大规模公司的开发日常流程,可能最终还是会把系统整体引向一片混沌。