模式:前端的后端

模式:前端的后端

写于

面向 UI 和外部各方的单一用途边缘服务

介绍

随着 Web 的出现和成功,交付用户界面的实际方式已从胖客户端应用程序转向通过 Web 交付的界面,这一趋势也促进了基于 SAAS 的解决方案的总体增长。通过网络提供用户界面的好处是巨大的 - 主要是因为(在大多数情况下)完全消除了客户端安装的成本,从而显着降低了发布新功能的成本。

然而,这个更简单的世界并没有持续多久,因为移动时代很快就到来了。现在我们遇到了问题。我们拥有服务器端功能,我们希望通过桌面 Web UI 和一个或多个移动 UI 来公开这些功能。对于最初开发时考虑到桌面 Web UI 的系统,我们经常面临适应这些新型用户界面的问题,通常是因为我们已经在桌面 Web UI 和我们支持的服务之间建立了紧密的耦合。

通用 API 后端

适应多种类型 UI 的第一步通常是提供单一的服务器端 API,并随着时间的推移根据需要添加更多功能以支持新型移动交互:

通用 API 后端

如果这些不同的 UI 想要进行相同或非常相似的调用,那么这种通用 API 很容易成功。然而,移动体验的本质通常与桌面 Web 体验截然不同。首先,移动设备的可供性非常不同。我们的屏幕空间较少,这意味着我们可以显示的数据较少。打开与服务器端资源的大量连接会耗尽电池寿命和有限的数据计划。其次,我们想要在移动设备上提供的交互的性质可能会有很大不同。想想一个典型的实体零售商。在桌面应用程序上,我可能允许您查看待售商品、在线订购或在商店预订。在移动设备上,我可能希望允许您扫描条形码以进行价格比较或在商店中为您提供基于上下文的优惠。随着我们构建了越来越多的移动应用程序,我们逐渐意识到人们对它们的使用方式非常不同,因此我们需要公开的功能也会有所不同。

因此,在实践中,我们的移动设备将希望进行不同的调用、更少的调用,并且希望显示与桌面设备不同(并且可能更少)的数据。这意味着我们需要向 API 后端添加额外的功能来支持我们的移动界面。

通用 API 后端的另一个问题是,它们根据定义为多个面向用户的应用程序提供功能。这意味着单一 API 后端可能会成为推出新交付时的瓶颈,因为试图对同一个可部署工件进行大量更改。

通用 API 后端倾向于承担多种职责,因此需要大量工作,通常会导致专门创建一个团队来处理此代码库。这可能会使问题变得更糟,因为现在前端团队必须与一个单独的团队进行交互才能做出更改 - 该团队必须平衡不同客户团队的优先级,并且还必须与多个下游团队合作以完成更改。当新的 API 可用时使用它们。可以说,此时我们刚刚在我们的架构中创建了一个智能中间件,它并不专注于任何特定的业务领域 - 这与许多人对明智的面向服务架构应该是什么样子的看法背道而驰。

使用通用支持的 API 时的常见团队结构

介绍前端的后端

我在 REA 和 SoundCloud 中看到的解决这个问题的一种方法是,不是使用通用 API 后端,而是为每个用户体验提供一个后端 - 或者(前 SoundClouder)Phil Calçado将其称为后端对于前端 (BFF)。从概念上讲,您应该将面向用户的应用程序视为两个组件 - 位于您的边界之外的客户端应用程序和位于您的边界内的服务器端组件(BFF)。

BFF 与特定的用户体验紧密耦合,并且通常由与用户界面相同的团队维护,从而更容易根据 UI 的需要定义和调整 API,同时还简化了两者的发布过程客户端和服务器组件。

每个用户界面使用一个服务器端 BFF

BFF 紧密关注单个 UI,而且只是那个 UI。这使得它能够集中,因此会更小。

有多少最好的朋友?

当谈到在不同平台上提供相同(或相似)的用户体验时,我看到了两种不同的方法。我更喜欢的模型是为每种不同类型的客户严格指定一个 BFF - 这是我在 REA 看到使用的模型:

