GraphQL指令被低估了

2020年12月30日11:17:59 发表评论 34 次浏览

本文概述

互联网上糟糕透顶的文章GraphQL除了其他API, 即REST。他们都因其强大的类型系统, 出色的开发人员经验, 一流的工具以及在架构上快速迭代的能力而吹捧它, 而这一切都不会导致获取不足或获取过多的情况。这是都是真的.

但是指令呢?令人惊讶的是, 关于它们的文章很少。它们在最流行的GraphQL服务器中的实现并非易事, 这可以解释为什么经常忽略指令的原因。

根据Apollo文档:

本文档的大部分内容与实现模式指令有关, 某些示例可能看起来非常复杂。无论你拥有多少工具和最佳实践, 都很难以可靠, 可重用的方式实现非平凡的模式指令。详尽的测试是必不可少的, 并且建议使用诸如TypeScript之类的类型化语言, 因为有太多不同的架构类型需要考虑。

它也可能与某些流行的GraphQL API中指令的有限可用性有关。例如, 两个都不GitHub的也不Shopify的公共模式提供单个指令。对于开发人员而言, 没有很多好的例子可以效仿。

指令通常并不难实施;如果是特定的GraphQL服务器, 则可能是因为其架构未针对指令进行优先级设置。某些GraphQL服务器比其他服务器更好地支持指令。

因此, 指令并非总是包含在GraphQL与REST的讨论中是有道理的, 但是很遗憾, 因为它们是GraphQL最强大的功能之一。在本教程中, 我们将通过解释指令如何增强和授权GraphQL API来填补空白。

什么是指令?

简而言之, 指令是一种功能, 可增强其他功能。它是使用@符号。例如, 在下面的查询中, @大写指令适用于标题字段, 将其结果转换为大写。

query {
  post(id: 1) {
    title
    ucTitle: title @upperCase
  }
}

运行查询产生以下内容。

{
  "data": {
    "post": {
      "title": "Hello world!", "ucTitle": "HELLO WORLD!"
    }
  }
}

你可以想到诸如面向方面的编程(AOP)之类的指令, 因为它们可用于跨其他功能。例如, 除了应用于发布‘s标题领域@大写上面查询中的指令可以应用于任何类型的字段String, 如那个用户‘s名称, 如图所示这个查询.

就AOP而言, 指令的最佳用例是跨领域的指令, 其中的功能是通用的, 可以在应用程序的不同部分上重复使用。这些包括:

我们为制作了一个自定义演示.
不完全是。点击这里查看.

GraphQL指令被低估了1
  • 分析/记录/指标
  • 授权书
  • 快取
  • 配置默认值
  • 输出操纵
  • 验证方式

Prisma的软件可以通过中间件在解析器本身中执行这些跨领域功能graphql-中间件包。但是, 我认为指令更合适。

并非所有指令均相等

目前尚不清楚GraphQL文档, 但有两种类型的指令:

  1. 在SDL中定义并在构建架构时执行的架构类型指令
  2. 查询类型的伪指令, 出现在查询中并在解析查询时执行

我们可以通过GraphQL规范中的三个保留指令来可视化它们的差异:@跳跃, @包括和@已弃用。这些伪指令属于以下类型。

  • @已弃用是一种架构类型指令, 用于将字段定义为在架构定义中已弃用
  • @跳跃和@包括是查询类型的指令, 用于决定是否在运行时执行查询的一部分

但是, 规范并未将其称为查询或架构类型的指令。相反, 它使用指令位置定义指令可以出现的位置。

有两组指令位置:可执行指令位置(即查询类型)和类型系统指令位置(即架构类型)。然后, @跳跃和@包括指令只能添加到领域, FRAGMENT_SPREAD, 要么INLINE_FRAGMENT位置(即查询中的字段或片段), 以及@已弃用可以添加到FIELD_DEFINITIONorENUM_VALUE位置(即, 模式中的字段或枚举的定义)。

