Python Tech

GraphQL+Django提供基本API

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),支持上目前来看是可以保证的。

avatar

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

  Subscribe  
提醒