Java中的GraphQL服务器:第二部分:了解解析器

简介: 在第一部分中,我们开发了一个非常简单的GraphQL服务器。该解决方案有一个严重的缺陷:所有字段都急切地加载到后端,即使前端未要求也是如此。通过不给客户任何选择,我们通过RESTful服务来接受这种情况。RESTful API始终返回所有内容,这意味着始终加载所有内容。另一方面,如果将RESTful API分为多个较小的资源,则可能会面临N + 1问题和多次网络往返的风险。

第二部分:了解解析器


在第一部分中,我们开发了一个非常简单的GraphQL服务器。该解决方案有一个严重的缺陷:所有字段都急切地加载到后端,即使前端未要求也是如此。通过不给客户任何选择,我们通过RESTful服务来接受这种情况。RESTful API始终返回所有内容,这意味着始终加载所有内容。另一方面,如果将RESTful API分为多个较小的资源,则可能会面临N + 1问题和多次网络往返的风险。GraphQL是专门为解决以下问题而设计的:

  • 仅获取必需的数据,以避免额外的网络流量以及后端不必要的工作
  • 允许在单个请求中获取客户端所需的尽可能多的数据,以减少总体延迟

RESTful API可以任意决定要返回多少数据,因此几乎无法解决上述问题。它要么获取过多,要么获取不足。好的,这是理论,但是我们对GraphQL服务器的实现不能以这种方式工作。无论是否请求,它仍将获取所有数据。伤心。


不断发展您的API


回顾一下我们的API返回一个实例PlayerDTO:

@Value
class Player {
    UUID id;
    String name;
    int points;
    ImmutableList<Item> inventory;
    Billing billing;
}

与此GraphQL模式匹配的:

type Player {
    id: String!
    name: String!
    points: Int!
    inventory: [Item!]!
    billing: Billing!
}

通过仔细地分析我们的应用程序,我意识到很少有客户billing在他们的查询中提出要求,但是我们必须始终提出要求billingRepository才能创建Player实例。许多急切的,不需要的工作:

private final BillingRepository billingRepository;
private final InventoryClient inventoryClient;
private final PlayerMetadata playerMetadata;
private final PointsCalculator pointsCalculator;
//...
@NotNull
private Player somePlayer() {
    UUID playerId = UUID.randomUUID();
    return new Player(
            playerId,
            playerMetadata.lookupName(playerId),
            pointsCalculator.pointsOf(playerId),
            inventoryClient.loadInventory(playerId),
            billingRepository.forUser(playerId)
    );
}


像这样的字段billing只能在请求时加载!为了了解如何使我们的对象的某些部分_图_(Graph-QL是也)懒加载,让我们添加一个名为新特性trustworthinessPlayer上:

type Player {
    id: String!
    name: String!
    points: Int!
    inventory: [Item!]!
    billing: Billing!
    trustworthiness: Float!
}


此更改是向后兼容的。事实上,GraphQL实际上没有API版本控制的概念。那么迁移路径是什么?有以下几种情况:

  • 您错误地将新架构提供给客户端,而没有实现服务器。在这种情况下,客户端会快速失败,因为它请求trustworthiness服务器尚未交付的字段。好。另一方面,使用RESTful API,客户端认为服务器将返回一些数据。这可能会导致意外错误或服务器故意返回的假设null(丢失字段)
  • 您添加了trustworthiness字段,但没有分发新的架构。还行吧。客户不知道,trustworthiness所以他们不要求它。
  • 服务器准备就绪后,便将新架构分发给客户端。客户端可能会或可能不会使用新数据。没关系。

但是,如果您犯了一个错误并向所有客户端宣布服务器的新版本支持某些架构而实际上却不支持该架构,该怎么办?换句话说,服务器假装为support trustworthiness,但是在被询问时它不知道如何计算。这有可能吗?

Caused by: [...]FieldResolverError: No method or field found as defined in schema [...] with any of the following signatures [...], in priority order:
  com.nurkiewicz.graphql.Player.trustworthiness()
  com.nurkiewicz.graphql.Player.getTrustworthiness()
  com.nurkiewicz.graphql.Player.trustworthiness

这是在服务器启动时发生的!如果您在不实施底层服务器的情况下更改了架构,它甚至将无法启动!这真是个好消息。如果您宣布支持某些架构,则不可能发布不支持该架构的应用程序。改进API时,这是一个安全网。仅当服务器支持架构时,才将架构交付给客户端。并且,当服务器宣布某些架构时,您可以100%确保其正常工作并正确格式化。响应中没有其他缺少的字段,因为您正在询问服务器的旧版本。不再有假装支持某些API版本的损坏服务器,而实际上,您忘记了将字段添加到响应对象。


用懒加载的Resolver代替贪婪加载值


