GraphQL for Elixir#2 リクエストとレスポンスの値について考える

（この記事は「[fukuoka.ex x ザキ研 Advent Calendar 2017](https://adventar.org/calendars/2873#list-2017-12-07)」の13日目です)

昨日は @twinbee さんの「[Elixirで一千万行のJSONデータで遊んでみた Rustler編](https://qiita.com/twinbee/items/e0878a21385b1576f479)」でした。


# はじめに

前回は[GraphQL for Elixir#1 基本的な実装について考える](https://qiita.com/kobatako/items/df18a99b8601c9a426ad)でGraphQLの基本的な実装について書きました。
GraphQLでは実際に通信する際のリクエストとレスポンス値を定義しておく必要があります。
ElixirのAbsintheでもリクエストとレスポンスの値を定義する必要があります。

# データの形式

まずは、リクエストとレスポンスで使うデータの形式、定義について見ていきたいと思います。
基本的には下記のものがGraphQLで使うことのできる型になります。これをScalarといいます。

```
id : uniqueな値
integer : 整数
string : 文字列
float : 小数
boolean : true or false
```

上記５つのScalarを使いリクエストとレスポンスの値を決めます。
これ以外のものを利用したい場合も自分で定義することができます。

##カスタム Scalar

下記のものはDateTimeの型を定義しています。

```Elixir
scalar :datetime, name: "DateTime" do
  serialize &DateTime.to_iso8601/1
  parse &parse_datetime/1
end
```
これで`datetime`としてobjectのfieldなどに指定することができます。
`serialize` は文字通り、シリアライズするときに呼ばれる関数で返り値が送信するときの値として実行されます。
`DateTime.to_iso8601` はDateTimeのものをiso8601の形式に沿ってstirngにします。

`parse &parse_datetime/1` はデータを受け取ったときに実行される関数を指定し、関数の返り値をリクエストを受け取るschemaの引数の値をして渡します。

```Elixir
  defp parse_datetime(%Absinthe.Blueprint.Input.String{value: value}) do
    case DateTime.from_iso8601(value) do
      {:ok, datetime, 0} -> {:ok, datetime}
      {:ok, _datetime, _offset} -> :error
      _error -> :error
    end
  end
```

このように受け取った値をパースします。今回はiso8601の形式になっているのでそれからDateTime型に変換して受け取ります。

## Enum

Enumも定義するとこもできます。

```Elixir
enum :color do
  value :red
  value :green
  value :blue
end
```
これは色の定義を行い、リクエストは下記のような形式で行います

```
{
  foo(color: RED)
}
```
実際にElixirで処理するときには下記のような形式でリクエストが来ます

```Elixir
%{color: :red}
```
これでEnumでのデータの型を定義することができます。

# リクエスト

リクエストはクライアントからくるリクエストになります。
リクエストでくる値は上記で説明したScalarとそれを組み合わせた構造体のようなものもできます。

```Elixir
input_object :contact_todo do
  field :title, :string
  field :body, :string
end
```
`:contact_todo` っというリクエストの際のデータを定義できます。
うけとるSchemaは下記のように定義します。

```Elixir
field :create_todo, :todo do
  arg :todo, :contact_todo
  resolve(&MyApp.Resolver.TodoContent.create_todo/3)
end
```
あとはクライアントから`contact_todo`のオブジェクトと同じ構造でリクエストを送ることができます。

# レスポンス
GraphQLではレスポンス値をクライアント側で決めることができます。
そのために返す値をサーバ側で定義します。

```Elixir
object :post do
  field :id, :id
  field :title, :string
  field :body, :string
end
```
`post`オブジェクトを定義して、その中で`id`、`title`、`body`のfieldを持っています。これをschemaの返り値として定義し、そのschemaを呼び出したときに受け取る値として、`id`、`title`、`body`を受け取れます。その際にクライアントではどれを受け取るか決めることができます。

```Elixir
schema search_post, :post do
  arg :id, :id
  resolve(&MyApp.Resolver.Content.search_post/3)
end
```
```
{
  search_post(id: 1) {
    id
    title
  }
}
```
schemaでは`post`オブジェクトの構造の形式でレスポンスを返します。クライアント側では`id`と`title`のみ受け取るようにします。

## 階層化

リクエストで送られてくるもので構造化したものをさらに別のobjectの一つのfieldととして定義することができます。
構造化されたデータを簡単に定義でき、自由度を高く使えるのはGraphQLの特徴の一つだと思います。

```Elixir
object :post do
  field :id, :id
  field :title, :string
  field :body, :string
end

object :user do
  field :id, :id
  field :name, :string
  field :email, :string
  field :posts, list_of(:post) do
end
```
`post`のobjectを`user`のfieldとして定義しています。
こうすることで`user`のレスポンス値の一部に`post`のリストを返すことができます。
さらにここで`user`はpostの中でも特定のもののみしか取得したくない場合があると思います。なので`post`の中でもさらに検索をかけることができます。

```Elixir
object :user do
  field :id, :id
  field :name, :string
  field :email, :string
  field :posts, list_of(:post) do
    arg :date, :date
    resolve &Resolvers.Content.list_posts/3
  end
end
```
クライアントから送られてくるリクエストは下記のようなものになります。

```
{
  user(id: "1") {
    name
    posts(date: "2017-01-01") {
      title
      body
      publishedAt
    }
  }
}
```

こうすることで自由度を高く構造化、階層化された値を返すことができます。

# まとめ

簡単ではありますが、リクエストとレスポンスのデータについて記載しました。
リクエストとレスポンスに使う値を定義、schemaを作成できたりとデータ構造に対して自由度を高く決めれました。特に階層化されたデータの取得は自由度を高く色々な構造のものが取れます。その構造化されたデータを取得できるweb APIを簡単に作ることができました。
ただ、構造化されたものを簡単に定義できる分、使いどころと設計はよく考えた方がいいかと思いました。

明日は @piacere_ex さんの「[関数型でデータサイエンス#3：インプットしたデータを集約する①](https://qiita.com/piacere_ex/items/ef434011d154dc73f0cd)」です！お楽しみに！