这两种类型的指令还为最终用户提供了不同级别的可见性。模式类型指令是私有的。 GraphQL架构的管理员/设计者/所有者使用它们来配置架构, 并且不向正在针对GraphQL端点执行查询的最终用户公开。最终用户为什么需要包括@已弃用指令在运行查询时?另一方面, 查询类型的指令是公共的。提供给最终用户以增强他们自己的自定义查询。

之前我曾提到GitHub和Shopify不提供指令。这意味着它们的公共模式不会公开任何查询类型的指令, 但是它并没有说明模式类型的指令, 它们是私有的, 并且只能由自己的团队访问。他们为什么没有查询类型的指令?这可能是因为他们没有使用户受益的引人注目的用例。也有可能它们所需的任何功能也可以在客户端应用程序中实现, 而不是在API层中实现。例如, 一个网站可以轻松实施@大写JavaScript中的指令。

似乎模式类型指令比查询类型指令更有用。此外, 考虑到指令的上述用例, 只有一个用例(输出操作)属于查询类型领域。所有其他(分析/日志记录/指标, 授权, 缓存, 配置默认值, 验证)均为架构类型。

也就是说, 模式和查询类型指令都非常有用。

指令为什么重要?

让我们开始探讨问题的症结所在, 我相信指令是一个很棒的功能(而且在很大程度上未被赞赏)的原因:它们不受监管。除了描述指令的语法外, 该规范并没有过多地说明指令, 它使每个GraphQL服务器实施者都可以自由控制自己的架构, 并决定它们可以支持的功能以及功能的强大程度。

实际上, 指令对于GraphQL服务器实现者和最终用户都是一个游乐场。 GraphQL服务器实现者可以开发规范当前不支持的功能, 而用户可以开发GraphQL服务器尚未实现的功能。

指令的不受管制的性质是功能, 而不是错误。为了说明指令的来源和方向, 让我们暂停一下, 以快速学习历史。

辩论指令精神

最初引入指令是为了支持@已弃用指示。查看这个问题登录到GitHub, 例如。

维护者tgriesser在2019年11月4日写道:

[…]从未打算将SDL用作构造模式的手段, 并且@deprecated指令仅出现在SDL中, 因为它也可以在自省查询中表示。[…]手写指令存在于以下事实中:首先编写SDL时的AST仅是因为它是有效的语法这一事实的结果, 但不一定要以这种方式使用。

在Facebook上设计GraphQL的团队没有看到从那里发展的必要性(大约在2015年), 甚至没有考虑完全删除指令。作为GraphQL的共同创始人Lee Byron, 已评论那时:

[…]我们实际上正在考虑进行一项更改, 以完全删除架构中的指令。

然后, 不久之后:

需要明确的是, 伪指令并不是由graphql的用户设计的, 而是由graphql核心本身的未来版本扩展的。这就是我们考虑将其从架构中删除的原因。这太令人困惑了, 因为graphql的用户定义并产生了架构, 而指令可以从同一部分代码中访问, 这与其他指令看起来并不一样。

指令从未被删除, 并且GraphQL生态系统中的不同参与者使用它们来扩展自己的功能, 成为其解决方案的基本要素。随着指令的使用变得越来越广泛, 越来越多的要求将指令授予更为重要和标准化的角色。看到这个评论来自2018:

[…]随着GraphQL生态系统的发展, 我认为我们应该考虑与第三方库的互操作性。我正在考虑的示例是Apollo缓存控制和Prisma, 它们使用指令提供一些元数据来使出色的底层功能正常工作。

除了用于以下方面的特殊用法外, 无意将指令自由地纳入规范中@跳跃, @包括和@已弃用。李·拜伦回覆对以上评论:

指令纯粹是GraphQL语言的功能, 而不是类型定义的一部分–这是因为不同的工具将以不同的方式使用和解释指令。

