使用Django和Strawberry创建的GraphQL服务器
这篇文章是2022年Django日历活动的第十天文章。
首先
由于今年工作发生了变化,我对Django的接触机会减少了。虽然并不完全不再接触Django,我仍然会在自己的网站上小规模地使用它。我想和大家分享一下使用Django + Strawberry来构建GraphQL服务器的乐趣。
Python的GraphQL情况
提到Python的GraphQL,Graphene是非常有名的。我也尝试过使用Graphene-Django,感觉不错,但是由于开发活跃度不高,并且对Django 4.0的支持也比较晚(现在已经支持了),曾经有一段时间被认为是一个”Dead Project?”,甚至有人提出了相应的问题。
因此我找到了一个代替品,就是草莓。
草莓的特点
这个库是受到 Python 3.7 中引入的数据类(dataclasses)的启发而创建的。
草莓是一个适用于Python 3的新GraphQL库,受到dataclasses的启发。
可以用这种方式进行定义。很容易理解,对吧?
import typing
import strawberry
@strawberry.type
class Book:
title: str
author: str
@strawberry.type
class Query:
books: typing.List[Book]
和许多其他库一样,它内置了GraphiQL用户界面,可以轻松进行操作验证。
与Django ORM进行整合
Strawberry 在 Python 的 Web 框架集成功能中也有相关支持,当然也支持 Django。
以下的代码是基于我个人私人网站的代码。
安装的东西
strawberry-graphql[debug-server]: Strawberryのコア(チュートリアルにあるもの)
strawberry-graphql-django: Djangoとの統合機能
与模型相匹配的类型
以下是对应于Django模型中的Inbox的InboxType的编写方式。您需要自己定义字段,但不必担心它们会被自动定义。
大致上可以使用自动的方法。它会根据Django的模型定义来处理得很好。
import strawberry_django
from strawberry import auto
@strawberry_django.type(Inbox)
class InboxType:
id: auto
继承(Mixin)也是可能的,但在某些情况下需要定义 is_type_of(GenericModelType 和 DjangoModelType 是我自己创建的 Mixin)的一种方式是这样的。
@strawberry_django.type(Inbox)
class InboxType(GenericModelType, DjangoModelType):
@classmethod
def is_type_of(cls, root, info):
return isinstance(root, (cls, Inbox))
在GraphQL中,有时候我们希望创建反向引用,而在Django ORM中,反向引用是默认支持的,所以可以很简单地编写。
@strawberry_django.type(Book)
class BookType(DjangoModelType, GenericModelType):
@strawberry.field
def references(self: Book) -> List[ReferenceType]:
return self.reference_set.order_by("sort_order")
枚举
Enum在中文里面的翻译大致是这样的。很简单吧。
@strawberry.enum
class InboxState(Enum):
ACTIVE = "active"
ALL = "all"
查询
查询的格式大致如下(虽然类型提示的写法有些过时)。将其设定为Optional后,返回值从必填变为可选。
@strawberry.type
class InboxQuery:
@strawberry_django.field
def inboxes(self, state: InboxState = InboxState.ACTIVE) -> List[InboxType]:
if state == InboxState.ALL:
return Inbox.objects.all()
elif state == InboxState.ACTIVE:
return Inbox.objects.incomplete_only()
else: # pragma: no cover
raise AssertionError
@strawberry_django.field
def inbox(self, number: int) -> Optional[InboxType]:
return Inbox.objects.filter(pk=number).first()
突变
Mutation的使用方式如下。请注意GraphQL的Mutation具有返回值。
@strawberry.type
class InboxMutation:
@strawberry.mutation
def add_inbox(self, name: str) -> InboxType:
inbox = Inbox.objects.create(name=name)
return inbox
@strawberry.mutation
def done_inbox(self, inbox_id: int) -> InboxType:
inbox: Inbox = get_object_or_404(Inbox, pk=inbox_id)
inbox.status = InboxStatus.DONE
inbox.save()
return inbox
合并
就是这种感觉。
from strawberry.tools import merge_types
TaskQuery = merge_types(
"TaskQuery",
(
ContextQuery,
InboxQuery,
LocationQuery,
NextActionQuery,
ProjectQuery,
RepeatTaskQuery,
SectionQuery,
TaskInconsistenciesQuery,
),
)
模式
用这样的方式创建。Query和Mutation通过merge_types合并,Types指定类型的列表或元组。
import strawberry
schema = strawberry.Schema(query=Query, mutation=Mutation, types=Types)
观看
最后是”View”。使用”GraphQLView”这个类。这个类具有以下继承关系。
-
- strawberry.django.views.GraphQLView
-
- strawberry.django.views.BaseView
- django.views.generic.base.View
总评
开始时,要熟悉GraphQL的概念是有些困难的,但是一旦设置完成,维护起来就很容易了。特别是类型的编写方式非常简单,非常好用。
我也尝试了各种不同的GraphQL库(主要是TypeScript),但大致上有两种类型。
-
- Resolverをスキーマから勝手に全部作ってくれるもの(Hasura, PostGraphileとか)
- Resolverを全部自分で書く必要があるもの
考虑到安全性和更名领域时的兼容性问题,前者不太理想,而后者则是我希望Mutation能变得容易,Query更轻松一些。
在Strawberry + Django中,字段可以明确指定,而类型则可以自动推断,这样非常方便。如果想将RDBMS直接转换为GraphQL结构但又想保留自由度,那就正好合适。
需要注意的地方是,该软件版本仍未达到1.0,并且有非常频繁的更新。截止到2022年12月5日,已经添加了488个标签。目前为止,还没有遇到与兼容性有关的问题。