第15章 BFF
第14章处理了服务间数据依赖的场景。除了这种频繁需要其他服务的数据的场景,其实还会碰到服务间依赖太杂乱的问题。本章讨论的就是如何缓解服务依赖复杂度的问题。

15.1 业务场景:如何处理好微服务之间千丝万缕的关系
本节所讲的系统包含商品、订单、加盟商、门店(运营)、工单(门店)这几个服务。
除了一个 App 面向客户以外,还有一个 App 是给公司的员工和加盟商的员工使用的。里面有各种角色的用户,比如总部商品管理、总部门店管理、加盟商员工、门店人员等。
后台服务架构
网关层负责如下工作:
| 职责 | 说明 |
|---|---|
| 路由 | 所有的请求都会通过网关层,网关层再根据 URI 把请求指向对应的后台服务,如果同一个服务有多个服务器节点,网关层还会做一些负载均衡的工作 |
| 认证 | 对所有的请求进行集中认证鉴权 |
| 监控 | 记录所有的 API 请求数据,API 管理系统可以对 API 调用进行管理和性能监控 |
| 限流熔断 | 当流量过大时,可以在网关层做限流;当后台服务出现响应延时或者故障时,可以主动熔断 |
遇到的问题
该架构看起来非常完美,有些类似于 Spring Cloud 标准架构,但它也存在一些问题:
问题一:页面需要多个服务的数据
比如 App 首页,它要根据用户的不同来显示不同的信息。如果是门店运营人员,就要显示:
- 工单数量、最近的工单
- 销售订单数据、最近待处理的订单
- 低于库存安全值的商品
问题二:一个操作需要修改多个服务的数据
比如一个工单操作要修改:
- 库存
- 销售订单状态
- 工单的数据
核心问题:这两种情况要调用的接口做在哪个服务上?
复杂的依赖关系
因为这样的需求非常多,所以服务经常会来回调用,最终服务调用关系就会变得纠缠不清。
这种复杂的依赖给迭代带来了地狱般的感受,在第12章中有详细的描述。
需要解决的问题
- 对于很多页面要用的接口,都要考虑放在哪个后台服务,导致决策效率低下,也导致一些职责划分不统一
- 服务之间的依赖非常混乱
为了解决这两个问题,项目组决定抽象出一个 API 层。
15.2 API 层

一般来说,客户端的接口会有以下需求:
| 需求 | 说明 |
|---|---|
| 聚合 | 一个接口需要聚合多个后台服务返回的数据,然后再返回给客户端 |
| 分布式调用 | 一个接口可能需要依次调用多个后台服务,去修改多个后台服务的数据 |
| 装饰 | 一个接口需要重新装饰一下后台返回的数据,删除一些字段,或者对某些字段再加一个封装 |
API 层架构
项目组决定在客户端和后台服务之间增加一个新的 API 层,专门来做这些事情:
- 所有的请求经过网关后都由一个共用的 API 层进行处理
- 这个 API 层没有自己的数据库
- 它做的事情就是去调用其他后台服务
解决的问题
| 问题 | 解决方案 |
|---|---|
| 接口归属纠结 | 聚合、装饰、分布式调用的逻辑放在 API 层;落库或查询数据库的逻辑看目标数据放在哪个服务 |
| 服务依赖混乱 | 只有 API 层去调用各个后台服务,后台服务之间的调用关系减少 |
架构看起来更完美了一些,但是会面临新的问题。
15.3 客户端适配问题
一般来说,有一系列的接口给各种客户端调用,比如 App、H5、PC 网页、小程序等。
存在的问题
| 问题 | 说明 |
|---|---|
| 不同客户端页面不一样 | App 功能多,小程序要求轻量化,同样的页面少一些数据 |
| 客户端经常做轻微改动 | 加一个字段、减一个字段,后台服务也经常要发布新版本 |
| 版本兼容问题 | 后台服务的版本发布又要同时考虑不同客户端的兼容问题 |
为了解决这些问题,可以考虑使用 BFF。
15.4 BFF(Backend for Front)

BFF 不是一个架构,而是一个设计模式。
它的主要理念是:专门为前端设计优雅的后台服务(也就是 API)。换句话说,就是每一种客户端有自己的 API 服务。
BFF 架构
不同的客户端请求经过同一个网关后会分别重定向到专门为这种客户端设计的 API 服务:
| 客户端 | 对应的 API 服务 |
|---|---|
| App | App API |
| PC 网页 | PC API |
| H5 | H5 API |
| 微信小程序 | WX API |
BFF 的优势
- 因为每个 API 服务只针对一种客户端,所以它们可以为特定的客户端进行优化
- 逻辑更轻便,响应速度更快(不需要判断不同客户端的逻辑)
- 每种客户端就可以自己发布,而不需要跟其他的客户端一起排期
大型系统的 BFF 架构