不同的移动平台,不同的 BFF,如 REA 使用的那样

我在 SoundCloud 看到过使用的另一种模型,每种类型的用户界面使用一个 BFF。因此,Android 和 iOS 版本的侦听器本机应用程序都使用相同的 BFF:

为不同的移动后端配备一个 BFF,如 SoundCloud 所使用的那样

我对第二种模型的主要担忧是,使用单个 BFF 的客户类型越多,它因处理多个问题而变得臃肿的诱惑就越大。这里要理解的关键是,即使共享 BFF,它也是针对同一类用户界面的 - 因此,虽然 iOS 和 Android 的 SoundCloud 侦听器本机应用程序使用相同的 BFF,但其他本机应用程序将使用不同的 BFF(例如例如,新的 Creator 应用程序 Pulse 使用不同的 BFF)。如果同一个团队同时拥有 Android 和 iOS 应用程序并且也拥有 BFF,我也会更轻松地使用此模型 - 如果这些应用程序由不同的团队维护,我更倾向于推荐更严格的模型。因此,您可以将组织结构视为模型最有意义的主要驱动因素之一(康威定律再次获胜)。值得注意的是,我采访过的 SoundCloud 工程师表示,如果今天再次做出决定,他们可能会重新考虑为 Android 和 iOS 侦听器应用程序配备一个 BFF。

我非常喜欢 Stewart Gleadow(他又将 Phil Calçado 和 Mustafa Sezgin 归功于他)的一条指导方针是“一次体验,一次最好的朋友”。因此,如果 iOS 和 Android 的体验非常相似,那么就更容易证明拥有一个 BFF 是合理的。然而,如果他们的分歧很大,那么拥有单独的好朋友就更有意义了。

Pete Hodgson 观察到,最好的朋友在围绕团队边界对齐时效果最佳,因此团队结构应该决定您拥有多少最好的朋友。因此,如果您有一个移动团队,您应该有一个最好的朋友,但如果您有单独的 iOS 和 Android 团队,您就会有单独的最好的朋友。我担心的是,团队结构往往比我们的系统设计更加流动。因此,如果您有一个专门负责移动设备的 BFF,然后将团队分为 iOS 和 Android 专业领域,那么您是否也必须拆分 BFF?如果最好的朋友已经分开,那么拆分团队会更容易,因为您可以重新分配已经独立的资产的所有权。不过,最好的朋友和团队结构之间的相互作用很重要,我们将很快对此进行探讨。

通常,减少 BFF 数量的驱动因素是重用服务器端功能,以避免过多的重复,但还有其他方法可以处理这个问题,我们很快就会介绍。

和多个下游服务(微服务!)

对于后端服务数量较少的架构来说,BFF 可能是一种有用的模式。然而,对于使用大量服务的组织来说,它们可能是必不可少的,因为聚合多个下游调用以提供用户功能的需求急剧增加。在这种情况下,对 BFF 的单次调用通常会导致对微服务的多个下游调用。例如,想象一个电子商务公司的应用程序。我们想要拉回用户愿望清单中的商品列表,显示库存水平和价格:

刹车 - 献血 有存货!(剩余 14 项) 5.99 美元 现在下单
蓝色果汁 - 可追溯 缺货 17.50 美元 预购
热门芯片 - 为什么有意义? 速度快(还剩 2 件) 9.99 美元 现在下单

多个服务保存着我们想要的信息。Wishlist 服务存储有关列表的信息以及每个项目的 ID。目录服务存储每个商品的名称和价格,库存水平存储在我们的库存服务中。因此,在我们的 BFF 中,我们将公开一个用于检索完整播放列表的方法,该方法至少包含 3 个调用:

进行多个下游调用以构建愿望清单的视图

从效率的角度来看,并行运行尽可能多的调用会更明智。一旦对 Wishlist 服务的初始调用完成,理想情况下我们希望同时运行对其他服务的调用,以减少总体调用时间。这种需要将并行运行的调用与顺序运行的调用混合在一起的需求很快就会变得难以管理,特别是对于更复杂的场景。这是反应式编程可以提供帮助的领域(例如RxJavaFinagle 的 futures系统提供的),因为多个调用的组合变得更容易管理。

