@query 选择个别字段返回什么类型_所有关于GraphQL抽象类型

以及为什么Union类型远非白银子弹

这篇文章是Production Ready GraphQL的摘录。 这是我正在为1.3版中的架构设计一章进行的更新的一部分。 我决定也公开发布它,因为抽象类型的这一方面讨论不多,而且接口和联合之间的细微差别并不总是容易掌握。 希望你喜欢!

GraphQL Schema设计器的工具箱中有两种不同的抽象类型可供选择:接口和联合。 虽然它们都使我们有可能在单个字段中返回不同的具体类型,但它们的工作方式却大不相同。

接口类型都是关于合同的。 对象类型可以实现接口,这是说该对象满足接口合同的一种方式。 以GitHub的GraphQL API中的Closable接口为例:

2576d5cc2b62a8814a64403ee39264da.png

界面很棒,因为在处理Closable时,客户不需要知道确切的类型。 他们可以简单地期望实现了Closable接口的类型来回答合同,在这种情况下,它具有一个closed和closedAt字段。 实际上,在接口上查询的查询如下所示:

77d730ce41e13e0ca551fd1a5b2b6623.png

因此,当您期望不同的具体类型共享相似的行为并且可以在它们之间找到共同的契约时,最好使用接口类型。 但是,当某个字段可以返回许多不同类型中的一种,而当您真正找不到它们之间的合同时,该怎么办呢? 这是联合类型出现的地方。

d094b73ed7dc4fde7f03aedfd1191469.png

联合类型在某种意义上是不同的,因为联合类型的一部分不了解该事实。 虽然实现接口的类型显式地将它们连接起来(通过SDL中的Implements关键字),但联合类型是特定字段的局部类型。 例如,在上面的示例中,BlogPost类型本身不知道它是SearchResult联合的一部分。

查询联合的方式与接口类似,但是它们之间的主要区别是:您不能选择任何公共字段,因为从技术上讲,联合中的可能类型没有任何共同的行为。 这意味着即使联合上的所有类型都共享字段,您也必须在每个具体类型片段上重复它们

38a98d9ac2fc5a859cfca4f81ea995d3.png

> Notice how even if createdAt is a field on all the types it still has to be queried at the three l

根据GraphQL规范,接口和联合类型都包括__typename字段。 这使客户端可以查询正在处理的具体类型。 如您所见,接口和联合非常相似。 它们是如此相似,以至于是否可以使用空接口删除它们可能是一个问题。

API演变

接口和联合之类的抽象类型可能对改进我们的模式很有帮助。 例如,使用一个称为actor的字段,它表示某个Event后面的实体。 如果最初系统中唯一的参与者是User,那么我们可能会这样设计模式:

ab9df828e0b7213d51d7f2c78421e04a.png

随着时间的流逝,当可能出现诸如机器人和组织之类的新角色类型时,此架构现在已被破坏。 一种抽象类型,例如Union,似乎可以将我们保存在这里:

214f82a9b8ee80bc7c20d03c271db4a9.png

没错:假设客户现在正在查询该实体联合字段:

5b50cb968b9114fb630eacd9350d9caf.png

如果我们要在联合中添加第4种可能的类型,则上述查询在技术上仍然有效(以GraphQL术语表示)。 但是,如果需要处理第四种类型或条件以使客户端逻辑有效,则可能会破坏该客户端。 例如,如果这是围绕交易的银行业务逻辑怎么办?

5e834b62af26dd8aef653e035a3567bd.png

客户端在这里查看一个事务,当它第一次实现它时,它可能是授权或收费。 之后,服务器添加了第三种可能性,即Refund。 对于处理货币的应用程序来说,这是一个如此重要的逻辑更改,即使上面的查询仍然有效,它也会以错误的方式计算事物,因为它忽略了退款! 这就是为什么使用并集类型不一定比其他任何事情都对进化有更大帮助的原因,尤其是在我们期望新的案例或可能的类型会改变客户行为的情况下。

例如,在某些编程语言中,典型的联合类型可以使API的使用者对联合中的类型进行详尽的大小写检查。 这意味着,例如,如果您未显式处理所有联合成员,则switch语句将无法编译。 这将提供如此出色的开发人员体验,因为它绝不会让我们犯错,例如忘记处理新的工会会员。 不幸的是,当涉及到网络API时,我们常常负担不起。 尤其是随着GraphQL倾向于采用连续演化方法,优雅地添加新的枚举值,联合成员和接口实现的方法,通常非常困难。 请注意,如果我们对架构进行版本控制并在每次添加新案例时都剪切新版本,则不一定是这种情况,但这有其自身的缺点。

就是说,除非您使用细粒度的版本对GraphQL模式进行版本控制,否则联合类型的功能远不如某些编程语言(例如OCaml)中的联合类型强大,联合语言具有可进行这些检查的强大类型系统。

接口也遭受类似的痛苦,但是至少我们可以依靠它们定义的契约。 客户可能无法处理新的具体类型,但是如果它们主要依赖于公共合同,则与工会不同,客户更有可能发展良好。

Porque no los dos

