howtographql-basic conception

/post/graphql-basic article cover image

学习文档

介绍

GraphQL 是一种新的 API 标准,它提供了一种更高效、更强大、更灵活的 REST 替代方案。它由 Facebook 开发和开源,现在由来自世界各地的公司和个人组成的大型社区维护。

特点

  • 支持声明式数据获取,客户端可以从API中准确制定需要的数据
  • GraphQL server只返回一个端点的数据给客户端(相比传统REST api)
  • 它是查询语言而非数据库,可以在任意API上下文中有效的接入

更高效的rest-api替换方案

  • 日益剧增的移动端市场对高效数据加载的需求
  • 能够更友好的与不同前端框架和平台之间相融合更利于维护
  • 更适合与快速快发、快速迭代的业务场景(不必每次都像REST去改动API)
  • 没有过度和不足的请求(在REST中多个请求组合一个数据、单个又不能满足的情况)
  • 有利于后端的内部审查(更细粒化得对冗余和不必要的字段进行检查修改)
  • 健壮的类型系统和模式约定能使前后端在开发过程中相互独立在不进行沟通的情况下完成工作

核心概念

the-schema-definition-languagesdl-模式定义语言

graphql拥有他自己的类型系统,用于定义API的模式。编写schema的语法被称为SDL

graphql
type Person {
  name: String!
  age: Int!
}

这个类型拥有两个字段name和age分别为String和Int类型,后面跟随的!代表这个字段是必须的

graphql
type Post {
	title: String!
	author: Person!
}

type Person {
	name: String!
	age: Int!
	posts: [Post!]!
}

也有可以表达两个类型之间的关联,上面的例子Person和Post之间关联

使用queries查询获取数据

相比于REST API的urlencode形式请求数据,graphql完全不同,它通常只公开一个端点并且返回的数据结构不是固定的,而完全是由客户端决定要返回什么样的数据。所以极具灵活性.

这意味着客户端需要向服务器发送更多信息来表达其数据需求——这些信息称为Queries.

graphql
{
	allPerSOns {
		name
	}
}

allPersons在查询中被称为根字段,跟字段之后的所有内容都称为查询的有效负载。在此查询负载中唯一指定的字段是name

json
{
	"allPersons": [
		{ "name": "john" },
		{ "name": "alex" }
	]
}

需要注意的是每个person的相应数据中都有name字段,但是不会有age返回那是因为只有name字段在查询中被指定,如果客户端需要age只需要将其包含在查询的负载当中

graphql
{
	allPersons {
		name
		age
	}
}

在GraphQL中一个主要的优点就是它天然支持嵌套信息的查询,如下例如果想要查询所有Person写的posts列表只需要像下面示例的类型结构去查询即可

graphql
{
	allPersons {
		name
		age
		posts {
			title
		}
	}
}

带有参数的查询

在Graphql中,每个字段在其查询模式中都可以有零到多个的参数被指定。例如,allPersons字段可以又一个last字段用以指定返回几个person,下例是对应查询语句该有的样子

graphql
{
	allPersons(last: 2) {
		name
	}
}

使用mutations写入数据

除了从服务器请求信息外,大多数应用也需要通过某些方式来更改当前存储在后端的数据。在GraphQL中,这些变更是通过所谓的mutations来实现,通常有三种mutations:

  • 创建新数据
  • 更新现在有数据
  • 删除现有数据

Mutations拥有和查询一样的语法结构,但是通常需要以mutation关键字作为开始,这是一个创建Person的例子

graphql
mutation {
	createPerson(name: "Alex", age: 18) {
		name
		age
	}
}

Mutations和我们之前编写的查询类似,都具有根字段这里是createPerson,同样的带有两个指定参数分别是nameage用作查询负载。 尽管在我们的示例中这显然不是很有帮助,因为当我们将它们传递给Mutation时,我们显然已经知道它们。但是,在发送Mutation时也能够查询信息可能是一个非常强大的工具,它允许您在一次往返中从服务器检索新信息!

上面的查询会返回如下数据:

json
{
	"createPerson": {
		"name": "Alice",
		"age": 18
	}
}

我们经常发现的一个模式是当一个新对象被创建时GraphQL类型是具有一个由服务端生成的唯一ID,扩展之前Person类型时,我们可以这样添加一个id:

graphql
type Person {
	id: ID!
	name: String!
	age: Int!
}

现在,当一个新Person被创建时,可以直接在Mutation的有效负载中询问id,因为这是事先在客户端上不可用的信息

graphql
mutation {
	createPerson(name: "Alice", age: 20) {
		id
	}
}

使用订阅实时更新

对于众多应用另一个重要的需求就是与服务端建立实时连接,以便立即了解重要事件。对于这个用例,GraphQL提供了订阅的概念。

当一个客户端订阅到一个事件时,它将初始化并和服务端保持一个稳定的连接,每当特定事件触发时,服务端就将相应的数据推送给客户端。与遵循典型"请求-相应-周期"的Queries和Mutations不同,订阅表示发送到客户端的数据流

订阅的编写方式与Queies和Mutations的语法相似,下面是订阅事件发生在Person类型上的例子:

graphql
subscription {
	nwePerson {
		name
		age
	}
}

定义一个schema

现在对query、mutation、subscription有了基本了解以后,把这些概念放到一起并学习如何编写一个schema来执行目前所见到过的示例。

schema是使用GraphQL API时最重要的概念之一。它指定了API的功能并定义客户端如何请求数据。它通常被视为服务器和客户端之间的合约。

通常,一个schema是一个简单的GraphQL类型集合,当为一个API编写schema时,有这些特殊的根类型。