然而,理解故障模式变得很重要。在上面的示例中,我们可以坚持所有下游调用都必须返回,以便我们将有效负载返回给客户端。但这明智吗?显然,如果愿望清单服务关闭了,我们就无能为力,但如果只有库存服务关闭了,那么仅仅降低我们传递回客户端的功能(也许只是删除库存水平指示器)不是更好吗?这些问题首先必须由 BFF 本身来管理,但我们还需要确保调用 BFF 的客户端可以解释部分响应并正确呈现它。

重用和最好的朋友

每个用户界面只有一个 BFF 的问题之一是,最终可能会导致 BFF 本身之间出现大量重复。例如,它们最终可能会执行相同类型的聚合,具有相同或相似的代码用于与下游服务交互等。有些人对此的反应是希望将这些重新合并在一起,因此拥有通用聚合 Edge API 服务。这种模型已经被一次又一次地证明会导致代码高度臃肿,并且将多个关注点挤在一起。

正如我之前多次说过的,我对跨服务的重复代码相当放心。也就是说,虽然在单个进程边界中,我通常会尽我所能将重复重构为合适的抽象,但当面对跨服务的重复时,我不会有相同的反应。这主要是因为我经常更担心提取共享代码可能会导致服务之间的紧密耦合 - 我比一般的重复更担心这一点。也就是说,在某些情况下这样做确实是有道理的。

我的同事皮特·霍奇森(Pete Hodgson)指出,当你没有最好的朋友时,“共同”逻辑通常最终会被融入到不同的客户本身中。由于这些客户端使用非常不同的技术堆栈,因此识别这种重复发生的事实可能很困难。由于组织倾向于为服务器端组件使用通用技术堆栈,因此拥有多个重复的 BFF 可能更容易发现和排除。

当确实需要提取共享代码时,有两个明显的选择。第一种方法通常是最便宜的,但也更麻烦,是提取某种共享库。这可能会出现问题的原因是共享库是耦合的主要来源,尤其是在用于生成客户端库以调用下游服务时。尽管如此,在某些情况下,这感觉是正确的 - 特别是当被抽象的代码纯粹是服务内部的一个问题时。

另一种选择是提取新服务中的共享功能,如果您可以概念化新服务具有围绕相关领域建模的功能,那么这种方法可以很好地发挥作用。

这种方法的一种变体可能是将聚合责任推向下游的服务。以上面我们讨论的愿望清单呈现为例。假设我们在两个地方渲染一个愿望清单 - Android、iOS Web。我们每个最好的朋友都在发出同样的三个电话:

多个 BFF 执行相同的任务

相反,我们可以更改 Wishlist 服务来为我们进行下游调用,从而简化调用者的工作:

将聚合职责进一步推向下游,以消除 BFF 中的重复

我不得不说,在两个地方使用相同的代码并不一定会导致我想要以这种方式提取服务,但如果创建新服务的交易成本足够低,我肯定会考虑它,或者我在多个地方使用它(例如可能在桌面网络上)。我认为,当你要第三次实现某些东西时创建抽象的古老格言仍然感觉像是一个很好的经验法则,即使在服务级别也是如此。

桌面 Web 及其他领域的 BFF

您可以将 BFF 视为仅用于解决移动设备的限制。桌面 Web 体验通常在具有更好连接性的功能更强大的设备上提供,其中进行多个下游调用的成本是可控的。这可以让您的 Web 应用程序直接对下游服务进行多次调用,而无需 BFF。

我也见过在网络上使用 BFF 也很有用的情况。当您在服务器端生成 Web UI 的较大部分(例如使用服务器端模板)时,BFF 是可以完成此操作的明显位置。它还可以在一定程度上简化缓存,因为您可以在 BFF 前面放置一个反向代理,从而允许您缓存聚合调用的结果(尽管您必须确保相应地设置缓存控件,以确保聚合内容的到期时间与聚合中最新鲜的内容需要尽可能短)。事实上,我已经看到它被多次使用,但没有称其为 BFF——事实上,通用 API 后端通常是从这样的野兽中发展而来的。

