BFF 与 BIF

Backend for Frontend,性能与效率的平衡

BIF, Backend in Frontend 的概念

Api CheatSheet 中,标准的 RESTful 接口是为,而在 PayPal 的实践中又引入了所谓 Complex Operation 的概念,来处理跨资源的逻辑操作。在业务极速变化的情况下,前端往往需要从

这时候 BFF 层就起到了接口聚合的作用,正如 GraphQL 的思想一样,让前端从单一接口请求,减少数据交互的频次。

数据组装一个是从数据源上,针对多源异构数据的组装;另一个是在于逻辑关系上,将原本数据库联查的压力转移到逻辑层或者前端。

GraphQL 与 BFF 并没有必然的联系,BFF 更多是对于关注点分离原则的实践

我们希望能够在开发效率与性能之间达成平衡,性能包括后台的数据库组装

student 以 JSON 字段的方式存放了 course 相关。我们的课程数据是相对静态数据,可以认为在某次 Session 周期内,其是可静态缓存的。student_profile 表中获取

如果我们在标准后台中,依靠数据库查询的方式进行数据组装,那么不可避免地要进行多次查询,或者相对性能损耗较大地模糊查询。

在所谓的 BFF 层去进行数据组装

BFF

随着无线技术的发展和各种智能设备的兴起,互联网应用已经从单一 Web 浏览器时代演进到以 API 驱动的无线优先(Mobile First)和面向全渠道体验(omni-channel experience oriented)的时代。

应用架构的新挑战是:

用户接入形式的多样性,无线(手机、Pad),Web,互联网电视,第三方合作应用等等,各种用户设备的屏幕大小,操控体验方式各不相同,例如,手机设备的屏幕较小,能够展示的数据量小,交互方式以触控为主,也可通过条形码扫描器;

有些用户设备的带宽受限,同时应用的 UI 一般宿主在客户端,有些页面需要组合好几个后台业务服务的数据和功能,如果直接在客户端发起对多个后台服务的调用,势必造成大量网络开销影响性能,这个有点类似数据库查询中的 n+1 问题。

BFF(Backend For Frontend)是应对上述应用架构挑战的一种模式和最佳实践,2015 年底,ThoughtWorks 在其网站上刊登了一篇称为 BFF@SoundCloud(SoundCloud 是一个类似音频 YouTube 的网站)的文章[附录 1],讲述 SoundCloud 如何利用 BFF 模式逐步将其单块 Rails 应用迁移改造为支持无线等多种用户体验的微服务架构。同期,ThoughtWorks 的顾问 Sam Newman,也就是《Building Microservices》那本书的作者,在 SoundCloud 等业界实践的基础上,写了一篇博客总结了 BFF 模式的原理、场景和用法[附录 2],建议大家阅读。

640

BFF 本质上是一个后端中间层,但是它的作用主要是适配前端不同用户体验(无线,桌面,智能终端等等),所以称为用户体验适配层,它的适配作用主要是:

裁剪和格式化,对后台的通用数据模型进行适当的裁剪和格式化,以适应不同的用户体验展示的需要;

聚合编排,对后台服务数据进行编排和预聚合,这样可以有效简化客户端逻辑和减少网络调用开销。

Sam 推荐理想情况下针对每种用户体验类型需要一个 BFF(one BFF per user experience),例如 Mobile BFF,Desktop BFF,这可以做到职责单一和关注分离(遵循有界上下文原则),但是 BFF 过多也会造成代码逻辑重复冗余的问题,需要权衡。UI 和 BFF 理想是同一个团队负责,这样可以减少沟通协调成本,加快变更迭代速度,这是遵循康威定律的体现。下图展示了一种 BFF 和团队职责边界划分方案。

default

Sam 还指出,对于一些跨横切面的关注点(cross cutting concerns),例如路由,安全认证,日志监控分析,限流等等,通常可由独立的网关(Gateway)层负责(如 Fig 6 所示),由独立基础设施团队运维,置于 BFF 之前,这样在架构上可以做到职责单一和关注分离,让 BFF 开发人员专注于聚合裁剪等业务功能,无需考虑跨横切面功能。但是如果对运维成本和调用性能有额外考虑,跨横切面的功能也可以直接做在 BFF 一层。一般来说,开发者只需要关注蓝色(functions)部分,而至于红色部分(stub 句柄)和黄色部分(socket 网络)部分呢,框架层面会把它解决掉。蓝色部分,服务端开发者要做的事情就是定义某个接口,客户端开发者要做的事情就是调用某个接口,一切开发模式都跟本地调用无差别。

GraphQL

GraphQL 最有趣的特性之一,是它本质上是面向消费者的,响应体的结构不取决于服务端,而是完全由客户端驱动。直观的理解就是 GraphQL 规定客户端在发起请求的时候必须显示的标注所需要获得的原子资源的类型以及在返回体中的位置,这一点和 Immutable.js 中的update函数很类似,你都需要显示指明到叶子节点。这样确实使得整个请求需要很多额外的数据参数与编码工作,但是它就将消费者解耦了,并且强迫服务端遵守 Postel 法则 ( 对自己严格,对他人宽容 )。