我们可以从GraphQLConf 中学到什么?

147 阅读11分钟

模式拼接:丰富无头GraphQL架构中的数据

在这个视频中,Roy Derks向我们介绍了通过以下方式使用模式缝合的例子@graphql-tools/stitch的例子,并提供了一个带有演示代码的GitHub repo

模式拼接是将不同服务的GraphQL模式组合成单一的、统一的GraphQL模式的艺术。其目的是产生一个网关服务,我们可以从中访问我们公司的所有服务。

模式缝合最早是由Apollo引入的,但他们决定停止使用,转而使用Apollo Federation。最近,The Guild接手了这个概念,重新实施并改进了以前的设计,产生了一个解决方案,提供了与联盟相当的好处,同时提供了一个更简单的思考问题的方法。

罗伊使用模式缝合来演示如何结合两个外部服务的数据。

  1. 一个内容管理系统,可(作为一个模拟)在下面使用localhost:3001
  2. 一个电子商务的API,可在下面找到(作为模拟)。localhost:3002

这两个数据源也与本地GraphQL服务器的模式相结合,可在localhost:3000

Accessing a unified GraphQL schema — Screenshot from Roy Derks' talk

访问统一的GraphQL模式--来自Roy Derks演讲的屏幕截图

采用的堆栈是基于。

在Next.js下创建了一个API路由,它暴露了GraphQL终端。 api/graphql.js下创建了一个来自Next.js的API路由,它在localhost:3000/api/graphql 下暴露了GraphQL端点。我们可以通过GraphQL Playground与之互动,在浏览器中打开端点的URL。

An image of the GraphQL Playground

GraphQL Playground的图片

API路由中合并模式的相关代码,以其最简单的形式,如下所示。

let localSchema = makeExecutableSchema({
    // Code here to create the local GraphQL schema
});

export default async function grapqhl(req, res) {
  // Setup subschema configurations
  const localSubschema = { schema: localSchema };

  const cmsSubschema = await createRemoteSchema({
    url: 'http://localhost:3001/graphql/'
  });

  const productsSubschema = await createRemoteSchema({
    url: 'http://localhost:3002/graphql/',
  });

  // Build the combined schema and set up the extended schema and resolver
  const schema = stitchSchemas({
    subschemas: [localSubschema, productsSubschema, cmsSubschema]
  });
}

这段代码使用makeExecutableSchema 来设置本地GraphQL模式,使用createRemoteSchemalocalhost:3001/graphqllocalhost:3002/graphql 来加载远程GraphQL模式,最后使用stitchSchemas 将它们合并在一起。

接下来,我们需要将这些模式相互联系起来,这样我们就可以通过帖子的用户ID(由外部CMS提供)来检索产品(由电子商务API提供)。

{
  cms_allPosts { # This data comes from the CMS
    id
    User {
      name
      Products { # This data comes from the e-commerce API
        id
        title
      }
    }
  }
}

组合模式是在stitchSchemas 中配置的。

// Build the combined schema and set up the extended schema and resolver
const schema = stitchSchemas({
  subschemas: [localSubschema, productsSubschema, cmsSubschema],
  typeDefs: `
    extend type Product {
      cmsMetaData: [Cms_Product]!
    }
  `,
  resolvers: {
    Product: {
      cmsMetaData: {
        selectionSet: `{ id }`,
        resolve(product, args, context, info) {
          // Get the data for the extended type from the subschema for the CMS
          return delegateToSchema({
            schema: cmsSubschema,
            operation: 'query',
            fieldName: 'cms_allProducts',
            args: { filter: { id: product.id } },
            context,
            info,
          });
        },
      },
    },
  },
});

最后,我们必须检查不同的模式是否在相同的类型或字段名下暴露其数据。例如,电子商务API也可能有一个叫做Post 的类型,和/或在字段allPosts 下公开,从而产生冲突。

避免这些冲突是通过createRemoteSchema'stransforms 参数完成的,它允许我们将一个模式中的类型和字段重命名为另一个模式。在这种情况下,我们让CMS的子模式将其类型Post 改为Cms_Post ,字段allPosts 改为cms_allPosts

const cmsSubschema = await createRemoteSchema({
  url: 'http://localhost:3001/graphql/',
  transforms: [
    new RenameRootFields(
      (operationName, fieldName, fieldConfig) => `cms_${fieldName}`,
    ),
    new RenameTypes((name) => `Cms_${name}`),
  ],
});

我们最终可以执行一个查询,从所有独立的服务中获取数据,通过一个单一的、统一的模式进行访问。

Executing a GraphQL query from a merged schema

从合并的模式中执行GraphQL查询 - Roy Derks演讲中的截图

