背景
常见的RESTful API中,每个API负责请求一种类型的对象,一个页面往往需要多次请求接口,且经常存在依赖关系,无法并行发送请求。在工作中是否经常遇到这个场景:
产品:本次需求我们需要在页面上新增***展示。
前端:目前后端提供的接口中没有返回,需要新增字段。
后端:这个接口现在多方在使用,需要评估是否会对其他使用方有影响,且目前排期已满,需要等到****版本才能做。
产品:Boss说明天要上线……
如果是个人项目,可能这个需求一两个小时就能完成,但如果是公司项目,这样一个小小的需求,动辄花费的时间要上周才能上线。那么,我们是否可以通过某种手段,将接口的返回值从静态变成动态,即调用者来声明接口返回什么数据,来进一步解耦前后端的关联?于是,Facebook在2016年9月,GraphQL正式进入生产环境。GraphQL的联合创建者Dan Schafer认为:
“GraphQL 是一种查询语言,用于我们在客户端和服务器之间转移合约的 API,允许服务器说‘这些是我公开的能力’,并允许客户端以一种方式来描述它们的要求,而这种方式最终使产品开发人员能够构建其希望创建的产品。”
在后台,它告诉API如何把检索到的数据呈现给客户端,是的开发人员能够发出精确的数据请求,不多也不少。
GraphQL的使用
01
几个重要的概念
https://graphql.cn/learn/queries/
GraphQL 的操作类型可以是 query、mutation 或 subscription,描述客户端希望进行什么样的操作
query 查询:获取数据,比如查找,CRUD 中的 R
mutation 变更:对数据进行变更,比如增加、删除、修改,CRUD 中的 CUD
substription 订阅:当数据发生更改,进行消息推送
1.2 对象类型和标量类型 Object Type & Scalar Type
如果一个 GraphQL 服务接受到了一个 query,那么这个 query 将从 Root Query 开始查找,找到对象类型(Object Type)时则使用它的解析函数 Resolver 来获取内容,如果返回的是对象类型则继续使用解析函数获取内容,如果返回的是标量类型(Scalar Type)则结束获取,直到找到最后一个标量类型。
对象类型:用户在 schema 中定义的 type
标量类型:GraphQL 中内置有一些标量类型 String、Int、Float、Boolean、ID,用户也可以定义自己的标量类型。
1.3 模式 Schema
它定义了字段的类型、数据的结构,描述了接口数据请求的规则,当我们进行一些错误的查询的时候 GraphQL 引擎会负责告诉我们哪里有问题,和详细的错误信息,对开发调试十分友好。Schema 使用一个简单的强类型模式语法,称为模式描述语言(Schema Definition Language, SDL)。
02
实战
Python GraphQL入门示例,采用Flask框架,API使用GraphQL实现一个web服务,提供书籍的查询与添加功能。
2.1 安装模块
pip install graphene
pip install Flask-GraphQL
type Book{
id: ID!
name: String!
}
type Query{
books: [Book!]!
}
type Mutation{
add(name: String!): Book!
}
2.3 web服务
from flask import Flask
from flask_graphql import GraphQLView
import graphene
app = Flask(__name__)
books = []
class Book(graphene.ObjectType):
""" Book
"""
id = graphene.ID(description="book ID")
name = graphene.String(description="book name")
create = lambda id, name: Book(id=id, name=name)
books.append(create(1, "The First Book"))
class Query(graphene.ObjectType):
""" query your books
"""
books = graphene.List(Book, description="list books")
version = graphene.String(description="version")
def resolve_books(self, info):
return books
def resolve_version(self, info):
return "v0.1"
# Mutation
class AddBook(graphene.Mutation):
""" Add books
"""
Output = Book
class Arguments:
name = graphene.String()
def mutate(self, info, name):
book = create(len(books) + 1, name)
books.append(book)
return book
class Mutation(graphene.ObjectType):
""" mutate books
"""
add = AddBook.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql',
schema=schema, graphiql=True))
app.run(port=4901, debug=True)
① books 是一个全局变量,用于保存所有的 books,可把它当做数据库,很多示例都有数据库,为了保持简单,最方便运行,所以,我只用了这么一个变量
② Query 是比较简单的,语义很清晰,和 API 文档不同的是,在 Query 中增加了一个 version,这是为了简单对比一个 restful ,如果是 restful 的话,我们要获取 books 和 version 这两个资源,需要两个 API,但 graphq只需要一个
③ Mutation 分为两步,第一步定义一个Mutate,里面必须要有mutate方法,第二个创建Mutation,用于组合所有的Mutate。
④ /graphql 是给我们提供一个可视化的 API 展示,类似 Swagger,当然,生产环境就不要加这个了,加在开发、测试环境即可
运行代码:
python server.py
2.4 查询
使用curl做测试,大家也可以尝试使用界面,更直观
http://127.0.0.1:4901/graphql\?query\=query%20%7Bbooks%20%7Bid%20name%20%7D%20version%7D
总结
我们上面介绍了GraphQL的优势,我们也介绍一下它的劣势及讨论一下为什么没有流行起来。
01
第一,Facebook 从来没有公开自己的 GraphQL 后端设计,使得大家必需要用第三方的,但体验显然不如我们在 Facebook 内部使用 GraphQL 好。我上面说了,数据必需已经以图的数据结构进行存储才有优势。Facebook 内部有非常好的后端做好了这件事情,而且还内置了基于隐私设置的访问控制。例如说你发的帖子有些是所有人可见的、有些是好友可见的、有些是仅同事可见的,我在打开你的页面时 Facebook 有一个中间层保证了根据我和你的关系我只能看到我该看到的帖子。GraphQL 在这一层之上,所以无论 GraphQL 怎么写我都不可能看到我不该看到的信息。
02
第二,并不是所有场景都适用于 GraphQL 的,有些很简单的事情就应该用 RESTful API 来实现。Facebook 内部用户增长部门的很多 API 都还不是 GraphQL,因为没必要迁移到 GraphQL。用户增长部门的 API 处理新用户注册、填写短信验证码之类的事情,这些事情都是围绕着一个用户的具体某项或多项信息发生的,根本没有任何图的概念。可以强行写作 GraphQL,但得不到显著的好处。既然老的 API 早就写好了,需要的时候做一些小改动,但没必要重写。
03
第三,GraphQL 尽管查询的数据是图状数据结构,但实际获得的数据视图是树状数据结构。每一个 GraphQL 查询或更新都有自己的根节点,然后所有的数据都是从根结点展开出去的。查询后获得的数据如果要在前端重新变回图的状态,那前端就不能简单地缓存查询得到的数据,必须用对用的 GraphQL 存储库,然后通过顶点的 ID 把不同节点之间的某些边重新连接起来。
而其没有流行起来的原因主要在于,GraphQL 的利好主要是在于前端的开发效率,但落地却需要服务端的全力配合。如果是小公司或者整个公司都是全栈,那可能可以做,但在很多前后端分工比较明确的团队里,要推动 GraphQL 还是会遇到各种协作上的阻力。