面向Java开发者的GraphQL:构建灵活的数据层
目录
GraphQL 基础为什么 GraphQL 非常适合 Spring 生态系统选择结合 Spring for GraphQL 的 Netflix DGS项目设置
- 依赖项 领域模型概览定义 GraphQL 模式使用 MongoDB 进行持久化使用 Netflix DGS 进行查询解析变更与输入验证在 MongoDB 中解析关系N+1 查询问题在 Netflix DGS 中使用 DataLoaderGraphQL 中的错误处理安全考量何时 GraphQL 是(或不是)正确的选择最佳实践回顾结语
多年来,REST 一直是 Java 生态系统中创建 API 的标准架构风格。诸如 Spring MVC 以及最近的 Spring WebFlux 等框架,使得在结构良好的服务层支持下,通过 REST 范式公开 HTTP 端点变得轻而易举。在许多情况下,这种模型运作良好,并成为众多企业解决方案的基础。
然而,随着应用程序的增长和前端需求变得更加动态,基于 REST 的 API 开始显露出其局限性。返回刚性 DTO(数据传输对象)的多个端点往往会导致过度获取、获取不足,以及为了满足略有不同的客户需求而激增的专用端点。随着时间的推移,API 的演进变得更加困难,除非进行彻底的变革。
GraphQL 从不同的角度处理这个问题。它不再公开一系列端点,而是公开一个定义可用数据和操作的强类型模式。客户端描述他们想要什么,服务器决定如何检索它。这种变化起初可能看起来很奇怪,但它与 Java 开发人员已经熟悉的概念出奇地契合:契约、类型安全和显式演进。
在本文中,我们将探讨如何使用 Spring for GraphQL、Netflix DGS 和 MongoDB 构建一个灵活的、可用于生产环境的 GraphQL 数据层。我们将重点关注设计决策、权衡、模式以及模型,这些对于 GraphQL API 超越实验阶段至关重要。
GraphQL 基础
从概念上讲,GraphQL 基于三个主要原则:
- 模式即 API 契约
- 查询和变更即操作
- 解析器(数据检索器)即执行逻辑
对于 Java 开发人员来说,与 REST 最重要的区别在于,模式不仅仅是文档。它是可执行的。每个查询在到达 Java 代码之前都会根据模式进行检查。仅凭这一点就消除了在松散指定的 API 中常见的一整类运行时错误。
另一个重要的变化是 GraphQL API 是由客户端驱动的。服务器不再决定响应的确切形式。相反,它确保请求的字段是可用的且在形式上是有效的。这使得 API 更容易演进,但也给后端开发人员带来了更大的责任,需要仔细考虑性能和数据访问模式。
为什么 GraphQL 非常适合 Spring 生态系统
Spring 应用程序已经按照一些既定原则进行组织:控制反转、声明式配置和关注点分离。GraphQL 并没有取代这些想法,而是对它们进行了补充。
Spring for GraphQL 将 GraphQL 集成到 Spring 生态系统中,允许 GraphQL 执行利用以下优势:
- 依赖注入。
- 验证。
- 安全过滤器。
- 可观察性和指标。
解析器只是 Spring Bean,GraphQL 请求通过与系统其余部分相同的应用程序上下文传递。这意味着 GraphQL 不是一种外来技术,而是基于 Spring 的现有架构的自然扩展。
选择结合 Spring for GraphQL 的 Netflix DGS
Spring for GraphQL 是 GraphQL Java 和 Spring 生态系统之间的桥梁,它处理模式加载、请求执行,并在 Spring 内部自然地连接运行时。对于较简单的 GraphQL API,它可能就足够了。
然而,随着 API 的增长,其他因素开始发挥作用:模式生命周期管理、解析器组织、批处理、可测试性和长期演进。Netflix DGS 在这些方面发挥了重要作用。
DGS 基于 Spring for GraphQL,采用“优先模式”的方法,将 GraphQL 模式视为一等公民的产物,而不是从 Java 代码隐式派生出来的东西。这使得契约显式化、版本控制化,并随着时间的推移更容易安全地演进。
DGS 还对与数据检索相关的所有方面提供强有力的支持,包括批处理和缓存。虽然 DataLoader 在 Spring for GraphQL 中可用,但 DGS 提供了更清晰的约定和一等抽象,减少了 N+1 查询问题等性能问题的可能性。
从可维护性的角度来看,DGS 非常适合大型代码库。它在查询、变更和字段解析器之间的清晰分离,以及专门的测试工具和可选的代码生成,有助于在添加更多开发人员和客户端时保持 GraphQL API 的可管理性。
最后,DGS 在设计时考虑了联合 GraphQL 架构。即使联合架构不是当前的需求,选择一个不限制架构未来演进的框架通常是一个务实的决定。
项目设置
为了创建本文的原型,我们假设我们将使用:
- Java 25。
- Spring Boot 3.5。
- MongoDB。
- Maven。
依赖项
Netflix DGS 启动器包含了在 Spring Boot 上使用 GraphQL 所需的所有依赖项。我们还将使用 Spring Data 与 MongoDB 进行交互。
此时,应用程序已准备好加载 GraphQL 模式并对其执行查询和变更。
领域模型概览
我们将处理一个非常简单但真实的领域:
- 用户
- 订单
- 产品
一个用户可以下多个订单,每个订单可以有多个产品。选择这个模型是因为它突出了 GraphQL 最重要的方面之一,即高效解析关系的能力。
定义 GraphQL 模式
在 Netflix DGS 中,模式是起点。
需要强调一些设计选择:
- 非空性是显式的且有意的。
- 输入类型与输出类型是分开的。
- 关系是模式的一部分,而不是事后补充。
该模式已经比大多数 REST 契约更清晰地传达了 API 期望。
使用 MongoDB 进行持久化
使用 Spring Data MongoDB 进行持久化是传统的处理方式。
避免直接将持久化模型用作 GraphQL 类型是一种良好的做法。MongoDB 文档倾向于快速演进,并且通常包含不应公开的内部字段或非规范化数据。
专用的映射层保持了职责的分离,并使模式演进更加安全。
使用 Netflix DGS 进行查询解析
要将 GraphQL 查询转换为后端操作,您需要使用查询解析器。其职责是编排,而不是应用业务逻辑。
从这个片段中,我们注意到:
- 解析器是一个简单的 Spring Bean。
- 模式决定了 API 的结构,而不是方法签名。
- 业务逻辑保留在服务层中。
保持解析器的精简使它们更易于测试和理解。
变更与输入验证
变更代表写操作,应像 REST POST 和 PUT 端点一样受到同等程度的重视。
输入验证和验证可以通过以下方式处理:
- Bean Validation 注解。
- 解析器中的显式检查。
- 自定义 GraphQL 错误映射。
GraphQL 支持部分失败,因此验证错误必须精确且易于管理。
在 MongoDB 中解析关系
与关系数据库不同,MongoDB 不支持连接操作。在 GraphQL 中,关系是逐字段延迟解析的。
这种方法很强大,但它带有一个显著的缺点。
N+1 查询问题
这是 GraphQL 在演示中看起来很简单,但在生产环境中可能导致重大问题的案例之一。
像这样的查询...
...很容易导致:
- 查询用户。
- 查询与用户关联的订单。
该模型需要考虑解决方案的可扩展性。
在 Netflix DGS 中使用 DataLoader
DataLoader 使 GraphQL 能够对在单个请求中检索的相关数据进行分组和缓存。
然后,字段解析器变为异步的:
这将 N 次数据库调用转换为一次分组查询,对于非平凡的模式应被视为强制性的。
GraphQL 中的错误处理
GraphQL 允许部分成功:一个字段可能会失败,而其他字段继续返回数据。这需要与 REST 范式不同的方法。
在实践中:
- 将领域异常映射到 GraphQL 错误。
- 避免对外传达内部细节。
- 使用错误扩展来传递结构化元数据。
Netflix DGS 提供了钩子来一致地自定义错误处理。
安全考量
GraphQL 通过单个端点暴露了巨大的攻击面,这使得传统的基于端点的安全性变得不足。强大的安全策略通常结合多层防护:
- HTTP 级身份验证,由 Spring Security 处理(JWT, OAuth2)
- 方法级授权,应用于解析器或服务
- 针对敏感数据的字段级限制
- 查询深度和复杂度限制,以防止服务滥用
这些措施使 GraphQL 能够保持灵活性,而不会变得过于宽松。
何时 GraphQL 是(或不是)正确的选择
GraphQL 是一个非常强大的工具,但它并不适合所有用例。
它适用于以下情况:
- 多个前端客户端使用同一个 API。
- 数据可视化需求频繁变化。
- 领域模型丰富且相互关联。
它不适用于以下情况:
- 具有大量写入和高吞吐量的 API。
- 简单的 CRUD 服务。
- 流式或二进制数据。
选择 GraphQL 是一个架构决策,而不是一个自动的决策。
最佳实践回顾
在结束之前,总结一些已解释的概念很重要:
- 最好优先开发模式。
- 将模式视为公共契约。
- 保持解析器简单且专门化。
- 从一开始就使用 DataLoader。
- 将 API 模型与持久化模型分离。
- 测量并限制查询复杂度。
应用这些简单的实践比特定的框架更重要,并且往往决定了应用程序本身的成败。
结语
Spring for GraphQL 和 Netflix DGS 为 Java 开发人员提供了一个成熟的、可用于生产环境的堆栈,用于创建灵活的 API。当与 MongoDB 结合使用时,它们启用了富有表现力的数据访问模式,但前提是必须将性能、安全和模式设计视为优先事项。
GraphQL 并不旨在在所有地方取代 REST 范式。相反,它是一种有针对性的选择,在某些情况下,您希望使用一种支持更简单、更即时更改的抽象。如果使用得当,它可以成为后端和前端团队之间的共同语言,而不会牺牲 Java 开发人员所期望的健壮性和清晰度。
获取源代码以获取本文中描述的原型。
文章《GraphQL for Java Developers: Building a Flexible Data Layer》首发于 foojay。