我见过至少有一个组织将 BFF 用于需要拨打电话的其他外部方。回到我常年使用的音乐商店示例,我可能会公开 BFF 以允许第 3 方提取版税支付信息、提供 Facebook 集成或允许流式传输到一系列机顶盒设备:

使用 BFF 将 API 公开给第三方

这种方法尤其有效,因为第三方通常没有能力(或没有意愿)使用或更改他们进行的 API 调用。使用通用 API 后端,您可能必须保留旧版本的 API,以满足一小部分无法进行更改的外部方的需求 - 有了 BFF,这个问题就大大减少了。

和自治

我们经常看到这样的情况:一个团队正在开发前端,而另一个团队正在创建后端服务。一般来说,我们试图通过转向围绕业务垂直领域的微服务来避免这种情况,但即使如此,在某些情况下这种情况也很难避免。首先,在一定规模或复杂程度下,需要多个团队参与。其次,执行良好的 Android 或 iOS 体验所需的技术深度通常需要专门的团队。

因此,构建用户界面的团队面临着这样的情况:他们正在调用另一个团队正在驱动的 API,并且 API 通常在用户界面开发的同时不断发展。BFF 可以在这方面提供帮助,特别是当它由创建用户界面的团队拥有时。他们在创建前端的同时改进了 BFF 的 API。他们可以快速迭代两者。BFF 本身仍然需要调用其他下游服务,但这可以在不中断用户界面开发的情况下完成。

使用 BFF 时的团队所有权边界示例

使用像这样沿着团队边界对齐的 BFF 的另一个好处是,创建界面的团队可以更加流畅地思考功能所在的位置。例如,他们可能决定将功能推送到服务器端,以促进未来的重用并简化本机移动应用程序,或者允许更快地发布新功能(因为您可以绕过应用程序商店审核流程)。如果团队同时拥有移动应用程序和 BFF,则可以由团队单独做出这一决定 - 不需要任何跨团队协调。

一般周边问题

有些人使用 BFF 来实现通用的外围问题,例如身份验证/授权或请求日志记录。我对此很伤心。一方面,大部分功能都很通用,我倾向于使用更上游的另一层来实现它,也许使用 Nginx 或 Apache 服务器层之类的东西。另一方面,这样的附加层只会增加延迟。BFF 通常用于微服务环境中,由于进行大量网络调用,我们对延迟非常敏感。此外,您必须部署更多层来构建类似生产的堆栈,这可能会使开发和测试变得更加复杂 - 将所有这些问题放在 BFF 中作为更独立的解决方案可能会很有吸引力:

使用网络设备来实现通用周边问题

正如我们之前讨论的,消除这种重复的另一种方法可能是使用共享库。假设您的好朋友使用相同的技术,这应该不会太困难,尽管有关微服务架构中共享库的常见警告适用。

何时使用

对于仅提供 Web UI 的应用程序,我怀疑只有当您在服务器端需要大量聚合时,BFF 才有意义。否则,我认为其他 UI 组合技术也可以同样工作,而不需要额外的服务器端组件(我希望很快会讨论这些)。

不过,当您需要为移动 UI 或第三方提供特定功能时,我会强烈考虑从一开始就为各方使用 BFF。如果部署额外服务的成本很高,我可能会重新考虑,但 BFF 可以带来的关注点分离使其在大多数情况下成为一个相当引人注目的主张。如果构建 UI 的人员与下游服务之间存在显着分离(出于上述原因),我会更倾向于使用 BFF。

进一步阅读(和查看)

结论

前端后端解决了使用微服务时移动开发的一个紧迫问题。此外,它们还为通用 API 后端提供了一个引人注目的替代方案,许多团队将它们用于移动开发以外的目的。限制他们支持的消费者数量的简单行为使他们更容易合作和改变,并帮助开发面向客户的应用程序的团队保留更多的自主权。

posted @ 2024-03-15 10:03  CharyGao  阅读(5)  评论(0编辑  收藏  举报