GraphQL最佳实践

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。

猜你喜欢

转载自blog.csdn.net/happyfreeangel/article/details/82935359
今日推荐