零.背景
服务端需要支持多种前端设备下的用户体验时,常常面临现有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协议能够抹平下游服务技术栈的差异;对于异步调用的管理,可以借助RxJava、Finagle等事件机制来简化这些异步流程控制;部分调用失败时的可用性问题,则可以通过在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通常不是图示的样子,主要变化在于:
按业务线拆分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团队一体化的另一个好处是,可以灵活选择由客户端实现还是服务实现(比如通用性强的由服务实现,甚至为了快速发版过审也可以由服务实现),而不需要跨团队协调
ts 哈哈,微服务BFF貌似必须,Graphql是不是也是个方案
有一点我不是很理解,为什么习惯把后端叫下游。从依赖原则上来讲,是BFF这个中间层依赖后端的数据,BFF只是在后端提供的服务的基础上做附加的服务。不知道这种叫法的来源,还是说我理解错了,希望指教一下!