Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Build REST API with GraphQL Ruby

Build REST API with GraphQL Ruby

Fb1b9f3d7332a7a7e262b70013b5f7dd?s=128

Fumiaki MATSUSHIMA

August 28, 2020
Tweet

Transcript

  1. @mtsmfm GraphQL Ruby で作る REST API

  2. @mtsmfm 松島 史秋 GraphQL Tokyo 主催者 DbD と Ruby と

    GraphQL が好き 主にバックエンドの開発
  3. 伝えたいこと - 宣言とクエリという考え方は、API サーバの実 装をシンプルにする - GraphQL は勝手にそうなる - REST

    API にも応用できるかも - Ruby は実装例として出したけど Ruby 以外も考え方は 同じはず
  4. 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という差分をどう表現するか
  5. 差分をどう表現するか 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
  6. 差分をどう表現するか 1. 両方とも thumbnail_url を入れる a. 深く考えないと一覧での処理が重くなりがち b. あとで問題になったときに実は使ってないかわかり にくい

    i. Web、iOS、Android とクライアントすべてコード 見ないと消せるかわからない
  7. 差分をどう表現するか 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
  8. 差分をどう表現するか 2. /articles/:id だけ thumbnail_url を入れる a. 同じところと違うところの表現が苦しくなりがち i. ArticleEntity、ArticleLiteEntity

    を ArticleCommonEntity から継承 or mixin ii. オプションで渡す 別テーブルだと preload の必要性の有無も 変わってくる
  9. 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
  10. GraphQL なら - 宣言的に Type class を 1 つ書けば OK

    - クエリによって評価されたりされなかったりによ る区別 => 宣言とクエリ - N+1 も Loader がクエリによって評価されたりさ れなかったりすることで、最低限の計算で解消
  11. GraphQL クライアント問題 - 原理的には JSON を HTTP POST して、レス ポンスが

    parse できればなんでもいい - 型の恩恵を受けようとすると、特に native でク ライアントライブラリへのランタイム依存を持た ない codegen の類が (ほぼ) ない - 既存アプリへの導入の課題となり得る - codegen の作りの問題ではある
  12. GraphQL のメトリクスやログ問題 - 既存の仕組みに乗っかれない - パスによる監視 - HTTP method による監視

    - ログ - 最悪クエリを parse しないと分析できないことがあるか も - BigQuery なら JS 動かせるのでなんでもあり - Operation 名つけとけばなんとかなりそうでは あるものの、既存資産の利用が困難
  13. Persisted Query - クエリを事前に登録しておく - クエリの形ごとに ID を振っておく - クライアントはクエリではなくクエリの

    ID を投げ つける
  14. 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
  15. Path based Persisted Query - (勝手に命名) - クエリをエンドポイント毎ベタ書きする - クライアントは普通の

    REST API のように使う
  16. 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
  17. ご清聴ありがとうございました - 宣言とクエリという考え方は、API サーバの実 装をシンプルにする - GraphQL は勝手にそうなる - REST

    API にも応用できるかも - Ruby は実装例として出したけど Ruby 以外も考え方は 同じはず