在上一篇文章RPC vs REST vs GraphQL中,对于这三者的优缺点进行了比较宏观的对比,并且咱们也会发现,通常比较简单的项目其实并不须要GraphQL,可是咱们仍然须要对新的技术有必定的了解和掌握,在新技术普及时才不会措手不及。前端
这篇文章主要介绍一些我接触GraphQL的这段时间,以为须要了解的比较核心的概念,比较适合一下人群:node
这些概念并不局限于服务端或者是客户端,若是你熟悉这些概念,在接触任意使用GraphQL做为技术背景的库或者框架时,均可以经过文档很快的上手。ios
若是你已经GraphQL应用于了实际项目中,那么这篇文章可能不适合你,由于其中并无包含一些实践中的总结和经验,关于实践的东西我会在以后再单另写一篇文章总结。web
介绍GraphQL是什么的文章网上一搜一大把,篇幅有长有短,可是从最核心上讲,它是一种查询语言,再进一步说,是一种API查询语言。数据库
这里可能有的人就会说,什么?API还能查?API不是用来调用的吗?是的,这正是GraphQL的强大之处,引用官方文档的一句话:django
ask exactly what you want.
咱们在使用REST接口时,接口返回的数据格式、数据类型都是后端预先定义好的,若是返回的数据格式并非调用者所指望的,做为前端的咱们能够经过如下两种方式来解决问题:编程
通常若是是我的项目,改后端接口这种事情能够随意搞,可是若是是公司项目,改后端接口每每是一件比较敏感的事情,尤为是对于三端(web、andriod、ios)公用同一套后端接口的状况。大部分状况下,均是按第二种方式来解决问题的。segmentfault
所以若是接口的返回值,能够经过某种手段,从静态变为动态,即调用者来声明接口返回什么数据,很大程度上能够进一步解耦先后端的关联。后端
在GraphQL中,咱们经过预先定义一张Schema
和声明一些Type
来达到上面说起的效果,咱们须要知道:api
这么说可能比较抽象,咱们一个一个来讲明。
对于数据模型的抽象是经过Type来描述的,每个Type有若干Field组成,每一个Field又分别指向某个Type。
GraphQL的Type简单能够分为两种,一种叫作Scalar Type(标量类型)
,另外一种叫作Object Type(对象类型)
。
GraphQL中的内建的标量包含,String
、Int
、Float
、Boolean
、Enum
,对于熟悉编程语言的人来讲,这些都应该很好理解。
值得注意的是,GraphQL中能够经过Scalar
声明一个新的标量,好比:
DateTime
和ID
这两个标量分别表明日期格式和主键Upload
标量来表明要上传的文件总之,咱们只须要记住,标量是GraphQL类型系统中最小的颗粒,关于它在GraphQL解析查询结果时,咱们还会再说起它。
仅有标量是不够的抽象一些复杂的数据模型的,这时候咱们须要使用对象类型,举个例子(先忽略语法,仅从字面上看):
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中关于一个数据模型的形状,同时还能够声明各个模型之间的内在关联(一对多、一对一或多对多)。
关于类型,还有一个较重要的概念,即类型修饰符,当前的类型修饰符有两种,分别是List
和Required
,它们的语法分别为[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!] }
咱们这里的做出的更改以下:
最终的Article
类型,就是GraphQL中关于文章这个数据模型,一个比较简单的类型声明。
如今咱们开始介绍Schema
,咱们以前简单描述了它的做用,即它是用来描述对于接口获取数据逻辑
的,但这样描述仍然是有些抽象的,咱们其实不妨把它当作REST架构中每一个独立资源的uri
来理解它,只不过在GraphQL中,咱们用Query来描述资源的获取方式。所以,咱们能够将Schema
理解为多个Query组成的一张表。
这里又涉及一个新的概念Query
,GraphQL中使用Query
来抽象数据的查询逻辑,当前标准下,有三种查询类型,分别是query(查询)、mutation(更改)和subscription(订阅)。
Note: 为了方便区分,Query
特指GraphQL中的查询(包含三种类型),query
指GraphQL中的查询类型(仅指查询类型)
上面所说起的3中基本查询类型是做为Root Query(根查询)
存在的,对于传统的CRUD项目,咱们只须要前两种类型就足够了,第三种是针对当前日趋流行的real-time
应用提出的。
咱们按照字面意思来理解它们就好,以下:
仍然以一个例子来讲明。
首先,咱们分别以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被建立或者更新时,推送新的数据对象。固然,在实际运行中,其内部实现仍然是创建于长链接之上的,可是咱们可以以更加声明式的方式来进行声明它。
若是咱们仅仅在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在解析这段查询语句时会按以下步骤(简略版):
Query
的Root Query
类型是query
,同时须要它的名字是articles
articles
的Resolver
获取解析数据,第一层解析完毕以后对第一层解析的返回值,进行第二层解析,当前articles
还包含三个子Query
,分别是id
、author
和comments
User
的Resolver
获取数据,当前field解析完毕author
还包含一个Query
, name
,因为它是标量类型,解析结束咱们能够发现,GraphQL大致的解析流程就是遇到一个Query以后,尝试使用它的Resolver取值,以后再对返回值进行解析,这个过程是递归的,直到所解析Field的类型是Scalar Type(标量类型)
为止。解析的整个过程咱们能够把它想象成一个很长的Resolver Chain(解析链)。
这里对于GraphQL的解析过程只是很简单的归纳,其内部运行机制远比这个复杂,固然这些对于使用者是黑盒的,咱们只须要大概了解它的过程便可。
Resolver自己的声明在各个语言中是不同的,由于它表明数据获取的具体逻辑。它的函数签名(以js为例子)以下:
function(parent, args, ctx, info) { ... }
其中的参数的意义以下:
article(id: Int)
中的id
)值得注意的是,Resolver内部实现对于GraphQL彻底是黑盒状态。这意味着Resolver如何返回数据、返回什么样的数据、从哪返回数据,彻底取决于Resolver自己,基于这一点,在实际中,不少人每每把GraphQL做为一个中间层来使用,数据的获取经过Resolver来封装,内部数据获取的实现可能基于RPC、REST、WS、SQL等多种不一样的方式。同时,基于这一点,当你在对一些未使用GraphQL的系统进行迁移时(好比REST),能够很好的进行增量式迁移。
大概就这么多,首先感谢你耐心的读到这里,虽然题目是30分钟熟悉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,只谈技术,不谈人生
![]()