前言
在过去的十多年中,REST已经成为设计web api的一个模糊标准。它提供了一套比较完整语义化结构化的标准,然后这种设计还是相对而已比较僵硬,不够灵活,不能快速的对API进行扩容,无法跟上目前客户的快速需求变化。然而Facebook提供了一套比较完整的API接口设计GraphQL,GraphQL解决了REST对于开发人员的一些不便,大大提供了开发效率,以及扩展性。
官方定义:
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
什么是GraphQl
GraphQL全称Graph Query Language ,语义上指图表化查询语言,是一种描叙客户端香服务器请求数据的API语法,类似于RESTful API规范,它与数据库没有任何关系,
它不依赖任何数据库,可以与任何的MySQL、NoSQL数据库一起使用。
GraphQl的特征
- schema
GraphQl schema是强类型的,可使用SDL来定义类似于数据库的Schema定义。每一个schema中允许出现三种根类型:Query、Mutation、Subscription,每一次调用GraphQL服务,需要指定嗲用Schema中的哪种根类型(默认是Query)
query是用来查询数据、mutation是用来增加、修改、删除数据的
query {
users(): [User!]!
user(id: Int): User!
}
mutation {
createUser(): User!
deleteUser(id: Int): User!
}
-
Type
是对于数据模型的抽象, 包含两种类型:一种是scalar type(标量类型) ,另一种是object type(对象类型)。
-
scalar type:
GraphQL中的内建的标量包含,String、Int、Float、Boolean、Enum,除此之外,GraphQL中可以通过scalar声明一个新的标量
-
object type
一个 GraphQL schema 中的最基本的组件是对象类型,如上面的User,它表示服务器将要返回的所有字段,如:
type User { id: ID! #id mobile: String #电话好吗 nickName: String #昵称 avatar:String #头像 createTime:Date #创建时间 updateTime:Date #更新时间 workList:[Work] }
-
User是一个GraphQL对象类型,可以理解就是一个object对象,里面有一些字段属性, id、mobile、nickName、avatar就是scalar type,
createTime、updateTime是自定义的scalar type,workList就是一个object type
在每一次操作User查询时,只会出现定义的字段如id、mobile等等,
id: ID!表示这个字段是非空,也就是GraphQL服务一定能保证这个字段有值。[Work] 表示一个work对象的数组对象。
- Resolve 解析器
上述中的schema(query、mutation)是用来操作数据,type是数据类型,而resolve就是说明如何执行相关的(query、mutation)操作放回数据的逻辑,
也是连接schema与type的
GraphQL中,默认有这样的约定,Query(包括query、mutation、subscription)和与之对应的Resolve是同名的,比如关于users(): [User!]!这个query,它的Resolve的名字必然叫做user
如:前端调用query语句时:
query{
users{
id
mobile
workList{
id
}
}
}
它的解析过程:
- 第一次解析时,当前的的类型时query,所以我们定义一个同名的 query:users 的Resolver
- 我们会使用query:user的Resolve获取解析数据
- 如id,mobile 第一层顺利解析,在解析workList时,我们会使用users:workList 的Resolve去解析
- 然后解析出来workList中的id
概括总结GraphQL大体解析流程就是遇见一个Query之后,尝试使用它的Resolver取值,
之后再对返回值进行解析,这个过程是递归的,直到所有解析Field类型是Scalar Type(标量类型)为止。
整个解析过程可以想象为一个很长的Resolver Chain(解析链)。
为什么使用GraphQL
-
GraphQL解决了什么问题
- 接口多难维护:接口的数量通常由业务场景的数量决定,为了尽量减少接口数量,我们经常对业务进行抽象,即使如此,由于业务总是多变的
露的接口还是很多。 - 接口不够灵活:出于带宽的考虑移动端我们要求接口返回尽量少的字段,PC 端通常要展现更多字段;
- 接口合并:如考虑首屏性能,我们又要求对接口做合并;传统 API 应对这些需求,前后端都面临改造,成本较高。
- 文档不全:由于接口文档几乎总是不能及时更新,前端工程师无法预知接口响应的数据格式,影响前端开发进度。
- 接口多难维护:接口的数量通常由业务场景的数量决定,为了尽量减少接口数量,我们经常对业务进行抽象,即使如此,由于业务总是多变的
-
GraphQL如何解决问题
-
接口多难维护:
GraphQL并不需要为每个业务提供一个对应的接口,只需要定义一些基本的schema即可 -
接口不够灵活:
在GraphQL中,是client需要哪些数据,server才精确返回哪些数据。如:query{ user(id:1):{ id mobile } } // response user:{ id:1 mobile:'139XXXXX139' }
query{ user(id:1):{ id mobile name } } // response user:{ id:1 mobile:'139XXXXX139' name:'YUX' }
基于Resolve会解析query中所有的字段,其他字段不会返回。
-
接口合并减少网络请求
基于一些数据量大的接口,rest一般会是通过多个接口并/串行组合数据,而graphql则可以通过嵌套编写复杂的schema达到一次请求{ user(id:1) { id mobile workList(limit: 10) { id url name comment(limit: 20) { id author title } } } }
如上,我们可以用一个借口获得与用户相关的所有信息,并不需要多个接口返回。
-
文档不全
GraphQL让你的整个应用共享一套API,通过GraphQL API能够更好的利用你的现有数据和代码,就是说通过我们定义的schema,很清晰的能看到
入参以及出参,并不需要过多的API文档说明。
-
概括总结:
客户端的对数据的述求:调用哪个方法,传递什么样的参数,返回哪些字段。服务端拿到这段 Schema 之后,
通过事先定义好的服务端 Schema 接收请求参数并执行对应的 resolve 函数提供数据服务。
整个过程可以想象成我们吃自助餐的过程,服务端 Schema 就好比自助餐线,
摆上我们能提供的所有食物;客户端 Schema 就描述了我们想要吃的食物,按需获取就好了
GraphQL 潜在问题
- N+1问题:在实现GraphQL代码中我们很容易写出性能比较差的查询,引起N+1的问题
```
works = Works.getAll();
works.forEach(item=>{
label = Label.get(item.id)
})
```
由于作品标签不存在work表中,所有第一次查询出作品列表,第二步循环去查询每个作品的标签信息,原本是一个查询的,结果
导致来N+1次,这就是N+1问题,在graphql中,很容易造成这样,主要是由于 GraphQL query 的逐层递归解析方式所引起的。
node解决方案采用DataLoader
-
安全问题
由于graphql自带强大的内省自检机制也就是我们上面提到的静态检测,可以直接获取后端定义的所有接口信息
{ __schema { types { name } } } response: { "data": { "__schema": { "types": [ { "name": "Query" }, { "name": "Int" }] } } }
所以对于每个接口的权限管理,就需要开发者定制比较合理的鉴权机制
-
拒绝服务
如果我们定义如下查询就会导致,服务器拒绝服务blogs(blogId: $blogId, systemType: $systemType) { _id title type content author { name blog { author { name blog { author { name blog { author { name blog { author { name # and so on... } } } } } } } title createdAt publishedAt } } publishedAt }
我们定义接口的时候尽量避免如此定义,也可以在GraphQL服务器上限制查询深度,可以采用graphql-depth-limit解决该问题
-
不易缓存
官方的翻译:在一个基于端点的API,客户端能够使用HTTP缓存轻易的避免重复获取资源和识别什么时候两个资源是一样的。客户端可以根据API中的URL作为全局唯一的标识符构建缓存。
在GraphQL中,没有类似URL的对象能够作为全局唯一的标识符。最佳实践是提供这么一个标识符供客户端使用。
小结
GraphQL只是一个API技术,它为API连接的前后端提供了一种新的便捷处理方案,与语言无关。无论如何,该做鉴权的就鉴权,该校验数据的还是一定得校验。
GraphQL会提供我们的开发效率,减少前后端的沟通成本,提高了程序的可扩展以及维护性。
网友评论