GraphQL学习之基础篇

前言

最近在实践GraphQL,项目算是作完了,这会须要总结一下。如下三篇文章都是基于的demo代码是: graphql-todo-demoreact

解读的graphql-js的版本是v14.3.0,规范是2018年6月份发布的标准git

GraphQL介绍

GraphQL是给API设计的一种查询语言,一个依据已有数据执行查询的运行时,为你的API中的数据提供一种彻底且容易理解的描述,使得API可以更容易的随着时间而演变,还支持强大的开发者工具。github

具备如下特性:(翻译自Getting up and running with GraphQL)web

  • 可描述性的:使用GraphQL,你获取的都是你想要的数据,很少也不会少; {:&.rollIn}
  • 分级性的:GraphQL自然遵循对象间的关系,经过一个简单的请求,咱们能够获取到一个对象及其相关的对象,好比说,经过一个简单的请求,咱们能够获取一个做者和他建立的全部文章,而后能够获取文章的全部评论;
  • 强类型的:使用GraphQL的类型系统,咱们能够描述可以被服务器查询的可能的数据,而后确保从服务器获取到的数据和咱们查询的一致;
  • 不作语言限制:并不绑定于某一特定的语言,实际上如今已经有一些不一样的语言有了实践;
  • 兼容于任何后台:GraphQL不限于某一特定数据库,可使用已经存在的数据,代码,甚至能够链接第三方的APIs.
  • 可自省的:GraphQL服务器可以查询架构的细节

为何须要GraphQL

咱们先来看看平时项目的一些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
  • GraphQL 版本化
  • GraphQL的语义化 => schema自己就是一份完美的自动生成的 API 文档

在后面的三篇文章中会逐一体现其优点。

GraphQL的类型系统

能够将 GraphQL 的类型系统分为标量类型(Scalar Types,标量类型)和其余高级数据类型,标量类型便可以表示最细粒度数据结构的数据类型,能够和 JavaScript 的原始类型对应。

Scalar Type

GraphQL 规范目前规定支持的标量类型有:

  • Int:带符号的32位整数,对应 JavaScript 的 Number
  • Float:带符号的双精度浮点数,对应 JavaScript 的 Number
  • String:UTF-8字符串,对应 JavaScript 的 String
  • Boolean:布尔值,对应 JavaScript 的 Boolean
  • ID:ID 值,是一个序列化后值惟一的字符串,能够视做对应 ES 2015 新增的 Symbol

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查询语法

GraphQL的一次操做请求被称为一份文档(document),即GraphQL服务可以解析验证并执行的一串请求字符串(Source Text)。完整的一次操做由操做(Operation)和片断(Fragments)组成。一次请求能够包含多个操做和片断。只有包含操做的请求才会被GraphQL服务执行。

其规范释义为:

只包含一个操做的请求能够不带OperationName,若是是operationType是query的话,能够所有省略掉,即:

{
  getMessage {
    # query也能够拥有注释,注释以#开头
    content
    author
  }
}
复制代码

当query包含多个操做时,全部操做都必须带上名称。

GraphQL中,咱们会有这样一个约定,Query和与之对应的Resolver是同名的,这样在GraphQL才能把它们对应起来。

Query

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

Field是咱们想从服务器获取的对象的基本组成部分。query是数据结构的顶层,其下属的allsingle都属于它的字段。

字段格式应该是这样的:alias:name(argument:value)

其中 alias 是字段的别名,即结果中显示的字段名称。

name 为字段名称,对应 schema 中定义的 fields 字段名。

argument 为参数名称,对应 schema 中定义的 fields 字段的参数名称。

value 为参数值,值的类型对应标量类型的值。

Argument

和普通的函数同样,query能够拥有参数,参数是可选的或必须的。参数使用方法如上图所示。

须要注意的是,GraphQL中的字符串须要包装在双引号中。

Variables

除了参数,query还容许你使用变量来让参数可动态变化,变量以$开头书写,使用方式如上图所示

变量还能够拥有默认值:

query gm($id: ID = 2) {
  # 查询数据
  single: getMessage(id: $id) {
    ...entity
  }
  all: getMessage {
    ...entity
    id
  }
}
复制代码

Allases

别名,好比说,咱们想分别获取所有消息和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

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

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"
      }
    ]
  }
}
复制代码

Mutation

传统的API使用场景中,咱们会有须要修改服务器上数据的场景,mutations就是应这种场景而生。mutations被用以执行写操做,经过mutations咱们会给服务器发送请求来修改和更新数据,而且会接收到包含更新数据的反馈。mutations和queries具备相似的语法,仅有些许的差异。

  1. operationType为mutation
  2. 为了保证数据的完整性mutations是串形执行,而queries能够并行执行,这点在原理篇会介绍到

Subscription

Subscription是GraphQL最后一个操做类型,它被称为订阅。当另外两个由客户端经过HTTP请求发送,订阅是服务器在某个事件发生时将数据自己推送给感兴趣的客户端的一种方式。这就是GraphQL 处理实时通讯的方式。

demo中咱们实现了TodoList的发布订阅功能,在语法上和别的operation相似,惟一变化的就是operation Type变为了Subscription,好比:

subscription todoSubscription($filter: String!) {
  todoChanged(filter: $filter) {
    type
    payload {
      id
      title
      complete
    }
  }
}
复制代码

更多的改造实现是在服务端上,须要借助graphql-subscriptionssubscriptions-transport-ws来实现整个流程。结合express-graphql的例子能够参考:Subscriptions support

最后

至此,GraphQL的基础篇讲完了,为了加深这些基础,欢迎继续阅读第二篇文章原理篇

参考

  1. Schemas and Types
  2. howtographql.cn/
相关文章
相关标签/搜索