关于从REST API移动到GraphQL API的好处,已经写了很多1。但是,让我们说你已经确信了。如果要将网站转换为数百万用户,请确保性能不会受到影响,并且只是不想拧紧:你是怎么做到的?

我们去年开始踏上这次旅程,并让它活着讲述故事!我们的GraphQL API现在是Okcupid的官方API,所有客户都采用它:我们的188bet金宝搏官网iOS和Android应用程序以及我们的桌面和移动Web单页React应用程序。188博金宝电子体育频道

所以,这是我们如何解决这个巨大的项目。我会谈谈我们建造的内容,我们提出的策略来测试我们发运的新代码,以及技术方面可能更好的事情。免责声明:本文更多的是该过程而不是代码本身;很快就会回来查看关于绩效问题的另一个帖子,我们必须克服我们以前的API达到平价。

但首先,一些统计数据

我们的GraphQL API已在生产中为1年,我们已停止向我们的REST API添加新功能。图表每分钟处理高达170k的请求,它由227个实体组成。

We haven’t fully deprecated our REST API, but we’re more than halfway through converting our clients if you look at request volume (we’ve added the entities that support the most popular pages), and maybe a little less than halfway there by entity count.

我们如何做到

由于这是我们全新的技术堆栈和我们的存储库(节点,Apollo Server,Docker2),我们需要弄清楚计划验证其疗效而不会扰乱生产。我们的进程是:

  1. 选择合适的页面来转换
  2. 建立架构
  3. 添加影子请求,以通过REST API仍然获取数据时调用新API
  4. 使用更改数据源的真实用户进行A / B测试

我们于2019年1月开始启动该项目,发布了1月28日的影子查询,于3月13日开始我们的A / B测试,并于4月30日完全发布。所以在只有4个“简单”的步骤中,您也可以在“只有”4个月内有一个图表!

所以让我们挖到每一步。

1.选择合适的页面来转换

我们决定制作Okcupid对话页面我们的测试床188bet金宝搏官网。在此页面上,用户可以看到他们拥有的正在进行的对话的列表,以及“相互匹配”列表(与他们可以启动新对话的人):

当时OKCupid对话页面的屏幕截图188bet金宝搏官网,具有与您在顶部匹配的人的水平列表和它下面的垂直消息列表
转换时的对话页面

选择一个页面非常重要,这将让您绘制您网站的某些核心部分;这将帮助您解决约定,肉体的重要部分数据模型,为未来的工作创造更好的基础,只是做出更好的概念证明。页面越“真实”,如果新的API正在上班,就可以帮助您了解。

我们选择了对话页面,这使我们考虑了如何代表:

  • 用户:有关用户帐户的基本信息
  • 比赛:有关两个用户如何彼此相关的状态信息(例如,匹配百分比,如果一个人喜欢对方等)
  • 会话:基本对话信息(例如,发件人,上一条消息的片段,发送时间)

它也思考了一些可重复使用的API概念,如分页。

2.构建您的架构

对于大量的团队首次进行架构设计,这可能是一个具有挑战性的步骤 - 这是对我来说!一些技巧:

  • 做研究。关于模式有很多伟大的写作,来自GraphQL文档中的基本示例, 至GitHub.喊叫的公共API,到继电器的文档。在这里的阿波罗团队大声喊叫;我们在这个阶段得到了很大的帮助。
  • 不要担心REST API如何格式化其数据。更好地设计你的模式更加富有表现力和惯用,而是感到受到你以前的API返回的内容的限制。
  • 始终如一。我们以前的API主要是Snake_case.,但有几个丑陋的词语(例如,用户身份显示名称)。这是您让您的字段名称更标准和可读的机会,所以请接受!
  • 请明确点。您可以更准确地命名图中的字段,如果需要进行断开更改,则更容易迁移到新字段。例如,user.essayswithdefaults.比这更好user.essays.
  • 参加您的研究,并制作适合您团队的东西。例如,当调查分页标准时,我被诱惑使用中继规范,但发现它依赖于术语边缘S和节点比我们想要在我们的图表中揭露客户的更像临床(我们改变了返回列表数据3.)。

3.添加影子请求

在拥有GraphQL向真实用户提供数据之前,我们在使用Shadow请求中测试了我们的系统:在我们的目标页面上,用户从REST API请求其数据,然后在显示REAS数据后与GraphQL相同(丢弃欺骗数据)。这让我们比较了两个API的性能并在用户找到它们之前解决问题。