拜伦(Byron)表示, 他并未就此事改变立场。他更好地解释了这一点来自2017的评论:

目前, 指令纯粹是GraphQL语言和IDL ["接口定义语言", SDL的另一种名称)的功能, 因此, 未使用IDL创建的架构在定义上将不包含指令。在代码中构建模式时, 你拥有主机编程环境的全部功能, 因此不需要使用指令。伪指令是一种工具, 用于提供其他意图, 以便稍后由主机编程环境进行解释。

一方面, 李·拜伦(Lee Byron)希望仅将指令保留为至少要使用的一种语言构造。同时, 部分社区希望指令在执行查询时发挥更大的作用。规范的维护者Ivan Goncharov以及graphql-js, 是GraphQL for JavaScript的参考实现, 解释了对比在这两个位置之间:

一方面, 我们希望类型系统尽可能严格, 以使其易于使用(在中间件或任何其他工具中)。另一方面, 我们希望指令尽可能灵活, 以便第三方开发人员可以尝试新语法而无需派生GraphQL解析器和创建新的GraphQL方言。

此注释还解释了Lee Byron将指令描述为"一种提供附加意图以供主机编程环境稍后解释的工具"时的含义。如果你一开始不了解它, 请不要担心-你并不孤单。

每个GraphQL服务器供应商仍然完整地控制指令以支持他们自己的解决方案。

通过指令推进GraphQL

指令履行了GraphQL的基本角色。尽管没有将它们随意添加到规范中, 但指令仍然可以帮助改进GraphQL。因为指令是不受管制的, 所以实现者可以根据需要尝试使用它们来解决给定的问题。如果所产生的功能变得不可缺少, 并且可以使GraphQL整体受益, 则可以考虑将其添加到规格中。

指令就像一个游乐场, 可以帮助发展GraphQL。通过这种方式, 指令可以使GraphQL标准化后的未来一瞥。

要简要了解指令如何在不久的将来改进GraphQL, 请查看标题为"GraphQL的未来展望。"其中, 在Facebook进行GraphQL工作的罗伯特·朱(Robert Zhu)提供了有关社区当前正在处理哪些指令的精彩摘要。

他特别提到以下内容。

  • @specifiedBy将可选的URI附加到自定义标量定义, 该标量定义指向保存了标量额外信息(数据格式, 序列化和强制规则)的文档。目前在草稿阶段
  • @defer和@流更快地呈现查询的各个部分, 从而无需等待更慢的字段进行计算(@defer), 并开始呈现集合中的前几个条目, 而无需等待整个集合(@流)。这些目前在提案阶段

鉴于@specifiedBy解决了服务器相互通信时的不兼容问题, @defer和@流有望改善应用程序性能, 并提供初始响应, 以便用户可以立即消耗掉。

的@defer指令最初是由Apollo服务器实现的。这是一个GraphQL服务器的示例, 该示例实现了规范中所缺少的功能, 最终变得如此有用, 以至于为规范提出了这一功能。

然后, 我们有了GraphQL服务器所缺少的功能, 这些功能由用户通过自定义指令实现。例如, [RFC]平链语法问题建议合并嵌套对象的结果, 如下所示:

{
  clients(limit: 5) {
    id
    programs: programs.shortName
  }
}

这将产生以下响应。

{
  "data": {
    "clients": [
      {
        "id": 2, "programs": ["ABC", "DEF"]
      }, {
        "id": 3, "programs": ["ABC", "XYZ"]
      }
    ]
  }
}

服务器仍不支持此行为, 它是graphql-lodash库, 提供指令@_塑造查询的响应。

graphql-lodash库

该指令只是临时解决方案, 直到提案在流程的最后阶段完成(目前位于稻草人舞台)并添加到规范中, 届时所有GraphQL服务器都必须满足新语法。但是, 即使那天从未到来, 用户也不必等待使用该功能。

