掌握API建模:基本概念和实践
GraphQL vs. REST APIs:为何不应使用GraphQL
什么是 GraphQL 和 REST,我们为什么要比较它们?
GraphQL 和 REST 是 API 开发和集成的两种最流行的架构,可促进客户端和服务器之间的数据传输。比较它们对于确定特定 API 场景的正确方法是必要的,因为两者都为不同的用例提供了优势。
GraphQL 与 REST 之间最大的区别在于数据如何发送给客户端。在 REST 架构中,客户端向不同的端点发送 HTTP 请求,数据作为 HTTP 响应发送回来;而在 GraphQL 中,客户端通过向单一端点发送带有查询的请求来获取数据。
还值得注意的是,在 REST 中,响应对象的结构是在服务器上定义的。在 GraphL 中,您可以在客户端上定义对象。
通过评估这两者,您可以决定哪种方法最适合您的项目在数据获取效率、API 灵活性、错误处理、实时支持、易于实施、集成和维护方面的需求。
GraphQL 和 REST 之间的区别
下表总结了 REST 和 GraphQL 的主要功能,以便于比较:
特征 | 休息 | 图形QL |
---|---|---|
数据过度获取/获取不足 | 获取的数据多于或少于客户端所需的数据 | 获取客户端请求的精确字段 |
Schema 定义 | 默认情况下没有强制执行的严格架构 | 使用 Schema Definition Language 强制执行强类型 schema |
缓存 | 使用标准缓存机制轻松实现网络级缓存 | 由于单个端点,难以在网络级别使用标准缓存机制 |
错误处理 | 使用 HTTP 状态代码进行错误处理 | 对所有请求返回 200 OK,并在响应对象中返回 error 属性 |
API 结构 | 根据资源量具有多个端点 | 所有查询/更改都有一个端点 |
实时通信 | 没有对实时通信的本机支持。它需要额外的协议,如 WebSockets 或 Server-Sent Events | 通过 GraphQL 订阅对实时客户端-服务器通信的原生支持 |
使用多个数据源
GraphQL 简化了从多个来源或 API 聚合数据的过程,并能够在一次 API 调用中就将数据解析并返回给客户端。相比之下,像 REST 这样的 API 技术则需要通过多次 HTTP 调用才能从多个来源访问数据。
Web 缓存
在这里,我想强调网络级缓存,因为您可以使用 Apollo Client 的内存缓存实现在数据库级别或客户端级别实现缓存。
例如,假设您在 HTTP 级别实现了一个缓存,其中包含一个存储请求内容的反向代理。这可以减少服务器的流量,或将经常访问的信息保存在靠近客户端的位置,例如内容交付网络。
由于 REST API 提供了许多端点,因此您可以轻松配置 Web 缓存以匹配某些 URL 模式、HTTP 方法或特定资源。此外,HTTP GET 方法自然可以由浏览器、代理和 CDN 缓存。
在 GraphQL 中,通常只有一个端点(通常是 HTTP POST 端点),用于发送所有查询。这会使缓存变得复杂,因为查询本身位于请求体中,而反向代理和内容分发网络(CDN)通常基于 URL 和头部信息进行缓存,因此很难在网络层使用标准的缓存机制。为了减少对 Web 服务器的流量,您可以使用 PersistGraphQL 来进行持久化查询。但请注意,在撰写本文时,该工具已不再维护,但它仍然可以工作。
PersistGraphQL 为 GraphQL 查询分配标识符,生成一个映射查询和标识符的 JSON 文件。使用此映射,客户端仅将查询的标识符和参数发送到服务器,因此它只需查找即可。但是,这增加了另一层复杂性,并且只能部分解决问题。
类型安全和查询深度
GraphQL 在其架构中附带了内置的类型安全。架构中的每个字段都经过类型化,确保客户端知道他们将接收的数据的确切结构和类型。这减少了客户端应用程序中的运行时错误。
REST 本身不提供类型定义,因此在客户端应用程序中容易出现运行时错误。
何时应使用 GraphQL?
GraphQL 是一种出色的解决方案,专门解决在构建和使用 API 时围绕灵活性和效率的独特问题,特别是在处理大型数据集时。当按照其设计方式使用时,GraphQL 可以凭借其以下特性成为一种理想的工具。
数据获取控制
GraphQL 旨在允许客户端仅请求所需的数据。虽然服务器可能能够为单个请求向客户端提供更多数据,但它只会发送客户端请求的数据。
如果您希望客户端控制所需的数据类型和数据量,GraphQL 非常适合您的项目。
缓解带宽问题
对于无法处理大量数据的小型设备(如手机、智能手表和 IoT 设备)来说,带宽是一个问题。
使用 GraphQL 有助于最大限度地减少此问题。由于 GraphQL 允许客户端指定它需要的数据,因此服务器不会发送多余的数据,这可能会在带宽有限时降低应用程序的性能。
快速原型制作
GraphQL 公开了一个允许您访问多个资源的终端节点。此外,不会根据您在应用程序内拥有的视图来公开资源。例如,如果您的 UI 发生更改,并且需要更多或更少的数据,则不会产生影响,也不需要服务器进行更改。
不断发展的数据模型
如果您的应用程序数据模型由于字段的添加或删除而频繁变化,或者资源之间的关系复杂,与 REST 相比,GraphQL 在适应这些变化方面会提供更大的灵活性。其动态模式,以及内省(introspection)和动态查询功能,使其更适合此类用例。而 REST 则需要在多个端点上进行更多的更新,并且在数据获取方面无法提供相同的粒度。
GraphQL 架构
通过允许您定义模式(schema),GraphQL 提供了许多好处,如自动验证和内省(introspection)。根据您如何编写或生成模式,当数据模型发生变化时,静态模式可能会成为问题。因为客户端收到的响应取决于模式定义和它们发出的查询。
结构的深度仅限于架构或查询设置的限制,但可以在运行时调整架构,并且可以进行动态类型定义。您可以通过使用代码优先方法以编程方式构建架构,在 GraphQL 中解决此问题。设计过程从对解析程序进行编码开始,并以编程方式生成 GraphQL 架构的 SDL 版本。
何时应使用 REST API?
现在我们已经概述了几个 GraphQL 使用案例,让我们探讨一下为什么在某些情况下您可能更喜欢 REST,并查看一些关键提示,以帮助您决定何时使用每个用例。
GraphQL 性能问题
作为 API 的查询语言,GraphQL 使客户端能够执行查询以获取所需的内容。但是,如果客户端发送查询请求许多字段和资源,该怎么办?
以以下示例为例,其中查询请求有关为特定作者的所有书籍发布评论的用户的信息:
author(id: '1234') { id
name
books {
id
title
reviews {
text
date
user {
id
name
}
}
}
}
使用 GraphQL,用户不能简单地运行他们想要的任何查询。必须仔细设计 GraphQL API;这不仅仅是将其放在 REST API 或数据库之上。
对于复杂查询,REST API 可能更易于设计,因为您可以针对特定需求建立多个终端节点,并且可以微调特定查询以有效地检索数据。
这一点可能有些争议,因为多次网络调用仍然会消耗大量时间。但是,如果您不小心,几个大型查询可能会极大地拖垮您的服务器。从这个意义上说,GraphQL 的最大优势也可能成为其最大的弱点。
在 GraphQL API 中,Dataloader 等工具允许您批处理和缓存数据库调用。但在某些情况下,即使这样还不够,唯一的解决方案是通过计算最大执行成本或查询深度来阻止查询。
graphql-query-complexity 等库有助于限制查询的大小和复杂性,以防止出现性能问题,并保护您的 GraphQL 服务器免受资源耗尽和 DoS 攻击。graphql-query-complexity 在复杂查询影响服务器资源之前拒绝它们:
const { ApolloServer } = require('apollo-server');const { getComplexity, simpleEstimator } = require('graphql-query-complexity');
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
{
requestDidStart: () => ({
didResolveOperation({ request, document }) {
const complexity = getComplexity({
schema: server.schema,
query: document,
variables: request.variables,
estimators: [simpleEstimator({ defaultComplexity: 1 })],
});
if (complexity > 100) {
throw new Error('Query too complex!');
}
},
}),
},
],
});
server.listen();
上述代码估计了查询的复杂度,并拒绝了超过定义阈值 100 的查询,从而防止了潜在的性能问题。
REST 可以完成 GraphQL 的大部分工作
请务必记住,GraphQL 是用于开发 API 的 REST 替代方案,而不是替代品。
使用 GraphQL 的主要好处是能够发送一个只指定所需信息的查询,并精确接收这些信息。然而,通过在 URL 中传递您想要使用的字段的名称,然后自行实现解析和返回数据的逻辑,您也可以使用 REST 达到同样的效果:
GET /books/1492030716?fields=title,pageCount
虽然这看起来更容易实现,但缺点是每个 REST 端点都需要自定义逻辑来解析和筛选字段,这增加了处理嵌套数据时的复杂性,而使用 GraphQL 更容易实现。
值得庆幸的是,JSON 架构有助于定义 API 响应的结构,从而实现验证和一致性,并且提供多种语言版本。如果希望在 REST 中使用架构和强类型的好处,可以使用 JSON 架构;许多库实现并支持 JSON 架构。但是,JSON Schema 缺乏 GraphQL 提供的内省和实时灵活性。
如果您想在 REST API 中使用查询语言,OData 是一个很好的替代方案。OData 是 Open Data Protocol 的简称,最初由微软于 2007 年开发。它是一种开放协议,能够以简单、标准的方式创建和使用可查询且可互操作的 RESTful API。OData 提供了丰富的查询功能,并且由于其开源方法和可扩展性,正在迅速获得认可。
有许多有效的替代方案,特别是对于使用 GraphQL 可能有点矫枉过正的小型应用程序和项目。出于同样的原因,您也会遇到这样的情况:实现这些库会很复杂,而使用 GraphQL 会更容易,它本身支持所有这些功能。但是,GraphQL 也会使事情复杂化,我们接下来将对此进行讨论。
GraphQL 使某些任务复杂化
不建议在简单的应用程序中使用 GraphQL。例如,在每次都以相同方式使用几个字段的应用程序中,使用 GraphQL 会增加更多复杂性,因为以下原因:
- 类型
- 查询
- Mutators (设置器)
- Resolvers
- Higher-order components
从维护的角度来看,这尤其有害,因为您需要随着后端引入的每个新更改来更新模式,并且还需要为每个查询和变更维护解析器。但是,即使使用 GraphQL 是合理的,也可能会遇到一些复杂情况。其中两个例子是错误处理和文件上传。
在 REST 中,检查响应状态是了解请求是否成功执行、是否存在服务器错误或是否未找到资源的唯一方法。但是,当 GraphQL 中发生错误时,您会收到类似于以下内容的内容:
{ "data": null,
"errors": [
{
"message": "Validation error...",
"locations": [
{
"line": 5,
"column": 6
}
]
}
]
}
您必须解析此消息以了解是否存在错误,不同的错误可能具有略有不同的格式或一些自定义字段。
一些库,如 Apollo Client,有助于处理错误,但这并不像在 REST API 中那么容易。
文件上传不是 GraphQL 规范的一部分,因此由您来实施。一些选项包括:
- Base64 编码,这使得请求更大,编码和解码成本更高
- 单独的服务器或 API
- 类似于 graphql-upload 的库,用于实现 GraphQL 多部分请求规范
第三种选择可能是最好的。但是,这意味着要添加另一个依赖项来管理你的项目,并且它可能不适用于所有编程语言。
错误处理和工具
REST 中的错误处理比 GraphQL 中的错误处理更容易。RESTful API 遵循有关资源的 HTTP 规范,并提供不同的 HTTP 状态代码来指示 API 请求的状态。
与此同时,GraphQL 会为每个 API 请求返回状态,包括错误状态。这使得错误管理变得困难,并且难以与监控工具集成。虽然 Apollo 客户端库通过内置机制来规范化错误,以区分 HTTP 状态错误和 GraphQL 特定错误,从而简化了这一过程,但它仍然没有解决与监控工具相关的挑战。
GraphQL 安全挑战
内省
自省是客户端动态查询架构并接收详细信息(包括其类型和活动)的能力。
攻击者可以执行精确的攻击,并通过暴露与底层数据模型相关的私有数据来访问未经授权的信息。一些缓解技术在生产中禁用内省,实施速率限制,并使用字段级授权来阻止对敏感架构部分的访问。
速率限制
在 GraphQL 中,速率限制(Rate Limiting)有助于在一定时间内限制操作的数量,从而防止滥用并保护系统免受拒绝服务攻击(Denial-of-Service Attacks)。如果没有有效的速率限制,攻击者可以发送大量的 API 请求来淹没服务器,导致 API 垃圾信息泛滥和服务耗尽或不可用。
查询深度
Query depth 是单个查询中嵌套字段的数量,它显示客户端可以浏览数据图表的深度。当客户端发送非常深或嵌套的查询时,可能会出现安全挑战,这可能会导致资源耗尽和服务器性能停机。
攻击者可以创建复杂的嵌套查询,从而导致拒绝服务问题。我们可以通过限制任何传入查询的最大深度来轻松防止恶意使用查询深度功能。
跨站点脚本
当无效的用户输入从 GraphQL 服务器发送到客户端的响应数据中以窃取个人信息或代表客户端执行未经授权的操作时,就会发生跨站点脚本 (XSS)。
XSS 攻击可能会产生多种后果,但常见的后果是将敏感数据(cookie、会话信息等)传输给攻击者或将受害者重新路由到另一个网站。输入验证和输出编码有助于减少 XSS 攻击。
验证和架构设计
设计不当的模式可能会意外地将敏感信息暴露给未经授权的用户。如果输入验证不当,还可能导致注入攻击。为了发现和修复模式及验证过程中的漏洞,必须定期进行安全评估和测试。
此外,通过使用类型系统,服务器和客户端可以在发出无效查询时轻松通知开发人员,而不是依赖于运行时检查。
GraphQL 替代方案
gRPC (Google 远程过程调用)
- 性能,使用 HTTP/2 和协议缓冲区的开源 RPC 框架
- 主要功能: 元数据、负载均衡、呼叫取消、拦截器、流
tRPC(键入的 RPC)
- Vercel 的开源框架,用于构建快速、类型安全的轻量级 API
- 限制:缺乏对 Python、Java 和 Ruby 等流行语言的支持
- 主要特点:效率、类型安全、单一事实来源、RPC 范式、易用性
Falcor
- Netflix 的快速数据获取框架,使用 JSON 图形模型将每个远程数据源表示为单个域模型
- 减少超额获取和网络传输次数
- 简化客户端-服务器连接
- 对并发持久请求进行批处理,将请求优化为点查询,并缓存最近使用的数据以提供高效的客户端/服务器交互
OData (开放数据协议)
- 使用基本 REST 操作(GET、POST、PUT、DELETE)简化创建和查询可互操作的 Restful API
- 提供强大的查询功能(排序、筛选、查询)不如 GraphQL 强大
REST API 安全问题
数据传输
通过不安全的通道传输敏感信息会使其面临被篡改的风险。如果没有使用适当的加密方法,例如 HTTPS,攻击者可能会在数据传输过程中拦截并获取对敏感数据的未经授权访问。管理不善的数据传输可能会导致数据泄露、身份盗窃或其他恶意活动。
跨域资源共享 (CORS)
CORS 是指一个域上托管的 Web 应用程序尝试向另一个域上托管的 API 发出请求。默认情况下,Web 浏览器实施同源策略,通过阻止跨域请求来遏制任何潜在的安全问题。
使用 CORS,服务器管理员可以指定哪个源可以访问他们的资源。任何错误的配置都可能导致攻击者发出未经授权的请求并泄露私人信息。
会话管理
会话管理是指对用户交互过程中的有状态信息进行维护。对用户会话的不当管理可能会导致诸如会话劫持等漏洞。会话令牌(session tokens)的生成、存储或传输方面存在问题可能会泄露敏感的用户数据,从而威胁到用户会话的完整性和保密性。
身份验证和授权
薄弱或设计不佳的身份验证和授权系统可能会导致未经授权的访问、数据泄露和潜在的 API 功能滥用。
结论
在本指南中,我们介绍了使用 GraphQL 可能导致性能挑战、架构复杂性和复杂查询问题的一些实例。我们还提供了指南,以帮助决定在您的项目中是实施 GraphQL 还是 REST 架构。
GraphQL 是一个强大的工具,与 REST 相比具有多项优势。但是,如果您更喜欢简单性和更快的性能,则选择 REST 架构可能是更好的选择。
请记住,此处概述的注意事项可能并不普遍适用,但值得考虑它们以确定它们与您的具体情况的相关性。
原文来源:https://blog.logrocket.com/graphql-vs-rest-api-why-you-shouldnt-use-graphql/