G

GraphQL 中的图表

2025-06-04

GraphQL 中的图表

如今,GraphQL 已成为构建 API 的普遍选择。这项由 Facebook 开源的技术允许客户端只获取他们需要的数据,并在一个独特的查询接口下聚合请求。借助 GraphQL,我们可以构建更快的应用程序,消耗更少的数据,并利用强大的开发工具。自 GraphQL 发布以来,我一直对它着迷。然而,一个问题一直萦绕在我的心头:它是如何利用图的强大功能的? graphQL 中的图在接下来的文章中,我们将首先了解图、树和递归属性。了解这些知识后,我们将深入研究 GraphQL 的原始规范和服务器运行时的 JavaScript 实现。我们将把 GraphQL 的内部工作原理分解成最简单、最细小的部分,然后再将它们重新组合在一起。在此过程中,我们将揭示如何使用数据结构来创建这项改变了我们所知的 Web 的技术。

什么是图表?

早在 GraphQL 出现之前,图就已经存在了,但它们究竟是什么呢?图是一种数据结构,类似于我们构建心智模型和关联概念的自然方式。在图中,所表示实体之间的关系与实体本身一样重要。
图表示例我们使用称为 或顶点的抽象对象来构建图node。两个节点之间的连接称为。然后,我们可以按照特定顺序递归地edge探索。graphedges

A-循环有向图

根据节点和边的排列方式,图可以分为不同类型。我们现在将重点介绍无环有向图,因为 GraphQL 中就包含这类图。有向边有起点和终点,并且只能沿着该方向遍历。添加方向会edges改变节点之间关系的含义,并引入层次结构。有向图例如,假设我们想用图来表示贷款。每条边代表借入的资金,方向代表资金从贷款方流向借款方。有向图示例

从图到树

根据施加的约束,图可以转换为不同的数据结构。图的环路或电路是一组边,其中最后一条边也是第一条边。没有环路的图称为无环图。同样无环的方向图称为有向图tree

图到树

树结构由于其递归特性而具有诸多优势。a 的基本单位tree是一个root节点以及一个或多个children节点。如果我们将数据建模为 agraph并对其施加必要的约束,我们就可以利用tree其属性来处理它。虽然可以对 a 进行整体遍历,但通常在局部层面逐个节点地进行操作更容易。通过在节点上执行函数,然后在后续节点上递归执行,tree可以将读写操作扩展到 a 的整个长度treerootchildren

使用 Graph(QL) 建模

众所周知,在 中GraphQL,我们使用 来表示我们的业务领域schema。模式本身是graph由 组成的type,代表不同的实体。类型是使用领域驱动技术从问题空间中提取的。它们可以有不同的字段,并且每个 都field指向另一种类型。GraphQL 类型在上图中,您可以看到lastnamefirstnameemail指向scalar类型StringScalar类型没有任何子字段,它们代表query树的叶子。穿过模式的路径始终会解析为类似于 的结构化标量集合tree。大多数 GraphQL 实现允许开发人员使用自定义验证和序列化函数添加自己的标量scalars。 与其字段之间的关系type是单向边,是模式的构建块。这使得 GraphQL 模式成为一个 。正如我们之前提到acyclic directed graph,这种图可以像树一样读取,在称为树遍历 的过程中访问每棵树一次。GraphQL是图中的一条路径,从根类型到其子类型,直到到达没有子字段的标量类型。因此,a是 GraphQL 模式的某个子集到树的投影。在后端,每个类型的字段都映射到一个函数,该函数在查询时返回其值。结果是通过合并从模式中提取的每个字段的运行函数的结果来创建的。然而,GraphQL 并不止于此。属性和递归函数不仅用于建模数据,还主要用于验证和执行针对该模式的查询。GraphQL 模式和查询queryqueryresolverGraphQL 解析器queryresolverTree

模式解析

GraphQl 服务器在执行时解析模式文档。类型被提取并存储为纯 JavaScript 代码Objects,并引用其字段和解析器函数,这些引用存储在名为 的字典typeMap中。当需要解析某个字段时,执行算法会在字典中查找该字段,并使用resolver函数及其子类型的引用来构建其值。



