BFF — Backend For Frontends,经典分布式架构设计模式之一。我在学习和工作经验累积中,逐渐加深了对 BFF 的理解。作为一种模式,它具有一些更加确切的使用场景,和一些能匹配的特定问题。
在本篇文章中,你们会与我一起穿越回BFF诞生的历史中,寻找其起源。并一同探索和学习这个在分布式系统中出镜率极高的架构模式。
寻找历史的线头
在毫无头绪的情况下,我们可以首先从Thoughtworks技术雷达中 BFF 的条目入手,去找到一些历史的蛛丝马迹。BFF 条目的发布时间是在 2015 年 11 月10 日。从这个信息我们可以获知,BFF 在历史崭露头角应该是在 2015 年 。
紧接着,在谷歌搜索关键字Backend for Frontends 以及将时间范围限定在 2015 年 1 月 1 日到 2015 年 11 月 10 日。通过对比搜索结果的时间,我们可以轻易发现最早出现 Backend for Frontends 词条的文章。文中提到,BFF 这个名字是由当时团队 Tech Leader Nick Fisher首次提出,通过投票获得了内部团队的认可。好了,我们现在获得了一个非常具体的证据。作为严谨的技术工作者,我们找到其他的交叉证据,提高这个结论的置信度。
非常幸运的是,在另一篇2015 年的 Thoughtworks 洞见文章中也提到了与上面证据相同的内容。终于,我们可以说 BFF 模式是在解决 SoundCloud的分布式系统问题中首次出现。下面,让我们一起回到BFF第一次发挥威力的现场吧。
神功初成
为了能让大家更容易了解到SoundCloud 当年究竟遇到了什么样的挑战,我会在下面通过分类分项来列举情况以及进行分析。
背景:
- SoundCloud主要是通过付费订阅与广告进行盈利(也就是说,越多的曝光渠道,会给SoundCloud 带来更多的盈利)
- SoundCloud 是一个单体系统,通过暴露共享 API 的方式为 Web 客户端、Android 和 iOS 应用程序以及互联网、合作伙伴等渠道提供服务。这些共享 API 随着功能和特性一起增长,最终变成了平台与客户端之间的集成点。
- 将 2007 年开始运行的 SoundCloud 从单体模式转变至微服务模式, 这里是具体改造过程。此时,单体服务已经被拆分为多个微服务。
- 支持在 iOS 平台上新增的应用程序(原来的产品主要是在 Web 端提供服务)
主要动机:
- 减少产品发布上线的时间
- 支持 iOS 平台新的应用程序,隔离新用户体验设计带来的风险。
- 增加后端团队与客户端团队合作的节奏,提高工作效率。
挑战:
- 为了让第三方开发人员能更自由地集成,需要 API 设计不对数据的使用方式做出任何假设。所以,为了提供简单的体验,也需要许多不同的 HTTP API 提供具有高数据宽容度的服务。最终,获取构建一个简单的页面的数据,也需要上百个 API 请求。
- 当团队需要变更现有 API 时,需要确保不会破坏现有的任何客户端以及重要的第三方集成。所以,一旦需要添加新内容,都必须投入巨大工作量来确保新功能不只适用于特定客户端。上面这些情况使协调日常工作变得更加困难,最终导致了新功能发布缓慢。
- 开始准备开发新 iOS 应用程序, 新平台上应用程序的用户体验会全部被重塑
通过分析上面的各种情况,可以得出当时SoundCloud 后端团队面对如下几个问题:
- 问题一:需要为第三方客户提供合适粒度的 API,结果提供的 API 数据粒度过细,导致想完成一个业务服务需要请求的 API 太多。
- 问题二:对外 API 与特定的使用方耦合严重,边界模糊,复杂度高导致维护 API 的工作量巨大,新功能发布缓慢。
- 问题三:iOS 平台新客户端改 进了用户体验和交互方式,需要隔离新App带来的风险,并且还要找到与多个客户端团队更好的合作方式。
这三个问题在后端团队进行微服务改造中往往也会遇到。让我们一起看看,当年的 SoundCloud 团队在面临同样的问题时,是如何一步步见招拆招,摸索出 BFF模式 这个内功心法的。
演进之路
接下来,BFF 模式演进这一分是由客户端团队获得的。由于他们是 API 的消费者, 可以将不同服务进行多次逻辑调用,混合到后端的用户配置(UserProfile)文件中。这样避免了对后端服务多次不同的调用,实现客户端对单个资源的简单请求。这将简化客户端代码并提高整体性能,例如:
- GET /user-profile/123.json
后端团队接受了这个逻辑,并开始试验这个方式。他们在 BFF 中编写了很多 Presentation Model。在完成一部分任务后,后端团队突然意识到 BFF 不只是被客户端使用的 API ,它本身就是申请的一部分。BFF 新的形态出现了,具体如下图所示:
随着时间推移,SoundCloud 的 BFF 也在增加。他们已经在生产环境同时维护着 5 个 BFF 了。为了进一步提高生产力,减少不必要的重复。用户配置(User Profile) 被从每个不同的微服务中抽取出来,变成一个独立的在 Services 与 BFF 之间的应用服务(Application Service)。
SoundCloud 的 BFF 依然随着时间在横向增长,不同的是这种横向增长不会再引起任何问题了。最终,BFF 模式的架构演变成与我们现在使用的几乎一致了。架构如下图:
总结
我们在维护和使用分布式架构,同时面对多客户端时,BFF 模式提供了一种很好的架构模式,使后端团队在构建面向客户端的复杂需求时,能够掌控自己的命运。并且,这种自主性对于快速迭代的客户端应用程序,能够提供快速而良好的体验。通过支持持续的演进和变化,这种模式可以将相同变化趋势的消费者行为,限制在一个可控范围内。使他们变得更容易合作和改变,并且更好满足不同客户端的特性需求。
在系统架构中,因为离需求频繁变化的前端比较近(网络和组织架构上),BFF很容易野蛮生长,成为各种“妥协”的自留地,在使用的过程中,我们需要明确架构中各层相关的职能和边界。同时,如果确实有不得不去做的一些“妥协”,我们也一定要用技术债的方式,继续跟踪和管理,避免“妥协”越来越多以后,BFF从一个解决不同变化速率和需求的适配器,变成分布式单体的一个转化器。
我们往往会在系统设计之初犯下一个错误,那便是希望所有东西在一开始都是可复用的。这种思路会给系统后续的开发和维护带来巨大的挑战,挑战可能是来自应用间的协调,也可能是兼顾复用带来的高工作量。特别是在维护多个客户端或消费者的场景下会带来更大的困难。我们应该在考虑通用用法之前,先专注于功能和特定用例。在了解系统现状的主次和具体情况后,再针对性地区分需要通用和特殊处理的部分。这种系统设计和开发的思路和方式,使我们能够拥抱变化,立于演进的不败之地。
参考文献
- https://philcalcado.com/2015/09/18/the_back_end_for_front_end_pattern_bff.html
- https://philcalcado.com/2015/09/08/how_we_ended_up_with_microservices.html
- https://samnewman.io/patterns/architectural/bff/
- https://martinfowler.com/articles/micro-frontends.html
- https://www.thoughtworks.com/insights/blog/bff-soundcloud
- 相关阅读 -
整洁架构之CSS
被误解的Event Sourcing