(この記事は「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
オブジェクトを定義して、その中でid
、title
、body
のfieldを持っています。これをschemaの返り値として定義し、そのschemaを呼び出したときに受け取る値として、id
、title
、body
を受け取れます。その際にクライアントではどれを受け取るか決めることができます。
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の特徴の一つだと思います。
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:インプットしたデータを集約する①」です!お楽しみに!