好了,那么我该如何添加trustworthiness以遵守新的架构?在_不那么聪明的_技巧是正确的,在该阻止我们的应用程序启动异常。它说它正在尝试寻找方法,获取器或字段trustworthiness。如果我们盲目地将其添加到Player类中,API将会起作用。那是什么问题 请记住,在更改架构时,旧客户端不知道trustworthiness。即使知道这一点的新客户,也可能永远不会或很少要求它。换句话说,trustworthiness只需要为一小部分请求计算出值。不幸的是,因为trustworthiness是现场上Player课,我们必须总是热切计算。否则,无法实例化并返回响应对象。有趣的是,使用RESTful API,这通常不是问题。只需加载并返回所有内容,让客户决定忽略什么。但是我们可以做得更好。

首先,trustworthinessPlayerDTO中删除字段。我们必须更深入,我是说懒惰。而是,创建以下组件:

import com.coxautodev.graphql.tools.GraphQLResolver;
import org.springframework.stereotype.Component;
@Component
class PlayerResolver implements GraphQLResolver<Player> {
}

保持空白,GraphQL引擎将指导我们。当尝试再次运行该应用程序时,该异常很熟悉,但并不相同:

FieldResolverError: No method or field found as defined in schema [...] with any of the following signatures [...], in priority order:
  com.nurkiewicz.graphql.PlayerResolver.trustworthiness(com.nurkiewicz.graphql.Player)
  com.nurkiewicz.graphql.PlayerResolver.getTrustworthiness(com.nurkiewicz.graphql.Player)
  com.nurkiewicz.graphql.PlayerResolver.trustworthiness
  com.nurkiewicz.graphql.Player.trustworthiness()
  com.nurkiewicz.graphql.Player.getTrustworthiness()
  com.nurkiewicz.graphql.Player.trustworthiness

trustworthiness不仅在Player类上,而且还在PlayerResolver我们刚刚创建的上寻找。您能发现这些签名之间的区别吗?

  • PlayerResolver.getTrustworthiness(Player)
  • Player.getTrustworthiness()

前一种方法Player作为参数,而后一种方法Player本身就是实例方法(getter)。目的是PlayerResolver什么?默认情况下,GraphQL模式中的每种类型都使用默认解析器。该解析器基本上采用例如的实例, Player并检查吸气剂,方法和字段。但是,您可以使用更复杂的默认解析器来装饰该默认解析器。一种可以懒惰地计算给定名称的字段。尤其是在Player课堂上没有这样的领域时。最重要的是,仅当客户端实际请求该字段时才调用该解析程序。否则,我们将退回到默认解析器,该解析器期望所有字段都属于Player对象本身。那么,您如何实现自定义解析器trustworthiness呢?该异常将指导您:

@Component
class PlayerResolver implements GraphQLResolver<Player> {
    float trustworthiness(Player player) {
        //slow and painful business logic here...
        return 42;
    }
}

当然,在现实世界中,实现会做一些聪明的事情。采取Player,应用一些业务逻辑,等等。真正重要的是,如果客户不想知道trustworthiness,永远不会调用此方法。懒!通过添加一些日志或指标来自己查看。是的,指标!这种方法还使您可以深入了解自己的API。客户非常明确,只询问必填字段。因此,您可以为每个解析器获取指标,并快速找出哪些字段已使用,哪些字段无效以及可以弃用或删除。另外,您可以轻松地发现哪个特定字段的加载成本很高。使用RESTful API的全有或全无的方法,这种细粒度的控制是不可能的。为了使用RESTful API停用字段,您必须创建资源的新版本并鼓励所有客户端迁移。


懒惰的一切


如果您想变得更加懒惰并消耗尽可能少的资源,则Player可以将的每个单个字段委派给解析器。模式保持不变,但Player类变为空心:

@Value
class Player {
    UUID id;
}

那么,如何GraphQL知道如何计算namepointsinventorybillingtrustworthiness?好吧,解析器上有一个方法可以解决以下每个问题:

@Component
class PlayerResolver implements GraphQLResolver<Player> {
    String name(Player player) {
        //...
    }
    int points(Player player) {
        //...
    }
    ImmutableList<Item> inventory(Player player) {
        //...
    }
    Billing billing(Player player) {
        //...
    }
    float trustworthiness(Player player) {
        //...
    }
}

实现并不重要。重要的是惰性:只有在请求某些字段时才调用这些方法。这些方法中的每一个都可以分别进行监视,优化和测试。从性能角度来看,这很棒。


性能问题


您是否注意到inventorybilling字段互不相关?即,获取inventory可能需要调用某些下游服务,而billing需要SQL查询。不幸的是,GraphQL引擎将响应组合成一个顺序。我们将在下一期中解决此问题,敬请期待!

