本文介绍了美国快餐连锁巨头Chick-fil-A在技术团队中引入Buf和Connect解耦API依赖并实现了良好的API定义模式。原文: Connect(ing) Chick-fil-A
背景
2018年,Chick-fil-A的客户技术团队遇到了一些API问题。该团队擅长构建API,这些API可以实现一些了不起的事情,比如将客户订单从手机通过迷宫般的网络发送到销售点的遗留系统,但与这些API交互需要团队之间的大量协作。更准确的说,我们遇到的是API契约问题。团队很少有明确、完备的合约。大多数合约要么缺乏文件记录,要么完全不明确。这在我们电子商务项目最初几年的创业环境中可以预料。我们团队最初在2016年推出了移动应用和电子商务平台,由于营销团队的努力以及新冠疫情的影响,电商业务从很小的一部分增长到销售额的重要部分。
随着业务越来越成熟,缺乏明确的、记录良好的合约就成了一个问题。前端开发人员经常需要直接联系后端开发人员,以确定某个值是否应该作为字符串或整数传递,或者某个字段是否真的需要。后端开发人员要么依赖记忆,要么必须深入研究源代码,以了解后端在做什么、需要什么、什么类型等等。这是一个显而易见的问题。随着团队数量增加,情况变得越来越糟。我们的目标是优化以"X-as-a-service"模式运行的团队,这种模式只需要团队之间有限的合作就可以集成。相反,我们发现所有团队都在"协作"交互模式下操作(参见: 团队拓扑[1])。
改变
所以你可能会想:"为什么不把合约文件写得更好呢?"公平的说,我们当然是这样做的。我们从团队编写简单的markdown文件开始,这些markdown文件以直接从Basecamp API文档中提取的方式深入介绍他们的API(感谢Jason Fried)。这对我们来说很有效,特别适用于拥有技术分析师的团队,这些技术分析师可以帮助消除开发人员必须清理历史文档的一些负担。然而,这个过程并没有应用于各个团队,并且严重依赖于人的记忆来确保文档保持最新。举个例子,我们的Location API团队有一个强大的技术分析师来保存文档,但是许多团队没有相同的技能。这一尝试帮助我们将几个关键API做得更好,但给团队增加了许多开销,并且仍然可能出现不可接受的人为错误。
尝试了几个月之后,我们渴望找到一个更适合我们组织的方法。我们开始寻找其他选择:
-
靠近代码(Proximity to code): 这是必须的。文档离代码(真实来源)越远,它在脆弱曲线上的位置就越远。我们发现,脆弱的文档可能会导致比单独依赖协作更糟糕的结果。
-
功能所有权(Functional ownership): 如果技术后端系统的产品所有者拥有的"经验"是API(强调接口),那么他们的客户是前端团队和其他后端团队,而不是传统的Chick-fil-A客户。因此,为了更好的为客户服务,这些API团队的SLO应该是基于可测量的东西,比如P95延迟和可用性,而不是像开发人员体验和易于集成这样难以测量的目标。但是,如果产品负责人对这些"模糊"的东西缺乏可视性,那就没有真正的所有权。将它们从代码中抽象出来,放到产品负责人可以可视化的视图中,他们就可以更好的负责整个产品。
-
契约优先的规范接口定义(Contract-first, canonical interface definitions): 这是迄今为止我们所尝试的最不一样的东西。我们需要引导契约,而不是简单的从后端实现的类中派生。在处理隐式契约时,很容易发生复制传递,例如前端团队认为客户标识符字段命名为
customerId
,而实际上后端团队将其命名为userId
。哦,不是说我们做过这种蠢事,但理论上可能会发生。如果我们能够推动接口定义规范化,那将是一大步。生成的SDK是首选,这样就无法引入错误。
有了这些启发,我们开始评估几种技术,这些技术已经帮助数百个组织解决了同样的问题。
GraphQL
GraphGQ有很多让人喜欢的地方。这是一项引人注目的技术,具有许多附加功能,特别是如果API是基于Node.js构建的时候。因为我们的API主要用Java和Go构建,所以工具支持并不像Node.js那样完善,而且服务器到服务器调用的人机工程让人感觉不舒服。此外,当我们可以同时控制前端和后端实现时,灵活性所带来的好处就不那么有价值了。
OpenAPI规范
OpenAPI几乎征服了我们,我们用在DTT(数字转换和技术,Digital Transformation & Technology)部门的其他部分。我们已经通过Springfox使用了Swagger。然而,我们并没有充分利用Swagger注释,这使得我们的文档感觉平淡无奇。零星注释也会让代码感觉有点混乱,并把所有责任都推给开发人员去更新。这可以通过团队流程和整体期望来解决,但没有给API功能的所有者足够的控制权。
gRPC
gRPC是Google设计的一个远程过程调用框架,有一些非常引人注目的优点。我们可以从包含接口和服务的一些基本定义的protocol buffer文件开始,在Service
中定义schema,并使用Message
定义接口。总的来说,这是一种相对简单的语法,比yaml更容易阅读。
gRPC有很多让人喜欢的地方:
-
✅通过生成的代码定义规范化接口。 -
✅靠近代码,能够在实现API的同时提交契约。 -
✅业务所有权,具有易于理解的语法,允许业务人员理解并为契约做出贡献。
看起来是全方位的胜利。
直到我们开始在概念验证之外使用。然后对话就成了这样:
"protoc",这个CLI是由一些业余人员构造的吗?哦,等等,不,它是由谷歌构造的。为什么这么笨重?不知道。好吧,我们能解决。等等,我究竟如何获得生成Java代码的"protoc"?好的,明白了,通过Maven插件,这似乎不同于其他语言使用"协议"的方式。现在我们可以生成一些代码,让我们把它放到Spring API中。哦,这里的支持似乎有点问题。让我们在Go API中试试。
最后,我们用Go语言构建了一个服务,并在环境中运行。
好吧,我们把一些外部流量引到这东西上。嗯,AWS中对gRPC的ALB支持是全新的,而且没有很好的文档。让我们看看能做些什么来让这个跑起来。哦,天哪,我们终于从前端客户端获得了流量!欧耶。
退一步说,让所有网络节点都支持gRPC并不是一件容易的事。最重要的是,gRPC建在一个有围墙的花园里,我们不能用以前的中间件,不能用curl,不能用常规调试代理,也不能用相同的HTTP库。我们被困住了。所有被吹捧的好处都换来了糟糕的开发者体验,这并不是一个理想的权衡,但我们仍在继续努力。在生产环境中运行后,我们最终得出结论,尽管我们喜欢Protobuf带来的许多好处,但无法忍受Protobuf和gRPC为前后端团队带来的糟糕的开发体验。
Connect
最后,我们遇到了一个更新的技术产品,Connect。那是多么美好的一天啊。当时,Buf有一个漂亮的CLI工具,承诺比"protoc"更快,更重要的是,比竞争对手提供了更好的人体工程学。他们也有一个疯狂的愿景,让Protobuf的整个世界变得更好、更干净、更容易使用,而不是一个有围墙的花园。当然,他们兑现了这一承诺。
Buf最终实现了将世界Connect起来的宏伟愿景。Connect是一个允许三种交互模式的协议:
-
gRPC互操作性: 向后兼容gRPC客户端和服务器(如果用作客户端)。 -
HTTP POST + Protobuf: 提供了Protobuf的序列化优势,并充分理解了POST(或者现在可选的 GET [2])请求的本质。 -
HTTP POST + JSON: 提供JSON的可见性,同时仍然具有强制的、可检测破坏性更改的契约。对于可追溯性远比延迟重要的低优先级环境,是一个完美的选择。
使用Connect和Buf使我们能够采用一种对我们来说非常有效的流程,并使我们远离"协作"模式,更接近"X-as-a-service"交互模式。
整个流程是这样的:
-
团队在Protobuf中定义API模型和契约。在开发新API时,Proto文件可以保留在分支中。该分支每次被推送到Github时都会自动同步到Buf Schema Registry作为"草案"。允许前端客户端或后端消费者在"测试"模式下生成代码,而合约仍在开发中。如果需要的话,还可以允许团队并行化工作。
-
发起Pull request,供接口的负责团队和消费团队进行审查。这一阶段将运行破坏性变更检测并验证检查规则。到目前为止,破坏性变更检测是我们最喜欢的好处,确保了API的前向和后向兼容性。后端团队可以放心发布产品,因为他们知道合约变更不会影响到消费者。
-
一旦团队对契约感觉良好,就会被合并到实现API团队的主分支中。然后与Buf Schema Registry同步,并允许消费团队查看API文档并使用生成的代码。
-
重复以上步骤。
Buf + Connect给了我们Protobuf和gRPC所承诺的许多好处,而且没有任何缺点。我们的许多团队现在都采用"契约优先的API设计",极大改善了我们构建API的方式和团队交互方式。
总结
随着Chick-fil-A客户技术团队的成长,我们经历了沟通渠道数量的指数级增长,因为团队依赖于紧密"协作"作为交互模式。很明显,我们需要努力使团队能够在可能的情况下对需求实现自助服务,以"X-as-a-service"的方式运作。这使我们发现了Protobuf以及Buf在其Buf Schema Registry (BSR)和Connect[3]中提供的工具。这些工具帮助团队朝着提供自助交互模式的方向发展,并帮助我们在Chick-fil-A数字商务的复杂世界中优化构建、记录和集成API的方式。
如果只是运营一个小团队,我们可以忍受团队互动的低效,因为沟通矩阵小而简单。然而,随着组织规模扩大,应该强烈考虑对Protobuf、Buf和Connect等工具进行投资,这些工具有助于提供一种媒介来简化团队交互模式。
你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!
参考资料
三种团队交互模式: https://itrevolution.com/articles/the-three-team-interaction-modes
[2]Introducing Connect Cacheable RPCs: https://buf.build/blog/introducing-connect-cacheable-rpcs
[3]Connect: A better RPC: https://buf.build/blog/connect-a-better-grpc
本文由 mdnice 多平台发布