架构设计系列-前端模式的后端(BFF)翻译PhilCalçado
本文翻译自PhilCalçado的官网:https://philcalcado.com/2015/09/18/the_back_end_for_front_end_pattern_bff.html
对我们的架构演变保持透明是我们技术战略的一部分。我们在无数场合谈过的但从未真正详细描述过的东西是我们应用后端用于前端架构模式或BFF。这篇文章记录了我对如何开发和应用这种技术的理解。
我对软件组件演变的理解
在完全分布式架构变得可行之前,组织通常会在一个或多个层中构建应用程序。层是应用程序的高度耦合但相当独立的组件。据加上在这,而不是服务,它被认为仅由一个应用程序使用的感觉。它独立于如何不作为同一过程的一部分运行,甚至通常不在同一台机器中运行。
让我们用三个虚构的应用来说明这一点,当时任何大公司都会发展出来:
这些体系结构可能变得非常复杂,但总体而言,在不同的应用程序之间绘制线条非常容易,清楚地划分出一个开始和另一个结束的位置。
当时,每个应用程序都有自己的数据副本和重复的常见业务流程实现。随着时间的推移,随着组织获得或构建越来越多的应用程序,我们意识到我们需要不同的东西。我们需要应用程序来共享数据和重用逻辑,而我们曾经简单的架构变得有点复杂:
随着对更多重用和整合的需求,软件行业的集体思维方式决定了一个非常抽象的概念,称为服务。实际上,这意味着上面的图表改为类似于此的东西:
上述架构的卖点是这些可重用服务提供的灵活性。理论上,在这个平台上构建应用程序现在是一个问题:
- 选择您需要的服务
- 写一些调用这些服务的胶水代码
- 将从中获得的数据合并到最终用户更熟悉的内容中
- 以最终用户可以使用的方式呈现此数据
与此同时,计算机和互联网正变得越来越流行。过去与职员或系统操作员交互的客户开始直接与应用程序本身交互。设计思维和用户体验研究使我们摆脱了复杂的用户界面,专注于让专家用户更高效地获得更丰富,更实用的体验,客户可以理解这些体验 - 没有人阅读网站手册。更丰富的经验需要丰富的数据,这意味着汇总来自各种来源的信息。
按照我们的示例,我们最终会得到如下图所示的内容。
我们不再拥有业务线系统的用户界面,而是越来越多的用户界面本身就是应用程序。这些应用程序通常用JSP,PHP或ASP编写,其代码包含用户界面和特定于应用程序的后端逻辑。
打破巨石
上面简单的例子与许多现代技术组织的架构发展方式没有什么不同。2011年,SoundCloud的网站如下所示:
Logic和All逻辑在一个地方。有一个系统,而这个系统是该应用程序。
如前一篇文章所述,我们发现此架构存在许多问题,并决定将逻辑提取到微服务中。尽管我们在提取后端服务方面取得了成功,但最长时间内母舰仍然处于每个请求的关键路径上。
我们正在进行的架构改变背后的主要动机是缩短新功能的上市时间,我们发现我们最糟糕的瓶颈在于任何必须触及整体的变化。考虑到用户界面更改的频率,从整体中提取代码是一种提高生产力的直观方法。然后,我们在其自己的组件中提取了我们的UI层,并使其从我们的公共API中获取数据:
早在2011年,当这些架构发生变化时,绝大多数用户都在网上。正如Fred Wilson所预测的那样,最终这种情况发生了变化,我们的用户群开始使用移动应用程序的方式比Web界面更频繁。SoundCloud已经为Android和iOS提供了很长时间的移动客户端,与我们的新Web应用程序类似,他们直接与我们的公共API进行了对话。
dogfooding带来的挑战
在现代软件工程中,狗食通常被认为是一件好事。在我们自己的API之上构建我们的产品被认为是确保我们的API具有高质量并且始终是最新的最佳方式。在实践中,我们遇到了这种方法的几个问题。
我们的第一个问题不一定与技术有关,而是产品开发的根本挑战。如果我们仅使用公共API,那么我们的平台中没有任何内容可供第三方API客户端使用。尽管我们想要一个蓬勃发展的SoundCloud集成生态系统,但我们还是一家广告公司,因此我们需要确保人们使用我们的属性,而不仅仅是我们的数据。创建我们自己的应用程序独有的功能意味着我们必须在许多地方不断检查OAuth范围,并使人们很难欺骗我们的“官方应用程序”密钥。
在一个更技术性的问题上,我们的公共API几乎按照定义是非常通用的。为了使第三方开发人员能够构建有趣的集成,您需要设计一个不会假设数据将如何使用的API。这会产生非常细粒度的端点,然后需要对多个不同端点的大量HTTP请求才能呈现最简单的体验。您可以在下面看到我们过去在整体时代提出的请求数量与我们为新的Web应用程序生成的请求数量:
要生成该单个配置文件页面,我们必须对不同的API端点进行多次调用,例如:
GET /tracks/1234.json
(该曲目的作者)GET /tracks/1234/related.json
(推荐相关的曲目)GET /users/86762.json
(关于该曲目的作者的信息)GET /users/me.json
(有关当前用户的信息)- ...
...然后,Web应用程序将合并以创建用户配置文件页面。虽然这个问题存在于所有平台上,但对于我们不断增长的移动用户群来说,情况更糟,因为他们经常使用不可靠且速度慢的无线网络
我们在上面的体系结构中遇到的第三个甚至更烦人的问题是,即使没有整体结构,我们仍然存在API的瓶颈。每当团队需要更改现有端点时,我们都需要确保更改不会破坏任何现有客户端(包括重要的第三方集成)。每当我们添加新内容时,我们都需要投入大量时间来确保新端点不会过度专用于特定应用,所有客户都可以轻松使用它们。所有这些协调使我们的日常工作变得比应有的困难,并使我们几乎不可能进行A / B测试并减慢新功能的推出。
前端模式的后端(BFF)
在上述架构首次亮相近一年后,我们开始准备开发新的iOS应用程序。这是一个庞大的项目,最终会改变所有房产的用户体验。如此高风险,开发过程中的实验和迭代至关重要。当工程团队开始考虑应用程序的体系结构时,我们发现上述挑战将成为项目的阻碍,我们需要重新思考我们的工作方式。
我们首先提出的解决方案是为移动和网络提供不同的API。我们的想法是,让客户拥有API的团队可以让他们更快地移动,因为它不需要部件之间的协调。我们最初的想法是为不同的前端设置不同的后端。BFF一词是由我们的网络技术主管Nick Fisher创造的(我最初的建议是BEFFE,但我们讲荷兰语的队友否决了这个选项)。
在它的第一个版本中,这些后端看起来仍然看起来像公共API,许多通用端点需要来自客户端的许多调用来呈现单个屏幕。但是,随着时间的推移,我们发现了一些有趣的事情。以用户配置文件页面为例,以前这是一个仅存在于客户端的概念。Web或移动应用程序将从各个端点获取数据,并使用它来创建我们称为用户配置文件的对象。这是一个特定于应用程序的对象。
在某些时候,我们的客户团队意识到,由于他们拥有API,他们可以将这个对象推向API。他们可以提取所有调用不同服务的逻辑,并将它们一起混合到后端的用户配置文件中。
这最终将简化代码并提高性能。不是对上面描述的多个端点进行多个不同的调用,而是需要请求的所有客户端都是单个资源:
GET /user-profile/123.json
当我们进一步尝试这个模型时,我们发现自己在BFF中编写了很多演示模型。在这个阶段,我们意识到BFF不是应用程序使用的API 。BFF是申请的一部分。
最终,我们的所有属性(包括API)都开始遵循此模式。
一直往下
在某些时候,我们生产了大约五种不同的BFF,我们已经开始研究如何进一步提高生产率。接下来是我们的用户配置文件示例,对我们来说显而易见的是,鉴于每个应用程序都有一个等效的用户配置文件页面,所有BFF都有很多重复的代码来获取和合并它们的数据。
复制并不准确,像网络浏览器这样的大屏幕在其用户个人资料页面上的信息要比微型移动应用程序多得多。然而,我们看到重复是一种难闻的气味,表明我们在域模型中缺少一个对象。为了解决这个问题,我们创建了一个UserProfileService
处理这个重复逻辑的方法。
随着时间的推移,我们发现了越来越多这样的情况。我们开始有意识地转向一个架构,在这个架构中,用户理解的大多数核心对象都有自己的微服务支持它们。