为GraphQL设计基于URL的查询语法

2021-06-18 14:43:31

目前,如果我们想在GraphQL中使用HTTP缓存,我们必须使用支持持久查询的GraphQL服务器。这是因为持久的查询已经拥有存储在服务器中的GraphQL查询;因此,我们不需要在我们的请求中提供此信息。

为了使GraphQL服务器还通过单个端点支持HTTP缓存,必须将GraphQL查询作为URL Param提供。 HTTP规范的GraphQL希望实现这一目标,为所有GraphQL客户端,服务器和库提供标准化语言来互相交互。

但是,我怀疑,所有尝试通过URL Param传递GraphQL查询将远非理想。这是因为必须提供URL Param作为单行值,因此查询需要编码或重新格式化,从而难以理解(对于我们的人类而非机器而言,难以理解

例如,这是GraphQL查询在用空格中替换所有线路时的查询如何使其适合单行:

{帖子(限制:5){id标题@titlecase excerpt @default(值:"没有标题"条件:is_empty)作者{name}标签{id name}评论(限制:3,订单:& #34;日期| DESC"){ID日期(格式:" d / m / y")作者{name} content}}}

这就是GraphIQL客户端如何将简单的查询{POST {id标题}}作为URL Param:

这两个例子都Evince问题:单行GraphQL查询可以从技术角度来看,将信息传输到服务器,但人们才能读写这些查询并不容易。

能够使用单行查询进行操作将具有许多好处。例如,我们可以直接在浏览器的地址栏中撰写查询,执行对某些GraphQL客户端的需求。

不是我不喜欢GraphQL客户端 - 事实上,我喜欢GraphIQL。但我不喜欢我依赖他们的想法。

在本文中,我将介绍一个替代语法,支持美国人类“易于阅读和写入”。

我并没有真正提出将此语法介绍给GraphQL - 我明白永远不会发生。但是,此语法的设计过程可以介于在HTTP规范上设计GraphQL时必须注意的内容。

让我们首先探索GraphQL语法的问题,然后将其概括为其他语法。

正如我所看到的,难度来自嵌套的GraphQL查询中的字段,其中嵌套可以在整个查询中提前和退出。这是这种进入的行为,使得在单线写入时难以掌握。

如果查询中的嵌套只有前进,那么了解它并不难。例如,拍摄此查询:

{帖子{id标题摘录评论{id日期内容作者{id name url posts {id title}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} 在查看始终前进查询时,并从左向右扫描它,我们仍然可以理解每个字段所属的实体: {帖子{id标题摘录评论{id日期内容作者{id name url posts {id title}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} 现在,考虑相同的GraphQL查询,但重新排列字段,以便在连接后出现叶子: {帖子{id注释{id日期作者{posts {id标题} Id name url} content} title excerpt}} {帖子{id注释{id日期作者{posts {id标题} Id name url} content} title excerpt}} 现在,了解查询并不是那么容易。 在撤退水平(即,连接后)之后,我们可能不记得在此之前出现了哪些实体,因此我们不会掌握该领域所属的地方:

(我猜这与人类大脑有限的短期记忆,能够一次不超过几个物品。)

当有许多级别的前进和背部时,它变得完全不可能完全掌握。此查询是可以理解的:

{帖子{id注释{id日期子女{id作者{name url}内容}作者{post {id标题标记{name} id name friends {id name} url} content} title excerpt}作者{name}}

{帖子{id注释{id日期子女{id作者{name url}内容}作者{post {id标题标记{name} id name friends {id name} url} content} title excerpt}作者{name}}

总之,GraphQL查询不能轻易地以单线表示,以这样的方式,我们可以使我们能够理解它,因为它的嵌套行为。

该问题并不特定于GraphQL。实际上,它会发生语法 - 任何语法 - 元素进入和退缩。

{"名称&#34 ;:" leoloso / pop&#34 ;,"描述&#34 ;:" pop monorepo&#34 ;,"存储库&#34 ;: [ {"类型&#34 ;:"""包&#34 ;: {"姓名&#34 ;:" leoloso-pop-api-wp /时事通讯 - 订阅 - 休息 - 端点&#34 ;,"版本&#34 ;:" master&#34 ;,"类型&#34 ;:" wordpress-plugin",& #34; source&#34 ;: {"网址&#34 ;:" https://gist.github.com/leoloso/6588f6c1bdcce82fc317052616d3dfb4" ;,"类型&#34 ;:&# 34; git&#34 ;,"参考文献&#34 ;:" master" }}}}}}}}}}}}}}} {"类型&#34 ;:"包和#34;"包&#34 ;: {"姓名&#34 ;:" Leoloso-pop- api-wp / disable-user-edite-profile""版本&#34 ;:" 0.1.1""类型&#34 ;:" wordpress-插件&#34 ;," source&#34 ;: {" url&#34 ;:" https://gist.github.com/leoloso/4e367eb8d8014a7aa7580567608bd5b4","类型& #34 ;:" git","参考&#34 ;:" master" }},{"类型&#34 ;:" vcs&#34 ;," url&#34 ;:" https://github.com/leoloso/wp-muplugin- loader.git" }],"最小稳定性&#34 ;:" dev""更喜欢稳定" true,"要求&#34 ;: {" PHP&#34 ;:"〜8.0&#34 ;," getpop / api-rest&#34 ;:" dev-master"" getpop / megin-wp- Bootloader&#34 ;:" dev-master" },"额外&#34 ;: {"分支alias&#34 ;: {" dev-master&#34 ;:" 1.0-dev" },"安装程序类型&#34 ;: [" graphiql-client&#34 ;," graphk-woyager" ],"安装程序路径&#34 ;: {" wordpress / wp-content / mu-plugins / {$ name} /&#34 ;: ["类型:wordpress-muplugin&#34 ; ]," wordpress / wp-content / plugins / {$ name} /&#34 ;: ["类型:wordpress-plugin"" getpop / megine-wp-bootloader&# 34; ]}}," config&#34 ;: {" sort-packages&#34 ;: true}}

{"名称&#34 ;:" leoloso / pop&#34 ;,"描述&#34 ;:" pop monorepo&#34 ;,"存储库&#34 ;: [ {"类型&#34 ;:"""包&#34 ;: {"姓名&#34 ;:" leoloso-pop-api-wp /时事通讯 - 订阅 - 休息 - 端点&#34 ;,"版本&#34 ;:" master&#34 ;,"类型&#34 ;:" wordpress-plugin",& #34; source&#34 ;: {"网址&#34 ;:" https://gist.github.com/leoloso/6588f6c1bdcce82fc317052616d3dfb4" ;,"类型&#34 ;:&# 34; git&#34 ;,"参考文献&#34 ;:" master" }}}}}}}}}}}}}}} {"类型&#34 ;:"包和#34;"包&#34 ;: {"姓名&#34 ;:" Leoloso-pop- api-wp / disable-user-edite-profile""版本&#34 ;:" 0.1.1""类型&#34 ;:" wordpress-插件&#34 ;," source&#34 ;: {" url&#34 ;:" https://gist.github.com/leoloso/4e367eb8d8014a7aa7580567608bd5b4","类型& #34 ;:" git","参考&#34 ;:" master" }},{"类型&#34 ;:" vcs&#34 ;," url&#34 ;:" https://github.com/leoloso/wp-muplugin- loader.git" }],"最小稳定性&#34 ;:" dev""更喜欢稳定" true,"要求&#34 ;: {" PHP&#34 ;:"〜8.0&#34 ;," getpop / api-rest&#34 ;:" dev-master"" getpop / megin-wp- Bootloader&#34 ;:" dev-master" },"额外&#34 ;: {"分支alias&#34 ;: {" dev-master&#34 ;:" 1.0-dev" },"安装程序类型&#34 ;: [" graphiql-client&#34 ;," graphk-woyager" ],"安装程序路径&#34 ;: {" wordpress / wp-content / mu-plugins / {$ name} /&#34 ;: ["类型:wordpress-muplugin&#34 ; ]," wordpress / wp-content / plugins / {$ name} /&#34 ;: ["类型:wordpress-plugin"" getpop / megine-wp-bootloader&# 34; ]}}," config&#34 ;: {" sort-packages&#34 ;: true}}

更重要的是,当语法使用间隔嵌套其元素时,它不会在单线中写入它。

服务:_defaults:public:true autocire:true autoconfigure:true pop \ api \ persistedqueries \ persistedquerymanagerInterface:class:\ pop \ api \ persistedqueries \ persistedquerymanager#覆盖服务pop \ componentmodel \ schema \ fieldqueryInterpreter接口:class:\ pop \ api \架构\ FieldQueryInterpreter Pop \ API \ Hooks \:资源:' ../ src / hooks / *'

我将描述GraphQL语法的替代设计:PQL语法,POP由POP(PHP中的GraphQL Server)用于接受基于URL的查询通过GET传递。

由于GraphQL语法的问题源于退回嵌套字段,因此解决方案似乎是明显的:查询的流程必须始终转发。

PQL如何实现这一目标?为了演示,让我们探索PQL语法。

别名是没有:,但是在稍后的“书签”中,使用@(和可选地,周围地包围)“

在我自己的经验中,当直接在浏览器的地址栏中编写查询时,我总是想到在写字段名称之后需要别名,而不是之前。因此,使用如GraphQL中的订单,我必须回到该位置(按左箭头键),添加别名,然后返回最终位置(按下右箭头键)。

这很繁琐。在字段名称之后放置别名,使其成为一个自然流动的感觉。

在字段名称之后定义别名时,它不再有意义使用:。 GraphQL使用此符号使JSON响应尊重查询的形状。一旦字段和别名之间的订单被反转,使用@似乎是自然的。

反过来,这意味着我们无法使用@来识别指令。相反,我选择了一个周围的语法< ...> (例如,< directivename>),因此指令也可以嵌套(例如,<指令1>>),使POP可以支持GraphQL来支持可组合指令特征。

在GraphQL中,我们可以通过在它们之间添加空格或行中断来添加两个或更多个字段:

我们还可以理解,查询可以直接在浏览器中组合,通过URL Param查询传递。

使用DevTools,我们可以看到GraphQL单端点支持HTTP缓存的支持:

对于下面演示的所有查询,将有一个链接在浏览器中执行查询。单击它们以可视化PQL如何在生产中的实际站点上工作。

类似于GraphQL,NewLines(以及空格)不添加语义含义。因此,我们可以方便地添加换行符以帮助可视化查询:

使用Firefox时,可以复制此查询(从文本编辑器,网页等)并粘贴到浏览器的地址栏中,并且将自动删除所有行中断,创建等效单行查询。

在PQL中,查询只有前进,从不撤退。因此,{,它是等效的,但是,但没有等同于}由于它不需要。

我们可以结合|和 。为任何实体获取多个字段。考虑此GraphQL查询:

此时,我们可以面临挑战:PQL如何只接受推进领域?

上面看到的查询总是推进。现在让我们解决问题的查询,也需要退出,就像这个GraphQL查询:

{posts {id作者{id name url}注释{id content}}}

PQL利用字符,加入元素。它类似于|对于加入字段,但具有根本差异:右侧的字段开始,从根中再次遍历图。

注意如何,使其在视觉上吸引人,名称|自从|自从左边有同样的填充物保持他们的路径帖子.Author ..但是之后没有左填充,因为查询再次从根开始。

然而,这不是如此退缩,因为它是“重置”。在GraphQL中,我们可以回到查询中的先前位置 - 即图表中的父节点 - 我们已经遍历了级别的级别。但是,在PQL中,我们不能:我们总是一直到图的根源。

再次从根开始,我们必须再次指定节点的完整路径以继续添加字段。这使得查询更详细。例如,上面查询中的POSTS路径在GraphQL中出现一次,但PQL中的两次。

这种冗余迫使美国人类在读取和编写图表中的每个级别时重新创建路径。这样做允许我们在单行表示时了解查询:

因为我们在我们的头脑中重新重新创建了这条路,我们不会遭受短期内存问题,导致我们在查看GraphQL查询时会丢失。

{用户{posts {author {id名称}注释{id content}}}}

要检索注释字段,我们再次需要添加用户.Posts ..进一步的级别下图,越长,可以复制的路径越长。

为了解决这个问题,PQL介绍了一个新的概念:一个“书签”,它为已经遍历的路径提供了快捷方式,以便我们方便地将数据加载到该点。

当迭代某些路径时,通过将其名称封闭其名称,然后在引用其书签时,通过将其名称与查询的root括起来,自动检索该路径。

为了使其更容易可视化,我们还可以将相同的填充添加到所应用的书签的左侧,匹配其路径的相同填充(因此评论显示在下面的帖子以下):

如果我们需要定义书签和别名,我们可以在[...]中嵌入@符号:

必须在浏览器中撰写查询时键入这些报价被证明非常讨厌;我经常忘记它们,然后必须用箭头键向左和向右导航以添加它们。

此外,有时可以隐含的字段论点;例如,当字段只有一个字段参数时。在这种情况下,PQL允许省略它:

在GraphQL中,变量在请求主体内定义为编码的JSON:

{"查询":"查询($ format:string){post {id标题日期(格式:$ format)}&#34 ;,"变量":& #34; {\"格式\":\" d / m / y \" }"}

{用户{... userdata帖子{注释{autory {suberData}}}}}}}} and user {id name url}上的片段userdata

在PQL中,片段遵循与其定义的变量相同的方法:作为$ _get或$ _post中的输入。他们参考 - :

PQL是GraphQL查询语法的超集。因此,使用标准GraphQL语法编写的任何查询也可以用PQL写入。

相反,不是使用PQL编写的每个查询都可以使用GraphQL语法编写,因为PQL支持GraphQL不支持的可编译字段和可组合指令等功能。

即使不支持这些元素,也可以通过不同的方法支持其底层功能。

操作缺少,因为它不需要它了:我们现在可以选择使用get(对于查询)或post(for umations)来请求查询。

当文档包含许多操作时,只需要在GraphQL中需要操作名称,并且我们需要指定要执行哪一个,或者可以一起执行其中几个,通过@Export绑住它们。

在前一个情况下,PQL不需要它 - 我们只通过必须执行的查询,而不是所有这些。

在后一种情况下,可以在单个请求中一起执行多个操作,同时通过加入它们来保证执行顺序;喜欢:

在GraphQL中,可变定义用于定义变量的类型,使得能够像GraphIQL这样的客户端,以便在类型不同时显示错误。这是一个很好的,但实际上不是执行查询本身。

由于我们可以使用指令@Include,使一个可协式字段IsType作为参数,以找到对象的类型,并根据此值,应用程序的类型,从而使用该元素。

让我们转换图形(和其他客户端)使用的内省查询,从GraphQL语法到PQL获取架构元数据。

查询内部{__schema {querytype {name} mutationtype {name} subscriptiontype {name}类型{nualtype}指令{name说明位置args {... inputvalue}}} frescment fulltype上__type {plice name说明字段(包括repecated :true){name说明args {... inputvalue}类型{... ineref} isdeprecated depecation areason} Infupfields {... inputvalue} interfaces {... inneref} enumvalues(chencesseprecated:true){name说明是disdecated depecation oriason} possibletypes {...键架}} __inputValue {name描述{...典型名称类型{dement oftype名称{specy name oftype名称{spice name oftype名称{spice name oftype名称{spice name oftype名称{spice name oftype名称{prope名称{spice name oftype名称{spice name oftype名称{spice name oftype name oftype name oftype名称{种唯一的nype名称{种唯一的名称{种唯一的名称{rype name ytype名称{种型号名称ofType {样的名字ofType {样的名字}}}}}}}}

?query = __schema [架构]。 querytype。名称,[架构]。 verationtype。名称,[架构]。 subscriptiontype。名称,[架构]。类型。 - 填充[架构]。指令。名称|描述|地点| args。 - Inputvalue&碎片[fulltype] =善良|名称|描述|字段(已包含:true)[@ fields]。名称|描述| args。 - [vittValue,[字段]。类型。 - typeref,[字段]。 Isdeprecated |贬值症,[字段]。 Inputfields。 - [vittValue,[字段]。接口。 - typeref,[字段]。 enumvalues(已包含:true)@enumvalues。名称|描述| Isdeprecated |贬值症,[字段]。 possibletypes。 - typeref&碎片[inputvalue] = name |描述| defaultValue |类型。 - typeref&碎片[typerf] =好的|名称| oftype。种类|名称| oftype。种类|名称| oftype。种类|名称| oftype。种类|名称| oftype。种类|名称| oftype。种类|名称| oftype。种类|名称

在浏览器中执行查询。 (请注意,此链接中的查询略有于上面的查询,因为我仍然需要在片段内添加对,令牌的支持。)

此查询将指令应用于片段,从而在片段中的所有字段上应用:

最后,有许多单行查询的示例直接嵌入为URL Param,并在本博客文章中包含来自PQL语法(本文中未描述)的其他属性。

为了支持HTTP缓存,我们当前必须使用支持持久查询的GraphQL服务器。

但GraphQL单端点怎么样?它也可以支持HTTP缓存吗?如果是这样,可以以人们可以编写查询的方式完成,而不是必须依赖于客户端或库?

对这些问题的响应是:是的,可以完成。但是,由于其嵌套行为,GraphQL语法目前妨碍。

在本文中,我演示了一个替代语法,称为PQL,它可以使GraphQL服务器通过URL Param接受查询,同时使人们能够在单行中读取和写入查询,即使是直接在浏览器的地址栏中。

虽然GraphQL具有调试请求和响应的一些功能,但为您的生产应用程序确保GraphQL可靠地为您的生产应用程序提供资金。如果您有兴趣确保对后端或第三方服务的网络请求成功,请尝试Logrocket。 https://logrocket.com/signup/ logrocket就像一个用于Web应用程序的DVR,字面上录制了您网站上发生的所有内容。您可以汇总和报告有问题的GraphQL请求以快速理解根本原因,而不是猜测问题。此外,您还可以跟踪Apollo客户端状态并检查GRA

......