在微服务体系结构中,客户端应用通常需要使用来自多个微服务的功能,在小型应用程序中,通常会使用客户端到微服务直接通信的方式:
在此种模式下,每个 Microservice 可能有一个不同的 TCP 端口,或者配置了不同的 URL 地址。
在一个基于微服务的小型应用程序中,它能基本满足,尤其是在客户端应用为服务器端 Web 应用程序(如 ASP.NET MVC 应用)的情况下。 但是,若要生成基于微服务的大型复杂应用程序(例如处理大量微服务类型),尤其是客户端应用是远程移动应用或 SPA Web 应用程序时,该方法将面临一些问题:
- 强耦合:各 App 与内部微服务之间强偶合,任何一边变换都可能对另外一边造成影响。
- 安全性问题:微服务暴露在「外部世界」中,相较于不直接使用内部微服务,这种情况下攻击面更大。攻击面越小,意味着应用程序越安全。
- 过多的请求:可能某个功能需要调用几个后端接口请求进行组合后展示。
- 没有专为移动应用适配:多个微服务的 API 设计可能无法满足不同客户端应用程序的需求。如,移动应用的需求不同于 Web 应用,它需要进一步的优化,以便数据响应能更有效。
BFF 架构的诞生
在 Sam NewMan 的一篇文章 Pattern: Backends For Frontends 中,最早提出 BFF 概念,认为 BFF 是复杂应用的自然产物。BFF 即是用户体验适配层,根据不同的设备类型,来返回不同的结果。
根据业务边界和客户端应用划分的 BFF 层,能提供诸如反向代理、接口聚会、裁剪等功能,除此之外,BFF 层的出现让领域模型与页面数据更好的解耦,让彼此更高效。
理想情况下,前端页面与 BFF 层由不同的同学开发,前端人员专注于页面数据;开发 BFF 的同学,则按需聚合、裁剪数据返回给前端。但是现实往往是 BFF 以及页面数据都由前端人员来开发,尤其是对于做中后台的系统来说,一个前端同学既要写复杂的业务逻辑,同时又负责 BFF 层的开发,久而久之,BFF 层的存在,退化到仅仅是一层代理,如同虚设。
我们也在寻找一种适合于我们自己业务模型的架构模式。
API 网关的介入
实践过后,我们决定去掉 BFF 层,而引入更为通用的从属于前端的 API 网关层(注:BFF 是 API 网关模式的一个特例):
在这个 API 网关层中,我们不再做数据的聚合与裁剪,因为这些对于一个中后台的内部系统来说,并不重要。
使用 API 网关,也着实解决了我们的一些痛点:
- 解耦合,API 网关有一个重要的功能,就是将用户的请求转发给后端的服务器,微服务进行重构时,只需要改 API 网关中的映射关系即可,无需修改前端代码。
- 前端代码易于维护。引入 API 网关以后,所有的前端接口都请求至 API 网关,由网关负责具体请求的服务器,而无需维护大量的 URL。
- 日志,所有的请求都是由网关处理,日志比较完善,比如接口耗时、请求方式、请求 IP、参数等。同时,还可以使用 traceId,追踪整个链路的调用,方便排查问题。
- 其他一些,比如限流和缓存等,在 API Gateway 也可以实现。
当然,我们也有一些 2C 的产品,这些产品中,有一部分我们是使用 Vue/React SSR 技术,
在这个 Vue/React SSR 中,一定程度上,也充当着 BFF 层的作用。
总结
我们并没有使用传统的 Client->API网关->BFF->微服务 架构模式,面对特殊的业务场景(大量的中后台应用),我们取消了 BFF 层,使用了 Client->API网关->微服务,而对于 2C 的应用使用 Clent(SSR)->API网关->微服务 这样架构模式。
当然,我们也在尝试、探索基于 Serverless 的 Gateway 架构:
(图片来自:github.com/nodejh/node…
Serverless 在更多、更复杂领域的实践值得期待。