30分钟理解GraphQL核心概念

写在前面

在上一篇文章RPC vs REST vs GraphQL中,对于这三者的优缺点进行了比较宏观的对比,并且咱们也会发现,通常比较简单的项目其实并不须要GraphQL,可是咱们仍然须要对新的技术有必定的了解和掌握,在新技术普及时才不会措手不及。前端

这篇文章主要介绍一些我接触GraphQL的这段时间,以为须要了解的比较核心的概念,比较适合一下人群:node

  • 据说过GraphQL的读者,想深刻了解一下
  • 想系统地学习GraphQL的读者
  • 正在调研GraphQL技术的读者

这些概念并不局限于服务端或者是客户端,若是你熟悉这些概念,在接触任意使用GraphQL做为技术背景的库或者框架时,均可以经过文档很快的上手。ios

若是你已经GraphQL应用于了实际项目中,那么这篇文章可能不适合你,由于其中并无包含一些实践中的总结和经验,关于实践的东西我会在以后再单另写一篇文章总结。web

什么是GraphQL

介绍GraphQL是什么的文章网上一搜一大把,篇幅有长有短,可是从最核心上讲,它是一种查询语言,再进一步说,是一种API查询语言。数据库

这里可能有的人就会说,什么?API还能查?API不是用来调用的吗?是的,这正是GraphQL的强大之处,引用官方文档的一句话:django

ask exactly what you want.

咱们在使用REST接口时,接口返回的数据格式、数据类型都是后端预先定义好的,若是返回的数据格式并非调用者所指望的,做为前端的咱们能够经过如下两种方式来解决问题:编程

  • 和后端沟通,改接口(更改数据源)
  • 本身作一些适配工做(处理数据源)

通常若是是我的项目,改后端接口这种事情能够随意搞,可是若是是公司项目,改后端接口每每是一件比较敏感的事情,尤为是对于三端(web、andriod、ios)公用同一套后端接口的状况。大部分状况下,均是按第二种方式来解决问题的。segmentfault

所以若是接口的返回值,能够经过某种手段,从静态变为动态,即调用者来声明接口返回什么数据,很大程度上能够进一步解耦先后端的关联。后端

在GraphQL中,咱们经过预先定义一张Schema和声明一些Type来达到上面说起的效果,咱们须要知道:api

  • 对于数据模型的抽象是经过Type来描述的
  • 对于接口获取数据的逻辑是经过Schema来描述的

这么说可能比较抽象,咱们一个一个来讲明。

Type

对于数据模型的抽象是经过Type来描述的,每个Type有若干Field组成,每一个Field又分别指向某个Type。

GraphQL的Type简单能够分为两种,一种叫作Scalar Type(标量类型),另外一种叫作Object Type(对象类型)

Scalar Type

GraphQL中的内建的标量包含,StringIntFloatBooleanEnum,对于熟悉编程语言的人来讲,这些都应该很好理解。

值得注意的是,GraphQL中能够经过Scalar声明一个新的标量,好比:

  • prisma(一个使用GraphQL来抽象数据库操做的库)中,还有DateTimeID这两个标量分别表明日期格式和主键
  • 在使用GraphQL实现文件上传接口时,须要声明一个Upload标量来表明要上传的文件

总之,咱们只须要记住,标量是GraphQL类型系统中最小的颗粒,关于它在GraphQL解析查询结果时,咱们还会再说起它。

Object Type

仅有标量是不够的抽象一些复杂的数据模型的,这时候咱们须要使用对象类型,举个例子(先忽略语法,仅从字面上看):

type Article {
  id: ID
  text: String
  isPublished: Boolean
}

上面的代码,就声明了一个Article类型,它有3个Field,分别是ID类型的id,String类型的text和Boolean类型的isPublished。

对于对象类型的Field的声明,咱们通常使用标量,可是咱们也可使用另一个对象类型,好比若是咱们再声明一个新的User类型,以下:

type User {
  id: ID
  name: String
}

这时咱们就能够稍微的更改一下关于Article类型的声明代码,以下:

type Article {
  id: ID
  text: String
  isPublished: Boolean
  author: User
}

Article新增的author的Field是User类型, 表明这篇文章的做者。

总之,咱们经过对象模型来构建GraphQL中关于一个数据模型的形状,同时还能够声明各个模型之间的内在关联(一对多、一对一或多对多)。

Type Modifier

