5种BFF最佳实践


 Backends-for-Frontends (BFF) 是解决许多团队面临的问题的一个有趣的解决方案,有目的地将前端与后端分离,使前端免受后端更改的干扰。

BFF 是 Backend For Frontend 的缩写,它是一种非常巧妙的应用程序架构方式,无论您提供多少种不同的前端体验,核心后端服务都能保持不变。

这有什么不同?有时,您可能需要提供截然不同的前端体验--多个应用程序都要重新使用您的后台服务,但每个应用程序都要为用户提供截然不同的用户体验。

想想有限的移动用户体验、功能丰富的桌面应用程序和报告短信界面。这是三种截然不同的用户界面/用户体验,其核心都是相同的业务逻辑。但是,如果试图创建一套独特而通用的微服务来满足所有这些用户界面的需求,就有可能在前端和后端之间引入不必要的耦合。这总会导致代码库臃肿、难以维护,以及业务逻辑泄漏到前端。

#1.为每种用户体验创建一个 BFF
BFF 的主要作用应该是处理特定前端客户端的需求,而不是更多。

请看上图,微服务仍然存在,它们拥有 "核心业务逻辑"。BFF 添加的额外层只是为了帮助解决每个用户体验的特定问题。

我的意思是什么?在上面的例子中,我为我们的应用程序接口提出了 3 个不同的客户端:

  • 短信
  • 手机
  • 桌面应用程序

每种客户端都能提供独特的用户体验--但这里最关键的是,我们讨论的是根据所提供的用户界面/用户体验来满足特定需求,而不是每种独立客户端实现的特定需求。

例如,即使有两个移动应用程序(一个用于 Android,一个用于 iOS),它们仍将提供(或多或少)相同的用户界面/用户体验,并有类似的数据需求,因此您只需要一个 "移动体验 "BFF,而不是一个用于 Android,一个用于 iOS 的 BFF。

按客户端来做只会导致无端的代码重复。特定用户界面/用户体验的需求 "与 "特定客户端的需求 "之间存在很大的差异,我们在构建 BFF 时必须牢记这一点。

#2.不要重新发明轮子
实施闺蜜模式可以是困难重重的,也可以是简单琐碎的。

我的建议是,尝试找到一个框架,让你无需做太多工作就能轻松实现这种模式。

归根结底,你想要的是在微服务之上建立一层抽象层。WunderGraph 就是这样一个框架。

WunderGraph 是一个 "BFF 框架",它允许您将所有底层服务、外部 API 或数据源汇集在一起,并创建一个统一的 API,从而将您的客户端与下游服务解耦。

下面的示例展示了如何轻松创建一个应用程序,将两个不同的外部 API 组合在一起,让前端开发人员无需直接调用即可使用。

首先。让我们创建一个包含 WunderGraph 的 NextJS 应用程序示例:

npx create-wundergraph-app my-weather --example nextjs

进入 my-weather 文件夹,运行 npm i 。完成后,使用 npm start 启动服务器。

你会看到一个显示 SpaceX Dragon 胶囊信息的页面(WunderGraph 使用 SpaceX GraphQL API 作为示例)。别管它,进入 .wundergraph/wundergraph.config.ts 文件,移除 SpaceX API 引用,然后编辑成下面的样子:

//...
const weather = introspect.graphql({
  apiNamespace: 'weather',
  url: 'https://weather-api.wundergraph.com/',
});
const countries = introspect.graphql({
  apiNamespace: 'countries',
  url: 'https://countries.trevorblades.com/',
});
// configureWunderGraph emits the configuration
configureWunderGraphApplication({
 apis: [weather, countries],
 //...
 },
 //...
});

我基本上删除了对 SpaceX API 的任何引用,并添加了两个新的 API 集成,即天气和国家 API,然后将它们添加到 apis 依赖关系数组中。

该文件包含 WunderGraph BFF 服务器的配置。在这里,你需要添加所有想要组合在一起的API--甚至不一定非得是微服务、数据库或外部API之类的数据依赖关系。WunderGraph还支持通过Clerk、Auth.js或您自己的验证解决方案来集成验证;甚至还支持兼容S3的文件上传存储。所有这些都已嵌入 BFF 层。

现在,你可以进入 .wundegraph/operations 文件夹,创建一个名为 CountryWeather.graphql 的文件,我们将在其中添加一个漂亮的 GraphQL 操作,将来自两个 API 的信息连接起来。

query ($countryCode: String!, $capital: String! @internal) {
  country: countries_countries(filter: { code: { eq: $countryCode } }) {
    code
    name
    capital @export(as: "capital")
    weather: _join @transform(get: "weather_getCityByName.weather") {
      weather_getCityByName(name: $capital) {
        weather {
          temperature {
            max
          }
          summary {
            title
            description
          }
        }
      }
    }
  }
}