// Simplified structure of the type map
let typeMap = {
  rootType: {
    fields: { // array with the fields of the root ype
      user: {
        type: {
          fields: {
            lastname: {...},
            settings: {...},
          }
        },
        resolve: () => ({})  // points to a resolve function for the type
      },
      settings: {
        type: {
          fields: {
            membership: {...},
          }
        },
        resolve: () => ({})  // points to a resolve function for the type
      }
    }
  },
};


Enter fullscreen mode Exit fullscreen mode

由于每个都type包含对其功能的引用resolver,因此可以通过重复三个步骤来解析整个模式:

  1. typetypeMap字典中检索
  2. 运行其resolver功能
  3. 重复同样的field操作type

总结一下:GraphQL 模式文档在服务器上解析。在解析过程中,提取类型并将其与函数引用一起存储resolver在一个名为 的字典中typeMap。由于该字典具有树状结构,因此可以使用遵循不同遍历的递归函数进行读写。

查询解析

GraphQL 服务器将每个查询解析为string抽象语法树 (AST)。AST 是特定语言源代码语法的树形表示。树中的每个节点代表 中的一条语句query,包括其类型、参数和位置。

抽象语法树AST编译器的常见抽象,用于在称为语义分析的过程中验证语法的正确性。同样,由于其树状结构,AST可以通过递归函数进行处理和解释。这个过程是queryGraphQL 编辑器通常提供的验证功能的背后支撑。

查询执行

一旦query将操作转换为AST并且其结构经过验证,我们就可以使用tree属性来执行query。执行算法的核心是一个递归函数,它按照深度优先搜索顺序在查询树的每个节点上运行。 遍历确保字段以稳定一致的顺序执行和解析。按照一阶遍历,将按照以下顺序在每个字段上调用字段执行函数:函数包含字段值解析背后的魔力,在GraphQL 规范中有很好的描述。函数参数是正在运行的 ,来自字典的该类型的定义函数。首先,算法执行函数并存储返回值。接下来,它根据其 完成字段值。如果字段类型是,则使用序列化函数简单地“强制”其值并直接返回。如果字段类型是 ,则启动该过程。该函数会将相应对象类型上所有尚未解析的子字段组合起来,并返回一个遵循深度 优先搜索 风格的有序数组。然后,对收集到的每个子字段并行递归运行。最后,该算法会合并并强制转换函数首次执行返回的值,并根据查询树中的顺序构建最终结果

查询树遍历

遍历结果executeFieldnametypetypeMapresolverresolvertypescalarGraphQL 字段执行算法ObjectcompleteValuecollectFieldsresolverfieldGrouparrayexecuteFieldresolvercompleteValueAST

上述解析算法是对 GraphQL 规范的简化。正确的error处理和响应构建会使实际实现更加棘手。将查询解析为树状结构可以利用递归特性简化解析算法,并确保对任何形状和大小的模式的查询字段执行的一致性。

总结

图是 GraphQL 成为构建和使用 API 的绝佳选择的核心原因。一方面,图允许开发者使用方向关系和层次结构以自然的方式对数据进行建模。GraphQL Schema 是基于自然语言的问题空间的直接表示。

另一方面,GraphQL 利用 AST 树的递归特性来验证和执行查询。查询树的深度一阶遍历实现了稳定且可预测的并行数据提取。查询的递归特性使得 GraphiQL 和 Apollo Client 等工具能够快速开发,这些工具利用它来进行客户端查询验证、缓存和缓存失效。

最后的想法

要构建卓越的软件,我们需要对所使用的工具有基本的了解。复杂的技术通常由一些简单的部件和谐地组合在一起构成。GraphQL 的核心抽象是图。这是一个线性代数概念,用于以非线性和分层的方式表示信息,或者简单地说:我们日常是如何思考信息的。

更令人着迷的是,在任何技术的核心中,我们都发现人类以令人难以置信的方式自然地解决问题。

最初发表于bogdanned.com

文章来源:https://dev.to/bogdanned/the-graph-in-graphql-1l99
PREV
在 Go 中实现清洁架构
NEXT
node vs deno 关于 Deno 和 Node 的未来