GraphQL最佳实践
下面列出了绑定到网络协议和数据格式,版本控制,授权,缓存和处理长列表(包括分页)领域的一些最佳实践。
JSON(使用 GZIP 压缩)
GraphQL 服务通常返回 JSON 格式的数据,但 GraphQL 规范 并未要求这一点。对于期望更好的网络性能的 API 层来说,使用 JSON 似乎是一个奇怪的选择,但由于它主要是文本,因而在 GZIP 压缩后表现非常好。
推荐任何在生产环境下的 GraphQL 服务都启用 GZIP,并推荐在客户端请求头中加入:
Accept-Encoding: gzip
客户端和 API 开发人员也非常熟悉 JSON,易于阅读和调试。事实上,GraphQL 语法部分地受到 JSON 语法的启发。
1。协议绑定和数据格式
GraphQL用于构建分布式系统,因此我们需要讨论如何将数据从客户端发送到服务器并返回。涉及哪些协议以及数据如何序列化?通常,我们将使用GraphQL运行时,它处理协议绑定,序列化和反序列化。请求和响应绑定到HTTP协议,JSON数据格式用于序列化。
1.1 HTTP请求绑定
在服务器端,有一个通用HTTP端点(/ graphql),它接收客户端发送给服务器的各种GraphQL请求。发送到GraphQL端点的GraphQL请求由以下组件组成:query / mutation / subscription,以GraphQL查询语言编写。
变量列表操作名称GraphQL请求可以通过两种方式映射到具有JSON数据结构的HTTP请求:
(1)以带有查询参数的HTTP GET形式,如1.1.1,或者
(2)以带有JSON文档的HTTP POST形式,如6.1.1.2节所示。
1.1.1 HTTP GET请求绑定
如果请求绑定到HTTP GET方法,则查询参数用于编码GraphQL查询,变量和操作名称。该查询只是GraphQL查询语言中的表达式。操作名称是一个简单的字符串。
变量是一个JSON对象,它包含variablename和variable-value的键值对。此对象根据JSON序列化规则进行序列化。结果如下。
HTTP GET / graphql?query = {customers {name}}&operationName = op&variables = {“var1”:“val1”,“var2”:“val2”} 6.1.1.2 HTTP POST请求绑定如果请求绑定到HTTP POST,JSON文档用于编码GraphQL查询,变量和操作名称。 请注意,除了查询字段的值之外,所有内容都是根据JSON规则序列化的。 它是一个字符串,该字符串根据GraphQL查询语言的规则进行序列化。 结果如下。
HTTP POST /graphql
Content-Type: application/json
{
“query”: “{customers{name}}”,
“operationName”: “op”,
“variables”: {
“var1”:“val1”,
“var2”:“val2”
}
}
1.1.2 HTTP响应绑定
GraphQL响应映射到具有JSON有效负载的HTTP响应,
响应是返回错误还是实际数据。 返回实际数据时,有效内容是JSON对象,其顶级名称为data。 此对象包含query / mutation / subscription中请求的对象和属性。
HTTP 200 OK
Content-Type: application/json
{
"data": {
“customers”:[
{
“name”:”Joe”
},
{
“name”:”Bill”
}
]
}
}
处理GraphQL请求失败时,将返回错误列表。 每个错误都会详细介绍其他信息,例如包含更多详细信息的消息。
HTTP 200 OK
Content-Type: application/json
{
”errors": [
{
“message”: ”An error occurred”
}
]
}
1.2版本控制成功的软件总是会发生变化.Frederick P. Brooks管理软件系统的变化和发展绝非易事,但是在松散耦合的分布式系统(如API解决方案)中管理变更尤其困难。 API中的一个小变化已足以打破一些使用API的客户端。 从API使用者的角度来看,寿命和稳定性是已发布API的重要方面。 当API发布时,它们可供消费者使用,并且必须假设消费者使用API构建应用程序。 发布的API无法以敏捷方式更改。 至少,API需要保持向后(和向前)兼容,以便旧客户端不会中断,新客户端可以使用新的和改进的功能。
1.2.1 API更改的类型
人们可能希望更改已发布API的各个方面。所有这些变化对客户来说同样严重吗?在本节中,我们将分析潜在的变化并根据其严重程度对其进行分类。严重的变化是那些不兼容的变化(参见第6.2.1.2节)并打破了客户。这些API更改并不严重,不会影响客户端。它们被称为向后兼容(参见第6.2.1.1节)。
1.2.1.1向后兼容的更改
如果未更改的客户端可以与更改的API交互,则API向后兼容。未更改的客户端应该能够使用旧API提供的所有功能。如果应该向后兼容更改,则禁止对API进行某些更改,而其他更改则是可能的。以下是向后兼容更改的列表:添加字段添加类型添加查询,突变和订阅
1.2.1.2不兼容的更改
如果对API的更改破坏了客户端,则更改不兼容。通常,删除和更改API的各个方面会导致不兼容。以下是不兼容更改的非详尽列表:删除现有字段更改现有字段删除类型删除查询,突变或订阅
1.2.2 GraphQL API中没有版本控制
由于难以管理演变,理想情况下,API应以这种方式构建,这种演变几乎是不必要的,任何可预见的变化都可以作为兼容的变化来实现。 GraphQL中的版本控制问题并不像构建API的其他哲学那样严重。在GraphQL中,客户端需要在发送请求时决定响应的形状。 GraphQL API仅返回客户端明确请求的字段。
1.3 API的数量
公司应该有多少个GraphQL API? 只有当所有相关数据都在同一个图表中时,才能充分利用GraphQL的强大功能。 尽可能多的数据应该链接到同一个图表。 然后可以在一个GraphQL API中公开此图。 因此,在同一个GraphQL API中使用完整的API组合是有意义的。
1.4身份验证和授权
在API安全性的上下文中使用两个类似的术语 - 身份验证和授权。对于以下讨论,必须了解两者之间的区别。身份验证是回答问题的概念:你是谁?身份验证是一种提供所声明身份证明的方法。
授权是一个回答问题的概念:你可以做什么?授权提供分配给已确认身份的权限,例如访问权限。身份验证是正确授权的先决条件。
GraphQL没有规定如何实现身份验证或授权。但是,可以观察到实现身份验证和授权的最佳实践。
1.4.1身份验证
最佳做法是在HTTP服务器中处理身份验证,例如在快递。
HTTP服务器可以使用任何用于认证的标准框架,优选地基于令牌的机制,例如OAuth [6,1]。然后,令牌或用户对象可以在上下文对象中传递给GraphQL解析器函数。
1.4.2授权
实施授权的一个好地方是业务逻辑层(参见4.2节),即GraphQL层中的resolve函数和数据库层之间的层。要处理授权,业务层需要有关经过身份验证的用户的信息。它可以通过从服务器传递给业务层的用户对象,通过解析器函数和上下文对象来接收它.
1.5缓存
GraphQL与后端的连接可能效率低下。
这是由于旋转变压器的结构。解析器的结构允许在服务器上编写干净的代码,其中每种类型的每个字段都具有用于解析相应值的专用函数。这种干净概念的简单实现将导致相对低效的实现,并为每个字段提供数据库访问。
解决方案可以是批处理多个后端请求,并缓存对象的所有字段的响应。 Facebook已出于此目的发布其数据加载器库。它允许构建可以处理缓存的业务层。
缓存需要标识符。在REST中,使用HTTP缓存,查询路径用于标识对象。有时使用类型特定的标识符,因为它们在数据库表中很容易获得。在GraphQL中,查询路径不是唯一标识符。可以使用各种查询路径访问相同的对象。
因此,优选使用全局唯一标识符而不是特定类型标识符用于高速缓存。
1.6长列表和分页
在GraphQL中处理长列表有几种约定:复数,切片和分页。让我们仔细看看这些选项。
1.6.1复数
当在模式中建模1:n关系时,我们使用具有复数形式的名称的字段。这是Plurals得名的地方。多个实现为数组。
多元实际上不使用任何形式的分页。
在下面的示例中,我们使用复数来模拟查询类型和书籍类型之间的关系。
type Query {
books: [Book]
}t
ype Book {
id: ID!
title: String
}
以下查询使用复数来检索所有书籍。
query {
books {
title
id
}
}
1.6.2引入切片(slice)
切片以限制返回的数据:仅检索前两个/三个/四个等书,或仅检索最后两个/三个/四个等,这是一个数据切片的示例。 这种切片方法可以用于分页,因为我们可以检索前两个,然后是接下来的两个,然后是切片中的下两个。 可以使用偏移(参见第6.6.3节)或使用光标(参见第6.6.4节)实现切片。
1.6.3使用偏移量
切片最佳实践是在GraphQL中使用参数优先使用切片。 该参数首先指定页面的大小,即该页面上的项目数。 参数after指定要启动的索引或要启动的元素的id。 在第三本书之后接收两本书对应于以下查询。
query {
books(first: 2, after: 3) {
title
id
}
}
基于第一个和后一个参数的偏移切片不是开箱即用的。它需要在书籍()的解析器功能(参见4.1.2.2和5.3.1节)中实现。
1.6.4使用光标
切片使用分页时,我们需要对列表中最后一页结束并开始下一页的点进行建模。这允许我们仅检索已经检索的数据之后存在的数据。到目前为止,我们已经为此目的使用了偏移量。游标是一种比偏移更灵活的概念。如果将新元素添加到列表或从列表中删除,则偏移可能会发生偏差。依赖于偏移量可能会导致我们多次读取相同的元素或跳过一堆元素。如果列表中的更改率高于我们从列表中读取元素的速率,则这尤其糟糕。因此,游标不依赖于列表中的位置,而是依赖于元素的身份。
游标放在哪里?我们不希望将它们作为业务对象的一部分,因为业务对象可能是多个cursored列表的一部分。这就是我们引入间接的原因。间接是页面的显式表示,其中包含要在页面上显示的业务对象列表和元数据,例如下一页,第一页和最后一页的游标以及是否存在下一页。带有游标的查询可能如下所示。
query {
books(first: 2, after: Fzmy33==) {
nodes {
title
id
}
pageInfo {
startCursor
nextCursor
endCursor
hasNextPage
}
}
}
页面上的业务对象被建模为一个数组,这里称为节点。
元数据在pageInfo对象中建模,其中包含字段startCursor,nextCursor,endCursor和hasNextPage。