关于类型,还有一个较重要的概念,即类型修饰符,当前的类型修饰符有两种,分别是ListRequired ,它们的语法分别为[Type]Type!, 同时这二者能够互相组合,好比[Type]!或者[Type!]或者[Type!]!(请仔细看这里!的位置),它们的含义分别为:

  • 列表自己为必填项,但其内部元素能够为空
  • 列表自己能够为空,可是其内部元素为必填
  • 列表自己和内部元素均为必填

咱们进一步来更改上面的例子,假如咱们又声明了一个新的Comment类型,以下:

type Comment {
  id: ID!
  desc: String,
  author: User!
}

你会发现这里的ID有一个!,它表明这个Field是必填的,再来更新Article对象,以下:

type Article {
  id: ID!
  text: String
  isPublished: Boolean
  author: User!
  comments: [Comment!]
}

咱们这里的做出的更改以下:

  • id字段改成必填
  • author字段改成必填
  • 新增了comments字段,它的类型是一个元素为Comment类型的List类型

最终的Article类型,就是GraphQL中关于文章这个数据模型,一个比较简单的类型声明。

Schema

如今咱们开始介绍Schema,咱们以前简单描述了它的做用,即它是用来描述对于接口获取数据逻辑的,但这样描述仍然是有些抽象的,咱们其实不妨把它当作REST架构中每一个独立资源的uri来理解它,只不过在GraphQL中,咱们用Query来描述资源的获取方式。所以,咱们能够将Schema理解为多个Query组成的一张表。

这里又涉及一个新的概念Query,GraphQL中使用Query来抽象数据的查询逻辑,当前标准下,有三种查询类型,分别是query(查询)mutation(更改)subscription(订阅)

Note: 为了方便区分,Query特指GraphQL中的查询(包含三种类型),query指GraphQL中的查询类型(仅指查询类型)

Query

上面所说起的3中基本查询类型是做为Root Query(根查询)存在的,对于传统的CRUD项目,咱们只须要前两种类型就足够了,第三种是针对当前日趋流行的real-time应用提出的。

咱们按照字面意思来理解它们就好,以下:

  • query(查询):当获取数据时,应当选取Query类型
  • mutation(更改):当尝试修改数据时,应当使用mutation类型
  • subscription(订阅):当但愿数据更改时,能够进行消息推送,使用subscription类型

仍然以一个例子来讲明。

首先,咱们分别以REST和GraphQL的角度,以Article为数据模型,编写一系列CRUD的接口,以下:

Rest 接口

GET /api/v1/articles/
GET /api/v1/article/:id/
POST /api/v1/article/
DELETE /api/v1/article/:id/
PATCH /api/v1/article/:id/

GraphQL Query

query {
  articles(): [Article!]!
  article(id: Int): Article!
}

mutation {
  createArticle(): Article!
  updateArticle(id: Int): Article!
  deleteArticle(id: Int): Article!
}

对比咱们较熟悉的REST的接口咱们能够发现,GraphQL中是按根查询的类型来划分Query职能的,同时还会明确的声明每一个Query所返回的数据类型,这里的关于类型的语法和上一章节中是同样的。须要注意的是,咱们所声明的任何Query都必须是Root Query的子集,这和GraphQL内部的运行机制有关。

例子中咱们仅仅声明了Query类型和Mutation类型,若是咱们的应用中对于评论列表有real-time的需求的话,在REST中,咱们可能会直接经过长链接或者经过提供一些带验证的获取长链接url的接口,好比:

POST /api/v1/messages/

以后长链接会将新的数据推送给咱们,在GraphQL中,咱们则会以更加声明式的方式进行声明,以下

subscription {
  updatedArticle() {
    mutation
    node {
        comments: [Comment!]!
    }
  }
}

咱们没必要纠结于这里的语法,由于这篇文章的目的不是让你在30分钟内学会GraphQL的语法,而是理解的它的一些核心概念,好比这里,咱们就声明了一个订阅Query,这个Query会在有新的Article被建立或者更新时,推送新的数据对象。固然,在实际运行中,其内部实现仍然是创建于长链接之上的,可是咱们可以以更加声明式的方式来进行声明它。

Resolver

若是咱们仅仅在Schema中声明了若干Query,那么咱们只进行了一半的工做,由于咱们并无提供相关Query所返回数据的逻辑。为了可以使GraphQL正常工做,咱们还须要再了解一个核心概念,Resolver(解析函数)

GraphQL中,咱们会有这样一个约定,Query和与之对应的Resolver是同名的,这样在GraphQL才能把它们对应起来,举个例子,好比关于articles(): [Article!]!这个Query, 它的Resolver的名字必然叫作articles

