Slide 1

Slide 1 text

@mtsmfm GraphQL Ruby で作る REST API

Slide 2

Slide 2 text

@mtsmfm 松島 史秋 GraphQL Tokyo 主催者 DbD と Ruby と GraphQL が好き 主にバックエンドの開発

Slide 3

Slide 3 text

伝えたいこと - 宣言とクエリという考え方は、API サーバの実 装をシンプルにする - GraphQL は勝手にそうなる - REST API にも応用できるかも - Ruby は実装例として出したけど Ruby 以外も考え方は 同じはず

Slide 4

Slide 4 text

REST API を作るときの課題 - 似て非なるリソース - GET /articles - [{id: 1, title: “a”}, {id: 2, title: “b”}, ...] - GET /articles/:id - [{id: 1, title: “a”, thumbnail_url: “example.com”}] - thumbnail_urlという差分をどう表現するか

Slide 5

Slide 5 text

差分をどう表現するか 1. 両方とも thumbnail_url を入れる class ArticleEntity < Entity; end def as_json {id: @article.id, title: @article.title, thumbnail_url: @article.thumbnail_url} end end get "/articles" do Article.all.map {|a| ArticleEntity.new(a) }.to_json end get "/articles/:id" do ArticleEntity.new(Article.find(params[:id]).to_json end

Slide 6

Slide 6 text

差分をどう表現するか 1. 両方とも thumbnail_url を入れる a. 深く考えないと一覧での処理が重くなりがち b. あとで問題になったときに実は使ってないかわかり にくい i. Web、iOS、Android とクライアントすべてコード 見ないと消せるかわからない

Slide 7

Slide 7 text

差分をどう表現するか 2. /articles/:id だけ thumbnail_url を入れる class ArticleListEntity < ArticleCommonEntity; end class ArticleDetailEntity < ArticleCommonEntity; def as_json super.merge(thumbnail_url: @article.thumbnail_url) end end get "/articles" do Article.all.map {|a| ArticleListEntity.new(a) }.to_json end get "/articles/:id" do ArticleDetailEntity.new(Article.find(params[:id]).to_json end

Slide 8

Slide 8 text

差分をどう表現するか 2. /articles/:id だけ thumbnail_url を入れる a. 同じところと違うところの表現が苦しくなりがち i. ArticleEntity、ArticleLiteEntity を ArticleCommonEntity から継承 or mixin ii. オプションで渡す 別テーブルだと preload の必要性の有無も 変わってくる

Slide 9

Slide 9 text

GraphQL なら class ArticleType < GraphQL::Schema::Object field :id, ID, null: false field :title, String, null: false field :thumbnail_url, String, null: false end class QueryType < Types::BaseObject field :articles, [ArticleType], null: true field :article, ArticleType, null: true do argument :id, ID, required: true end def articles; Article.all; end def article(id:); Article.find(id); end end class AppSchema < GraphQL::Schema query QueryType end post "/graphql" do AppSchema.execute( Params[:query], Params[:variables] ).to_json end

Slide 10

Slide 10 text

GraphQL なら - 宣言的に Type class を 1 つ書けば OK - クエリによって評価されたりされなかったりによ る区別 => 宣言とクエリ - N+1 も Loader がクエリによって評価されたりさ れなかったりすることで、最低限の計算で解消

Slide 11

Slide 11 text

GraphQL クライアント問題 - 原理的には JSON を HTTP POST して、レス ポンスが parse できればなんでもいい - 型の恩恵を受けようとすると、特に native でク ライアントライブラリへのランタイム依存を持た ない codegen の類が (ほぼ) ない - 既存アプリへの導入の課題となり得る - codegen の作りの問題ではある

Slide 12

Slide 12 text

GraphQL のメトリクスやログ問題 - 既存の仕組みに乗っかれない - パスによる監視 - HTTP method による監視 - ログ - 最悪クエリを parse しないと分析できないことがあるか も - BigQuery なら JS 動かせるのでなんでもあり - Operation 名つけとけばなんとかなりそうでは あるものの、既存資産の利用が困難

Slide 13

Slide 13 text

Persisted Query - クエリを事前に登録しておく - クエリの形ごとに ID を振っておく - クライアントはクエリではなくクエリの ID を投げ つける

Slide 14

Slide 14 text

Persisted Query post "/graphql" do AppSchema.execute(params[:query]).to_json end post "/graphql" do query = QueryStore.get(params[:query_id]) AppSchema.execute(query).to_json end

Slide 15

Slide 15 text

Path based Persisted Query - (勝手に命名) - クエリをエンドポイント毎ベタ書きする - クライアントは普通の REST API のように使う

Slide 16

Slide 16 text

Path-based Persisted Query get "/articles" do AppSchema.execute(<<~GQL).to_json query { articles { id, title } } GQL end get "/articles/:id" do AppSchema.execute(<<~GQL, {id: params[:id]}).to_json query getArticle($id: ID!) { article(id: $id) { id, title, thumbnail_url } } GQL end

Slide 17

Slide 17 text

ご清聴ありがとうございました - 宣言とクエリという考え方は、API サーバの実 装をシンプルにする - GraphQL は勝手にそうなる - REST API にも応用できるかも - Ruby は実装例として出したけど Ruby 以外も考え方は 同じはず