分层架构:一个经典却得不到优的难题

经典的分层架构

当系统变的复杂,无论是为了便于维护还是为了便于理解,将系统进行分层都是一个经典的操作,这也更贴近人理解事物和拆解问题的方式。

回顾一些经典的分层架构模型,最贴近用户的层往往提供更高的价值,离用户最远的层往往是更底层的技术实现,如经典的三层架构(表现层、逻辑层、数据访问层)和OSI的七层网络模型(应用层、表示层、会话层、传输层、网络层、数据链路层、物理层)。对用户而言,随着“层距离”的增加,对层的理解难度会逐渐提高,甚至到无法理解。

分层通过对每层的职责定义以及层与层之间的交互方式定义,让知识在层与层之间做到合理的切割。对不同的用户而言,需要理解的层次是不同的,也就做到了知识的隔离。


分层架构实现并不容易

分层架构结构简单,非常易于理解,但这样简单的架构实现起来并不那么容易,我们来看两个例子:

例子一:越俎代庖

BFF作为分层架构中的一层,一定程度上起到了隔离前端UI展示逻辑和后端业务逻辑的作用,使得前后端都能够独立的演进。BFF通过组合后端业务逻辑接口,实现前端展示需求,这里的重点是 实现前端展示需求,那么BFF是否应该组合后端的各种能力,提供一种新的业务能力呢?

实施落地过程中,为了快速满足一个业务上的需求,BFF作为有着上帝视角的存在,掌握了所有后端业务服务的接口,可以很容易调度能用的后端服务接口有序的完成了业务功能。在这种实现下,BFF这一层的知识就不再限于前端的展示逻辑,还越俎代庖地实现了一些业务逻辑。

然而,当业务逻辑变更时,很难在后端业务服务中找到全部的逻辑,极端的情况是修改遍布了整个调用链。这种做法显然是不可取的。

例子二:臃肿的中间层

这个例子相信大家也不陌生(左侧原图参见Martin Fowler微服务进程内架构设计),我在图中用不同模块的大小来表达每个模块在实现过程中占的比重,虽然老马最初的设计图并无此意图。即便在最初做了各种设计,希望每个模块各司其职,但实现过程中将所有的逻辑写到Service层却是司空见惯,甚至是一定会发生的。

这些逻辑可能包括对请求参数的校验,对数据模型的转换和拼接,对业务模型属性的操作,对数据库读写的控制,对其他服务调用的控制等等,而这些应该却是其他模块的职责。

臃肿的中间层把一个服务的进程内代码全部揉在一起,就像一个“大单体”,其他层的组件仅仅是一层薄薄的和其他模块集成的必须代码,这种分层架构几乎等于没有分层,自然无法享有分层带来的任何好处。

其他会遇到的问题

从上面两个例子来看,无论进程间组件之间,还是进程内组件之间,分层本应该能很好的起到隔离作用,而不好的实现会将这种作用化为乌有。

不仅如此,层与层的边界不清或交互混乱还会带来很多新的问题(这感觉就像 单体架构拆分成微服务架构,以前不必关心的问题现在却成了难题),典型的问题还有:

  • 穿透性修改 层之间没有做到合理的切割,一个业务功能的实现从最外层一直贯穿到最内层,当业务功能变更时,每层都要做调整才能实现。
  • 层过多或过少引发的问题 这有点像 拆分微服务 ,当层过多时,层与层之间的依赖关系很难在设计时理清,交互逻辑复杂,甚至会出现层之间的循环依赖;而过少的层很难起到知识的隔离作用。
  • 跨层调用让系统丧失进化能力 跨层调用不是一定有问题,但我仍然不建议采用跨层调用,特别是一些基础的组件层。一方面跨层调用破坏了架构的统一,也就破坏了知识的边界。另一方面也破坏了层的进化能力。例如希望在某一层增加一些通用逻辑,比如权限校验或缓存时,如果这一层被跳过,这个进化就无法实现。

