LoginSignup
12
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-06-23

(この記事は「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は単一のエンドポイントのみとなりますので、ルーティング自体はシンプルです。

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

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

  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しか使わないのでその二つの定義を見ていきます。

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で使う objectinput_objectscalar などを定義します(この後に詳しく説明します)
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の定義をしておきます。

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 を記述しておきます。

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

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

4. resolve関数の実行

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

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. 実際のリクエスト

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

スクリーンショット 2018-06-23 23.18.39.png

リクエストとレスポンス、正常にできました。
ちなみに今回使ったGraphQLのクライアントは「GraphQL Playground」です。

まとめ

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

明日は @takasehideki さんの「ElixirでIoT#1.3.2:プロセッサコアの動作周波数を揃えて比較評価してみた」になります

12
3
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
12
3