这次项目所针对的系统非常庞大:
- 整个业务链条所涉及的工作都包含在这个系统中
- 服务有近百个,由几百人组成的研发团队在维护
- 分为新零售、供应链、财务、加盟商、售后、客服等几个部门
大家共同维护一个 App,共同维护一个用户界面。新零售、售后、加盟商、客服还有各自的小程序和 H5。
解耦方案:每个部门维护自己的一系列 API 服务,App 与 PC 前端也要按部门实现组件化。
15.4.1 技术架构上怎么实现
整套架构还是基于 Spring Cloud 实现,主要的 3 层分别如下:
| 层级 | 技术选型 | 说明 |
|---|---|---|
| 网关 | Spring Cloud Zuul | Zuul 拉取注册到 ZooKeeper 的 API 服务,通过 Feign 调用 API 服务 |
| API 服务 | Spring Web 服务 | 没有自己的数据库,主要逻辑是聚合、分布式调用以及装饰数据,通过 Feign 调用后台服务 |
| 后台服务 | Spring Web 服务 | 有自己的数据库和缓存 |
15.4.2 API 之间的代码重复怎么解决
一般来说,H5、小程序之间的需求都是不一样的。重复的代码逻辑主要存在于 PC 和 App 的 API,因为它们有些页面功能是一样的,只不过布局不一样。
各部门的做法:
| 做法 | 说明 |
|---|---|
| 共用 JAR | 将重复的代码放在一个 JAR 里面,让几个 API 服务共用 |
| Common API | 将重复的代码抽取在一个独立的 Common API 服务中,其他 API 服务调用这个 Common API |
| 保留重复代码 | 因为重复逻辑占少数,维护这些重复代码的成本小于维护 JAR 或 Common API 服务的成本 |
关于仅为代理的 API 接口
如果有些 API 服务的出入参和后台服务提供接口的出入参一模一样,该怎么办?
两个方案:
- 网关直接调用后台服务:破坏了分层,直接被否决
- API 服务层做拦截器:如果 URI 找不到对应 API 服务中的 controller mapping,就尝试直接通过 URI 去找后台的服务
最终决定:不去掉这些接口代码,因为:
- 会增加系统的复杂度,出问题后调查起来很麻烦
- 好处只是去掉一些看起来有些累赘的代码,收益不大
- 这些代码的编写成本非常低,对整体的接口列表来说是可控的
15.4.3 后台服务与 API 服务的开发团队如何分工
分工方案:有一个专门的 API 团队负责这些 API 服务,后台的服务再根据领域来划分小组职责。
| 优点 | 缺点 |
|---|---|
| API 团队对所有的服务有个整体的认识 | API 团队整体业务逻辑偏简单 |
| 由一个中心团队控制接口的划分,不会出现后台服务划分不清楚、服务重复的情况 | 无法让人员长久在岗,需要定期进行岗位轮换 |
15.5 小结

BFF 模式的核心理念
| 要点 | 说明 |
|---|---|
| 定义 | 专门为前端设计优雅的后台服务 |
| 核心 | 每一种客户端有自己的 API 服务 |
| 优势 | 响应快、发布独立、职责清晰 |
分层架构的设计原则
| 层级 | 职责 |
|---|---|
| 网关层 | 路由、认证、监控、限流熔断 |
| API 层 | 聚合、装饰、分布式调用(无数据库) |
| 后台服务 | 业务逻辑、数据存储(有数据库) |
实施效果
BFF 这一章并不是介绍一个技术方案,而是整体接口开发的管理和设计方案,所以其内容基本都是一些设计思路和具体会碰到的场景。
虽然本章关于 BFF 的内容只占一小部分,大部分是后台服务的分层设计,但是 BFF 的理念贯彻始终。
- 接口归属问题:聚合、装饰、分布式调用的逻辑放在 API 层
- 服务依赖问题:只有 API 层去调用各个后台服务,后台服务之间的调用关系大幅减少
- 客户端适配问题:每种客户端有自己的 API 服务,可以独立发布
至此,微服务相关的架构已经讲完了,接下来将会进入第5部分开发运维场景实战,讨论如何让开发更高效。