分层架构能做到“优”吗

问题的深层次原因

造成上面问题的原因主要是在分层架构中层与层之间没有做到很好的隔离,各层的职责和边界不清晰。在看看能做点什么之前,我们先来探究下其深层次原因:

  • 架构设计之初并没有严格的层级和边界设计: 架构师们通常有意识和能力在开发设计过程中判断每个功能的归属,和每层需要实现的工作。如果这种原则只存在于架构师的头脑中,而没有在团队内形成共识,完全依赖每个开发人员的经验和对当前架构的理解,就会不受控,很容易演变成层与层之间只是物理代码的隔离,逻辑上却严重耦合。
  • 有层级设计,但每层的职责设计难以支撑业务演进: 以微服务为例,如何设计微服务,不同的架构师观点也不尽相同。在我经历的项目里,有些微服务曾被设计成了”贫血“的服务,服务端仅保存了实体模型和简单的逻辑,如果要实现复杂的业务功能,需要由前端来实现; 这种设计给我们带来了很多运维上的问题,当前端不止一端时,相似的功能需要在每一端都实现一次。当某一端的实现有调整时(特别是不同的端由不同团队维护时),跨前端的逻辑不一致,导致后端数据在两个端相互覆盖。 可以看到,层的职责是会相互影响的,如果某一层没有承担起它该有的责任,其他层只能把这个责任担起来,从而引入不必要的问题。
  • 开发团队的认知水平和协作水平低下: 软件开发是一个团队协作共同创作的过程,如果在每个阶段都完全依靠每个个体的能力,软件的架构和质量取决于团队内最差的个体的能力和认知水平。这也是我在标题上写分层架构是“经典却很得不到优”的原因。 架构设计只是个开始,能否把架构按设计完美的实现才是关键。就像我们从来不缺创业的点子,缺的是把点子真正落地的实力。

我们能做点什么

软件架构的终极目标是,用最小的人力成本来满足构建和维护该系统的需求。 —— 《架构整洁之道》

为了实现这个目标,开发团队需要做到以下几点来消灭经典的分层架构在落地过程中产生的问题:

  1. 合理设计分层,清晰定义每层的职责 这一点毋庸置疑。但我要强调的是,这个设计要是全团队的共识,是顶在开发团队头上的第一个原则,任何的架构变更都要回到这里,来审视是否破坏了最初的设计原则。

  2. 通过技术手段守护架构 当定义清楚了分层架构,必然也就有了分层的一些原则,在《演进式架构》中推荐尽早确定系统的适应度函数,并定期的审查,根据业务和技术的需求修改当前的适应度函数或增加新的适应度函数,已保证架构能够按照设计的方向发展。

在进程内,有一些自动化工具可以通过测试的方式来验证代码的架构是否遵循了预先设计的原则(如ArchUnit),可以高效的帮团队识别出在开发过程中破坏原则的代码实现。

  1. 提升团队整体认知水平和协作水平 在团队不断成熟的过程中,很难保证所有的开发人员都能够有能力守护架构,因此除了通过技术手段守护架构,也非常有必要采取一定的手段来提升整个团队的认知水平和协作水平。

首先可以在团队内建立架构评审委员会,关于架构的关键决策需要由这个团队来拍板,而不是每个开发人员都可以决策。另外在做详细的实现设计时,要由有经验有能力守护架构的同学进行设计,并将工作内容拆分成更加可操作的Task。通过这种方式将整个团队的认知水平底线提升到可以守护架构的程度。


结语

软件系统的腐败背后似乎有一双非常有力的手在推动着,即便是简单容易理解的分层架构,在业务需求和技术都快速变更的大背景下,也很难在相对长的时间内做到“优”的水平。作为架构师,更应该保持一个时刻应对变化的心态,带领团队披荆斩棘,解决架构演进路上的各种问题。


相关文章推荐

All rights reserved
Except where otherwise noted, content on this page is copyrighted.