LoginSignup
4
2

Django + Strawberryで作るGraphQLサーバ

Last updated at Posted at 2022-12-09

この記事はDjango Advent Calendar 2022 10日目の記事です。

はじめに

今年は仕事が変わったのもあって、Djangoに触る機会が減りました。全くDjangoを触らなくなったのではなく、細々と自分のサイトで使っているのですが、Django + StrawberryでGraphQLサーバを作ると楽だぞという話をします。

PythonのGraphQL事情

PythonのGraphQLと言えばGrapheneが有名です。Graphene-Djangoも触ってみて良い感触でしたが、開発が活発ではなくDjango 4.0の対応が遅れていたため(今は対応しています)、一時期はDead Project?というIssueが立つほどでした。

そこで代わりに見つけたのがStrawberryです。

Strawberryの特徴

Python 3.7から採用されたデータクラス(dataclasses)に触発(インスパイア)されて作ったライブラリです。

Strawberry is a new GraphQL library for Python 3, inspired by dataclasses.

こんな感じで定義できます。見やすいですね。

import typing
import strawberry

@strawberry.type
class Book:
    title: str
    author: str

@strawberry.type
class Query:
    books: typing.List[Book]

他の多くのライブラリと同じくGraphiQL UIを内蔵していて、簡単に動作確認ができます。

Django ORMと組み合わせる

StrawberryはPythonのWebフレームワークとの統合機能があります。もちろんDjangoにも対応しています

以下のコードは自分がプライベートで作っているサイトのコードを元にしています。

インストールするもの

  • strawberry-graphql[debug-server]: Strawberryのコア(チュートリアルにあるもの)
  • strawberry-graphql-django: Djangoとの統合機能

モデルと対応したType

DjangoのモデルInboxに対応したInboxTypeは次のように書けます。フィールドは自前で定義する必要がありますが、勝手に定義されたりしないので安心です。

型はだいたい auto でいけます。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

Enumはこんな感じです。簡単ですよね。

@strawberry.enum
class InboxState(Enum):
    ACTIVE = "active"
    ALL = "all"

Query

Queryはこんな感じです(型ヒントの書き方が古いですが)。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

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

最後にViewです。GraphQLViewというクラスを使います。これは次のような継承関係になっています。

  • strawberry.django.views.GraphQLView
  • strawberry.django.views.BaseView
  • django.views.generic.base.View

総評

GraphQLの概念に慣れるまで最初は大変でしたが、一旦セットするとメンテが楽です。特にTypeの書き方がシンプルでいいです。

他にもいろいろGraphQLライブラリ(主にTypeScript)を試してみましたが、だいたい2種類なんですよね。

  • Resolverをスキーマから勝手に全部作ってくれるもの(Hasura, PostGraphileとか)
  • Resolverを全部自分で書く必要があるもの

前者はセキュリティやフィールドをリネームしたときの互換性のことを考えると微妙だし、後者はMutationはともかくQueryは楽したいなぁと思っていました。

Strawberry + Djangoならフィールドは明示的に指定できますが、型は勝手に推測してくれるので楽です。RDBMSをそのままGraphQLの構造に変えるが、自由さは残したい、そんなときにちょうどいいです。

注意が必要なところとしては、まだ1.0未満で、アップデートがめちゃくちゃ多いです。2022/12/5現在で488個のタグが打たれています。ただ今のところ互換性でトラブったことはないです。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2