系列文章:javascript
GraphQL 核心概念(本文)java
最近由于工做上新产品的须要,让我有机会了解和尝试 GraphQL。按照套路,在介绍一项新技术的时候总要回答 3 个问题:What, Why & How。github
正如副标题所说,GraphQL 是由 Facebook 创造的用于描述复杂数据模型的一种查询语言。这里查询语言所指的并非常规意义上的相似 sql 语句的查询语言,而是一种用于先后端数据查询方式的规范。sql
当今客户端和服务端主要的交互方式有 2 种,分别是 REST 和 ad hoc 端点。GraphQL 官网指出了它们的不足之处主要在于:当需求或数据发生变化时,它们都须要创建新的接口来适应变化,而不断添加的接口,会形成服务器代码的不断增加,即便经过增长接口版本,也并不可以彻底限制服务器代码的增加。(更多不足,前往官网查看)redux
既然,GraphQL 指出了它们的缺点,那么它天然解决了这些问题。segmentfault
如何解决的哪?那就得说说 GraphQL 的 3 大特性。后端
首先,它是声明式的。查询的结果格式由请求方(即客户端)决定而非响应方(即服务器端)决定,也就是说,一个 GraphQL 查询结果的返回是同客户端请求时的结构同样的,很少很多,不增不减。api
其次,它是可组合的。一个 GraphQL 的查询结构是一个有层次的字段集,它能够任意层次地进行嵌套或组合,也就是说它能够经过对字段进行组合、嵌套来知足需求。服务器
第三,它是强类型的。强类型保证,只有当一个 GraphQL 查询知足所设定的查询类型,那么查询的结果才会被执行。
回到以前的问题,也就是说,当需求或数据发生变化时,客户端能够根据需求来改变查询的结构,只要查询结构知足以前的定义,服务器端代码甚至不须要作任何的修改;即便不知足,也只需修改服务器端的查询结构,而没必要额外添加新的接口来知足需求。
可能你会问,按套路这节不应是 HOW to use GraphQL,怎么变成了 Core Concepts?
因为,GraphQL 是一种规范,因而,它的实现不限制某种特定语言,每种语言对 GraphQL 均可以有本身的实现,好比相对 JavaScript 就有 graphql-js。既然,实现都不相同,那么,使用的方法也会不一样,因此便不在这里细述了。
这篇文章主要分享的是 GraphQL 的核心概念,主要分为:Type System
, Query Syntax
, Validation
和 Introspection
四部分。
类型系统是整个 GraphQL 的核心,它用来定义每一个查询对象和返回对象的类型,将全部定义的对象组合起来就造成了一整个 GraphQL Schema。
这个概念比较抽象,空说很难理解,仍是拿例子来边看边说。我的博客相信你们都很熟悉,这里就尝试用一个简单的博客系统的例子来讲明,这会比官网星战的例子简单一点。
Let's go!
既然是一个博客,那么,文章确定少不了,咱们首先来创建一个文章的类型。
type Post { id: String, name: String, createDate: String, title: String, subtitle: String, content: String }
这样,一个简单的文章类型就定义好了,它是一个自定义的类型,包含了一系列的字段,巧合的是这些字段的类型正好都是 String
(字符串类型)。
String
没有定义过,为何能够直接使用哪?由于,String
是 GraphQL 支持的 scalar type(标量类型),默认的标量类型还包括 Int
,Float
, Boolean
和 ID
。
许多的博客网站都支持给每篇文章打标签,那么咱们在来创建一个标签的类型。
type Tag { id: String, name: String, label: String, createDate: String }
标签类型和文章类型怎么整合到一块儿哪?
GraphQL 不仅仅支持简单类型,还支持一些其余类型,如 Object
, Enum
, List
, NotNull
这些常见的类型,还有 Interface
, Union
, InputObject
这几个特殊类型。
PS:一直没搞明白 Interface
和 Union
的区别在哪,它们分别适用于什么场景?谷歌了一下,还真有篇文章说它们的区别,不过恕我愚钝,仍是没能领悟,还望大神点拨...
再修改一下以前的文章类型,使一个文章能够包含多个标签。
type Post { id: String, name: String, createDate: String, title: String, subtitle: String, content: String, tags: [Tag] }
一般在博客网站的标签列表中会显示该标签下的一些文章,因为 GraphQL 是以产品为中心的,那么在标签类型下也能够有文章类型。因而,标签类就变成了
type Tag { id: String, name: String, label: String, createDate: String, posts: [Post] }
可能你会疑惑,文章类型和标签类型这样相互嵌套会不会形成死循环?我能够负责任的告诉你:不会。你能够尽情地嵌套、组合类型结构来知足你的需求。
最后,根据整个博客网站的需求,组合嵌套刚刚定义的文章类型和标签类型,创建一个根类型做为查询的 schema。
type Blog { post: Post, // 查询一篇文章 posts: [Post], // 用于博客首页,查询一组文章 tag: Tag, // 查询一个标签 tags: [Tag], // 用于博客标签页,查询全部标签 }
OK,咱们的类型和 schema 都定义好了,就能够开始查询了。怎么查哪?那咱们来看看 GraphQL 的查询语法。
GraphQL 的查询语法同咱们如今所使用的有一大不一样是,传输的数据结构并非 JSON 对象,而是一个字符串,这个字符串描述了客户端但愿服务端返回数据的具体结构。
知道了概念,那么一个 GraphQL 的查询到底长什么样哪?继续咱们的例子,假设,咱们如今要查询一篇文章,那么,GraphQL 的查询语句就能够是这样。
query FetchPostQuery { post { id, name, createDate, title, subtitle, content, tags { name, label } } }
它相对应的返回就会是相似这样的一个 JSON 数据。
{ "data": { "post": { "id": "3", "name": "graphql-core-concepts", "createDate": "2016-08-01", "title": "GraphQL 核心概念", "subtitle": "A query language created by Facebook for decribing data requirements on complex application data models", "content": "省略...", "tags": [{ "name": "graphql", "label": "GraphQL" }] } } }
从中咱们能够看到,数据返回了整个文章的属性以及部分的标签属性。其中,标签属性并无返回所有的字段,而是只返回了 name 和 label 字段的属性,作到了返回数据的结构完成同请求数据的结构相同,没有冗余的数据。
查询添加参数的需求也很是基本,在 GraphQL 的查询语法中也至关简单,就拿刚刚的例子,要查询特定的文章就能够把它改为这样。
query FetchPostQuery { post(name: 'graphql-core-concepts') { id, name, createDate, title, subtitle, content, tags { name, label } } }
返回的结果会是和以前的同样。查询关键字只有在多个查询时才必须,在单个查询时能够省略。同时,也能够对查询的返回起别名,再来看看博客的首页但愿展现一个粗略的文章列表,那么这样的一个查询语句能够是
{ postList: posts { id, name, createDate, title, subtitle, tags { name, label } } }
这里,咱们省略了查询关键字,并将 posts
起了一个别名为 postList
,返回的结果就会是
{ "data": { "postList": [{ "id": "3", "name": "graphql-core-concepts", "createDate": "2016-08-01", "title": "GraphQL 核心概念", "subtitle": "A query language created by Facebook for decribing data requirements on complex application data models", "tags": [{ "name": "graphql", "label": "GraphQL" }] }, { "id": "2", "name": "redux-advanced", "createDate": "2016-07-23", "title": "Redux 进阶", "subtitle": "Advanced skill in Redux", "tags": [{ "name": "javascript", "label": "JavaScript" }, { "name": "redux", "label": "Redux" }, { "name": "state-management", "label": "State management" }, { "name": "angular-1.x", "label": "Angular 1.x" }, { "name": "ui-router", "label": "ui-router" }, { "name": "redux-ui-router", "label": "redux-ui-router" }] }, { "id": "1", "name": "getting-started-with-redux", "createDate": "2016-07-06", "title": "Redux 入门", "subtitle": "A tiny predictable state management lib for JavaScript apps", "tags": [{ "name": "javascript", "label": "JavaScript" }, { "name": "redux", "label": "Redux" }, { "name": "state-management", "label": "State management" }, { "name": "angular-1.x", "label": "Angular 1.x" }] }] } }
一样,查询全部标签的语句就能够是这样
{ tags { id, name, label, posts { name, title } } }
这样,一个 GraphQL 的接口,知足了一个简单博客网站的全部需求,是否是很神奇?
因为 GraphQL 是一个强类型语言,因此它能够在执行查询以前检查每一个查询语句是否知足事先设定的 schema,符合则合法,若是查询语句不合法则不进行查询。
以上所举的都是合法的例子,官网上举了一些例子,这里就不贴了,咱们就总结看看要注意的有哪几点。
fragment
不能引用本身从而造成一个循环
不能查询类型中不存在的字段
查询的字段若是不是 scalar type(标量类型)或 enum type(枚举类型),则须要明确该字段下所包含的字段
同上一条相对,若是查询字段是 scalar type(标量类型),那么它就不能再有子字段
Introspection 这个词的意思是内省,自我检查(第一次发现英语有语义如此丰富的词,又暴露词汇量少了-_-||)。
不扯远了,在 GraphQL 中 Introspection 是一个很是有用的功能,它能够用来查询当前 GraphQL 的 schema,从而得知服务器端支持何种类型的查询。
这是一个很是强大且有用的功能,能够想象一下,如今大型公司的开发基本上都是先后端分离的,客户端并不知道服务器端提供的 schema 结构,但经过 Introspection,客户端就能得到当前服务器端所提供的 schema,这不管对开发,仍是调试错误都颇有帮助。
仍是拿刚刚的博客系统来作例子,咱们能够经过查询 __schema
字段来得到当前所支持的查询类型。
// query string { __schema { types { name } } } // response data { "data": { "__schema": { "types": [ { "name": "String" }, { "name": "BlogType" }, { "name": "PostType" }, { "name": "ID" }, { "name": "TagType" }, { "name": "__Schema" }, { "name": "__Type" }, { "name": "__TypeKind" }, { "name": "Boolean" }, { "name": "__Field" }, { "name": "__InputValue" }, { "name": "__EnumValue" }, { "name": "__Directive" }, { "name": "__DirectiveLocation" } ] } } }
从返回的数据中能够看到,咱们自定义的 BlogType, PostType 和 TagType 类,剩下的都是 GraphQL 内部类型,其中又分为两类:一类是 ID, String 和 Bealoon 所表示的标量类型,另外一类以双下划线开头的是用于自我检查的类型。
知道了自定义类,假设,还想知道自定义类中包含哪些属性以及属性的类型,就能够这样查询
// query string { __type(name: "PostType") { name fields { name, type { name, kind } } } } // response result { "data": { "__type": { "name": "PostType", "fields": [ { "name": "id", "type": { "name": null, "kind": "NON_NULL" } }, { "name": "name", "type": { "name": null, "kind": "NON_NULL" } }, { "name": "createDate", "type": { "name": null, "kind": "NON_NULL" } }, { "name": "title", "type": { "name": null, "kind": "NON_NULL" } }, { "name": "subtitle", "type": { "name": "String", "kind": "SCALAR" } }, { "name": "content", "type": { "name": "String", "kind": "SCALAR" } }, { "name": "tags", "type": { "name": null, "kind": "LIST" } } ] } } }
总结一下,GraphQL 是一种客户端同服务端之间数据交互的概念,具备强大、灵活、易扩展等的特色。既然,它是一种概念,那么,不一样的语言就能够有各类不一样的实现方式。
概念并很少,在于灵活运用。
PS:再次强调,本文主要讲的是 GraphQL 的核心概念,Type System 中所定义的类,都是设计类,并非具体实现代码。实现请听下回分解。