我们当然不是第一个想到这一点的人,但这对我们来说是一个非常重要的一步。我们这个API的第一次草稿几乎两次时间其余的API,这显然不冷却。释放影子请求允许我们在不影响网站上的真实用户体验的情况下进行分类。

很快就会回到有关出错的技术方面的帖子以及我们如何让GraphQL达到速度平价。

4.运行实验

最后一步是用真正的用户测试对阵旧的新API!由于我们已经验证了响应时间与阴影请求类似,因此我们感到自信地发布A / B测试。

实验您期待的看到一个改变很棘手,因为你试图证明没有任何事情发生。所以在这样的实验中,除非有问题,否则您跟踪的统计数据将永远不会达到意义。

因此,而不是寻找统计数据的重大变化,您应该为您的实验设置持续时间;一旦你达到了这个持续时间并且仍然没有显着变化,就可以充满信心地发射。对我们来说,这是一个月的运行(每组超过100k用户)。而......它的工作!

什么可能更好

没有第一个草案是完美的(也没有任何第二个草案,至少为我)。虽然释放API的过程进展顺利,但在我们发布后有一些技术人员。

误差处理

我们在GraphQL突变中返回错误的情况下我们没有任何结构,并且在我们意识到存在问题时,我们有一种强大的方式,我们向客户展示了错误。似乎真正有趣的解决方案是标准化错误我们可以在给定的突变有效载荷中扩展的类型。这个中期帖子有一个非常深入的写作良好的错误样式

业务逻辑应该去哪里?

遇到涉及业务规则的产品功能时,它可能很诱人地添加到API层,特别是如果您否则依赖另一个团队来实现它。

例如,我们构建了一个显示一个喜欢和邮件您的每个人的列表。我们向付费用户展示整个列表,但对于免费用户,我们只显示第一个,然后显示一系列占位符。我们的第一个发布此功能具有检查用户付费状态的逻辑,并将卡与API层中的占位符替换。

After working with the graph for a while now, we’ve realized that the business logic works best when centralized in the back-end, and that the role of our graph is to fetch, format, and present the back-end’s data in a way that makes sense to clients.

那是,你们都是

总的来说,我们的过程非常好;它使我们能够快速恢复生产以验证我们的技术决策,在他们到达用户之前修复错误,并测试我们对以前API的更改。

如果您决定采取类似的旅程,我们希望这条路线图有用。祝好运!


谢谢凯瑟琳埃里克森Raymond Sohn.以及okcupid 188bet金宝搏官网web团队阅读本文的草稿。

脚注



1.对于我们来说,它逐渐达到了:客户端与我们的数据交互更加富有表现力的方式,一种更加表现的方法可以检索具有更少的网络请求的数据,我们的客户在图表中创建新功能的更灵活性,无需API更改。建立一点,并作为API的社区标准迅速采用的技术。




2.这是一个绿地项目,内置于新的存储库中,并与我们的后端和客户端代码库分开部署。它在节点中运行,使用Apollo Server和Express。我们的数据由对初始版本的REST API进行调用提供,但我们自从移动以直接使用GRPC调用我们的后端。

使用Docker部署API:我们使用CI构建Docker映像,并使用Docker Swarm将这些图像释放到我们的Web服务器中。一个巨大的,真正巨大的呼喊走向惠格,在我们的ops团队上划合of我们的ops ove coker swarm和一个发射脚本与它互动,以及大量的码头经验和支持!也是情感支持。

我们在所有平台(桌面/移动Web,iOS和Android)上使用Apollo客户端,并与Apollo Studio集成,以便使用其操作注册表来安188博金宝电子体育频道全,并跟踪速度和现场使用统计信息。




3.边缘S和节点S对我们感觉不对,但寻呼光标的继电器描述是漂亮的。因此,我们使用物品的数据阵列以及中继启发PageInfo.实体:

“”在描述分页数据页面时使用的常用格式。“”“类型”页面{“”“”“”获取上一页结果的键,如果可用。“”之前:“”之前:字符串“”“关键获取下一页结果,如果可用。“”“”“之后:字符串”“”“布尔表示,表示更多的结果。”“”“”hasmore:boolean!“”“可用的结果总数。”“”总计:int!“”“一个界面,以确保登记结果有关于当前页面的信息。”“”“接口PageResult {PapeInfo:PageInfo!“”“用户对话的名单。”“”类型对话连接实现PageResult {Data:[对话]!PageInfo:PageInfo!扩展类型用户{“”“此用户对话的列表。”“”对话(限制:int = 20之前:string之后:字符串:字符串):对话连接!}