在我自己的项目中, 我实施了一个@出口指示为了我的PHP中的GraphQL服务器, 它将字段(或字段集)的值导出到变量中, 以在查询中的其他地方使用。此功能是在[RFC]动态变量声明问题, 也正处于稻草人阶段。

当GraphQL服务器需要的功能不是规范的一部分时, 指令将填补空白。当你需要GraphQL服务器不提供的功能时, 自定义指令会填补空白。

购买GraphQL服务器

你如何在众多可用的GraphQL服务器之间进行选择?假设你希望API长期处于积极开发中, 请首先询问以下内容:

  • 它支持创建自定义指令吗?
  • 自定义指令的功能有多强大?

为自定义指令提供强大支持的服务器将引领GraphQL的未来。他们处于适当的位置, 可以在其他所有人之前实现新颖的功能, 并成为其他人模仿的参考实现。对自定义指令的支持越好, 服务器更新和与最新规范兼容的可能性就越大。

相反, 将指令作为事后考虑的服务器可能会产生API, 这些API停滞不前, 并且升级难度越来越大, 这使它们成为长期投资不佳的对象。

指令不是万能的

指令并不能解决所有问题。使用它们, 但不要滥用它们。

如前所述, 指令对于扩展GraphQL功能非常有用, 但它们并不是每种功能的最合适工具。现场解析器可以更好地解决某些任务。对于其他人, 指令根本不能提供最佳解决方案。

让我们研究一下指令方面遇到的一些问题。这些例子表明, 为什么指示并不像最初看起来的那样是瑞士军刀-至少现在还不是。

定义结果类型或特定字段的指令

我制作了一个@cdn指示为了自己GraphQL服务器在PHP中, 它可以转换网址(自定义标量)类型, 方法是将其域替换为CDN中的域。

query {
  user(id: 1) {
    url
    cdn: url @cdn(from: "https://newapi.getpop.org", to: "https://nextapi.getpop.org")
  }
}

运行此查询产生以下内容。

{
  "data": {
    "user": {
      "url": "https://newapi.getpop.org/author/leo/", "cdn": "https://nextapi.getpop.org/author/leo/"
    }
  }
}

仅当字段为类型时, 此指令才有意义网址, 在架构中只有少数几个字段:网址领域用户和发布和src来自媒体类型。

理想情况下, 我想在架构中定义@cdn指令仅在字段为类型时才可用网址或将其应用于白名单字段列表时。不幸的是, 我所能做的就是将其指令位置定义为领域orFIELD_DEFINITION, 但不是哪个字段或由谁定义。

我已经看到一些解决此问题的方法。例如, 阿波罗建议了以下解决方案@上指示.

class UpperCaseDirective extends SchemaDirectiveVisitor {
  visitFieldDefinition(field) {
    const { resolve = defaultFieldResolver } = field;
    field.resolve = async function (...args) {
      const result = await resolve.apply(this, args);
      if (typeof result === "string") {
        return result.toUpperCase();
      }
      return result;
    };
  }
}

