(この記事は「fukuoka.ex x ザキ研 Advent Calendar 2017」の6日目です)
昨日は @twinbee さんの「ElixirのiexでとりこぼしたPIDをひろう」 でした。
#はじめに
最近GraphQLのことを調べる & 使う機会が増えてきたのでここでまとめていきたいと思います。
GraphQL
GraphQLがなんなのかは公式のページを見た方が一番いいと思うのでリンク貼っておきます。
簡単にいうとWeb APIです。それでいい感じに取得したいデータをリクエストしていい感じにデータを返してくれます(雑)。
GraphQL for Elixir
ElixirのGraphQLライブラリで有名なのは「Absinthe」が有名です。
Phoenixへ対応しているのとPlug、Ecto用のライブラリも提供されているのでかなり使いやすい印象です。
実装環境
Elixir : 1.6.5
Phoenix : 1.3.2
実装方法
0. インストール
まずは mix
でインストールします
defp deps do
[
{:absinthe, "~> 1.4"},
{:absinthe_plug, "~> 1.4"}
]
end
バージョン1.4系で最新のものを利用します。今回はPlugも使うので absinthe_plug
も一緒にインストールしておきます。
1. ルーティングの設定
Phoenixと一緒に使ったのでまずはPhoenix側でのルーティングの設定をします。GraphQLは単一のエンドポイントのみとなりますので、ルーティング自体はシンプルです。
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
ルーティングの設定はこれだけです。これでエンドポイントを指定しています。
forward(
"/",
Absinthe.Plug,
schema: MyApp.Schema
)
これがエンドポイントになります。Absinthe.Plug
を利用し、schema: MyApp.Schema
でこのGraphQLのschemaの指定しています。
forward(
"/graphiql",
Absinthe.Plug.GraphiQL,
schema: MyApp.Schema,
interface: :simple,
context: %{pubsub: MyApp.Endpoint}
)
ちなみにこっちはgraphiqlのエンドポイントを指定しています。graphiqlはGraphQLのクライアントソフトから接続することができ、schema定義を参照できたりとドキュメントとして見ることもできます。
2. schema定義
schemaを定義します。今回はQueryとMutationしか使わないのでその二つの定義を見ていきます。
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なしです)
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の定義をしておきます。
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
を記述しておきます。
object :todo do
field(:id, :id)
field(:title, :string)
field(:check, :boolean)
end
オブジェクトの定義です。todo
オブジェクトを定義し、その中にid、title、checkの項目を持っています。
これを先ほどのschemaの部分で利用し、レスポンスの値として使います。
ここの定義の方法に関してもっと複雑のことができるのですが、それだけでけっこうなボリュームになってしまうので今回は割愛します。
4. resolve関数の実行
では早速resolveで指定した関数を見ていきたいと思います。
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. 実際のリクエスト
これで一通りリクエストとレスポンスができる状態になりました。
では一旦ここでリクエストを送って見たいと思います。
リクエストとレスポンス、正常にできました。
ちなみに今回使ったGraphQLのクライアントは「GraphQL Playground」です。
まとめ
GraphQLは最近ちょいちょい話題になったりならなかったりしていますが、使ってみた感じとして自分はしっくりきました。仕様自体にバリデーションがあるのと構造化したデータの取得、その時のレスポンス値の担保など開発者を楽にしてくれるのでハイでしょうか。ただ銀の弾丸までとはいきませんのでRESTFulでAPIをつくるのかGraphQLにするのかはシステムによるのでそこの選定基準をある程度持っておかないといけないですかね。あとは設計がかなり大変だと思います。
明日は @takasehideki さんの「ElixirでIoT#1.3.2:プロセッサコアの動作周波数を揃えて比較評価してみた」になります