介绍
GraphQL 是一种 API 查询语言和运行时,用于使用现有数据完成这些查询。GraphQL 为您的 API 中的数据提供了完整且易于理解的描述,并让客户能够准确地询问他们需要什么,仅此而已。
随着时间的推移,它简化了不断发展的 API,并启用了强大的开发人员工具。在本指南中,我们将了解 GraphQL 的优点和缺点,以便您可以自行决定它是否适合您的项目。
如果你正在寻找一个GraphQL安装指南,我们使用覆盖教程GraphQL与Ruby on Rails的和GraphQL用Node.js的。
精确数据获取
GraphQL 精确数据获取功能的重要性和实用性怎么强调都不为过。使用 GraphQL,您可以向您的 API 发送查询并准确获取您需要的内容,不多也不少。真的就是这么简单。如果将此功能与 REST 的传统直观性质进行比较,您就会明白这是对我们最初做事方式的重大改进。
GraphQL 通过根据客户端应用程序的需求对数据进行选择性处理,最大限度地减少了在线传输的数据量。因此,移动客户端可以获取更少的信息,因为与 Web 应用程序的大屏幕相比,小屏幕上可能不需要这些信息。
因此,与返回固定数据结构的多个端点不同,GraphQL 服务器只公开一个端点并准确响应客户端请求的数据。
考虑一种情况,您要调用具有两个资源(艺术家及其曲目)的 API 端点。
为了能够请求特定艺术家或他们的音乐曲目,您将拥有这样的 API 结构:
METHOD /api/:resource:/:id:
使用传统的 REST 模式,如果我们想使用提供的 API 查找每个艺术家的列表,我们必须像这样向根资源端点发出 GET 请求:
GET /api/artists
如果我们想从艺术家列表中查询单个艺术家怎么办?然后我们必须像这样将资源 ID 附加到端点:
GET /api/artists/1
本质上,我们必须调用两个不同的端点来获取所需的数据。使用 GraphQL,每个请求都可以在一个端点上执行,所采取的操作和返回的数据都在查询本身中定义。假设我们想要获得艺术家的曲目和持续时间,使用 GraphQL,我们将有一个这样的查询:
GET /api?query={ artists(id:"1") { track, duration } }
此查询指示 API 查找 ID 为 1 的艺术家,然后返回其曲目和持续时间,这正是我们想要的,不多也不少。同样的端点也可用于在 API 中执行操作。
一个请求,多个资源
GraphQL 的另一个有用的特性是它可以通过一个请求轻松获取所有需要的数据。GraphQL 服务器的结构使得以声明方式获取数据成为可能,因为它只公开一个端点。
考虑这样一种情况,用户想要请求特定艺术家的详细信息,例如姓名、id、曲目等。使用传统的 REST 直观模式,这将需要至少两个请求到两个端点/artists
和/tracks
。但是,使用 GraphQL,我们可以在查询中定义我们需要的所有数据,如下所示:
// the query request
artists(id: "1") {
id
name
avatarUrl
tracks(limit: 2) {
name
urlSlug
}
}
在这里,我们定义了一个 GraphQL 查询来请求多个资源(艺术家和曲目)。此查询将返回所有且仅请求的资源,如下所示:
// the query result
{
"data": {
"artists": {
"id": "1",
"name": "Michael Jackson",
"avatarUrl": "https://artistsdb.com/artist/1",
"tracks": [
{
"name": "Heal the world",
"urlSlug": "heal-the-world"
},
{
"name": "Thriller",
"urlSlug": "thriller"
}
]
}
}
}
从上面的响应数据可以看出,我们要取两者的资源/artists
,并/tracks
用一个单一的API调用。这是 GraphQL 提供的强大功能。正如您已经想象的那样,此功能对于高度声明性 API 结构的应用是无限的。
现代兼容性
现代应用程序现在是内置的综合方式,其中单个后端应用程序提供运行多个客户端所需的数据。Web 应用程序、移动应用程序、智能屏幕、手表等现在可以仅依赖单个后端应用程序来使数据高效运行。
GraphQL 拥抱这些新趋势,因为它可以用于连接后端应用程序并满足每个客户端的要求(数据的嵌套关系、仅获取所需数据、网络使用要求等),而无需为每个客户端专用单独的 API。
大多数时候,要做到这一点,后端会被分解成多个具有不同功能的微服务。这样,通过我们所说的模式拼接,将特定功能专用于微服务变得很容易。模式拼接使从不同模式创建单一通用模式成为可能。因此,每个微服务都可以定义自己的 GraphQL 模式。
之后,您可以使用模式拼接将所有单独的模式编织成一个通用模式,然后每个客户端应用程序都可以访问它。最后,每个微服务都可以拥有自己的 GraphQL 端点,而一个 GraphQL API 网关将所有模式整合到一个全局模式中,使其可用于客户端应用程序。
为了演示模式拼接,让我们考虑Sakho Stubailo 所采用的相同情况,同时解释我们有两个相关 API 的拼接。新的公共 Universes GraphQL API 用于 Ticketmaster 的 Universe 事件管理系统和Launchpad 上的Dark Sky 天气API,由Matt Dionis创建。让我们看一下我们可以分别针对这些 API 运行的两个查询。首先,使用 Universe API,我们可以获得有关特定事件 ID 的详细信息:
使用 Dark sky weather API,我们可以获得相同位置的详细信息,如下所示:
现在使用 GraphQL 模式拼接,我们可以执行合并两个模式的操作,这样我们就可以轻松地并排发送这两个查询:
您可以深入了解Sashko Stubailo 的 GraphQL 模式拼接,以更深入地了解所涉及的概念。
通过这种方式,GraphQL 可以将不同的模式合并为一个通用模式,所有客户端都可以从中获取资源,轻松拥抱新的现代开发风格。
字段级弃用
这是一项 GraphQL 特性,我个人感到很高兴。作为开发人员,我们习惯于调用不同版本的 API,并且经常得到非常奇怪的响应。传统上,当我们对资源或我们当前拥有的资源结构进行更改时,我们会对 API 进行版本控制,因此需要弃用和发展新版本。
例如,我们可以有一个 API api.domain.com/resources/v1
,在以后几个月或几年的某个时间点,会发生一些变化,资源或资源的结构会发生变化,因此,接下来最好的事情就是发展这个API 来api.domain.com/resources/v2
捕获所有最近的更改。
此时,某些资源v1
将被弃用(或在用户迁移到新版本之前保持活动状态一段时间),并且在收到对这些资源的请求时,将收到诸如弃用通知之类的意外响应。
在 GraphQL 中,可以在字段级别弃用 API。当要弃用特定字段时,客户端在查询该字段时会收到弃用警告。一段时间后,当没有多少客户端不再使用它时,不推荐使用的字段可能会从架构中删除。
因此,不是完全版本化 API,而是可以随着时间的推移逐渐演变 API,而无需重构整个 API 模式。
缓存
缓存是数据的存储,以便可以更快地处理未来对该数据的请求;存储在缓存中的数据可能是早期计算的结果或存储在其他地方的数据的副本。缓存 API 响应的目的主要是为了更快地从未来的请求中获取响应。与 GraphQL 不同,缓存内置于 RESTful API 能够利用的 HTTP 规范中。
使用 REST,您可以通过 URL 访问资源,因此您将能够在资源级别缓存,因为您将资源 URL 作为标识符。在 GraphQL 中,这变得很复杂,因为即使在同一实体上操作,每个查询也可能不同。
在一个查询中,您可能只对艺术家的名字感兴趣,但是,在下一个查询中,您可能想要获得艺术家的曲目和发行日期。这是缓存最复杂的地方,因为它需要字段级缓存,这对于使用 GraphQL 实现并不容易,因为它使用单个端点。
也就是说,GraphQL 社区认识到了这个困难,并且一直在努力让 GraphQL 用户更容易缓存。已经开发了像 Prisma 和 Dataloader(基于 GraphQL)这样的库来帮助处理类似的场景。但是,它仍然没有完全涵盖浏览器和移动缓存等内容。
查询性能
GraphQL 使客户能够执行查询以准确获取所需内容。这是一个了不起的功能,然而,它可能有点争议,因为它也可能意味着用户可以根据需要在尽可能多的资源中请求尽可能多的字段。
例如,用户定义了一个查询,该查询要求提供对特定艺术家的所有曲目发表评论的所有用户的列表。这将需要这样的查询:
artist(id: '1') {
id
name
tracks {
id
title
comments {
text
date
user {
id
name
}
}
}
}
此查询可能会获得数以万计的数据作为响应。
因此,尽管允许用户请求他们需要的任何东西是一件好事,但在一定的复杂程度下,这样的请求会降低执行速度并极大地影响 GraphQL 应用程序的效率。
对于复杂的查询,REST API 可能更容易设计,因为您可以拥有多个满足特定需求的端点,并且对于每个端点,您可以定义特定查询以高效地检索数据。鉴于多次网络调用也可能需要大量时间,这也可能有点争议,但如果您不小心,一些大查询可能会使您的服务器瘫痪。
数据不匹配
正如我们之前在后端使用 GraphQL 构建时所举例说明的那样,通常情况下,您的数据库和 GraphQL API 将具有相似但不同的模式,从而转换为不同的文档结构。因此,track
来自数据库的 a 将具有一个trackId
属性,而通过您的 API 获取的同一轨道将track
在客户端拥有一个属性。这导致客户端/服务器端数据不匹配。
考虑在客户端获取特定曲目的艺术家姓名,它看起来像这样:
const getArtistNameInClient = track => {
return artist.user.name
}
但是,在服务器端做完全相同的事情将导致完全不同的代码,如下所示:
const getArtistNameInServer = track => {
const trackArtist = Users.findOne(track.userId)
return trackArtist.name
}
通过扩展,这意味着您错过了 GraphQL 在服务器上进行数据查询的绝佳方法。
值得庆幸的是,这并非没有解决办法。事实证明,您可以很好地运行服务器到服务器的 GraphQL 查询。如何?你可以通过将你的 GraphQL 可执行模式和你的 GraphQL 查询一起传递给 GraphQL 函数来做到这一点:
const result = await graphql(executableSchema, query, {}, context, variables);
根据Sesha Greif 的说法,重要的是不要仅仅将 GraphQL 视为纯粹的客户端-服务器协议。GraphQL 可用于在任何情况下查询数据,包括使用Apollo Link State的客户端到客户端,甚至在使用Gatsby的静态构建过程中。
模式相似性
在后端使用 GraphQL 构建时,您似乎无法避免重复和代码重复,尤其是在涉及架构时。首先,你的数据库需要一个 schema,你的 GraphQL 端点需要另一个 schema;这涉及相似但不完全相同的代码,尤其是在涉及模式时。
您必须定期为您的模式编写非常相似的代码已经够难的了,但更令人沮丧的是您还必须不断地保持它们同步。
显然,其他开发人员已经注意到了这个困难,到目前为止,GraphQL 社区已经努力修复它。以下是我们发现的两个最受欢迎的修复程序:
- PostGraphile从你的 PostgreSQL 数据库生成一个 GraphQL 模式,并且
- Prisma还将帮助您为查询和突变生成类型。
结论
GraphQL 是一项令人兴奋的新技术,但在做出昂贵且重要的架构决策之前了解权衡很重要。某些 API,例如那些实体很少以及实体之间的关系(如分析 API)的 API,可能不太适合 GraphQL。然而,像许多不同的域对象的应用程序è –电子商务的应用,你有项目,用户,订单,付款,等可能能够利用GraphQL得多。
GraphQL 是一个强大的工具,在您的项目中选择它的原因有很多,但请不要忘记最重要且通常是最好的选择是选择适合项目的工具。我在这里提出的优点和缺点可能并不总是适用,但在查看 GraphQL 时值得考虑它们,看看它们是否可以帮助您的项目或了解是否已经解决了缺点。
如果你正在寻找一个GraphQL安装指南,我们使用覆盖教程GraphQL与Ruby on Rails的和GraphQL用Node.js的。