graphql
type Query { }
type Mutation { }
type Subscription{ }

Query、Mutation、和Subscription类型是客户端发送请求的入口点,要启用之前看到的allPersons,Query类型需要这么编写:

graphql
type Query {
	allPersons: [Person!]!
}

allPersons在API中被称为根字段.再次考虑将最后一个参数添加到allPersons字段的示例,需要这样编写Query:

graphql
type Query {
	allPersons(last: Int): [Person!]!
}

同样,对于createPersonmutation,我们必须向Mutation类型添加一个根字段.

graphql
type Mutation {
	createPerson(name: String!, age: Int!): Person!
}

需要注意的是根字段也可以携带两个参数,Personnameage.最后对于subscriptions,我们必须添加newPerson根字段:

graphql
type Subscription {
	newPerson: Person!
}

把前面说有的内容汇总在一起就是所有Query和Mutation的完整模式:

graphql
type Query {
  allPersons(last: Int): [Person!]!
  allPosts(last: Int): [Post!]!
}

type Mutation {
  createPerson(name: String!, age: Int!): Person!
  updatePerson(id: ID!, name: String!, age: String!): Person!
  deletePerson(id: ID!): Person!
}

type Subscription {
  newPerson: Person!
}

type Person {
  id: ID!
  name: String!
  age: Int!
  posts: [Post!]!
}

type Post {
  title: String!
  author: Person!
}

架构

用例

接下来将会介绍3种不同类型的架构,其中包括GraphQL服务器:

  1. 具有连接数据库的GraphQL服务
  2. GraphQL服务通过单个GraphQL API作为一个薄层集成在许多第三方或遗留系统之前
  3. 连接数据库和第三方或遗留系统的混合方法,都可以通过相同的 GraphQL API 访问

这三种架构都代表了GraphQL的主要用例,并展示了它在上下文中使用的灵活性

1带有连接数据库的graphql服务

这种架构将是最常见的新建项目,在设置中,会有一个实现了GraphQL规范的web服务器。当查询到达GraphQL服务时,服务器会读取查询的有效负载并从数据库中获取所需信息。这被称为解析查询。 然后它按照官方规范中的描述构造相应对象并返回给客户端。

需要注意的是,GraphQL 实际上与传输层无关。这意味着它可以与任何可用的网络协议一起使用。因此,有可能实现基于 TCP、WebSockets 等的 GraphQL 服务器。

GraphQL也不关心数据库或用于存储数据的格式.你可以用使用SQL数据库或NoSQL.

graphql-at1

2集成于现有系统的graphql层

GraphQL的另一个主要用例是将多个现有的系统集成在一个连贯的GraphQL API之后,这对于拥有遗留基础设施和许多不同API的公司尤为具有吸引力,这些API多年来不断发展,现在带来了很高的维护负担。这些遗留系统的一个主要问题是,它们实际上无法构建需要需要访问多个系统的创新产品(通过GraphQL这个集成层可以访问任意系统)

在这种情况下,GraphQL可用于统一这些现有系统并将其复杂性在一个不错的GraphQL API后面。这样新开发的客户端应用只需要和GraphQL服务器对话即可获取所需数据。然后GraphQL服务器负责从现有系统中获取数据并按照GrapQL的相应格式打包起来。

就像在之前的架构中GraphQL服务器不关心正在使用数据库类型一样,这一次它并不关心它需要获取解析查询所需的数据的数据源。

graphql-at2

3连接数据库和现有系统混合实现

最终,可以将这两种方法结合起来,构建一个GraphQL服务器,该服务器具有连接的数据库,但仍与旧系统或第三方系统通信。

当服务器接收到查询时,它将解析它并从连接的数据库和某些集成的API中检索所需要的数据。

graphql-at3

解析器函数

但是我们如何通过GraphQL获得这种灵活性呢?为什么它非常适合这种不通类型的用例?

正如前面张杰所了解的,GraphQL查询的有效负载由一组字段组成。在GraphQL服务器实现中,这些字段中的每一个实际上都对应于一个成为解析起的函数。解析器的唯一目的就是为其字段获取数据。

当服务器收到查询时,它将调用查询有效负载中指定的字段的所有函数。因此,它解决了查询并能够为每个字段检索正确的数据。一旦所有解析器都返回,服务器将以查询描述的格式打包数据并将其发送回客户端。

graphql-rf

上图中包含了一些已解析的字段名称,查询中的每个字段对应一个解析器函数。当查询进入以获取指定数据时,GraphQL会调用所有必需的解析器。

graphql-client-libraries

GraphQL特别适合前段开发人员,因为它完全消除了REST API遇到的许多不便和缺点,例如过度请求和不足请求。复杂性被推到服务端,强大的及其可以处理繁重的计算工作。 客户端不需要知道它获取的数据实际来自哪里,并且可以使用单一、连贯且灵活的API。

让我们考虑一下GraphQL引入的主要变化,即从一种相当必要的数据获取方法转变为另一种声明式的方法。当从REST API请求数据时,大多数应用必须经历如下步骤:

  1. 构建并发送HTTP请求
  2. 接受并解析服务响应
  3. 本地保存数据(内存或本地)
  4. 在UI中展示数据

对于理想的声明式数据请求方法,客户端不应该超过以下两个步骤:

  1. 描述数据需求
  2. 在UI中展示数据

所有低级别的网络任务以及存储数据都应该被抽象出来,数据依赖关系的声明应该是主要部分。

这正是Replay和Apollo等GraphQL客户端库应该让你做的事情。它们提供了你需要能够专注于应用程序的重要部分而不是必须处理基础设施的重复实现所需要的抽象。