GraphQL两年实战避坑经验

2020 年 7 月 18 日

GraphQL两年实战避坑经验

本文作者分享了在生产环境中使用GraphQL的一些经验和解决方法,并给出了一些构建实用GraphQL查询和变更(Mutation)的建议。


本文最初发布于Medium,经原作者 Stein Janssen 授权由 InfoQ 中文站翻译并分享。


GraphQL已得到广泛认可并日益流行。我们在使用中遇到了一些非常有挑战性的问题,值得撰文分享。本文将使用一个示例配置来阐释问题,并给出相应的解决方法。



本文作者使用 GraphQL Voyager 生成的关系概览图


首先谈谈我们为什么会选择 GraphQL?


  • 无需操心如何更新文档,所有的查询(Query)和变更会自动形成文档。

  • 无需获取整个数据集,我们可以编写仅仅返回所请求数据的查询。

  • 对前端提供统一的访问点。从数十个不同API中获取数据并非易事。GraphQL支持开发人员将所有API进行拼接(Stitching)。


拼接(Stitching)


拼接(Stitching)让我们可以从同一端点获取所有数据。这听上去不错,但它也会导致一些非常棘手的问题。举例说明:



如上图所示,一个前端与 Public API 通信。Public API 拼接了 Order API,后者又拼接了 Product API。前端唯一能访问的是 Public API。看上去这种链式拼接方式并没有太大的问题,但是在面对数十层级的 API 拼接时,应用发布将成为一场灾难。


问题出在哪里?一旦 Product API 的 Schema 发生更改,那么需要依次重新启动 Order 和 Public API 才能重新加载 Schema。GraphQL Schema 每次更新时,都必须重新启动多个 API。这非常繁琐。


另一个可能出现的问题是,如果应用需要逆链反向查询,而非顺链而下查询,这时拼接无法工作。例如从 Product 访问 Order,由于 Order API 需要加载 Product 的 Schema,因此只有在 Product API 运行时才能启动 Order API。


这意味着,虽然可以获取属于给定 Order 的 Product:


order {    identifier    products {        identifier    }}
复制代码


但无法获取给定 Product 所在的 Order:


products {    identifier    order {        identifier    }}
复制代码


为解决这个问题,我们需要重新拼接 API。我们可以让 Public 负责加载所有的 Schema。这样只需重启该 API,即可加载所有 Schema。



鉴于现在 Public API 获取所有的 Schema,我们可以添加处理 order 属性的代码,扩展 Product 的 Schema。这样,我们就可以通过查询 Product 获取 Order 信息。扩展 Schema 的详细做法,参见Apollo文档


现在,我们并不需要通过 Public API 获得所有查询和变更的访问权。例如,我们并不想让客户能够通过触发变更去更改支付的状态。对此,一种解决方法是过滤掉特定查询和变更。具体而言,应用遍历 Schema 中所有的查询和变更,并与给定的列表做对比。如果查询存在于列表中,则设为可见。如果不在列表中,就从 Schema 中移除。另一个解决方法是添加中间件,由中间件检查当前用户是否有权限触发特定的查询和变更。


实践中,我们组合使用了上面两种方法。但现在我们面对一个新的问题。当前并非所有的变更都可通过 Public API 访问,因此在更新支付的状态时需要直接调用 Payment API。这对于变更不存在问题,但并不适用于所有的查询,因为父对象和子对象只是在 Public API 做拼接。为解决这个问题,我们需要再次重新编排配置,如下图所示:



这里,我们新建了一个 Gateway API,负责拼接所有 Schema。而 Public API 只拼接 Gateway API,并移除所有前端无需访问的查询和变更。这样,Gateway 可与后端服务部署在同一网络,后端在进行查询和变更时可直接使用 Gateway API。


查询分页(Paginated)


一些情况下,实现查询分页很有必要。我们采用了基于游标的方法,实践中很好用。需要获取 Product 时,可使用如下查询:


products(first: 5, after: "cursor") {    edges {        node {            identifier        }    }}
复制代码


