关于BFF中间层可以做什么

2,635 阅读5分钟

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

背景

在前端领域的快速发展状态下,前端的开发早已从MVC的传统模式转变为现如今的前后端分离架构。

MVC的开发模式中,前后端开发过分耦合,导致开发效率低下,分工不均。出于解耦的目的,提出了前后端分离的架构。前端通过AJAX调用后端接口进行交互,实现前后端项目分离。

image.png

随着前端领域的不断扩大,后端服务在复杂的前端业务背景下,为兼容不同的业务逻辑变得臃肿而难以维护。

在这样的背景下,后端微服务架构逐渐成熟,领域之间的解耦也成为后端服务的主流。然而,前端需要自身去实现数据的聚合、裁剪等功能。虽然我们可以通过前端去请求不同的服务然后做数据的操作,但是由于不同的生态下,会存在一定的限制,例如微信小程序的域名数量限制。并且,服务的底层协议也会存在一定的限制,如RPC协议。为此,BFF中间层架构是一个比较不错的选择。

BFF(Back-end for Front-end) - 服务于前端的后端。

image.png

BFF能做什么

既然BFF架构相对于基本的前后端分离能解决一些复杂的业务问题,但是具体能做什么?

构建SSR服务解决C端网站SEO和白屏问题

当我们需要开发一个C端项目(面向用户)的时候,页面的快速响应搜索引擎优化(SEO) 是我们必须着手去解决的问题。但是SPA页面的特性会导致页面一定的白屏时间。SPA页面下源码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
   />
    <title><%= VUE_APP_TITLE %></title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

当用户打开页面时,页面展示白屏,通过AJAX请求后端服务加载数据后构建DOM,展示页面。

image.png

同时,对于爬虫来说,获取的内容也仅仅是模版文件的内容。

解决

在BFF中间层中,我们可以用SSR(服务端渲染) 的方式去解决以上问题。具体实现参考官方文档

import express from 'express'
import { createSSRApp } from 'vue'
import { renderToString } from 'vue/server-renderer'

const server = express()

server.get('/', (req, res) => {
  const app = createSSRApp({
    data: () => ({ count: 1 }),
    template: ``
  })

  renderToString(app).then((html) => {
    res.send(`
    <template></template>
    `)
  })
})

server.listen(3000)

image.png 在这个过程中,就不会存在等待AJAX请求的时间。同时,渲染的DOM结构是完整的HTML而不是仅是模版的HTML。

应对不同端对同一API的不同需求

我们可能存在这样的需求:应对不同的客户端开放不同的权限和功能,如小程序端仅支持阅读而不能添加评论,如果需要评论信息,只能在APP端进行。

面对这样的需求,微服务架构下的BE(后端开发)可能不会为了这样的功能单独实现。所以我们可以通过BFF层实现自己的业务逻辑判断,实现服务自治。

image.png

主要目的是对不同的客户端进行特定的业务处理;同时,集中处理统一逻辑,降低开发成本。

聚合不同服务API的数据

我们经常会遇到一个页面的初始化需要多个接口的数据,甚至是不同服务的数据。比如用户服务、订单服务等。

通常,这些服务以依赖的关系去请求,如订单服务需要依赖用户的信息。

image.png

使用BFF架构去优化网站:减少请求数,这样就可以在客户端只发送一个请求,由BFF层去做一些数据的融合和多服务请求的操作。

image.png

同时,在数据BFF返回数据的过程中,我们可以移除部分不需要的冗余数据。

定制自己的登陆和权限控制方案

最近在做一个B端的项目,项目有这样一个需求:根据不同的角色,实现接口访问控制。关于登陆的实现可参考之前的文章关于如何基于Google OAuth2.0 搭建系统鉴权

但是,对于服务提供方来说,我只是提供服务的,并不会对权限进行控制。所以,需要实现这个需求,只能自身从BFF层考虑。

实现

整体实现基于NestJS+Typescript。

定义一个装饰器

实现Nest自定义的装饰,用于Controller层队接口进行校验拦截。

import { SetMetadata } from '@nestjs/common'

export const Permissions = (...permissions: string[]) => SetMetadata('permissions', permissions)

实现守卫

守卫的目的就是实现拦截的手段。

async canActivate(context: ExecutionContext): Promise<boolean> {
    const roles: string[] = this.reflector.get('permissions', context.getHandler())
    if (!roles) {
      return true
    }
    // 获取请求Req
    const req = context.switchToHttp().getRequest<any>()
    const token = req.headers['Authorization'] || req.headers['authorization']
    if (!token) {
      throw new UnauthorizedException()
    }
    
    // 业务代码
    //
    //
    //
    
      return this.hasPermission(permissions || [], roles)
    }
  }

控制层使用

@Post()
  @Permissions('0101')
  async getBizData(@Body() data: QueryDto) {
    return this.myService.getBis(data)
  }

BFF所带来的问题

当一个架构能从一个方面带来了优势的同时,必定也会牺牲其他方面

资源问题

当BFF层多了,资源使用就成了问题,毕竟会多一个服务。尤其是使用SSR服务端渲染的时候,随着QPS(每秒请求数,就是说服务器在一秒的时间内处理了多少个请求。)的波动,CPU的使用和内存总是会有比较高的波动。不过这方面考虑使用服务提供商的伸缩性服务。

维护问题

随着BFF层的使用,前端也不仅仅只是需要关注浏览器端的知识点。同时也需要掌握后端服务的知识,如数据库、服务器、并发问题等等。会使得前端的同学维护起来较为困难。

但是,也建议大家跳出自己的舒适圈。

服务链路问题

在引入BFF层之前,可能只是单纯的服务调用。服务之间的依赖在前端比较清晰。

在使用BFF层架构之后,流程变得繁琐,要同时走前端、服务端的研发流程,多端发布、互相依赖,导致流程繁琐。出现问题的时候,需要清晰的链路追踪,这里一定要将日志的链路完善好,避免造成问题排查的困难。

结束语

2022.02.17

木更