你现在要做的,就是进入 index.tsx 文件,使用 useQuery 钩子来请求你最喜欢的国家的天气。在我的例子中,我将这样做:

const weather = useQuery( {
  operationName: 'CountryWeather',
  input: {
   countryCode: 'ES'
  }
 })
结果,我将得到这个 JSON:

{
   "data":{
      "country":[
         {
            "code":"ES",
            "name":"Spain",
            "capital":"Madrid",
            "weather":{
               "temperature":{
                  "max":306.68
               },
               "summary":{
                  "title":"Clear",
                  "description":"clear sky"
               }
            }
         }
      ]
   },
   "isValidating":true,
   "isLoading":false
}

我们使用了这两个 API,收集并拼接了它们的响应,但从前端开发人员的角度来看,他们只需调用 BFF 服务器上的一个端点即可,该端点使用持久化、散列 GraphQL 查询来获取客户端 UI/UX 所需的数据,并通过 RPC 以 JSON 格式提供最终响应!(如果您想详细了解该项目的全部源代码,请点击此处 here )。

#3.注意扇出反模式
使用 BFF 模式时,总有可能出现所谓的 "扇出fan-out "问题。

这是指你的 BFF 负责协调对多个后端服务或第三方 API 的 API 请求,从而引入多个故障点。如果这些被称为下游的服务中的任何一个出现故障、问题或不可用,都会导致 BFF 出现连锁故障,最终影响前端用户体验。

这些问题的潜在解决方案包括

  • 断路器模式。通过这种模式,您可以以可控的方式处理故障和失效的后端服务。当后端服务出现问题时,断路器会迅速阻止 BFF 发送其他请求,从而减少等待无响应服务的时间。通过这种方式隔离故障后端服务,断路器可防止级联故障影响 BFF 或前端应用程序的其他部分。这就确保了在后端服务不可用期间的优雅降级。
  • 缓存。如果后端服务不可用,BFF 可提供缓存或默认数据,而不是返回错误。缓存经常访问的数据还有助于减少对后端服务的依赖,并在后端服务不可用期间改善响应时间--尤其是与断路器结合使用时。
  • 服务监控。如果您想了解服务的使用情况(请求数量、最常见的请求等),那么 BFF 层是实施日志记录、监控后端服务健康状况以及跟踪其可用性和性能的绝佳场所。最重要的是,您可以利用这种洞察力主动发现并解决问题。

#4.在 BFF 上一致理错误地处
任何一个外部 API 都有可能失败。如果发生了,它们都会以完全不同的方式报告错误。您需要确保您的 BFF 知道如何以一致、统一的方式处理这些错误,并以有意义的方式将这些错误反馈给用户界面。

换句话说,利用 BFF 层的优势,将其用作错误汇总器/翻译器。客户端应用程序无法理解在 JSON 主体中存在错误的 HTTP 500 和 HTTP 200,也不应该理解。

取而代之的是,让 BFF 层对错误处理进行标准化。这不仅包括响应的格式,还包括其内容(如标题、错误信息等)。这样一来,编写客户端应用程序以有意义的方式向用户反馈错误就变得更容易了,因为就客户端而言,所有的内部错误状态都是规范的。

#5.使用基于 Node 的服务器,以便利用 TypeScript
为你的 BFF 使用基于 NodeJS 的服务器(Express.js、NestJS 或 WunderGraph 的 BFF 实现--它使用 Node/Go 服务器层)可以让你在前端和后端都使用 TypeScript。拥抱它的优势!

TypeScript 是 JavaScript 的超集,它增加了静态类型,从而增加了一层安全性--可以帮助你在开发早期发现错误,使代码更易于维护和重构。如果您可以使用 TypeScript 开发前端和 "后端"(BFF 层),您就可以在整个应用程序中实现语言一致性,使开发人员可以更轻松地在客户端和服务器代码之间切换,而无需在不同语言之间进行上下文切换。

正如 Sam Newman 在博文中提到的,理想情况下,前端团队应该拥有 BFF 的所有权。使用基于 Node 的服务器和 TypeScript 进行前端和 BFF 开发,可以让他们在一个 monorepo 中同时拥有这两种语言,从而使开发、迭代、维护、测试和部署变得更容易。

事实上,TypeScript 还可以让您在前端和 "后端"(实际上是 BFF)之间共享某些代码、类型和接口。例如,如果您在客户端应用程序和 BFF 之间共享验证逻辑,TypeScript 可以确保这些共享组件在两个地方都能正确使用。

总结
归根结底,BFF 并不是什么花哨或奇怪的东西,而是用户界面和核心业务逻辑之间的另一层抽象。

通过采用这五种最佳实践,您就能很好地构建可扩展、可维护和高性能的 BFF,确保用户和开发人员都能获得良好的体验。