Backend For Frontend (BFF)

零.背景

服务端需要支持多种前端设备下的用户体验时,常常面临现有API与某一端UI紧耦合的情况

比如为PC端页面设计的API需要支持移动端,发现现有接口从设计到实现都与桌面UI展示需求强相关,无法简单适应移动端的展示需求

一.现状

一个后端API层对多个前端(PC端、移动端等)的话,一般是支持一端后,再添加更多功能来支持其它端

It’s tempting to design a single back-end API to support all clients with a reusable API.

多个前端UI要求的API相似的话,这样做没什么问题(稍微增强下现有API就能支持另一种前端体验),但移动端UI体验通常与PC端不一样

Valuable services support many variations in clients, such as mobile versus web and different forms of web interface.

例如:

  • 屏幕空间小,所能显示的数据也少

  • 建立多个连接会增加耗电,前端做数据聚合成本高

  • 交互方式差异很大,后端API需要支持的功能也不同。比如PC端填表单,移动端扫码……

所以,移动端通常要:

  • 进行不同的(或更少的)API调用,比如做聚合

  • 展示不同的(或更少量的)数据

另外,一个后端API通常要为多个前端应用提供支持,此时单一后端可能会成为版本迭代的瓶颈,因为每版工作量巨大(对接N个前端应用)。从而产生一个庞大的独立后端团队,负责给多个前端团队提供API,然后各前端团队都需要与该团队沟通变更,而该团队要平衡多个前端团队的需求优先级……继而面临跨团队协作低效、资源协调困难等问题

二.BFF的由来

由于以上种种,我们不再寄希望于一个大后端为多端体验统一提供API支持,而是给每种用户体验对应一个后端(one backend per user experience),称为Backend For Frontend (BFF),译作用户体验适配层

Consequently it’s often best to define different back-end services for each kind of front-end client.

概念上,把每个前端应用拆成两部分:客户端应用与服务端的部分(BFF)。其中,BFF是面向特定用户体验的,由实现这部分UI的前端团队负责实现及维护(即UI与对应的BFF由同一个团队负责)

These back ends should be developed by teams aligned with each front end to ensure that each back end properly meets the needs of its client.

同一个团队的优势在于:

  • 更容易根据UI来定义或调整API

  • 简化了客户端、服务端的发布流程(依赖项更少了)

  • 一个BFF只专注于一个UI,更小也更灵活

从服务的角度看,BFF实际上是限制了单一服务所支持的消费者(指前端应用)数量,从而让它们更易于使用(更贴合前端需要)和更改,并帮助开发前端应用的团队保留更多的自主权:

The simple act of limiting the number of consumers they support makes them much easier to work with and change, and helps teams developing customer-facing applications retain more autonomy.

三.具体实现

要求BFF与用户体验一对一,也就是要把一个大后端拆成多个小后端

细分粒度

既然要拆分,那应该按什么来分?细分到什么程度?

不难想到3种拆分方式:

  • 用户体验级(UI级):每一种UI交互对应一个BFF,比如PC端1个,移动端3个(例如小屏手机、中屏手机和大屏手机三者UI交互差异很大的话,有必要拆成3个BFF)

  • 端级:每种前端设备对应一个BFF,比如PC、Android、iOS、手表、车机等等

  • 团队级:每个前端团队对应一个BFF,按现有组织结构来分

建议进行用户体验级拆分,因为端级拆分如果多端UI类似的话,接口大概率是能够直接复用的,没必要拆,而组织结构是能够灵活调整的,不应该限制技术方案

对接下游服务(微服务)

每个BFF需对接多个下游服务,那么势必存在几个问题:

  • 如何对接多个技术栈不同的下游服务?

  • 如何管理、如何组合这些调用?

  • 某一个调用失败时,如何保障可用性?

统一的RPC协议能够抹平下游服务技术栈的差异;对于异步调用的管理,可以借助RxJavaFinagle等事件机制来简化这些异步流程控制;部分调用失败时的可用性问题,则可以通过在BFF层容错,同时前端保证可接受不完整的响应内容来解决

复用问题

拆开之后,多个BFF之间容易产生冗余代码,尤其是一些通用的后端逻辑(如授权、认证、限流等等)

为了消除BFF间的代码冗余,一般采用两种做法:

  • 要么多BFF合一

  • 要么在BFF之上加一层Edge API service

多B合一的话,又回到了最初的问题,因为想要灵活性才拆开,又因为想要复用而合起来……白折腾了。另一个选项是加一层网关服务(Edge API service),把通用逻辑放进去,让BFF得以专注业务逻辑:

It validates and authenticates incoming requests; it enforces rate limits to protect the platform from undue load, and it routes requests to the appropriate upstream services. Factoring these high-level features into the edge service is a huge win for platform simplicity.

回到复用问题本身,我们想消除冗余,又不想因为抽离可复用代码而导致BFF间紧耦合,所以就有了一种折衷的态度:容忍BFF间冗余、消除单BFF内冗余。也就是允许一定程度的BFF间冗余

当然,复用的前提是多BFF技术栈相同,然后识别出冗余部分,并重构掉。具体地,到需要抽离公用部分的阶段,有几个选择:

  • 提出公共库

  • 抽出去作为独立service

  • 下沉到下游服务

