1. 架构从何而来
不知道大家在学习软件架构时,有没有这样的困惑,就是开发人员之间讨论的架构都是类似这样的:
而如果去看一些架构师做的汇报,或者去看一些开源项目的架构图,会发现他们的架构图是这样的:
就好像两者讨论得是两个不同的东西一样。为什么会这样呢?这种现象我称之为“架构的二象性”,灵感来自于物理学中光的波粒二象性。同一个对象从不同角度去观察,会显现不同的性质,软件架构同样如此。本文将从业务视角和实现视角两个角度去讨论架构,希望能够帮助大家理解架构的本质。
首先,就像文章有主题有中心思想,架构也是类似。架构的目标就是去解构业务需求。架构师会对业务需求进行分析拆解,然后提炼出一些关键的领域知识,并建立领域模型,然后以这些信息为依据设计初版架构,而这种设计方式就是所谓的“领域驱动设计”。其次,无论何种架构最终总要落地实现,这个时候就需要把承载架构的技术栈考虑进来,结合技术栈自身的特性和限制,甚至有时还要考虑实际的开发资源,对架构进行调整或补充,从而形成最终的软件架构。那么可以说架构的两个输入就是业务需求和实现细节,两者共同决定了架构的形态。既然有了两个输入,那么意味着从这两个输入的视角去看架构,必然能看到架构的不同形态。
2. 从业务视角做架构
第一个为项目做业务架构的人往往不是技术人员,可能是产品经理、需求分析人员或者直接是部门领导,总之是在项目初期第一批介入的人,他们对项目的目标有着最清晰的认识,也是最适合去做业务架构的人。另外业务架构本身就是和技术无关的。我们现在讨论的虽然是软件架构,但是软件只是架构的载体,而且不是唯一的载体。它的载体可以是一个熟悉业务的人,或者一个按架构划分职能的组织,甚至可以是一个大语言模型。假设未来的软件是以大语言模型为载体的,类似Siri那样通过对话来交互,那么实现方式想必也不再是写代码而是写提示词去约束或引导大语言模型的行为。所以既然业务架构与技术无关,那么做架构的人也自然不必懂技术。
从业务视角做架构的方法也很简单,主要就是把业务模块化,然后再理清模块之间的关系。人的大脑对于复杂问题的处理能力是有限的,系统的复杂度上到一定的规模就会让人觉得无法处理,因此“分而治之”是降低系统复杂度最有效的办法之一。但是怎么“分”是有技巧的,就像“庖丁解牛”的故事里说得那样,要把牛切成最小的块,得先理解牛的结构。对于业务或者系统来说就是要理清系统重各部分之间交互关系、依赖关系,再从“薄弱处”下刀开始切分。这样分出来的模块才具备独立性,如果两个模块间有大量交互,那么很可能这个两个模块间有“强依赖”,或许考虑把它们合为一个整体会更合适。一个模块只有当它的内部细节远大于它与外部的交互时,才能称之为一个有深度的模块。只有深模块才能向系统隐藏细节,才能减少系统的复杂度。相比之下过多的浅模块不仅没能降低系统复杂度,反而会增加认知负担。当一个模块被正确划分出来后,就相当于划定了一个作用域,把一部分业务细节藏于内部,同时也将该模块内的bug影响范围限制在模块内部,更容易定位和修复。这样才实现了模块的“自治”。
因此,常见的业务架构图一般都是一个倒树形结构,根节点是支撑系统运行的基础设施,然后就是业务的逐层拆分,直到叶子节点是最小的业务单元。例如下面这个最简单的例子:
这就是一个简单的基础设施支持业务的结构,而图中的每个业务模块又可以进一步拆解成类似的结构,所以说架构图一般都符合几何学中“分形”的概念。展开后就是下图这样:
3. 从实现视角做架构
到了这一步终于轮到技术人员上场了,但上场顺序还是有分先后的。只看开发人员的话,一般服务端开发会先开始做架构,此时虽然有产品经理所设计的产品原型或交互设计作为参考,但是这些对于服务端开发来说并不能直接照搬。因为服务端关注的是数据结构与其之间的关系,而从数据角度做的模块划分与交互角度往往并不是完全一致的。因此服务端开发人员首先会根据现有架构和需求来理解业务,然后再对业务进行“ER建模(实体-关系建模)”,这个模型会指导服务端开发如何设计数据库表,当然也可以依此来划分模块或者微服务。在这之后才会轮到前端开发人员来做前端的技术架构。
其实这里可以间接回答一个问题,就是“为什么很多前端开发会觉得职业发展中有透明天花板”。因为前端开发对业务的理解往往弱于服务端开发和产品经理,当前端人员开始做架构时,已经有了两个依据或者说两个约束,即产品经理的产品原型和服务端的接口文档,两者已经分别从不同角度对业务做了架构设计。此时前端人员已经不需要对业务做拆解,或者说已经没有拆解的余地,只需要在对应的页面去取对应的数据完成对应的交互即可。因此没法做业务架构的前端人员只好去关注MVVM、MVI等等这类前端架构模式,而不幸的是这些方面往往不是领导所关心的。
回到前端技术架构上来,从前端实现视角来看可以把需求比拟为一个流水线的模型,即由多个模块相互配合分阶段完成任务。因此这类模型最重视职责划分,讲究分工均匀,避免任务在某个环节堆积从而成为瓶颈。与上述的模块化架构不同,流水线型架构没法讲bug的影响范围限制在模块内,因为流水线需要每个环节协作来完成一个任务,一个环节出问题很可能导致整条流水线停摆。因此这类架构更注重模块之间的协作,虽然总共只有M、V、X(VM\C\P)三个模块但其组合方式却有多种,随着UI框架的改变其协作方式又会发生变化,这也是为什么前端架构一直在演进,而业务架构基本千篇一律的原因。近年来最流行的是“状态驱动UI”的架模式,因为该模式具有“可追溯”的特性,即UI状态的变化可以追溯到某个事件,而特定的UI状态组合又可以对应唯一一个UI样式,这样就提升了系统的可预测性,系统里发生的变化都可以复现,更好维护。关于这方面进一步的讨论可以参考这篇文章通用应用架构——如何设计一个适用于多平台的前端通用架构。
4. 殊途同归
虽然从不同视角去看架构貌似看到的内容会大相径庭,但实际上两者是相辅相成的,在真实的开发场景中也需要两方面配合才能更好的实现业务需求。可以用部队作战来类比,业务视角关注的是战略,注重排兵布阵,会将目标拆解成多个任务,然后分配给不同的部队去执行;而实现视角关注的是如何完成任务,注重让小队中的成员互相协作,各司其职。可见两种视角都是必不可少的,只有两者结合才能更好的完成任务。