如前所述,如果你想查看带有代码的 repo观看完整的 GraphQLConf 视频,这里有一些链接。

带有数据联合的GraphQL与GraphQL服务的联合

Tanmai Gopal是Hasura的联合创始人和首席执行官,该服务通过Postgres提供实时的GraphQL APIs。

Hasura目前正在建立他们自己的联盟版本,它是基于与Apollo Federation不同的架构。在他的演讲中,Tanmai向我们介绍了Hasura的联盟解决方案试图解决的所有挑战,以及为什么Apollo联盟的方法不能解决这些问题。

Tanmai首先描述了什么是联盟,以及何时和为何使用联盟是合理的。

What is federation in the GraphQL context? A slide from Tanmai Gopal's talk

Why should we even have federation? A slide from Tanmai Gopal's talk

Good signs for needing a unified GraphQL API

然后,Tanmai深入探讨了关于联盟的两种不同思考方式。

  1. GraphQL服务的联盟(Apollo所遵循的方法),它让每个底层来源执行其查询的部分
  2. 联合数据的GraphQL,它在解决查询之前集中了来自不同服务的数据。

Federating GraphQL services vs. GraphQL on federated data

然后,Tanmai解释说,虽然这两种不同的方法产生了相同的结果,但当使用GraphQL on federated data的方法时,在性能上有相当大的提升。

A massive impact on performance

Tanmai提供了几个查询的例子来描述为什么联合GraphQL服务不能很好地扩展。他强调,跨不同服务的数据聚合不能有效地完成,因为在解决包含跨服务的跨数据库连接的复杂查询时,网关对涉及的子域的所有实体没有一个统一的上下文。

Example 2: Top N Queries

最后,Tanmai解释说,Hasura正在研究一个基于使用GraphQL与联合数据的解决方案,它能够从不同的服务中获取所有相关数据,并从一个集中的位置解决查询,而不是让每个服务解决其查询的一部分,从而为复杂的查询提供更好的性能。

Faster performance

为了比较Apollo和Hasura的联盟方法,我们需要再等一等。Tanmai承诺这个新功能将很快推出,但没有提到具体时间。Hasura的网站也没有谈及这个问题,而且还没有任何文档。目前,我们只有这次会议的谈话。

然而,我想象Hasura的方法比Apollo的更有限制性,因为它很可能依赖于Hasura的基础设施来运作,其吸引力主要取决于Hasura的服务价格。另一方面,Apollo联盟被设计为一种基于指令分割图的方法,所以它不需要任何外部工具或基础设施(尽管Apollo也提供管理的联盟),使用GraphQL中预先存在的语法即可工作。

最后,Hasura的联盟方法应该与GraphQL工具的模式缝合进行对比,正如我们在之前的演讲中看到的,它可以以更简单的方式提供相同的结果。

在这里观看视频

迁移GitHub的全局ID

Andrew Hoglund是GitHub的API团队的一名高级软件工程师。在他的演讲中,Andrew分享了他的团队目前如何将GitHub的GraphQL API中所有实体的全局标识符迁移到不同的格式,他们为什么要这样做,以及他们所遇到的挑战。

我们可以通过查询任何对象上的字段id ,来可视化全局ID的格式。例如,获取 leoloso/PoP存储库对象的ID,可以通过这个GraphQL查询完成。

{
  user(login: "leoloso") {
    repository(name: "PoP") {
      id
    }
  }
}

执行查询,我们得到了这个响应。

{
  "data": {
    "user": {
      "repository": {
        "id": "MDEwOlJlcG9zaXRvcnk2NjcyMTIyNw=="
      }
    }
  }
}

仓库对象的ID是MDEwOlJlcG9zaXRvcnk2NjcyMTIyNw== 。这个格式有以下属性。

  • 它是base64编码的
  • 包含对象类型和对象ID
  • 它的目的是不透明的

解码ID,例如通过Bash命令,将显示出存储的信息。

$ echo `echo MDEwOlJlcG9zaXRvcnk2NjcyMTIyNw== | base64 --decode`
010:Repository66721227

底层数据,010:Repository66721227 ,是由以下部分组成。

  • 一个校验和
  • 对象类型
  • 数据库ID

The current format of GitHub's global IDs

这种简单的格式最初为GitHub提供了很好的服务,因为GitHub将其数据存储在一个单一的数据库中。实体的全局ID已经提供了在数据库中找到该实体并检索其数据所需的所有信息。

The entity global ID could be used to locate items in the database

一段时间后,GitHub使用Vitess迁移到分片数据库,这是数据库内数据的水平分区。数据库分片可以提高性能,因为通过将所有数据分散在多个数据库中,每个数据库表的行数会减少--从而减少索引大小,使搜索速度加快--而且不同的分片可以放在不同的机器上,这样就可以为不同的数据片优化硬件。

