先贴参考文件:http://jerryzou.com/posts/10-questions-about-graphql/#61-n1-%E9%97%AE%E9%A2%98
https://ithelp.ithome.com.tw/articles/10188294
GraphQL官方定义:针对API的查询语言。
类比ECMAScript和JavaScript的关系。GraphQL更像是一个标准
golang服务器端实现例子:https://github.com/graphql-go/graphql
Type
GraphQL 里面的基本类型 Type (GraphQLScalarType) 有以下五种,都可以轻易的转换成任何程式语言的值:
- String
- Int
- Float
- Boolean
- ID
其他四种很容易理解,不過 ID
是怎样的一个东西呢?看代码就能很清楚知道它就是任何的 Int
或 String
。
- 不允许空值 设置
GraphQL 预设所有值可以是null,要避免空值的话可以在后面加上!来指定不能为null,例如:
String!复制代码
就代表不能为null的String
- List
List的话则是放入[]来表示,例如:
[Int]复制代码
结合下eg: 不能为null的list里面放不能是null的String就会是
[String!]!复制代码
- ObjectType
在GraphQL中,可以用Object的方式来形成另一个Type,例如一个Post Type,里面可以用到前面所有的Type:
type User {
name: String!
}
type Post {
id: ID!
author: User! //再包Type也是可以
title: String!
body: String!
comments: [String!]!
}复制代码
/*
在用graphql的时候,最多的时间都是花在定义schema和type上。但完善后,就能尽查询的人随便去使用查询。 性能上Facebook搭配使用的DataLoader
*/
REST问题
参数、回传值类型不固定
多版本时,前后端常不匹配
不断增多的(endpoint接口)
不容易处理多层级资源
相同点
都是服务端所承载的系统对外的服务接口。所以,两者肯定可以共存。
- 都有资源这个概念,而且都能通过ID去获取资源。
- 都可以通过HTTP GET方式来获取资源
- 都可以使用JSON作为响应格式
GraphQL与RESTful有什么区别?
核心差异:资源的描述信息与其获取方式相分离。
- 接口
RESTful,核心是资源。讲究接口操作单一资源。因此,会出现大量接口。
GraphQl是单一入口,一般配置在[host]/graphql/,所有的资源都从该入口通过graphql的语句获取或者修改。 (GraphQL也是支持多入口的)
- 数据关联性
RESTful操作的资源是离散的,GraphQL数据更具有整体性
- 接口定义方式
GraphQL的URL请求里面指定了我们所需要的资源以及在该资源中我们所关心的字段。另外,我们是主动请求得到与book相关的author数据的,而不是服务端替我们决定的。
GET /user/:userId/friends/
而A有20个好朋友,那我们共需要发送20+1次REST请求
或者特殊设计 GET /user/:userId/friendsAndHisFriends/
在GraphQL中,
用法示例:
step1:先给User定义Schema
type User {
id: ID!
name: String!
friends: [User]
}复制代码
step2:假设我们在Graph root上只挂一个Node,叫user:
type Query {
user(id: ID!): User
}复制代码
那么我们从客户端发送的query就可以写成这样:
GET /graphql?query={ book(id: "1") { title, author { firstName } } }
query ($userId: ID) {
user(id: $userId) {
name
friends {
name
friends {
name
}
}
}
}复制代码
特点:这个query可以无限写下去!~~~~~~~~~ It’s Graphs All the Way Down *
but,1+N问题又来了。
1+N问题是什么?怎么解决?
在orm框架中,可以设置关联对象,比如user对象关联dept。假如查询出n个user,那么需要做n次查询dept,查询user是一次select,查询关联的dept,是n次,所以说是n+1问题。
简述:
1+n是执行一次查询n条主数据后,由于关联引起的执行n次查询从数据;它带来了性能问题
一般来说,通过懒加载 可以部分缓解1+n带来的性能问题
如何解决在GraphQL中的N+1问题?
以下方案仅针对关系型数据库
- 针对一对一的关系,将所需要的数据join到一张表里。别等着ORM框架懒加载那些你需要的数据。
- 针对多对一或者多对多的关系,用工具库: DataLoader,其主要功能:batching&caching,可以将多次数据库请求合并成一个,加载过的数据可以从DataLoader的缓存空间中获取到。
GraphQL可以做修改(curd系列)吗?就只做查询?
可以, GraphQl中加入了操作符,mutation。 亮点较少,不记录了。
https://graphql.org/learn/queries/#mutations
栗子放上:
const usersById = {
1: {
id: 1,
name: 'chentsulin',
},
};复制代码
exports.schema = buildSchema(`
type User {
id: ID!
name: String!
}
type Post {
id: ID!
title: String!
body: String!
}
type Query {
users: [User!]!
posts: [Post!]!
}
type RemoveUserPayload{
deletedUserId: Int!
}
type Mutation {
addUser(name: String!): User
renameUser(id: Int!, name: String!): User
removeUser(id: Int!): RemoveUserPayload
}
`);复制代码
let nextId = 2
const mutation = {
addUser: ({name}) => {
const newUser = {
id: nextId,
name,
};
usersById[nextId] = newUser;
nextId++;
return new GraphQLUser(newUser);
},
renameUser: ({id, name}) => {
usersById[id].name = name;
return new GraphQLUser(usersById[id]);
},
removeUser: ({id}) => {
delete usersById[id];
return {
deletedUserId: id,
};
},
}
exports.rootValue = {
hello: () => 'Hello world!',
users: () => Object.keys(usersById).map(id => usersById[id]),
posts: () => Object.keys(postsById).map(id => postsById[id]),
...mutation, //es6语法, 追加
};
class GraphQLUser {
constructor({id, name}) {
this.id = id;
this.name = name;
}
}复制代码
GraphQL与RESTful相比有什么优点?
1数据关联性和结构化
2更易于前端缓存数据
apollo-client,都已经做好。 移步apllo-client
3Versionless API
相比于RESTful为了兼容新老客户端所添加的版本号,在GraphQL中,如果你需要服务端提供与以前不一样的行为,只需要修改root Query的定义,在上面额外添加你想要的Node即可。
4更健壮的接口
一切面向前端的接口都有强类型Schema做保证,且完整类型定义因introspection完全对前端可见,一旦发送的query与Schema不符,能快速感知到产生了错误。
5期待的subscription
浏览器中接受服务端推送。 GraphQL 也计划引入除了 query
, mutation
以外的第三种操作符 subscription
尬点:目前就apollo社区为Node.js写的graphql-subscriptions方案
调试GraphQL接口
- livedemo: https://graphql.org/swapi-graphql/
- GraphiQL: https://github.com/graphql/graphiql 调试神器,Facebook
GraphQL客户端的实现
常见框架: 1、Relay 2、Apollo Client热度高
GraphQL处理分页问题
上代码
query ($userId: ID) {
user(id: $userId) {
name
friends(first: 2, offset: 3) {
name
}
}
}复制代码
上面例子从$userId的第4个朋友开始算起,取前2个朋友
类似的设计
- friends(first:2, after: $friendId)
- friends(first:2, after: $friendCursor)
Relay风格的分页接口,注:apllo-client兼容该分页接口
query {
user {
name
friends(first: 2, after: $cursor) {
edges {
cursor
node {
id
name
}
}
pageInfo {
hasNextPage
}
}
}
}复制代码