接入层与 BFF:网关职责与多端适配

接入层的边界可以用一句话划定:与业务无关的横切关注点归它,涉及业务语义的判断一律不归它。这条线一旦模糊,网关就会变成变更最频繁、测试最难、单点风险最高的服务。BFF(Backend for Frontend,服务于前端的后端)是接入层的一种形态,不是第二个网关。

一、网关该做什么

职责具体内容为什么放在这一层
路由与发现按路径、域名、Header 路由到上游服务,感知实例上下线全网唯一入口,改路由不必动业务
鉴权与身份传递校验令牌与签名、解析身份,向下游传递受信的用户/租户标识认证集中一处,业务服务只信任内部标识
限流与配额按 IP、用户、租户、接口的粗粒度配额入口拦截成本最低(算法细节见 system-design 的限流章节)
协议与格式转换HTTP 与 gRPC 互转、WebSocket 升级、协议版本适配前端与内部协议解耦,内部可自由演进
可观测性注入生成并透传 traceId、记录访问日志、打点入口指标链路起点唯一,注入成本最低(体系设计见「可观测性」一章)
安全与流量治理TLS 终止、WAF、防重放、灰度染色与标签路由全局策略集中配置,口径统一

这几项的共同点:策略是全局的、判断不依赖业务数据、出问题时影响面大但修复方式统一。

二、网关不该做什么

反模式典型表现后果
业务校验「满 199 减 30」写在网关里每次营销变更都要发网关,回归范围扩大到全网
多服务聚合在网关里同时调订单、库存、用户再拼响应网关没有领域知识,聚合很快演变成业务逻辑
领域规则状态机、库存判定、细粒度权限规则分散两处,长期必然不一致
业务缓存缓存订单、用户资料等业务数据缓存策略与业务耦合,脏数据难定位
直连数据库网关连业务库做校验数据所有权被打穿(见「数据所有权」一章)

判断标准很简单:这段逻辑变更时是否需要产品经理参与、是否会因业务规则调整而变化? 会,它就不属于接入层。

三、分层:网关、BFF、服务网格各管一段

客户端 ──► 网关 ──────────────► BFF ─────────► 领域服务
         鉴权 / 限流 / 路由     端侧聚合 / 裁剪   业务规则 / 数据所有权
         (全局策略,无业务)    (端侧语义,无规则)
                    ▲
            服务网格:服务间 mTLS、重试、熔断、流量镜像(进程外,业务无感)
  • 网关管南北向入口策略,对客户端负责。
  • BFF 管端侧适配,对一个端的体验负责,可以有多个。
  • 服务网格管东西向通信策略,对服务间调用的可靠性负责。

三者职责不重叠,接入层才是清晰的;两处都在鉴权、都在限流,出故障时无法判断谁生效。

四、BFF 的适用与反模式

适用信号:

  • 各端需要的字段、聚合次数、分页方式差异大,一个通用接口要同时满足三端,会退化成「返回所有字段再由端上丢弃」。
  • 端侧团队有独立迭代节奏,需要在不动后端服务的前提下调整接口形状。
  • 移动端弱网、流量敏感,需要减少请求数与响应体大小。

反模式:

  • BFF 变业务黑洞:聚合里出现库存判断、优惠计算,等于把领域逻辑搬进了一个没有领域模型的地方。
  • 多个 BFF 共享库表:BFF 之间通过数据库耦合,等于绕过服务边界;BFF 之间也不能互调。
  • 无契约测试:BFF 的接口只服务一个客户端,容易被「悄悄改」,所以 BFF 与服务之间、BFF 与客户端之间各要一层契约校验。
  • 与网关职责重叠:鉴权、限流两边都做,问题排查时无法定位生效点。
  • 一服务一 BFF:BFF 按端划分而不是按服务划分,按服务拆出来的只是多一层转发。

五、多端适配的三种做法

做法说明端侧成本服务端成本发布独立性主要风险
服务端按需字段接口支持字段过滤或 GraphQL,端上声明需要什么低中:查询层要支持投影与权限裁剪中:契约在服务端复杂查询引发 N+1;裁剪漏项导致越权
每端一个 BFF每端一个适配服务,各自聚合与裁剪最低高:适配代码与人员成倍高:端侧可独立发布逻辑重复、版本碎片化
通用网关 + 客户端适配服务端只提供领域接口,端上多次调用并组合高:请求数与弱网体验差最低高端侧逻辑重、流量与能耗差,端团队维护成本高

选择依据不是「哪个先进」,而是端差异是否长期存在与端侧、服务端团队的规模比:端差异大且长期存在、端团队独立,选 BFF;端差异小,选按需字段;端数量少而服务端人力紧张,客户端适配也能活,但要接受端侧复杂度上升。

六、落地要点

BFF 的聚合编排要守住三条:

  1. 超时预算自上而下分配,每层只花自己那一份,不允许下游把上游拖死。
层预算说明
客户端1500ms用户可感知的上限
网关1200ms留 300ms 给网络与自身开销
BFF 聚合800ms并行扇出,取最慢一路
单个下游服务300ms(含 1 次重试)单跳预算,重试不得突破本级预算
  1. 聚合只做编排,不做决策:缺数据就给缺省展示,而不是替业务判断「该不该允许」。
  2. 降级路径在 BFF 定义:某一路聚合失败时返回部分数据加标记,好过整个页面 500。
// 聚合示意:并行取数、各自超时、整体预算封顶,失败只降级不阻断
CompletableFuture<UserBrief> user = async(() -> userClient.brief(uid), 200);
CompletableFuture<List<OrderBrief>> orders = async(() -> orderClient.recent(uid, 10), 300);
// 任一失败按缺省值填补:用户信息缺失不阻塞订单列表展示
return assemble(awaitAll(user, orders, 800));

小结:接入层的划分依据是「逻辑是否含业务语义」。网关承担路由、鉴权、限流、协议转换、可观测性注入这些全局横切职责,不做业务判断;BFF 按端划分解决多端适配,按服务划分就只是转发层。多端适配的三种做法各有代价,选哪种取决于端差异的长期性与团队结构;而无论选哪种,超时预算、降级路径与契约测试都必须在上线前落到配置与用例里。

笔记加载中…