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是也)懒加载,让我们添加一个名为新特性trustworthiness到Player上:

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,这通常不是问题。只需加载并返回所有内容,让客户决定忽略什么。但是我们可以做得更好。

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

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知道如何计算name,points,inventory,billing和trustworthiness?好吧,解析器上有一个方法可以解决以下每个问题:

@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) {
        //...
    }
}

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


性能问题


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

相关文章
|
机器学习/深度学习 JSON Java
Java调用Python的5种实用方案:从简单到进阶的全场景解析
在机器学习与大数据融合背景下,Java与Python协同开发成为企业常见需求。本文通过真实案例解析5种主流调用方案,涵盖脚本调用到微服务架构,助力开发者根据业务场景选择最优方案,提升开发效率与系统性能。
2389 0
|
存储 运维 安全
阿里云服务器2核4G与4核8G配置解析:实例规格、价格及选择指南
阿里云服务器2核4G和4核8G配置凭借其均衡的性能与性价比,成为中小企业及开发者群体的热门选择。通用算力型u1实例2核4G5M带宽80G ESSD Entry云盘特惠价199元1年,个人用户最低531.79元1年,4核8G配置价格最低的是通用算力型u1实例,活动价格为955.58元1年起,除此之外,还有计算型c7、c9i、c8y等计算型实例有2核4G与4核8G配置可选。具体配置还需要看想要购买的云服务器实例规格和配置及带宽大小。本文将结合2025年最新活动价格,深度解析阿里云服务器2核4G与4核8G配置的实例规格、适用场景及选择策略,以供参考。
|
12月前
|
存储 机器学习/深度学习 人工智能
硅谷GPU单节点服务器:技术解析与应用全景
“硅谷GPU单节点服务器”代表了在单个物理机箱内集成强大计算能力,特别是GPU加速能力的高性能计算解决方案。它们并非指代某个特定品牌,而是一类为处理密集型工作负载而设计的服务器范式的统称。
Java的CAS机制深度解析
CAS(Compare-And-Swap)是并发编程中的原子操作,用于实现多线程环境下的无锁数据同步。它通过比较内存值与预期值,决定是否更新值,从而避免锁的使用。CAS广泛应用于Java的原子类和并发包中,如AtomicInteger和ConcurrentHashMap,提升了并发性能。尽管CAS具有高性能、无死锁等优点,但也存在ABA问题、循环开销大及仅支持单变量原子操作等缺点。合理使用CAS,结合实际场景选择同步机制,能有效提升程序性能。
|
存储 弹性计算 运维
阿里云服务器ECS是什么?你真的了解吗?云服务器解析
阿里云服务器ECS是阿里云提供的虚拟服务器服务,用户可按需租赁使用,无需自建机房。它具备高可用、高安全、弹性伸缩、节约成本等优势,适用于网站搭建、数据应用、运维测试等多种场景。本文详解ECS组成、架构、使用方法及与传统服务器的区别。
871 1
|
存储 弹性计算 网络协议
阿里云服务器ECS自定义购买流程:亲测图文全解析
本文详细图解阿里云ECS自定义购买全流程,涵盖付费模式、地域选择、实例规格、镜像、存储、网络、安全组及登录设置等核心配置,助您轻松掌握专业级云服务器搭建方法。
365 0
|
Java 开发者
Java并发编程:CountDownLatch实战解析
Java并发编程:CountDownLatch实战解析
692 100
|
12月前
|
弹性计算 定位技术 数据中心
阿里云服务器配置选择方法:付费类型、地域及CPU内存配置全解析
阿里云服务器怎么选?2025最新指南:就近选择地域,降低延迟;长期使用选包年包月,短期灵活选按量付费;企业选2核4G5M仅199元/年,个人选2核2G3M低至99元/年,高性价比爆款推荐,轻松上云。
1267 11

热门文章

最新文章

推荐镜像

更多
  • DNS