但是,由于我们更改了 Schema,在获取 Product 对应的 Order 时会生成分页结构:


order {    products(first: 5, after: "cursor") {        edges {            node {                identifier            }        }    }}
复制代码


但是这里我们并不需要分页结构,因为给定 Order 的 Product 数量并不多。针对该问题,我们考虑分别编写两个查询,一个实现了分页,另一个则不考虑分页。另一个做法是针对拼接 Product 到 Order 的情况,使用 Schema 包装(Schema Wrapping)移除分页。Schema 包装是一个非常强大的方法,尤其是针对同一 API 种拼接了所有远程 Schema 的情况。详细信息,参见Schema包装的官方文档


一些建议


上面我们列举了部分主要问题。下面给出一些有助于构建可维护 GraphQL API 的小建议:


  • 类型和枚举的命名必须唯一。例如,如果需要对Product添加一个状态,建议命名为ProductStatus,而不要直接使用Status,以免出现类型冲突问题。

  • 建议在查询中添加过滤,以免额外单独编写查询。推荐一个很好的查询实现例子,访问页面右侧的“doc”选项卡,并搜索 assetFilter。

  • 对查询和变更定义自己的命名规则,以简化对查询和变更的查找。

  • 在使用查询分页时,设置默认值和最大上限。以免他人运行API时导致崩溃。

  • 推荐使用GraphQL Voyager,可生成对Schema所有查询、变更、关系的概览图。


原文链接: 2 Years of GraphQL in Production


2020 年 7 月 18 日 09:002431

评论

发布
暂无评论
发现更多内容

如何建立你自己的开发知识体系

iHTC

创业的使命是什么

Neco.W

创业 重新理解创业

去掉美颜和滤镜后的数字货币

亨利笔记

比特币 区块链 数字货币 DCEP libra

macOS 查看文件内容常用的命令小结

iHTC

UITableView 手势延迟导致subview无法完成两次绘制

AlienJunX

年轻人的世界

boyzcl

年轻人 系列

Hello World !

ATGU:阿宝哥

Java Hello World ! Info

关于「后浪」,ta 们在说什么?

boyzcl

年轻人 系列 后浪

程序员的macOS系列:精选Mac App

iHTC

我的编程之路-1(启蒙)

顿晓

编程 入门 启蒙 经历 故事

在InfoQ上开博的第一天

罗琦

开博 文章链接

信仰

小天同学

人生 个人成长 思考 读书感悟 信仰

开通InfoQ写作平台测试

ytl

Redis源码之常用数据结构和函数

心平气和

redis

新人工作的时候遇到问题怎么办

波波

学习 编程 职场 新人

关于沟通成本的一些认知

大鱼读书

项目管理 软件开发

程序员的macOS系列:高效Alfred进阶

iHTC

开张咯~

李绍俊

生活 随想

LeetCode 1396. Design Underground System

liu_liu

LeetCode

从每个Python文件快速得到项目根目录的绝对路径

良少

Python 路径 绿色

排序系列bogo排序

脚动两轮男之漂流小王子

程序员的macOS系列:Mac开发环境配置

iHTC

赚钱的6个层次

品牌运营|陆晓明

创业 技术人 赚钱思维 层次 商机

技术人赚钱的9个路线

品牌运营|陆晓明

副业 赚钱 技术人 码农 生财有术

系统的伸缩性以及扩展性设计

Janenesome

读书笔记 程序员 架构

[读书随笔]从哲学上的问题分类看TDD

老狗

哲学 TDD

能够实现的想法才有价值

伯薇

创业 创意 点子 商业价值

python面向对象的魔法方法详解

半面人

Python

详解iOS性能优化,安装包瘦身

Usama Bin Laden

ios 源码分析 性能优化 性能 原理

关于CodeReview的一些思考

Yezhiwei

在今天种下一棵树

陈医僧Ethan

感悟 育儿

GraphQL两年实战避坑经验-InfoQ