GraphQL for Elixir#1 基本的な実装について考える

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

昨日は @twinbee  さんの「[ElixirのiexでとりこぼしたPIDをひろう](https://qiita.com/twinbee/items/5ef58c6f4c1200173197)」 でした。

#はじめに

最近GraphQLのことを調べる & 使う機会が増えてきたのでここでまとめていきたいと思います。


# GraphQL

GraphQLがなんなのかは公式のページを見た方が一番いいと思うのでリンク貼っておきます。

https://graphql.org/

簡単にいうとWeb APIです。それでいい感じに取得したいデータをリクエストしていい感じにデータを返してくれます（雑）。

## GraphQL for Elixir

ElixirのGraphQLライブラリで有名なのは「Absinthe」が有名です。
Phoenixへ対応しているのとPlug、Ecto用のライブラリも提供されているのでかなり使いやすい印象です。

https://hexdocs.pm/absinthe/overview.html

## 実装環境

Elixir : 1.6.5
Phoenix : 1.3.2

## 実装方法
### 0. インストール

まずは `mix` でインストールします

```Elixir
defp deps do
  [
    {:absinthe, "~> 1.4"},
    {:absinthe_plug, "~> 1.4"}
  ]
end
```

バージョン1.4系で最新のものを利用します。今回はPlugも使うので `absinthe_plug` も一緒にインストールしておきます。

### 1. ルーティングの設定

Phoenixと一緒に使ったのでまずはPhoenix側でのルーティングの設定をします。GraphQLは単一のエンドポイントのみとなりますので、ルーティング自体はシンプルです。

```Elixir:router.ex
scope "/" do
  pipe_through(:api)

  forward(
    "/",
    Absinthe.Plug,
    schema: MyApp.Schema
  )

  forward(
    "/graphiql",
    Absinthe.Plug.GraphiQL,
    schema: MyApp.Schema,
    interface: :simple,
    context: %{pubsub: MyApp.Endpoint}
  )
end
```

ルーティングの設定はこれだけです。これでエンドポイントを指定しています。

```Elixir
  forward(
    "/",
    Absinthe.Plug,
    schema: MyApp.Schema
  )
```
これがエンドポイントになります。`Absinthe.Plug`を利用し、`schema: MyApp.Schema`でこのGraphQLのschemaの指定しています。

```Elixir
  forward(
    "/graphiql",
    Absinthe.Plug.GraphiQL,
    schema: MyApp.Schema,
    interface: :simple,
    context: %{pubsub: MyApp.Endpoint}
  )
```
ちなみにこっちはgraphiqlのエンドポイントを指定しています。graphiqlはGraphQLのクライアントソフトから接続することができ、schema定義を参照できたりとドキュメントとして見ることもできます。

### 2. schema定義

schemaを定義します。今回はQueryとMutationしか使わないのでその二つの定義を見ていきます。

```Elixir:schema.ex
defmodule MyApp.Schema do
  use Absinthe.Schema
  import_types(MyApp.Schema.ContentTypes)

  query do
    field :all_todos, list_of(:todo) do
      resolve(&MyApp.Resolver.Content.all/3)
    end
  end
  
  mutation do
    field :create_todo, :todo do
      arg(:title, non_null(:string))
      arg(:check, non_null(:boolean))
      resolve(&MyApp.Resolver.Content.create_todo/2)
    end
  end
```
schema用のモジュールを作ります。
`use Absinthe.Schema` を最初に呼びます。
`import_types(MyApp.Schema.ContentTypes)` はSchemaで使う `object` や `input_object`、`scalar` などを定義します（この後に詳しく説明します）
mutationでは今回は詳しくは書きませんが、引数を指定しています。
`arg`関数で引数となる項目と型を決めます（`non_null`はそのまま、nullなしです）

```Elixir
query do
  field :all_todos, list_of(:todo) do
    resolve(&MyApp.Resolver.Content.all/3)
  end
```
ここが実際のschemaの定義部分でQueryの定義になります。`all_todos`っというschemaを定義し、返り値として`list_of(:todo)`を定義しています。ちなみに`list_of`は配列で値を返すようにします。
`resolve(&MyApp.Resolver.Content.all/3)`でGraphQLの返り値を返す関数を指定します。

### 3. Objectの定義

GraphQLで使うObjectの定義をしておきます。

```Elixir:content_type.ex
defmodule MyApp.Schema.ContentTypes do
  use Absinthe.Schema.Notation

  object :todo do
    field(:id, :id)
    field(:title, :string)
    field(:check, :boolean)
  end
```

Objectを定義するのにまずは`use Absinthe.Schema.Notation` を記述しておきます。

```Elixir
object :todo do
  field(:id, :id)
  field(:title, :string)
  field(:check, :boolean)
end
```
オブジェクトの定義です。`todo`オブジェクトを定義し、その中にid、title、checkの項目を持っています。
これを先ほどのschemaの部分で利用し、レスポンスの値として使います。
ここの定義の方法に関してもっと複雑のことができるのですが、それだけでけっこうなボリュームになってしまうので今回は割愛します。

### 4. resolve関数の実行

では早速resolveで指定した関数を見ていきたいと思います。

 ```Elixir:content.ex
defmodule MyApp.Resolver.Content do
  alias MyApp.Repo
  alias MyApp.Todo

  def all(_args, _info) do
    {:ok, Repo.all(Todo)}
  end
```

`Repo.all(Todo)` でTodoの全てのリストを返すようにします。
リクエストが正常に返る場合は返り値のタプルの先頭に`:ok`を記述します。
もし失敗の場合は先頭に`:error`を記述します。

### 5. 実際のリクエスト

これで一通りリクエストとレスポンスができる状態になりました。
では一旦ここでリクエストを送って見たいと思います。

<img width="650" alt="スクリーンショット 2018-06-23 23.18.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/215989/8c4e06c5-43bd-486f-db8a-502c5ed9fe3a.png">

リクエストとレスポンス、正常にできました。
ちなみに今回使ったGraphQLのクライアントは「[GraphQL Playground](https://github.com/prismagraphql/graphql-playground)」です。


# まとめ

GraphQLは最近ちょいちょい話題になったりならなかったりしていますが、使ってみた感じとして自分はしっくりきました。仕様自体にバリデーションがあるのと構造化したデータの取得、その時のレスポンス値の担保など開発者を楽にしてくれるのでハイでしょうか。ただ銀の弾丸までとはいきませんのでRESTFulでAPIをつくるのかGraphQLにするのかはシステムによるのでそこの選定基準をある程度持っておかないといけないですかね。あとは設計がかなり大変だと思います。

明日は @takasehideki さんの「[ElixirでIoT#1.3.2：プロセッサコアの動作周波数を揃えて比較評価してみた](https://qiita.com/takasehideki/items/ac3bdb8c66a5b6710d11)」になります