相关文章
|
8月前
|
存储 域名解析 弹性计算
阿里云上云流程参考:云服务器+域名+备案+域名解析绑定,全流程图文详解
对于初次通过阿里云完成上云的企业和个人用户来说,很多用户不仅是需要选购云服务器,同时还需要注册域名以及完成备案和域名的解析相关流程,从而实现网站的上线。本文将以上云操作流程为核心,结合阿里云的活动政策与用户系统梳理云服务器选购、域名注册、备案申请及域名绑定四大关键环节,以供用户完成线上业务部署做出参考。
|
10月前
|
网络协议
利用Private Zone DNS - 搭建AD但不搭建DNS服务器如何加域
利用Private Zone DNS - 搭建AD但不搭建DNS服务器如何加域
利用Private Zone DNS - 搭建AD但不搭建DNS服务器如何加域
|
网络协议 安全 Linux
阿里云服务器国际站dns服务器不可用怎么办?dns可以随便改吗?
阿里云服务器国际站dns服务器不可用怎么办?dns可以随便改吗?
4884 0
|
存储 缓存 网络协议
阿里云特惠云服务器99元与199元配置与性能和适用场景解析:高性价比之选
2025年,阿里云长效特惠活动继续推出两款极具吸引力的特惠云服务器套餐:99元1年的经济型e实例2核2G云服务器和199元1年的通用算力型u1实例2核4G云服务器。这两款云服务器不仅价格亲民,而且性能稳定可靠,为入门级用户和普通企业级用户提供了理想的选择。本文将对这两款云服务器进行深度剖析,包括配置介绍、实例规格、使用场景、性能表现以及购买策略等方面,帮助用户更好地了解这两款云服务器,以供参考和选择。
|
存储 弹性计算 安全
阿里云服务器ECS通用型规格族解析:实例规格、性能基准与场景化应用指南
作为ECS产品矩阵中的核心序列,通用型规格族以均衡的计算、内存、网络和存储性能著称,覆盖从基础应用到高性能计算的广泛场景。通用型规格族属于独享型云服务器,实例采用固定CPU调度模式,实例的每个CPU绑定到一个物理CPU超线程,实例间无CPU资源争抢,实例计算性能稳定且有严格的SLA保证,在性能上会更加稳定,高负载情况下也不会出现资源争夺现象。本文将深度解析阿里云ECS通用型规格族的技术架构、实例规格特性、最新价格政策及典型应用场景,为云计算选型提供参考。
|
Java Linux 定位技术
Minecraft配置文件参数说明(JAVA服务器篇)
Minecraft JAVA版服务器启动后会生成server.properties配置文件,位于minecraft_server/根目录下。该文件包含多项关键设置,如游戏模式(gamemode)、最大玩家数(max-players)、难度(difficulty)等。此文档详细说明了各配置项的功能与默认值,帮助用户高效管理服务器环境。
3691 60
|
存储 机器学习/深度学习 人工智能
阿里云服务器第八代通用型g8i实例评测:性能与适用场景解析
阿里云服务器通用型g8i实例怎么样?g8i实例采用CIPU+飞天技术架构,并搭载最新的Intel 第五代至强可扩展处理器(代号EMR),不仅性能得到大幅提升,同时还拥有AMX加持的AI能力增强,以及全球范围内率先支持的TDX机密虚拟机能力。这些特性使得g8i实例在AI增强和全面安全防护两大方面表现出色,尤其适用于在线音视频及AI相关应用。本文将深入探讨g8i实例的产品特性、优势、适用场景及规格族,以帮助您更好地了解这款产品,以供参考和选择。
|
前端开发 Cloud Native Java
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
|
存储 缓存 负载均衡
阿里云服务器实例选择指南:热门实例性能、适用场景解析对比参考
2025年,在阿里云的活动中,主售的云服务器实例规格除了轻量应用服务器之外,还有经济型e、通用算力型u1、计算型c8i、通用型g8i、计算型c7、计算型c8y、通用型g7、通用型g8y、内存型r7、内存型r8y等,以满足不同用户的需求。然而,面对众多实例规格,用户往往感到困惑,不知道如何选择。本文旨在全面解析阿里云服务器实例的各种类型,包括经济型、通用算力型、计算型、通用型和内存型等,以供参考和选择。
|
存储 机器学习/深度学习 应用服务中间件
阿里云服务器架构解析:从X86到高性能计算、异构计算等不同架构性能、适用场景及选择参考
当我们准备选购阿里云服务器时,阿里云提供了X86计算、ARM计算、GPU/FPGA/ASIC、弹性裸金属服务器以及高性能计算等多种架构,每种架构都有其独特的特点和适用场景。本文将详细解析这些架构的区别,探讨它们的主要特点和适用场景,并为用户提供选择云服务器架构的全面指南。
1253 18

推荐镜像

更多
  • DNS