自从@上指令只能应用于Strings, 阿波罗提议验证if(typeof result ===" string"){在应用逻辑之前。这让我发抖。如果你仅接受每种类型并使用一些自定义逻辑对其进行验证, 并准备在将来产生一些错误, 那么GraphQL的强类型系统有什么意义?

在这种情况下, 如果指令可以在其指令位置接收细粒度的信息, 则GraphQL会更好。

代码优先的解决方案成为二等公民

在一个上一篇文章, 我描述了创建GraphQL服务的模式优先(SDL优先)和代码优先的方法之间的区别。主要区别在于, 代码优先方法不依赖SDL创建架构。

由于模式类型指令是SDL的构造, 因此, 如果没有SDL, 就不会有这样的指令, 并且GraphQL服务器会错过大量功能。就是这样关系, 例如。因为它是代码优先的解决方案, 所以它可以找到自己无法实现对阿波罗联盟的支持, 它依赖于SDL上定义的一些自定义指令。

将输入传递给模式类型指令可能是不明确的

这可能是例外情况, 但很高兴知道你在项目中遇到过这种情况。

的Apollo文档解释了如何实施@日期指令, 它接收其可选格式输入来自它修改的字段的字段参数, 今天.

graphql(schema, `query {
  today(format: "d mmm yyyy")
}`).then(result => {
  // Logs with the requested "d mmm yyyy" format:
  console.log(result.data.today);
});

问题是你可以将多个指令应用于同一字段。这些指令可能是由不同的供应商创建的, 这意味着你无法控制它们, 并且可能期望使用相同名称的参数。

例如, 一个src来自的字段图片类型可以有指令@darkenColors和@调整大小应用。这将相应地修改图像, 将其保存在云中, 并返回新图像的URL。如果两个指令都接收一个参数百分比(第一个指示要应用于图像的变暗量, 第二个指示指定如何调整图像大小), 然后查询src(百分比:" 50%")领域是模棱两可的。是否" 50%"适用于变暗或调整大小?

指令修改类型

按照上面的示例, 今天字段返回一个日期, 并将此类型转换为String当应用@格式指示。这种行为令人困惑, 并且与强类型化的想法背道而驰。

我提出了一个解决此问题的方法:可组合字段允许字段充当其他字段的输入。这样, 如果格式字段是String输入并接收日期类型, 它可以组成一个今天类型字段日期喜欢格式(今天为" d mmm yyyy"), 类似于这个查询, 使用修改后的语法。但是, 我的提案不太可能获得批准, 因为它引入了一种解析字段的新方法, 并且破坏了变更极度灰心.

总结

指令是GraphQL最有用, 最有趣的功能之一, 但是通过阅读有关GraphQL的特殊之处的绝大多数博客文章和文章, 你不会知道它。指令被低估并且功能强大。

GraphQL服务器对指令的支持有多强?它是否允许你执行梦dream以求的所有功能?那么非常基本的功能呢?实施起来困难还是容易?

每个人都可以使用相同的工具(例如图形语言), 以改善开发人员的体验并提高生产率。每台GraphQL服务器都支持准确获取所需的数据, 而不会导致数据获取不足或获取过多。但是并非每个GraphQL服务器都为自定义指令提供相同的支持。最终, 这可能是将可以经受时间考验的GraphQL服务与最终会停滞并必须从头开始重新构想的GraphQL服务分开的最重要因素。

简而言之, 不得过分考虑指令。因此, 在购买GraphQL服务器时, 请在评估其他功能之前评估它们对自定义指令的支持。

继续阅读

本文是有关概念化, 设计和实现GraphQL服务器的系列文章的一部分。该系列的前几篇文章是:

  1. 设计GraphQL服务器以获得最佳性能
  2. 简化GraphQL数据模型
  3. GraphQL中的代码优先与模式优先开发
  4. 加快对GraphQL模式的更改
  5. GraphQL中的版本控制字段

监视生产中失败的和缓慢的GraphQL请求

尽管GraphQL具有一些调试请求和响应的功能, 但要确保GraphQL能够可靠地为你的生产应用程序提供资源, 将使事情变得更加棘手。如果你有兴趣确保对后端或第三方服务的网络请求成功完成,

尝试notlogy

.

GraphQL指令被低估了2
LogRocket仪表板免费试用横幅

https://notlogy.com/signup/

日志火箭就像Web应用程序的DVR一样, 实际上记录了你网站上发生的一切。你可以猜测问题的GraphQL请求并进行汇总, 以快速了解根本原因, 而不必猜测为什么会出现问题。此外, 你可以跟踪Apollo客户端状态并检查GraphQL查询的键/值对。

notlogy用你的应用程序记录基线性能计时, 例如页面加载时间, 到第一个字节的时间, 缓慢的网络请求, 并记录Redux, NgRx和Vuex的操作/状态。

免费开始监控

.

一盏木

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: