相信大家在求职的时候,经常会看到 JD 中有“熟悉后端或服务端开发经验优先”这样的字眼,比如某节招聘前端的 jd。。。
可能不少前端会感到奇怪,现在前端就已经够复杂了,会后端技术又能够用在哪里呢?
让我们从技术的演进的过程中去探索一下原因:
从单体应用到微服务
随着近年来容器化技术的发展,许多软件从单体应用的架构模式逐渐迁移到了微服务架构:
可能你会觉得,怎么突然讲到微服务去了,跑题了吧?先别急着走,实际上这是重要的,因为后端的架构模式的改变,连带影响到了前端与后端的通信方式,在微服务架构下,前端需要通过与不同的服务进行通信,来获取不同服务提供的数据。
如果不知道微服务是什么,可以看这篇文章:
微服务中的 API 网关模式
在说到 BFF 之前,要先介绍一下 API Gateway 这个在微服务中常用的模式,因为严格来说 BFF 不过是 API Gateway 的一个变形。
这里的 API Gateway 不是指某某云的那个 API Gateway,而是指一种模式,可以说 AWS 的 API Gateway 服务也是以这个模式为基础构建出来的服务,只是提供了更多强大的功能。
前面说到当后端转换成微服务架构后,前端需要与不同的服务去进行通信,如果用最简单的方法,前端就只能分别与每个服务去通信,这种方式又被称为客户端直接与微服务通信
不过这个方法的的缺点也很明显,显然大多数的应用不会采用现在的架构:
- 客户端与服务端之间高度耦合
客户端的应用会与各个服务紧密耦合,如果哪天服务端更新了,客户端也需要进行相应的改变,这样会造成难以维护的结果。
- 过多的往返通信
也许客户端的某个单一页面需要对多个服务进行调用,这会导致客户端与服务端之间有多个网络往返通信,增加了延迟的可能性。
- 安全性问题
在客户端直接与微服务通信的模式下,所有服务的端点都必须开放给外部,这样会增加被恶意攻击的机会。
- 横切关注点(Cross Cutting Concerns)
每个服务可能会存在一些共有的逻辑,例如 Authorization 或 SSL 加密,与其在各个服务上都实现同样的东西,不如统一在一起进行处理。
这样看来客户端直接与微服务通信在微服务架构中显然不是一个很好的选择。那么 API 网关模式能否解决上述的问题呢?
从上图可以看出,API 网关模式其实就是在客户端与服务间的一层代理,它的优点与特性主要有:
- 可以作为反向代理
- 处于第七层的路由,能够使客户端与服务解耦
- 当需要将应用架构从单体重构为微服务时,API Gateway 可以使客户端在不知情的状况下,渐进式的将 API 从单体式过渡到微服务架构。
- 对请求进行聚合,这可以解决前面提到的直接与服务端通信可能会产生的往返通信过多,而导致延迟的问题。客户端的某个页面中可能需要来自不同服务的数据,与其直接从客户端发出多个网络请求,不如客户端对 API 网关发出单一请求,再由 API 网关代理服务去调用各个服务,并将客户端所需的数据先聚集起来,一次性交给客户端。这样就可以达到减少客户端与服务端通信的目标,毕竟一般来说服务器端的通信延迟要远小于客户端,另外这个方式也能使程序得到更好的维护与扩展。
- 将一些通用的逻辑与功能放到 Gateway 层做,但是要注意网关层的工作量,并不适合把过多的工作搬到这一层,这点稍后会详细说明。
不过 API 网关模式也有一些缺点:
- 可能产生单点故障
- 因为在客户端与服务间多加了一层,从理论上来说会增加延迟,不过这个额外的请求成本往往低于在客户端过于频繁的直接调用微服务所产生的延迟
- 架构变的复杂,需要额外的开发与维运成本
- 如果需要一个专门的团队来开发网关层,似乎会让开发效率变得低落,沟通成本也会增加,不过对于这个问题,BFF 会提供一个解决方案。
微服务中的 BFF
前面所说的 API 网关代理可以看作是一个通用 API 后端,不管是什么类型的客户端都需要先经过它,然后再与不同的服务进行通信。
但是当客户端的种类增加时(例如 Web 应用、移动应用、桌面应用),它们又都需要同一个后端端点的的数据时,这种方式可能会产生一些问题。因为每个客户端所需要的数据可能会不太ㄧ样。例如手机版的页面与桌面版的页面比起来,一般会因为版面大小的问题,减少要显示的数据;移动端与桌面的交互行为也有很大的差异,例如手机上可能希望尽量减少网络请求来节省电量的消耗,再极端一点的例子是不同种类的客户端要求的数据格式不同(例如一个接受 JSON、而另一个则是 XML)。
面对这些不一致,只能依赖这个通用 API 后端针对不同的客户端去做处理,当然这样的程序架构必定会造成混乱,而且也不易扩展,如果哪天要加入新的客户端 种类,只能在通用 API 后端额外对新的客户端的需求做对应的逻辑处理。
BFF(Backend For Frontend) 是 Sam Newman 在 2015 年首先提出的架构模式,即前端模式的后端。其主要的概念是根据用户体验去切分 Gateway 的种类,达到“每种用户体验一个后端”的效果。
这种架构的优势有:
- 更容易根据每种客户端来调整 API
- 一个 BFF 专注于特定种类的客户端,更易维护与扩展
这时我们要回到最开始的问题了,前端开发者在什么状况下需要负责开发 API 呢?如果用了 BFF 架构,那么这就会是一个非常适合的场景。
如果采用了 BFF 架构,在最理想的情况下应该是每个 BFF 后端都交给对应的客户端团队负责开发,例如 Web 前端团队应该负责 Web BFF 后端服务的开发,App 团队则应该负责 App BFF 后端服务的开发。这样一来就可以针对不同客户端的数据需求进行迅速的开发或修改,而后端团队则可以把精力集中在 Services 的开发上。通常在这种架构下,每个 BFF 会选用跟对应客户端相同的技术栈或语言来进行开发(例如 Web 端用 JavaScript 来做全端开发),在这种架构下,前端开发者是需要对后端开发有一定的基础认识与技术的,同样的也需要编写前端要调用的 API。
接下来是 BFF 的拆分方式,前面提到最基本的拆分方式是按照终端设备去进行拆分,但是不同的拆分方式可是会大大影响团队的分工模式,所以只能说必须依照具体情况与需求去找到适合的拆分方式,而没有绝对适合的场景。例如有的团队会在 App 团队中再细分 iOS 团队与 Android 团队,这时可能会是这样的架构:
这种架构适合用在 iOS 与 Android 的行为或是数据非常不一致的状况,如果两个平台的功能与 UI 都差不多,也许更加适合单一的 BFF 架构
毕竟如果两个平台的行为几乎一致,硬要拆出两个 BFF 只能会产生大量重复冗余的程序,还会增加资源与部署成本。
所以 BFF 的拆分方式实际上还是要按照每个项目的具体情况去做调整,没有通用且完美的拆分方式。
在采用 BFF 以后,可能会遇到的一个问题,就是不同 BFF 间其实存在一些重复的代码与逻辑,有些相同的部分甚至可以拆分出去,遇到这种状况,有这样几种方案:
- 将多个 BFF 合成一个 BFF (但这显然违反一开始拆成多个 BFF 的初衷)
- 将通用的逻辑拆成独立的下游服务
- 在 BFF 之上再加一层边缘服务,把通用逻辑放到这里,BFF 则专注于不同客户端的业务逻辑,如下图所示:
同样这个问题没有绝对正确的方案,不同情况下有不同的方法。
总结
随着架构的不断演化,微服务的架构逐渐被广泛应用,在微服务架构下许多模式也被了提出来,这改变了前后端的通信与开发模式。再来提到了 API Gateway 模式, BFF 实际上可以算是 API Gateway 模式的一种变形,主要希望可以达到“每种用户体验一个后端”的效果。当然是否采用这种架构还需要按照现有项目与团队的具体情况去做近一步的判断。它主要的优点有:
- 关注点分离,后端可以更专注在服务端的业务逻辑,前端则专注在用户体验上。
- 前端团队拥有比以往更多的 API 的自主权,可以快速应对变化与需求。
我认为在这种架构下,前端工程师应该成为一个“前端型的全端工程师”,了解一些后端开发的技术,除了客户端应用之外,也负责开发与维护 BFF 服务,来提升开发与通信的效率。
不过不同的客户端需要的数据不同的这个问题,GrapgQL 似乎是不错的解决方案,所以有人也主张使用 GraphQL 来当作 BFF。