当查询分片数据库时,全局ID格式变得不适合,尽管它仍然可以用来检索数据,因为实体的数据只存在于一个数据库中,而不是所有分片中,全局ID不会指出数据位于哪个数据库中。

为了定位数据,GitHub的GraphQL API必须对所有的数据库进行查询,而其中只有一个数据库会产生匹配,这使得查询的执行效率非常低。

The query execution is ineffective

因此,GitHub的API团队决定将全局ID迁移到一种新的格式,同时提供包含实体数据的数据库名称,这样就可以再次有效地进行检索。

GitHub decided to migrate their global IDs to a new format containing the database name for each entity

这种新格式比以前的格式更复杂。它由两个元素组成。

  • 一个类型提示,表明它是什么类型的实体
  • 一个所有权方案,它包含检索该类型实体所需的数据

The new format for GitHub's global IDs

所有权方案是按实体定制的,因为不同的实体需要不同的数据来识别。例如,一个工作流的运行需要触发它的拉动请求的ID。

这种新的格式运行良好,使GitHub能够解决当前的问题,并预见到未来的一些问题。特别是,GitHub最终可能会以多区域的设置来存储数据,而新的格式也可以表明存储数据的区域名称。

由于这种新的ID格式与之前的格式不兼容,GitHub需要实施一个缓慢的、渐进的推广,以确保它不会破坏服务。为了完成这项任务,GitHub设置了一个废弃期,在此期间两种ID格式将共存,并创建了一些工具来帮助服务从旧格式迁移到新格式。

A progressive rollout of the new global IDs will avoid breaking GitHub services

你可以观看视频以了解更多。

用GraphQL推动电子商务的发展

Stuart Guest-Smith是BigCommerce的主要架构负责人。在他的演讲中,Stuart探讨了商家如何利用GraphQL构建高性能、可扩展和个性化的电商体验。

Stuart在演讲开始时宣称。

电子商务要求企业做到快速、灵活和个性化。GraphQL在很大程度上使之成为可能。

一个电子商务服务通常会跨越多个软件,其中包括。

  • 企业资源规划(ERP)
  • 订单管理系统(OMS)
  • 产品信息管理(PIM)
  • 客户关系管理(CRM)
  • 内容管理系统(CMS)

GraphQL使我们能够访问所有这些后端系统的数据并将其联系起来,实现电子商务的最新趋势之一,即 "可组合商务"。

Modern commerce is composable

来自这些多个后端系统的数据可以通过GraphQL API访问不同的前端,例如。

  • 一个电商店面
  • 一个原生移动应用程序
  • 一个无头的CMS
  • 一个无头的数字体验平台(DXP)
  • 一个定制的渐进式网络应用程序(PWA)店面

这种灵活性使我们能够为用户创造一种个性化的、增强的体验。

Build differentiated shopper experiences with GraphQL

然后,Stuart解释说,GraphQL可以通过使用BfF架构模式作为多个电子商务后端和多个前端之间的接口,其目的是为每个用户提供定制的后端体验。

Using the BfF model

Tame the commerce class with a unified backend in GraphQL

GraphQL可以帮助提高性能,因为它可以只检索所需的数据,而不进行欠取或过取。但除此之外,我们必须添加一个缓存层,并管理查询的复杂性(或冒着让恶意行为者执行昂贵的查询,从而拖慢系统的风险)。

Scalable and performance commerce

最后,Stuart解释说,GraphQL为电子商务带来的最重要的好处之一是客户体验的个性化。

Delivering personalized customer experiences with GraphQL

你可以在这里观看 完整的 视频

总结

GraphQLConf 2021为我们带来了GraphQL生态系统目前正在讨论的内容,表明在GraphQL推出五年后,仍有大量的发展在发生,新的方法论正在创建,以解决社区的持续需求。

这篇文章总结了关于四个不同的新发展的谈话。

  • Roy Derks解释了如何使用新的@graphql-tools/stitch 库进行模式拼接
  • Tanmai Gopal展示了Hasura是如何创建Apollo Federation的竞争者服务的。
  • Andrew Hoglund分享了GitHub GraphQL API的全局ID格式如何被迁移到一个新的格式中,该格式可以支持数据库分片和多区域设置。
  • Stuart Guest-Smith解释了如何利用GraphQL,使用BfF模式,实现可组合的商务和个性化的用户体验。

尽管GraphQL已经很成熟了,但看到新的开发正在发生,还是非常令人激动的要知道还有什么正在发生,请查看GraphQLConf 2021的所有视频