GraphQL是Facebook去年开源的一套数据查询语言,对于大型系统,GraphQL提供一种灵活的访问通用数据的方式。当时正好有一个项目,需要前端调用后台的数据(Express+Postgresql)实现图表展示,我就从Postgresql里把数据导出成JSON文件(后来用Promise改成直接查询数据库),前端直接用GraphQL获取自己需要的数据,后台就省掉了制作API、写SQL的工作,运行至今基本稳定。
前两个月发布了graphene-django,做的事情不多,但可以搭配其他插件例如django-filters来实现更丰富的查询。GraphQL的优势在于查询语句的通用性和易读性,相比REST架构来说时间较短还不够成熟,如果是复杂需求后台的开发量仍然不小,现阶段在生产环境用Django做一个API服务还是rest framework等成熟的方案会更好。本文实现一个针对Django自带的User Model的简单API。
环境:
Django 1.10.4
graphene-django-1.2.0
Setup
安装graphene-django,如果是新建Django项目,创建后执行migrate创建表,再创建admin用户:
pip install graphene-django
django-admin startproject dmyz
cd dmyz
python manage.py migrate
python manage.py createsuperuser
编辑settings.py,将graphene-django加入INSTALLED_APPS:
INSTALLED_APPS = (
# ...
'graphene_django',
)
再编辑urls.py,导入GraphQLView,加上graphql的设置:
from graphene_django.views import GraphQLView
urlpatterns = [
# ...
url(r'^graphql', GraphQLView.as_view(graphiql=True)),
]
执行runserver,这时候访问/graphql会报错,显示没有提供schema。
Schema
在dmyz目录中新建schema.py文件,目录结构如下:
dmyz
├── db.sqlite3
├── dmyz
│ ├── __init__.py
│ ├── schema.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
编辑schema.py文件,导入DjangoObjectType和graphene,处理django自带的User Model,代码如下:
from django.contrib.auth.models import User as UserModel
from graphene_django import DjangoObjectType
import graphene
class User(DjangoObjectType):
class Meta:
model = UserModel
class Query(graphene.ObjectType):
users = graphene.List(User)
@graphene.resolve_only_args
def resolve_users(self):
return UserModel.objects.all()
schema = graphene.Schema(query=Query)
最后编辑settings.py,加上配置:
GRAPHENE = {
'SCHEMA': 'dmyz.schema.schema'
}
现在重新访问/graphql,可以看到自带的前端界面,在左上方文本框中输入:
{
users {
username
email
}
}
运行(点击上方▶️按钮)返回admin用户的username和email。右侧的Docs会显示Query信息,下划线命名也会自动转成驼峰命名。
查询语句完整格式是 query 名称{},上面的查询语句省掉了query,因为GraphQL默认会作为query语句执行,如果是mutation就需要加上了。关于查询语句可以参考官方文档:http://graphql.org/learn/queries/
Schema & Type & Field
无论后台是Python还是Nodejs,查询语句都是一样的,但不同语言对Schema的实现方式不同。Schema定义数据的呈现结构,包含各种Types,其中Query和Mutation是两个特殊的Type,Type通过Fields指定返回的数据字段。以之前的代码为例,代码中定义了Type(Query)和Fields(users),指定resolve来处理数据。
Filter
先增加一个测试用户,在后台添加或者执行:
python manage.py shell -c "from django.contrib.auth.models import User;User.objects.create_user(username='dmyz',email='admin@dmyz.org',password='dmyz.org')"
执行之前的查询语句会返回两条记录。修改schema.py的修改Query:
class Query(graphene.ObjectType):
users = graphene.List(User,
id=graphene.Int()) #指定id字段作为查询参数
# @graphene.resolve_only_args 注释装饰器,接收args参数
def resolve_users(self, args, context, info):
if args == {}: #如果没有参数就返回所有结果
return UserModel.objects.all()
return UserModel.objects.filter(pk=args.get('id')) #否则返回过滤后的结果
schema = graphene.Schema(query=Query)
执行新的查询语句,指定id:
query userId{users(id: 2) {id,username,email}}
新的查询语句用逗号分隔字段,增加了名字,执行后只返回id=2的用户:
curl "http://127.0.0.1:8000/graphql?query=query%20userId%7Busers(id%3A%202)%20%7Bid%2Cusername%2Cemail%7D%7D&operationName=userId" | python -m json.tool
{
"data": {
"users": [{
"id": "2",
"username": "dmyz",
"email": "admin@dmyz.org"
}]
}
}
Mutation
先继承graphene.Mutation创建一个类,定义输入的参数,再处理成ObjectType传递给Schema:
class CreateUser(graphene.Mutation):
class Input:
username = graphene.String()
password = graphene.String()
email = graphene.String()
ok = graphene.Boolean()
user = graphene.Field(lambda: User)
def mutate(self, args, content, info):
user = User(username=args.get('username'))
ok = True
return CreateUser(ok=ok, user=user)
class Mutation(graphene.ObjectType):
create_user = CreateUser.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
查询语句必须申明是mutation,传入Input中定义的字段,还要定义返回的内容:
mutation {
createUser(username: "graphql", password: "test") {
ok
user {
username
}
}
}
执行后返回数据:
{
"data": {
"createUser": {
"ok": true,
"user": {
"username": "graphql"
}
}
}
}
Afterword
如前文提到的,GraphQL不如REST架构历史悠久,但也有自己的独特优势:一是返回结果跟查询语句相关,对于返回的结构能有清晰认识,REST请求时并不知道会返回JSON还是XML,也不知道会返回那些字段;二是数据变动更灵活,不需要像以前一样v1/v2…vn来标明版本,采用单一查询入口更容易扩展。GraphQL是React生态的一部分(还有Relay),支持上目前来看是可以保证的。