在介绍Resolver以前,是时候从总体上了解下GraphQL的内部工做机制了,假设如今咱们要对使用咱们已经声明的articles的Query,咱们可能会写如下查询语句(一样暂时忽略语法):

Query {
  articles {
       id
       author {
           name
       }
       comments {
      id
      desc
      author
    }
  }
}

GraphQL在解析这段查询语句时会按以下步骤(简略版):

  • 首先进行第一层解析,当前QueryRoot Query类型是query,同时须要它的名字是articles
  • 以后会尝试使用articlesResolver获取解析数据,第一层解析完毕
  • 以后对第一层解析的返回值,进行第二层解析,当前articles还包含三个子Query,分别是idauthorcomments

    • id在Author类型中为标量类型,解析结束
    • author在Author类型中为对象类型User,尝试使用UserResolver获取数据,当前field解析完毕
    • 以后对第二层解析的返回值,进行第三层解析,当前author还包含一个Query, name,因为它是标量类型,解析结束
    • comments同上...

咱们能够发现,GraphQL大致的解析流程就是遇到一个Query以后,尝试使用它的Resolver取值,以后再对返回值进行解析,这个过程是递归的,直到所解析Field的类型是Scalar Type(标量类型)为止。解析的整个过程咱们能够把它想象成一个很长的Resolver Chain(解析链)。

这里对于GraphQL的解析过程只是很简单的归纳,其内部运行机制远比这个复杂,固然这些对于使用者是黑盒的,咱们只须要大概了解它的过程便可。

Resolver自己的声明在各个语言中是不同的,由于它表明数据获取的具体逻辑。它的函数签名(以js为例子)以下:

function(parent, args, ctx, info) {
    ...
}

其中的参数的意义以下:

  • parent: 当前上一个Resolver的返回值
  • args: 传入某个Query中的函数(好比上面例子中article(id: Int)中的id
  • ctx: 在Resolver解析链中不断传递的中间变量(相似中间件架构中的context)
  • info: 当前Query的AST对象

值得注意的是,Resolver内部实现对于GraphQL彻底是黑盒状态。这意味着Resolver如何返回数据、返回什么样的数据、从哪返回数据,彻底取决于Resolver自己,基于这一点,在实际中,不少人每每把GraphQL做为一个中间层来使用,数据的获取经过Resolver来封装,内部数据获取的实现可能基于RPC、REST、WS、SQL等多种不一样的方式。同时,基于这一点,当你在对一些未使用GraphQL的系统进行迁移时(好比REST),能够很好的进行增量式迁移。

总结

大概就这么多,首先感谢你耐心的读到这里,虽然题目是30分钟熟悉GraphQL核心概念,可是可能已经超时了,不过我相信你对GraphQL中的核心概念已经比较熟悉了。可是它自己所涉及的东西远远比这个丰富,同时它还处于飞速的发展中。

最后我尝试根据这段时间的学习GraphQL的经验,提供一些进一步学习和了解GraphQL的方向和建议,仅供参考:

想进一步了解GraphQL自己

我建议再仔细去官网,读一下官方文档,若是有兴趣的话,看看GraphQL的spec也是极好的。这篇文章虽然介绍了核心概念,可是其余一些概念没有涉及,好比Union、Interface、Fragment等等,这些概念均是基于核心概念之上的,在了解核心概念后,应当会很容易理解。

偏向服务端

偏向服务端方向的话,除了须要进一步了解GraphQL在某个语言的具体生态外,还须要了解一些关于缓存、上传文件等特定方向的东西。若是是想作系统迁移,还须要对特定的框架作一些调研,好比graphene-django。

若是是想使用GraphQL自己作系统开发,这里推荐了解一个叫作prisma的框架,它自己是在GraphQL的基础上构建的,而且与一些GraphQL的生态框架兼容性也较好,在各大编程语言也均有适配,它自己能够当作一个ORM来使用,也能够当作一个与数据库交互的中间层来使用。

偏向客户端

偏向客户端方向的话,须要进一步了解关于graphql-client的相关知识,我这段时间了解的是apollo,一个开源的grapql-client框架,而且与各个主流前端技术栈如Angular、React等均有适配版本,使用感受良好。

同时,还须要了解一些额外的查询概念,好比分页查询中涉及的Connection、Edge等。

大概就这么多,若有错误,还望指正。

欢迎关注公众号 全栈101,只谈技术,不谈人生
clipboard.png
相关文章
相关标签/搜索