【后续补充Apollo相关】
前言
REST 的 API 配合JSON格式的数据交换,使得前后端分离、数据交互变得非常容易,而且也已经成为了目前Web领域最受欢迎的软件架构设计模式。
REST的缺点:
1、大量滥用,导致大量相似度很高(具有重复性)的API冗余;
2、对于前端而言:REST API粒度较粗,难以一次性符合前端的数据要求,前端需要分多次请求接口数据。增加了前端人员的工作量。
3、对于后端而言:前端需要的数据往往在不同的地方具有相似性,但却又不同,比如针对同样的用户信息,有的地方只需要用户简要信息(比如头像、昵称),有些地方需要详细的信息,这就需要开发不同的接口来满足这些需求。当这样的相似但又不同的地方多的时候,就需要开发更多的接口来满足前端的需要。增加了后端开发人员的工作量和重复度。
当遇到当前端需求变化,涉及到改动旧需求时,有两种手段:
- 加法
- 减法
【其实用到的数据大多都是来自于同一个DO或者DTO,不过是在REST接口组装数据时,用不同的VO来封装不同字段,或者,使用同样的VO,组装数据时做删减。】
那么有没有一种方案或者框架,可以使得在用到同一个领域模型(DO或者DTO)的数据时,前端对于这个模型的数据字段需求的改动,后端可以根据前端的改动和需要,自动适配,自动组装需要的字段,返回给前端呢?
针 对 这 痛 点 , 出 现 了 G r a p h Q L \color{red}{针对这痛点,出现了GraphQL} 针对这痛点,出现了GraphQL
GraphQL 官网
GraphQL简介
- GraphQL是一种新的API标准,它提供了一种比REST更有效、更强大和更灵活的替代方案。
- 它是由Facebook开发并开源的,现在由来自世界各地的公司和个人组成的大型社区维护。
- GraphQL本质上是一种基于api的查询语言,现在大多数应用程序都需要从服务器中获取数据,这些数据存储可能存储在数据库中,API的职责是提供与应用程序需求相匹配的存储数据的接口。
- 它是数据库无关的,而且可以在使用API的任何环境中有效使用,我们可以理解为GraphQL是基于API之上的一层封装,目的是为了更好,更灵活的适用于业务的需求变化。
GraphQL | REST API |
---|---|
声明式数据获取,使得接口数据精确返回,数据查询流程简洁,照顾了客户端的灵活性 | 接口灵活性差、接口操作流程繁琐 |
一个服务仅暴露一个 GraphQL 层,消除了服务器对数据格式的硬性规定,客户端按需请求数据,可进行单独维护和改进 | 客户端拓展功能时要不断编写新接口(依赖于服务端) |
传输层无关、数据库技术无关使得 GraphQL 有更加灵活的技术栈选择,能够实现在网络协议层面优化应用 | REST API 基于HTTP协议,不能灵活选择网络协议 |
GraphQL获取数据
1、首先要设计数据模型,用来描述数据对象,它的作用可以看做是VO,用于告知GraphQL如何来描述定义的数据,为下一步查询返回做准备;
2、前端使用模式查询语言(Schema)来描述需要请求的数据对象类型和具体需要的字段(称之为声明式数据获取);
3、后端GraphQL通过前端传过来的请求,根据需要,自动组装数据字段,返回给前端。
Schema 和类型
Query 、Mutation and Subscription
GraphQL对数据支持的操作有:
- 查询(Query):获取数据的基本查询。
- 变更(Mutation):支持对数据的增删改等操作。
- 订阅(Subscription):用于监听数据变动、并靠websocket等协议推送变动的消息给对方。
Schema(图表模式)【 核 心 概 念 \color{red}{核心概念} 核心概念】
引入 schema 的原因
因为一个 GraphQL 查询的结构和结果非常相似,因此即便不知道服务器的情况,你也能预测查询会返回什么结果。但是一个关于我们所需要的数据的确切描述依然很有意义,我们能选择什么字段?服务器会返回哪种对象?这些对象下有哪些字段可用?
每一个 GraphQL 服务都会定义一套类型,用以描述你可能从那个服务查询到的数据。每当查询到来,服务器就会根据 schema 验证并执行查询。
想要描述数据,就必须离不开数据类型的定义。所以GraphQL设计了一套Schema模式(可以理解为语法),其中最重要的就是数据类型的定义和支持。
------
类
型
(
T
y
p
e
)
\color{red}{类型(Type)}
类型(Type)就是模式(Schema)最核心的东西
什么是类型?
- 对于数据模型的抽象是通过类型(Type)来描述的,每一个类型有若干字段(Field)组成,每个字段又分别指向某个类型(Type)。这很像Java、C#中的类(Class)。
- GraphQL的Type简单可以分为两种,一种叫做Object Type(对象类型),另一种叫做Scalar Type(标量类型)。
那么就分别来介绍下两种类型。
标量类型(Scalar Type)
一个对象类型有自己的名字和字段,而某些时候,这些字段必然会解析到具体数据。这就是标量类型的来源:它们表示对应 GraphQL 查询的叶子节点。
下列查询中,name 和 appearsIn 字段将解析到标量类型:
查询
hero {
name
appearsIn
}
}
响应
{
"data": {
"hero": {
"name": "R2-D2",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
]
}
}
}
我们知道这些字段没有任何次级字段 —— 因为让它们是查询的叶子节点。
GraphQL 自带一组默认标量类型:
- Int:有符号 32 位整数。
- Float:有符号双精度浮点值。
- String:UTF‐8 字符序列。
- Boolean:true 或者 false。
- ID:ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。ID 类型使用和 String 一样的方式序列化;然而将其定义为 ID 意味着并不需要人类可读型。
大部分的 GraphQL 服务实现中,都有自定义标量类型的方式。例如,我们可以定义一个 Date 类型:
scalar Date
然后就取决于我们的实现中如何定义将其序列化、反序列化和验证。例如,你可以指定 Date 类型应该总是被序列化成整型时间戳,而客户端应该知道去要求任何 date 字段都是这个格式。
对象类型(Object Type)
仅有标量类型是不能满足复杂抽象数据模型的需要,这时候我们可以使用对象类型。
通过对象模型来构建GraphQL中关于一个数据模型的形状,同时还可以声明各个模型之间的内在关联(一对多、一对一或多对多)。
一个 GraphQL schema 中的最基本的组件是对象类型,它就表示你可以从服务上获取到什么类型的对象,以及这个对象有什么字段。使用 GraphQL schema language,我们可以这样表示它:
type Character {
name: String!
appearsIn: [Episode!]!
}
虽然这语言可读性相当好,但我们还是一起看看其用语,以便我们可以有些共通的词汇:
Character
是一个 GraphQL 对象类型,表示其是一个拥有一些字段的类型。你的 schema 中的大多数类型都会是对象类型。name
和appearsIn
是Character
类型上的字段。这意味着在一个操作Character
类型的 GraphQL 查询中的任何部分,都只能出现name
和appearsIn
字段。String
是内置的标量类型之一 —— 标量类型是解析到单个标量对象的类型,无法在查询中对它进行次级选择。后面我们将细述标量类型。String!
表示这个字段是非空的,GraphQL 服务保证当你查询这个字段后总会给你返回一个值。在类型语言里面,我们用一个感叹号来表示这个特性。[Episode!]!
表示一个Episode
数组。因为它也是非空的,所以当你查询appearsIn
字段的时候,你也总能得到一个数组(零个或者多个元素)。且由于Episode!
也是非空的,你总是可以预期到数组中的每个项目都是一个Episode
对象。
现在你知道一个 GraphQL 对象类型看上去是怎样,也知道如何阅读基础的 GraphQL 类型语言了。
列表和非空(Lists and Non-Null)
那么,类型系统仅仅只有类型定义是不够的,我们还需要对类型进行更广泛性的描述。
类型修饰符(Type Modifier)就是用来修饰类型,以达到额外的数据类型要求控制。
比如:
- 列表:[Type]
- 非空:Type!
- 列表非空:[Type]!
- 非空列表,列表内容类型非空:[Type!]!
在描述数据模型(模式Schema)时,就可以对字段施加限制条件。 实 例 可 按 参 考 上 方 t y p e C h a r a c t e r \color{red}{实例可按参考上方 type Character} 实例可按参考上方typeCharacter
其他类型(接口,联合,输入)
除了上面的,Graphql还有一些其他类型来更好的引入面向对象的设计思想:
接口类型(Interfaces):
其他对象类型实现接口必须包含接口所有的字段,并具有相同的类型修饰符,才算实现接口。
例如,你可以用一个 Character 接口用以表示《星球大战》三部曲中的任何角色:
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
这意味着任何实现 Character 的类型都要具有这些字段,并有对应参数和返回类型。
例如,这里有一些可能实现了 Character 的类型:
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
}
联合类型(Union Types):
联合类型和接口十分相似,但是它并不指定类型之间的任何共同字段。几个对象类型共用一个联合类型。
union SearchResult = Human | Droid | Starship
在我们的schema中,任何返回一个 SearchResult 类型的地方,都可能得到一个 Human、Droid 或者 Starship。注意,联合类型的成员需要是具体对象类型;你不能使用接口或者其他联合类型来创造一个联合类型。
这时候,如果你需要查询一个返回 SearchResult 联合类型的字段,那么你得使用条件片段才能查询任意字段。
请求
{
search(text: "an") {
__typename
... on Human {
name
height
}
... on Droid {
name
primaryFunction
}
... on Starship {
name
length
}
}
}
响应
{
"data": {
"search": [
{
"__typename": "Human",
"name": "Han Solo",
"height": 1.8
},
{
"__typename": "Human",
"name": "Leia Organa",
"height": 1.5
},
{
"__typename": "Starship",
"name": "TIE Advanced x1",
"length": 9.2
}
]
}
}
_typename
字段解析为 String,它允许你在客户端区分不同的数据类型。
此外,在这种情况下,由于Human
和 Droid
共享一个公共接口(Character),你可以在一个地方查询它们的公共字段,而不必在多个类型中重复相同的字段:
{
search(text: "an") {
__typename
... on Character {
name
}
... on Human {
height
}
... on Droid {
primaryFunction
}
... on Starship {
name
length
}
}
}
注意name
仍然需要指定在Starship
上,否则它不会出现在结果中,因为 Starship
并不是一个Character
!
输入类型(Input Types):
更新数据时有用,与常规对象只有关键字修饰不一样,常规对象时 type 修饰,输入类型是 input 修饰。
比如定义了一个输入类型:
input ReviewInput {
stars: Int!
commentary: String
}
你可以像这样在变更(mutation)中使用输入对象类型:
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
VARIABLES
{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
响应
{
"data": {
"createReview": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
}
客户端响应数据问题
数据约定:
格式 {
m
e
s
a
g
e
\color{red}{mesage }
mesage 附带的信息
s
t
a
t
u
s
\color{red}{status }
status 值 -1,0,1,2,3,4,100大于0存在异常 其中100 代表graphql抛出的异常状态
c
o
d
e
\color{red}{code }
code 值String 在status ==100 下 code 有以下值
- Graphql-500 服务器内部错误或者远程接口500
- Graphql-501 前端的查询语法错误
- Graphql-502 接口数据格式不正确
- Graphql-503 接口访问失败
- Graphql-504 服务器状态码非正常状态 输出服务器返回的信息
- Graphql-505 参数格式不对
- Graphql-UnKnown 未知错误
d a t a \color{red}{data } data 数据内容
}