公共库通常是耦合的主要来源,比如调用下游服务的公共逻辑会引发BFF间耦合。独立service的方案相对更好一些,可以进一步把新service概念化到领域模型里。类似地,也可以把公用逻辑下沉到下游服务,让平级的下游服务变成有依赖的树结构

无论怎样解决复用问题,都应该在有必要进行复用时才去做,一般原则是:

Creating an abstraction when you’re about to implement something for the 3rd time.

四.应用场景

与移动设备相比,PC设备性能足够好,那么是不是能直接调多个下游服务(成本不很高),不走BFF呢?

实际上,与直接面向前端应用的下游服务相比,BFF的意义在于适合用来实现:

  • 服务端模板

  • 对数据进行聚合(合并多个接口调用)、编排(格式化成前端想要的样子)、裁剪(去掉前端不需要的信息)

  • 缓存聚合调用的结果

  • 给某种UI体验(如移动端)提供特定功能

  • 供第三方使用的API,便于维护因第三方限制而添加的那部分逻辑

因为BFF是位于下游服务之上的一层,并且细分到用户体验粒度,所以要比下游服务更灵活,尤其适合为第三方提供定制API等差异化场景

五.业界实践

按照BFF理念,把大后端按前端体验拆分开,如下图:

BFF structure

具体实践中,BFF通常不是图示的样子,主要变化在于:

  • 按业务线拆分BFF

  • 加一层网关,负责实现路由、认证、监控、限流熔断、安全等功能

按业务线拆分的BFF更像是建立在下游基础服务之上的业务型微服务,只是这些微服务由对应业务的前端团队负责开发维护。广义的,可以理解为更细粒度的BFF,即每块业务对应一个BFF(不再按用户体验差异去分)

网关层负责实现通用的边界服务,如认证、限流等,让BFF更专注于业务相关的部分:

前端体验
--------------------
  ^      ^
  |      |
    网关
------------
 BFF    BFF
-----  -----
 ^ ^    ^ ^
/   \  /   \
--------------------
下游服务

更有甚者,把网关层也拆开与BFF一一对应:

前端体验
--------------------
  ^      ^
  |      |
 网关    网关
-----  -----
 BFF    BFF
-----  -----
 ^ ^    ^ ^
/   \  /   \
--------------------
下游服务

P.S.另外,还有不引入BFF,而只添一层转发服务来解决数据的聚合、编排、裁剪等问题的,类似于GraphQL,但容易出现无意义的数据透传,所以一般不是全覆盖的(并非所有数据请求都走转发服务):

前端体验
--------------------
   ^           ^
   |           |
转发服务        |
--------       |
  ^  ^         |
 /    \        |
--------------------
下游服务

探索

实践中对BFF的探索方向主要有3个:

  • 稳定性:保障BFF的可靠性,如通过日志、错误分析、监控、报警等手段

  • 同构:让BFF与前端体验使用相同的技术栈,如基于Node的同构方案

  • 一体化:一方面针对下游服务提供mock方案,另一方面允许同构、非同构应用共存

毕竟在BFF模式下,要求前端开发掌握一定程度的全栈知识(如服务端技能,运维、安全等知识),所以,自然地想要通过同构或一体化方案来提升开发体验,降低门槛,让技术无感化

希望越来越多的开发者,可以不用再关注流程、构建、环境、部署等各种事,希望能做到技术无感化(Techless),让每一位开发着能安安静静的快乐编码。

六.优势

关注点分离

BFF模式最大好处是关注点分离(separation of concerns),下游服务可以专注于业务领域模型,前端UI专注于用户体验:

后端可以专注于业务领域,更多从领域模型的视角去思考问题,页面视角的数据则交给前端型全栈工程师去搞定。领域模型与页面数据是两种思维模式,通过BFF可以很好地解耦开,让彼此更专业高效。

从分工的角度看:

BFF模式不仅仅是一种技术架构,从社会分工角度讲,BFF更是一种多元价值导向的分层架构,每一层都有不错的空间去施展。

自主权

从团队角度看,传统的前后端分离主要问题在于:

  • 服务的扩展性及复杂度

  • 前后端团队的多对一关系,因为前端团队必然需要细分(由于技术实现的差异,需要专人来做)

  • 跨团队协作成本,例如前端UI开发过程中,后端API可能发生变化(存在跨团队的沟通协调成本)

在BFF模式下,API的owner是负责实现对应用户体验的前端团队,也就是说前端团队拥有API的自主权,可以快速调整变化

前端与BFF团队一体化的另一个好处是,可以灵活选择由客户端实现还是服务实现(比如通用性强的由服务实现,甚至为了快速发版过审也可以由服务实现),而不需要跨团队协调

参考资料

Backend For Frontend (BFF)》上有2条评论

  1. Bug Man

    有一点我不是很理解,为什么习惯把后端叫下游。从依赖原则上来讲,是BFF这个中间层依赖后端的数据,BFF只是在后端提供的服务的基础上做附加的服务。不知道这种叫法的来源,还是说我理解错了,希望指教一下!

    回复

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code