在某些情况下,我们可以利用并集和接口的功能。 错误是其中之一。 在GraphQL中表示错误状态的常见方法是使用联合类型:

eddd1fdaa05ae5d8eb57f59128700330.png

但是,正如我们前面介绍的那样,这种设计确实使客户端很难处理新的错误类型。 不过,这里的联合仍然很有用,它使客户可以很快发现所有可能的错误。 另一件事是,设计不会让服务器返回多个错误(这不一定很糟糕,但值得注意)。 我们可以四处走动,并取得潜在的未来证明:

55326890eb9c489670fdcc244910acb4.png

不错,但是我们失去了快速判断在那里可能发生哪种错误的能力。 许多事情都可能实现Error,但这并不意味着它们可能会出现在createPost突变中。 解决该问题的一种方法可能是为每个突变创建一个特定的错误接口,但奇怪的是,感觉像工会应该做什么。

361d919f71cc2ebb6a5b4aca04834dd4.png

有点时髦吧? 现在,我们有了一个联合,可以清楚地传达可能发生的错误,但是这些错误也实现了Error接口。 这使我们可以在"错误"片段下查询以选择公共字段,即使是在返回联合的字段下也可以。 即使字段没有直接返回接口,我们也始终可以选择接口!

请注意,我也不一定不喜欢纯界面解决方案。 但是由于缺乏错误之间的契约,仅工会的方法对我而言并不那么强大。 在最后一个示例中,我们可能要确保添加到结果联合中的所有新错误都实现Error。 我们可以使用https://github.com/cjoudrey/graphql-schema-linter之类的方法来实现。

因此……一般而言,应如何将抽象类型合并到架构中? 我认为我们可以在此处遵循一些最佳实践,这些最佳实践可能会引导我们朝着一种API的方向发展,该API将以更好的方式发展,并最终也变得更加可用。

只要类型共享行为/字段,接口优先于联合

如您所见,接口比联合类型更有趣,尤其是当您期望具体类型的集合会随着时间而变化时,接口的发展更加有趣。 在可能的情况下,随着时间的流逝,使用接口可能是最佳选择。 使用具有接口支持的可能类型的联合可能也是一个不错的选择。

优先选择小型,集中的接口而不是超级通用接口

我们的界面越具体,当添加新的具体类型时,客户就越了解要寻找的内容。 通常,选择在现场环境中最有意义的接口可能是一个更好的选择。

21f70daadcfeddba077177ca17b267c3.png

在这种情况下,它使用户可以在查询时选择"赞助者"特定字段。 请注意,当需要更多通用字段时,它们也可以在Actor上使用…。 但是他们可以放心,无论出现哪种新的发起人,totalDonations和SponsorShips都将始终存在。 假设出现了与Actor完全不同的事物,则只需实现这两个字段即可,而不必成为完整的Actor。

突变呢?

输入联合即将到来! 这将允许许多不同的情况,例如更好的批处理突变。 不过,目前很难表达接口和联合类型与突变输入之间的关系。 在典型的编程语言中,我们可能希望函数接收特定的接口或联合类型作为输入。 在GraphQL中,我们还没有这种机制。

我们可以做的一件事是,至少要对突变处理的类型进行注释。 这在接受ID的突变中最常见

792e639cdc522c82d9cd39a4f856b71b.png

如果我们使用指令让我们的模式开发人员和客户端开发人员都接受这里的输入怎么办?

c6671a9695abcf3a937534e72e3169c0.png

这给了我们两个非常酷的东西:

· 我们的文档/参考现在可以使用此文档来记录突变可以接受的类型。

· 当新类型实现可关闭时,我们可以通过linter / CI检查来验证closeClosable是否已使用可以关闭该新对象的代码进行了更新。 然后可以更新@possibleTypes指令。

我们可以以某种方式改善GraphQL的发展吗?

我希望API提供程序可以帮助客户优雅地处理新的具体类型。 如果客户可以提前选择具体类型,那就太好了。

a69fb58b51474e4d73b66c0884679363.png

当然,这并不是一个简单的更改,因为今天这是一个无效的查询☝️也许是联合和接口(类似于已弃用)的特殊指令:

@upcomingChange(change: ConcreteTypeAddition, type: "Discussion")

结论

为了结束这篇文章,让我们在这一切上花些许细微差别。 从应用程序会因添加新类型而被破坏的意义上说,并非所有接口或联合都至关重要。 (但是肯定会出现错误。我会想到这些错误)抽象类型仍然是一种非常强大的工具,可以用于更具表现力的架构,并且可以更轻松地适应变化。 仍然,期望联合类型像某些编程语言一样强大的开发人员可能会想在GraphQL API(用于Web上下文)中使用它们太多。

最后一件事:最近,由于新的RFC允许接口实现其他接口,接口类型变得更具表现力,您可以在此处查看https://dev.to/mikemarcacci/intermediate-interfaces-generic-utility-types 在图ql-50e8

下次设计GraphQL模式时请记住这一点!

(本文翻译自Marc-André Giroux的文章《All About GraphQL Abstract Types》,参考:https://medium.com/@__xuorig__/all-about-graphql-abstract-types-2da8f18e11a0)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值