Advertisement

你真的理解 GraphQL 了吗?

阅读量:

💡GraphQL 为什么要叫 GraphQL,其中的 Graph 体现在什么地方❓

GraphQL 的查询语句,看起来是 JSON 写法的一种简化。而 JSON 是一个 Tree 树形数据结构。为什么不叫 TreeQL,而是 GraphQL 呢?

Tree 🆚 Graph

那我们先来看看 什么是 Tree,什么是 Graph,它们有什么关系?

Tree 的结构示意图:

Tree 有且只有一个 Root 节点,并且对于每个非 Root 节点,有且只有一个父节点;它们组成了一个层次结构。其中任意两个节点,有且只有一条连接路径;没有循环,也没有递归引用。

Graph 的结构示意图:

而 Graph 里的节点之间,可能存在不只一种连接路径,可能存在循环,可能存在递归引用,可能没有 Root 节点。它们组成了一个网络结构。

我们可以把 Graph 这种网络结构,通过裁剪连接路径,把它压缩成任意节点只有唯一连接路径的简化形态。如此网络结构退化成层次结构,它变成了 Tree。

也就是说,Graph 是比 Tree 更复杂的数据结构,后者是它的简化形式。 拥有 Graph,我们可以按照不同的裁剪方式,衍生出不同的 Tree。而 Tree 里包含的信息,如果不增加其它额外数据,不足以构建足够复杂的 Graph 结构。

GraphQL 里的 Graph 结构

在 GraphQL 里,承担 构建网络结构 的,并非 GraphQL 查询语句,而是基于 GraphQL Type System 构建的 Schema

上图是一个 GraphQL Schema定义了 A, B, C, D 和 E 五种数据类型,它们分别挂载到 入口类型 Query 里的 a, b, c, d 和 e 字段里

A, B, C, D, E 里面,包含着递归的结构。A 里面包含 B 和 C,B 里面包含 C 和 D,……,E 里面又包含 A,又回到了 A。

有了这个基于数据类型的 Graph 关系网络,我们可以实现从 Graph 中派生出 JSON Tree 的能力。

上图是一个 GraphQL 的查询语句,它是一个包含很多 key 的层次结构,亦即一个 Tree。

它从根节点里取 a 字段,然后向下分层,找到了 e。而 e 节点里也包含一个跟根节点同类型的 a 字段,因此它可以继续向下分层,重来一遍,又到了 e 节点,此时它只取了 data 字段,查询中止。

用一个简单的 Resolver 函数,用来演示查询结果:

Query 里返回跟字段名一样的字母,任何子节点的数据,都是拼接父节点的字母串。如此我们可以从查询结果看出数据流动的层次。

查询结果如下:

第一个 e 节点的 data 字段里,拿到了父节点里的 data 数据,其父节点的 data 数据又是通过它的父节点里获取的,因此有一个数据链条。

而第二个 e 节点同理,它有两段链条。

只要不编写后续字段,我们可以停留在任意节点的 data 字段里。

也就是说,我们用作为 Tree 的 Query 语句,去裁剪了作为 Graph 的 Schema 数据关联网络,得到了我们想要的 JSON 结构。

通过这个角度,我们可以理解为什么 GraphQL 不允许 Query 语句停留在 Object 类型,一定要明确的写出对象内部的字段,直到所有 Leaf Node 都是 Scalar 类型。

这不仅仅是一个所谓的最佳实践,这也是 Graph 本身的特征。对象节点里,可能通过循环或者递归关系,拓展出无限大的数据结构。Query 语句必须写清楚,才能帮助 GraphQL 去裁剪掉不必要的数据关联路径。

Graph 网络结构的实际价值

前面的 A, B, C, D, E 案例 ,并不能直观的让大家感受到,Graph 网络结构的实际价值。它看起来像一个连线游戏。

放到 Facebook 的社交网络场景下,其必要性和价值就凸显了。

假设我们要一次性获取用户的好友的好友的好友的好友的好友,利用 Graph 这种递归关联的结构,实现这种查询轻而易举。

我们定义了一个 User 类型,挂到 Query 入口上的 user 字段里。User 类型的 friends 字段又是一个 User 类型的列表。这样就构建了一个递归关联。

getFriends 查询语句,可以不断地从任意用户开始,关联其 friends,得到 friends 数组结果。任意一个 friend 也是 User,它也有自己的 friends。查询语句在最外层的 friends 停了下来,它只查询了 id 和 name 字段。

社交关系网络里使用 GraphQL 特别有效,不意味着其它场景下,GraphQL 不能带来收益。

