最近在实践GraphQL,项目算是作完了,这会须要总结一下。如下三篇文章都是基于的demo代码是: graphql-todo-demoreact
解读的graphql-js的版本是v14.3.0
,规范是2018年6月份发布的标准git
GraphQL是给API设计的一种查询语言,一个依据已有数据执行查询的运行时,为你的API中的数据提供一种彻底且容易理解的描述,使得API可以更容易的随着时间而演变,还支持强大的开发者工具。github
具备如下特性:(翻译自Getting up and running with GraphQL)web
咱们先来看看平时项目的一些api使用场景。数据库
假如某个订单业务的接口在app端使用到如下数据:express
{
id,
tradeNo,
price
}
复制代码
而一样的接口在web端用到的是以下数据:api
{
id,
price,
tags,
toAddr
}
复制代码
因而在咱们开发中就会有这样的对话:数组
服务端A: 小林啊,这是xx接口返回的dto,大家网关直接使用就好了。bash
{
id
tradeNo
price
tags
hasPaied
toAddr
fromAddr
shopId
...
}
复制代码
小林:好的,我将数据都透传给app app端B: 小林啊,你这接口怎么这么多字段?哪些字段是咱们用得上的咧?你就不能修剪一下参数再给咱们吗? 小林: 很差修剪呀,这个接口web端的童鞋也在用呀,大家就挑选本身用获得的字段就好了 app端B: ....服务器
app端童鞋这会很无语,感受很费解,明明我只要四个字段,你给我返回几十个字段干啥呢?那不是浪费带宽吗?
是的,这类现象就是所谓的“过分获取”。同时也暴露出网关这层,在处理同一个接口返回给不一样终端的数据差别性的复杂,须要你增长额外的判断去实现,这种问题咱们称之为“冗余判断”,这会致使系统维护成本增长。
那么有解决办法吗?固然有!咱们反过来操做如何?由客户端来告诉网关须要哪些字段,网关这边准备完整的数据,而后有个什么东东来pick这些数据给客户端。因而乎,这个东东就是后来Facebook在2015年开源的GraphQL。
那么GraphQL除了解决刚才说两个广泛问题,还有别的其余优点吗?
of course。
以官方文档来阐述,即是:
在后面的三篇文章中会逐一体现其优点。
能够将 GraphQL 的类型系统分为标量类型(Scalar Types,标量类型)和其余高级数据类型,标量类型便可以表示最细粒度数据结构的数据类型,能够和 JavaScript 的原始类型对应。
GraphQL 规范目前规定支持的标量类型有:
Scalar Types
的JavaScript参考实现代码能够查看这里。
咱们可使用scalar
关键字,自定义标量类型,好比咱们能够定义一个Date类型: scalar Date
Interface
是包含一组肯定字段的集合的抽象类型,实现该接口的类型必须包含interface
定义的全部字段。好比:
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}
复制代码
对于interface
的返回须要你使用inline fragments
来实现,以下:
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
}
}
复制代码
Union类型很是相似于interface,可是他们在类型之间不须要指定任何共同的字段。一般用于描述某个字段可以支持的全部返回类型以及具体请求真正的返回类型。好比定义:
union SearchResult = Human | Droid | Starship
复制代码
这个时候的查询语句多是这样的:
{
search(text: "an") {
__typename
... on Human {
name
height
}
... on Droid {
name
primaryFunction
}
... on Starship {
name
length
}
}
}
复制代码
又称Enums,这是一种特殊的标量类型,经过此类型,咱们能够限制值为一组特殊的值。好比:
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
复制代码
input类型对mutations来讲很是重要,在 GraphQL schema 语言中,它看起来和常规的对象类型很是相似,可是咱们使用关键字input而非type,input类型按以下定义:
# Input Type
input CommentInput {
body: String!
}
复制代码
为何不直接使用Object Type呢?由于 Object 的字段可能存在循环引用,或者字段引用了不能做为查询输入对象的接口和联合类型。
使用[]
来表示数组,使用!
来表示非空。Non-Null
强制类型的值不能为null
,而且在请求出错时必定会报错。能够用于必须保证值不能为null
的字段
GraphQL schema最基本的类型就是Object Type。用于描述层级或者树形数据结构。好比:
type Character {
name: String!
appearsIn: [Episode!]!
}
复制代码
GraphQL的一次操做请求被称为一份文档(document),即GraphQL服务可以解析验证并执行的一串请求字符串(Source Text)。完整的一次操做由操做(Operation
)和片断(Fragments
)组成。一次请求能够包含多个操做和片断。只有包含操做的请求才会被GraphQL服务执行。
其规范释义为:
只包含一个操做的请求能够不带OperationName
,若是是operationType是query的话,能够所有省略掉,即:
{
getMessage {
# query也能够拥有注释,注释以#开头
content
author
}
}
复制代码
当query包含多个操做时,全部操做都必须带上名称。
GraphQL中,咱们会有这样一个约定,Query和与之对应的Resolver是同名的,这样在GraphQL才能把它们对应起来。
Query
用作读操做,也就是从服务器获取数据。以上图的请求为例,其返回结果以下,能够看出一一对应,精准返回数据
{
"data": {
"single": [
{
"content": "test content 1",
"author": "pp1"
}
],
"all": [
{
"content": "test content",
"author": "pp",
"id": "0"
},
{
"content": "test content 1",
"author": "pp1",
"id": "1"
}
]
}
}
复制代码
Field是咱们想从服务器获取的对象的基本组成部分。query
是数据结构的顶层,其下属的all
和single
都属于它的字段。
字段格式应该是这样的:alias:name(argument:value)
其中 alias 是字段的别名,即结果中显示的字段名称。
name 为字段名称,对应 schema 中定义的 fields 字段名。
argument 为参数名称,对应 schema 中定义的 fields 字段的参数名称。
value 为参数值,值的类型对应标量类型的值。
和普通的函数同样,query能够拥有参数,参数是可选的或必须的。参数使用方法如上图所示。
须要注意的是,GraphQL中的字符串须要包装在双引号中。
除了参数,query还容许你使用变量来让参数可动态变化,变量以$开头书写,使用方式如上图所示
变量还能够拥有默认值:
query gm($id: ID = 2) {
# 查询数据
single: getMessage(id: $id) {
...entity
}
all: getMessage {
...entity
id
}
}
复制代码
别名,好比说,咱们想分别获取所有消息和ID为1的消息,咱们能够用下面的方法:
query gm($id: ID = 2) {
# 查询数据
getMessage(id: $id) {
...entity
}
getMessage {
...entity
id
}
}
复制代码
因为存在相同的name,上述代码会报错,要解决这个问题就要用到别名了Allases。
query gm($id: ID = 2) {
# 查询数据
single: getMessage(id: $id) {
...entity
}
all: getMessage {
...entity
id
}
}
复制代码
Fragments是一套在queries中可复用的fields。好比说咱们想获取Message
,在没有使用fragment以前是这样的:
query gm($id: ID = 2) {
# 查询数据
single: getMessage(id: $id) {
content
author
}
all: getMessage {
content
author
id
}
}
复制代码
可是若是fields过多,就会显得重复和冗余。Fragments在此时就能够起做用了。使用了Fragment以后的语法就如上图所示,简单清晰。
Fragment支持多层级地继承。
Directives提供了一种动态使用变量改变咱们的queries的方法。如本例,咱们会用到如下两个directive:
query gm($id: ID = 2, $isNotShowId: Boolean!, $showAuthor: Boolean!) {
# 查询数据
single: getMessage(id: $id) {
...entity
}
all: getMessage {
...entity
id @skip(if: $isNotShowId)
}
}
fragment entity on Message {
content
author @include(if: $showAuthor)
}
### 入参是:
{
"id": 1,
"isNotShowId": true,
"showAuthor": false
}
复制代码
@include: 只有当if中的参数为true时,才会包含对应fragment或field;
@skip:当if中的参数为true时,会跳过对应fragment或field;
结果以下:
{
"data": {
"single": [
{
"content": "test content 1"
}
],
"all": [
{
"content": "test content"
},
{
"content": "test content 1"
}
]
}
}
复制代码
传统的API使用场景中,咱们会有须要修改服务器上数据的场景,mutations就是应这种场景而生。mutations被用以执行写操做,经过mutations咱们会给服务器发送请求来修改和更新数据,而且会接收到包含更新数据的反馈。mutations和queries具备相似的语法,仅有些许的差异。
Subscription
是GraphQL最后一个操做类型,它被称为订阅
。当另外两个由客户端经过HTTP
请求发送,订阅是服务器在某个事件发生时将数据自己推送给感兴趣的客户端的一种方式。这就是GraphQL 处理实时通讯的方式。
在demo中咱们实现了TodoList的发布订阅功能,在语法上和别的operation相似,惟一变化的就是operation Type变为了Subscription
,好比:
subscription todoSubscription($filter: String!) {
todoChanged(filter: $filter) {
type
payload {
id
title
complete
}
}
}
复制代码
更多的改造实现是在服务端上,须要借助graphql-subscriptions
和subscriptions-transport-ws
来实现整个流程。结合express-graphql
的例子能够参考:Subscriptions support
至此,GraphQL的基础篇讲完了,为了加深这些基础,欢迎继续阅读第二篇文章原理篇