Djangoでgraphql


#GraphQLとは
facebookが開発し、オープンソースとして公開中。
従来のリソース指向のRESTAPIにある課題を解決するため、サーバサイドへの通信に利用できるクエリー言語として開発された。
以下の特徴を備えている。

- 柔軟なインターフェース
- 単一エンドポイント
- 型システム

#RESTAPIに対するメリット
#####Over/UnderFetch問題
RESTAPIではリソースごとにエンドボイントが作成されるため、
クライアントによっては以下の問題が発生し得る
1. 必要な情報を得るために複数のAPIをコールしなければならないケース（UnderFetch）
2. APIのインターフェース上、クライアントが必要以上の情報を取得してしまうケース(OverFetch)

これらのケースに対し、GraphQLでは単一のエンドポイントでクライアントが必要な情報を選択して取得できるというメリットを備えている

#####スキーマ
RESTAPIではAPI毎のインターフェースに合わせて、リクエストの型をクライアントに定義する必要がある
GraphQLではサーバ側でスキーマを定義するので、ドキュメントの生成が容易である

# GraphQLの主な機能
###query
RESTAPIでいうところのGETに相当
対象のリソースとフィールドを指定してリクエストすることで、
条件にあった情報を取得できる

###mutation
POST/PUTに相当
更新対象のリソース、フィールドを指定してリクエストすることで、
対象を更新できる。

###subscription
常時接続のメッセージモデルであるPublish/SubScribeのようなもの。
リソースに更新があった場合に、サーバからクライアントに通知することができる。

#事前準備

###環境
Python 3.6.0
Django 2.0.1
graphene-django 2.2.0

以下をインストールしておく

```
pip install graphene-django
```


##アプリケーション作成
適当なプロジェクトを作成後、サンプルアプリケーションのディレクトリを作成する
今回はitemsとした。

プロジェクトディレクトリのsettingsに、graphene_djangoのパッケージとアプリのパスを追加しておく

```python
# project/settings.py

# sample project application
INSTALLED_APPS += [
    'graphene_django',
    'items',
]
```

####model
最小構成

```python

# project/items/models.py
from django.db import models


class Items(models.Model):
    name = models.CharField(max_length=100)
    count = models.IntegerField()

    def __str__(self):
        return self.name

```

makemigrationでマイグレーションファイルを作成後
migrateでマイグレーションをしておく

####Schema

```python
# project/items/schema.py
import graphene
from graphene_django import DjangoObjectType
from items.models import Items


class Item(DjangoObjectType):
    class Meta:
        model = Items


class Query(graphene.ObjectType):
    items = graphene.List(Item)
```


project直下にもschemaファイルを作成する

```python
# project/schema.py

import graphene
import items.schema


class Query(items.schema.Query,
            graphene.ObjectType):
    pass


schema = graphene.Schema(
    query=Query,
)
```

settingsにスキーマのパスを追加しておく

```python
# project/settings.py

GRAPHENE = {
    'SCHEMA': 'schema.schema'
}
```


### url
urlを設定する
GraphQLなので設定するのは単一のエンドポイントになる

```python
# project/urls.py

from django.conf.urls import include, url
from django.contrib import admin
from graphene_django.views import GraphQLView


urlpatterns = [
    url(r'^graphql/', GraphQLView.as_view(graphiql=True)),  # <- graphql_sample
    url(r'^admin/', admin.site.urls),
]
```

# 実行
runserverして http://localhost/graphql/ にアクセスするとコンソール付きの画面が表示される
以下のqueryを実行してみる

```graphql

query {
  items {
    id,
    name,
    count,
  }
}
```
構造は以下のような形
query ... 情報取得系クエリであることの宣言
items ... 取得対象のリソース。
id, name, count ... 取得対象のフィールド。書き換えることでレスポンスで取得する内容を変更できる。

DBとは疎通していないためこの時点では空の結果が返ってくる

```

{
  "data": {
    "items": null
  }
}
```

schemaに以下を追加する

```python
# items/schema.py

class Query(graphene.ObjectType):
    items = graphene.List(Item)

    def resolve_items(self, info, **kwargs):
        return Items.objects.all()
```
実際の値が返る

```
{
  "data": {
    "items": [
      {
        "count": 1,
        "name": "orange"
      },
      {
        "count": 1,
        "name": "banana"
      },
      {
        "count": 20,
        "name": "apple"
      },
      {
        "count": 10,
        "name": "pineapple"
      }
    ]
  }
}
```



##mutation
schemaに以下を追加する

```python
# items/schema.py
class CreateItem(graphene.Mutation):
    class Arguments:
        name = graphene.String()
        count = graphene.Int()
    
    item = graphene.Field(lambda :Item)

    def mutate(self, info, name, count):
        item = Items.objects.create(name=name, count=count)
        return CreateItem(item=item)

class Mutation(graphene.ObjectType):
    create_item = CreateItem.Field()
```

project直下のスキーマにも追加する

```python

class Mutation(items.schema.Mutation,
               graphene.ObjectType):
    pass

schema = graphene.Schema(
    query=Query,
    mutation=Mutation,  # 追加
)
```

以下の形式でリクエストすると、レコードが作成されレスポンスに登録結果が返ってくる

```graphql

mutation {
  createItem(name:"grape", count:3) {
    item {
      name
      count
    }
  }
}
```
mutation ... 更新系のクエリであることの宣言
createItem() ... 実行クエリ。引数が更新内容になる
item ... 更新後のリソース情報

```
{
  "data": {
    "createItem": {
      "item": {
        "name": "grape",
        "count": 3
      }
    }
  }
}
```