设想一个电商平台的场景 ,它有用户、产品和订单这组铁三角,其它库存、价格,优惠券,收藏等先不提。在最简单的场景下,GraphQL 依然可以发挥作用。

我们构建了 UserProductOrder 三个类型,它们彼此之间有字段上的递归关联关系,是一个 Graph 结构。在 Query 入口类型上,分别有 user, productorder 三个字段。

据此,我们可以实现从 user, product 和 order 任意维度出发,通过它们的关联关系,实现丰富而灵活的查询。

比如,查看用户的所有订单及其跟订单相关的产品,Query 语句如下:

我们查询了 id123 的用户,他的名字和订单列表,对于每个订单,我们获取该订单的创建时间,购买价格和关联产品,对于订单关联的产品,我们获取了产品 id,产品标题,产品描述和产品价格。

当我们的后端人员组织架构是按照领域模型来划分时,用户,产品和订单,通常是 3 个团队,他们各自提供领域相关的接口。通过 GraphQL 我们可以很容易将它们整合到一起。

再比如,查看一个产品下的所有订单及其关联用户,Query 语句如下:

我们查询了 id 为 123 的产品,它的产品标题,产品描述和价格,以及关联的订单。对于每个关联订单,我们查询了订单的创建时间,购买价格以及下订单的用户,对于下订单的用户,我们查询了他的用户 id 和名称。

如你所见,只要构建出了 Graph 结构的数据网络,它不像 Tree 那样有唯一的 Root 节点。从任意入口出发,它都可以通过关联路径,不断的衍生出数据,得到 JSON 结果。

🤔 几个问题:
  • GraphQL 一定要操作数据库吗?
  • GraphQL 跟 RESTful API 是对立的吗?
  • GraphQL 一定是一个后端服务吗?
  • GraphQL 一定需要 Schema 吗?
  • GraphQL 一定返回 JSON 数据格式吗?
🥤GraphQL 不一定要操作数据库

GraphQL 不仅可以不操作数据库,它甚至可以不从其它地方获取数据,而直接写死数据在 Resolver 函数里。查看 graphql.js 的官方文档,我们轻易可以找到案例:

上图定义了一个 schema,只有一个类型为 String 的 hello 字段,它的 resolver 函数里,无视所有参数,直接 return 一个 hello world 字符串。

☕️GraphQL 跟 RESTful API 不是对立的

之前总是对比的讲 GraphQL 和 RESTful API,但 GraphQL 和 RESTful API 不仅不对立,还是互相协作的关系。

在上面这个关于 Resolver 函数的图片中,我们可以看到,可以在 GraphQL Schema 的 Resolver 函数里,调用 RESTful API 去获取数据。

当然,也可以调用 RPC 或者 ORM 等方式,从别的数据接口或者数据库里获取数据。

因此,实现一个 GraphQL 服务,并不需要挑战当前整个后端体系。它具有高度灵活的适配能力,可以低侵入性的嵌入当前系统中。

🥛GraphQL 不一定是一个后端服务

尽管绝大多数 GraphQL,都以 server 的形式存在。但是,GraphQL 作为一门语言,它并没有限制在后端场景。

最下面一行,就是一个普通的函数调用,它发起了一次 graphql 查询,其 response 结果如下:

复制代码
    {
    	"data": {
    		"hello": "Hello world!"
    	}
    }
    
    
      
      
      
      
      
    

这段代码,不只能在 node.js 里运行,在浏览器里也可以运行。因此,我们完全可以将 GraphQL 用在纯前端,去实现 State Management 状态管理。Relay 等框架,即包含了用在前端的 graphql。

🍵 GraphQL 不一定需要 Schema

GraphQL 语言设计里的两个组成部分:
1)数据提供方编写 GraphQL Schema;
2)数据消费方编写 GraphQL Query;

这种组合,是官方提供的最佳实践。但它并不是一个实践意义上的最低配置。

社区还有一种动态类型的 GraphQL 实践

它跟静态类型的 GraphQL 差别在于,没有了基于 Schema 的数据形状验证阶段,而是直接无脑地根据 query 查询语句里的字段,去触发 Resolver 函数。

它也不管 Resolver 函数返回的数据类型对不对,获取到什么,就是什么。一个字段,不必先定义好,才能被前端消费,它可以动态的计算出来。

🍹GraphQL 不一定返回 JSON 数据格式

上图来自 graphql-anywhere 里的例子。

在这里,它实现了一个 gqlToReact 的 Resolver,可以把一个 graphql 查询转换为 ReactElement 结构。

🔗 推荐链接

全部评论 (0)

还没有任何评论哟~