LoginSignup
4
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-06-30

(この記事は「fukuoka.ex x ザキ研 Advent Calendar 2017」の13日目です)

昨日は @twinbee さんの「Elixirで一千万行のJSONデータで遊んでみた Rustler編」でした。

はじめに

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

データの形式

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

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

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

カスタム Scalar

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

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の引数の値をして渡します。

  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も定義するとこもできます。

enum :color do
  value :red
  value :green
  value :blue
end

これは色の定義を行い、リクエストは下記のような形式で行います

{
  foo(color: RED)
}

実際にElixirで処理するときには下記のような形式でリクエストが来ます

%{color: :red}

これでEnumでのデータの型を定義することができます。

リクエスト

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

input_object :contact_todo do
  field :title, :string
  field :body, :string
end

:contact_todo っというリクエストの際のデータを定義できます。
うけとるSchemaは下記のように定義します。

field :create_todo, :todo do
  arg :todo, :contact_todo
  resolve(&MyApp.Resolver.TodoContent.create_todo/3)
end

あとはクライアントからcontact_todoのオブジェクトと同じ構造でリクエストを送ることができます。

レスポンス

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

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

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

schema search_post, :post do
  arg :id, :id
  resolve(&MyApp.Resolver.Content.search_post/3)
end
{
  search_post(id: 1) {
    id
    title
  }
}

schemaではpostオブジェクトの構造の形式でレスポンスを返します。クライアント側ではidtitleのみ受け取るようにします。

階層化

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

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の中でもさらに検索をかけることができます。

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:インプットしたデータを集約する①」です!お楽しみに!

4
2
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
4
2