关于 bff-design
帮助前端团队决定是否采用 BFF、如何划分与治理 BFF 的架构技能。内容聚焦于体验归属、职责边界、上游聚合韧性和用户身份传递,避免 BFF 演变为共享公共 API 或业务决策层。
主要功能
- 01评估采用时机与 BFF 数量
- 02按产品体验划分所有权
- 03界定展示整形与业务决策
- 04设计并行聚合及部分失败策略
- 05规范向上游传递用户身份
适用场景
- 01判断单页应用是否需要 BFF
- 02为多个客户端体验确定 BFF 边界
- 03设计上游服务故障时的降级响应
- 04避免跨体验共享 BFF 导致接口固化
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ citypaul/ .dotfiles --skill bff-design安装后在 Claude Code 中输入/bff-design确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlreferences/adoption-and-granularity.mdaggregation-and-resilience.mdalternatives.mdupstream-identity.md详细介绍
BFF 设计
后端前端化(BFF)首先是一种所有权模式,而非一项技术:客户端应用使用的 API 是该应用的一部分,由负责前端的团队所有,且不会被其他应用使用(该模式的创始人 Calçado)。从架构上看,它是面向某种用户体验的驱动主机,负责塑造、聚合和调解;但不负责决策。
本技能负责模式层面的决策:是否采用 BFF、采用多少个、各自可以负责什么、如何聚合并应对上游故障,以及如何将用户身份传递给上游服务。加载 bff-entry-points,以保护 BFF 自身边界——访问分类、预先配置的注册器、会话/CSRF、实时通信保护、强制执行门禁;本技能不会重复这些内容。加载 structure-codebase,了解物理 endpoints/ 目录树;加载 hexagonal-architecture,了解驱动主机的定位;加载 api-design,了解如何处理不受你控制的消费者所使用的契约(健康的 BFF 从来不会有这类消费者);加载 secure-oauth-oidc,了解上游身份参考中所有内容背后的 OAuth/OIDC 协议不变量。
做出决策前,请阅读相关参考资料:
- 在决定是否采用、运行多少个 BFF、由谁负责、如何处理移动端版本偏差,以及如何选择可部署服务或模块的规模时,请阅读
references/adoption-and-granularity.md。 - 当 BFF 向上游服务扇出请求时,请阅读
references/aggregation-and-resilience.md:其中介绍组合、部分故障、超时/重试预算和缓存。 - 当 BFF 代表用户调用上游服务时,请阅读
references/upstream-identity.md:其中介绍令牌交换、缩小范围、防止混淆代理问题,以及撤销传播。 - 在权衡 BFF 与共享网关、GraphQL 联邦、令牌中介后端、直接从 SPA 调用 API,或元框架服务器时,请阅读
references/alternatives.md。
你需要 BFF 吗?
| 信号 | 解读 |
|---|---|
| 即将增加第二种客户端类型(移动应用、合作伙伴界面、电视) | 最强的采用信号——Newman:从一开始就考虑为每个参与方设置一个 BFF |
| 一个页面/屏幕需要扇出调用许多下游服务 | 聚合应放在服务附近的服务器端,并针对每种体验进行定制 |
| 前端团队每新增一个端点都要排队等待通用 API 团队处理 | SoundCloud 遇到的问题——BFF 将契约转移到前端团队内部 |
| 浏览器应用处理敏感/个人数据(默认情况下,任何需要登录的应用都符合条件) | IETF 浏览器应用 BCP 轨道的指南仅基于安全考量,就强烈建议采用 BFF 架构——令牌保留在服务器端 |
| 只有一个界面,且只向一个后端发出简单、相似的请求 | 不要采用——这是 Azure 明确指出的“不适用”场景——除非触发了另一行条件:各行回答的是不同问题,而仅安全这一项就足以为处理敏感数据、仅连接单个后端的 SPA 提供采用 BFF 的理由(限制令牌无需以聚合需求为前提) |
| 已在运行带有前端专属解析器的 GraphQL,或使用元框架服务器(Next/Remix/SvelteKit) | 你可能已经拥有一个 BFF——将本技能应用于现有 BFF,而不是再增加一个组件 |
元框架服务器就是 BFF:Remix 明确将其服务器描述为 BFF,Next.js 路由处理程序也承担相同的职责(并面临同样的风险——默认情况下,它们可从公开网络访问,因此 bff-entry-points 同样适用于它们)。
一种体验,一个 BFF
粒度单位是体验——一个连贯的产品界面,无论它由哪些平台呈现。渲染同一种体验的 iOS 和 Android 听众应用是一个消费者,有两个构建版本,而不是两个消费者;创作者应用则是另一种体验,应拥有自己的 BFF(这是 SoundCloud 的做法,也符合 Newman 的原则)。
- 体验决定默认数量。团队结构可以覆盖这一默认值:即使体验几乎相同,iOS 和 Android 团队分开也可能足以支持单独的 BFF,因为这种模式的价值在于由团队掌控自己的节奏(Conway)。一种体验由两个团队提供服务,说明所有权划分存在问题——应指定一个负责团队,或接受拆分;不要让多个团队共用同一个 BFF。
- 前端团队拥有自己的 BFF:语言、发布节奏和优先级均由其决定。由后端平台团队拥有的 BFF,会重建这种模式旨在消除的协调队列。
- BFF 和前端是一个逻辑部署单元:条件允许时放在同一仓库中,让契约共同演进,而不是分别版本化。版本管理的严格程度取决于消费者的可追溯性——采纳参考资料会说明 Web 端放宽与移动端集中管理之间的区别,以及 CDC 这一替代方案。
塑形,不做决策
BFF 负责呈现模型相关工作:聚合、重塑、精简负载、设置各体验的默认值,以及将上游结果转换为单个 UI 要渲染的内容。它不负责:
- 业务规则。 定价、资格判定、工作流状态——BFF 一旦开始决定这些,就会与领域服务争夺职责,变成同时迎合两个主人的代码。SoundCloud 记录的失败模式是功能集成和业务逻辑在 BFF 中不断累积。判断边界的测试是:将列表筛选为当前用户的等级允许查看的内容,属于决策(形似授权,应由领域操作负责);对当前屏幕所需的返回结果进行截断、重排或精简,则属于塑形。拿不准时,使用下文的删除测试。
- 产品授权。 在 BFF 调用的应用操作内部决定(
bff-entry-points、六边形架构参考);BFF 只需如实传递主体信息,不做更多。 - 持久领域状态。 BFF 可以缓存并持有每个会话或连接的状态;记录系统位于领域服务之后。
决策型逻辑应立即下沉到领域服务中,不论是否会造成重复——这套阶梯只适用于邻近呈现层的重复逻辑。当这类逻辑出现在第二个 BFF 中时,不要试图向上合并 BFF——这会造成“高度臃肿、将多种关注点挤在一起的代码”(Newman)。补救阶梯如下:首先容忍重复;只有非领域层面的基础设施逻辑才提取为共享库(并注意多个 BFF 可能因此连锁升级);到第三次出现时——也就是三次原则——再问问重复逻辑是否从一开始就属于领域逻辑;如果是,就应将其下沉到所有 BFF 之下的领域服务中。三次原则为呈现基础设施逻辑的抽象设定时机;它绝不会让业务或授权决策获得三次免费通行。
聚合、弹性、身份——简明版
- 并行扇出;传递每个请求的截止时间,并扣除已耗费的时间;为每个上游设置基于实测数据的超时(使用延迟百分位,而不是凭猜测)。
- 每条边只指定一个重试负责人,并为重试设置预算和随机抖动——层层重试会造成自我引发的拒绝服务。
- 在契约中将每个响应区段分类为必需或可选;对可选区段进行降级(过期缓存 → 默认值 → 带错误的空值),而不是让整个页面失败;明确规定部分响应的结构,使其可预测。
- 缓存时,要么将身份纳入键中,要么完全不缓存:每个请求内的记忆化始终安全;共享缓存应以租户/用户为键(或以实际使用的更粗粒度属性为键);身份变化但键中未体现的响应会导致缓存投毒泄漏。
- 面向上游时,转发用户的身份,绝不要同时发送 BFF 的服务身份和用户标头——那会造成混淆代理问题。使用访问令牌时,应针对每个上游,将绑定会话的令牌交换为受众范围更窄、授权范围更小的令牌(若无法交换,则按资源获取令牌——参考资料中的阶梯方案)。采用事务令牌模型的部署,则应依照该模型自身的规则,原样传递受信任域范围的签名上下文。无论采用哪种方式,上游都应根据已验证的声明进行授权。
以上每一点都浓缩了参考资料中的一个章节,其中包含来源和实现机制——实施前请先阅读参考资料。
公共 API 的失效模式
当第二种使用场景开始使用某个 BFF 时,它就不再只是一个 BFF:契约会被冻结,协调成本卷土重来,而“应用程序的一部分”也会悄然变成未经版本管理的公共 API。以下是三条防护措施:
- 分开不同界面,并严格落实边界。 浏览器和原生应用都是公共客户端——它们携带的任何内容都无法通过密码学手段证明客户端软件的身份;即使是原生应用注册,也只能识别注册信息,而不能识别二进制文件(RFC 8252)。因此,严格来说,“验证客户端身份”并不可行;可行的是:每个 BFF 都有独立的主机/源和凭据模式——Web BFF 的浏览器请求策略和会话签发规则(
bff-entry-points)可排除来自浏览器源的跨界滥用;移动 BFF 只接受其签发者、受众和授权方声明均指向其自身界面的用户持有者令牌。获得自身会话的蓄意脚本客户端,受这些策略和滥用防护措施约束,而不是被密码学手段排除——应在威胁模型中如实说明,不要夸大其效果。证明 API 和客户端遥测只能作为风险信号,绝不能当作已验证身份。 - 为新的使用方提供独立界面。 另一个使用场景(包括由你构建并负责的合作伙伴集成——SoundCloud 会运行专用的合作伙伴 BFF)应有自己的 BFF;对于你不控制的使用方,或非前端使用方,应提供真正的公共 API(
api-design)。数据流既不是一种使用场景,也不应由 BFF 提供:应通过领域服务提供数据流——BFF 不持有可用于提供数据流的持久状态。 - 监控此类情况:记录每个请求所接受的凭据模式、签发者和客户端注册信息(宽事件结构见
observability);BFF 遥测中出现不熟悉的注册信息或凭据模式,属于架构事故,而不是支持问题。
纠正架构偏移(审计场景——第二个使用方已与之耦合):明确承认现状,以增量方式冻结契约,建立该使用方自己的界面(其 BFF 或公共 API),按照绞杀者模式逐路由迁移,并公布弃用日期,之后才强制执行边界拒绝策略。先强制执行会破坏你已接纳的使用方;永不强制执行,则意味着这种偏移已经成了架构本身。
反模式
- 由第二种体验、第二个团队或任何你无法控制的使用方使用的 BFF。(同一体验的两个平台版本——iOS 和 Android 上呈现的同一应用——算作一个使用方,而非两个。)
- 将业务规则、产品授权或记录系统放在 BFF 中。
- 一个通用型“BFF”服务于所有客户端——这就是换了个时髦名字的“一刀切” API。
- 为了消除重复而向上合并 BFF,而不是将共享的领域逻辑下沉。
- 平台/后端团队拥有前端团队使用的内容。
- 按顺序扇出调用;每一层都重试;某个可选分区的上游不可用,就导致页面返回 500。
- BFF 使用客户端凭据调用上游,并附带
X-User-Id标头。 - 在已经承担 BFF 职责的元框架服务器旁边再添加一个独立的 BFF 服务。
- 采用 N 个微服务 BFF,而不是模块化单体 BFF(按体验划分模块,部署单元只有一个);后者能以极低的运维成本服务相同的边界——每个 BFF 都是一个完整的部署单元,有自己的生命周期、安全面和 SLO。
完成检查
- 你能说出这个 BFF 服务的唯一体验,以及负责它的唯一团队吗?
- 第二种客户端类型会有自己的 BFF 吗?当前边界是否会拒绝不受支持的来源、凭据模式和未知客户端注册,并在威胁模型中记录脚本客户端带来的剩余风险?
- BFF 中的每段逻辑是否都符合呈现层的需求——删除 BFF 是否不会丢失任何业务规则?
- 聚合契约是否明确说明哪些分区可以降级?某个可选上游故障时,页面是否仍能正常显示?
- 每条边是否恰好只有一个重试责任方,并且是否传递了截止时间?
- 上游调用是否携带经过验证的用户身份,而非 BFF 自身的身份——使用交换后、受众范围收窄的访问令牌,或按照该模型的规则使用未更改的信任域事务令牌?
- 是否按照
bff-entry-points对 BFF 自身的入口进行分类并强制执行相应规则? - 如果你运行元框架服务器,是否已对它应用此技能和
bff-entry-points,而不是把它当作“纯前端”来处理?