RailsでのREST API開発の知見共有

RailsでのREST API開発の知見共有

【オンライン開催】銀座Rails#40 (2021/12/17 19:00〜)

銀座Railsは、Railsについての知識を交換し、日々の開発に役立てていこうという人々の集まるコミュニティです。 公募を締め切りました。最新の銀座への登壇応募は公開されている最新銀座RailsのConnpassページをご覧下さい。 最新情報は@GinzaRails をご確認ください。 1月の銀座Rails登壇応募も近々開始予定です。 なお、ご登壇頂いた方には銀座Rails Tシャツかパーカーのうちどれか一つ、お好きなものをプレゼント致します。 https://suzuri.jp/ginzarails ※発送は銀座Rails実施後になります 『こんな内容で大丈夫だろうか』『発表枠が長いので相談したい』などのご心配がありましたら、 Twitter @GinzaRails までDM等でご相談下さい。 (原則先着順のため、相談中の枠の完売につきましてはご容赦下さい) 19:05 - 19:35 osyoさん 12月25日にリリースされる Ruby 3.1 に備えよう! Ruby 3.1 で新しく追加されるデバッガやエラー箇所のハイライト、Hash の省略記法といった新機能の紹介や新しく追加されるメソッド、変更点などを紹介し、来たるべき Ruby 3.1 に備えていきたいと思います。 わたしは Refinements が好きなので import_methods が入るのが楽しみです。 19:35 - 19:50 松谷勇史朗(@uuushiro)さん Railsメジャーバージョンアップを安全にカナリアリリースする 最近、弊社のRailsアプリケーションをv5.1からv6.1へメジャーバージョンアップしました。メジャーバージョンアップは大きな変更ですが、カナリアリリースにより安全にリリースすることができました。この発表では、Railsアプリケーションの各種プロセス(puma, sidekiq, delayed_job, cronなど)をカナリアリリースする際のチップス、及びバージョンアップする際に問題となった箇所について共有します。 19:55 - 20:10 kon_yuさん RailsでのREST API開発の知見共有 RailsでREST APIサーバを作成する場合 APIドキュメントはOpenAPI(Swagger)を使ってメンテナブルにしたい SPAやスマフォアプリなどクライアントとでOpenAPIを使ってAPIアクセス部を自動生成したい これらの要望を満たすためにライブラリはいくつかあり、その中で下記のライブラリを本番環境で使いました。 Grape rspec-openapi committee-rails その経験からPros/Consを共有しつつ、現在時点でのおすすめをお話します。 20:30 - 21:10 ゲストスピーカー 加藤尋樹 ( cockscomb) さん GraphQLの高速道路 WEB+DB PRESS Vol.125 の特集「GraphQL完全ガイド」で、GraphQLについて幅広く紹介しました。本発表では、GraphQL APIを実装する上でのプラクティスを中心に紹介します。GraphQLに素早くキャッチアップする一助となれば幸いです。 ※ 運営の都合により変更がある場合がございます。予めご了承下さい。 ZOOMを用いたリモート開催となります。 ZOOMのウェビナー機能を利用致します。参加時に表示名とメールアドレスの入力が必要となります。恐れ入りますがご了承下さい。 開場時間前後になりますと、本connpassにご登録されたアドレスに、参加のためのURLが配布されますので、そちらを用いてJoinをお願い致します。 no-reply@connpass.com からのメールが迷惑メールフォルダ等に入らないよう、ご注意下さい。

【オンライン開催】銀座Rails#40 (2021/12/17 19:00〜)

銀座Rails #40 に登壇してきました。

登壇資料資料はこちら

以下は資料をテキストにしたものです。

目次

目次

PR

UZUMAKIではアジャイル開発で新規事業の開発から、大規模Webアプリケーションのアーキテクチャ更新のなど開発をしています。

お問い合わせはUZUMAKIのHPのお問合せフォームから

OpenAPIを利用する要件

OpenAPIを使う要件を分解してみよう

  • メンテナブルなAPI設計書をつくりたい
  • モックサーバを使ってスキーマ駆動開発がしたい
  • TypeScriptのAPIアクセス部分を自動生成したい

メンテナブルなAPI設計書をつくりたい

  • API設計書を自動生成してドキュメント作成コストを下げたい
    • ソースコードとドキュメントの二重管理を避けたい
  • 手動で作るとだんだんメンテナンスされなくなる
    • この規模のサービスでもドキュメントない/メンテされてないの!?なんてことあるよね?

モックサーバを使ってスキーマ駆動開発がしたい

  • OpenAPI version3に対応したメジャーなモックサーバ

使い方はとても簡単

> prism mock doc/openapi.yaml

レスポンスはopenapi.yamlの各メソッドのexampleを返してくれる

TypeScriptのAPIアクセス部分を自動生成したい

  • OpenAPIから各種言語のフロントエンドの自動生成
    • openapi-generator
    • Rubyも生成できるし、Swift, Kotlin, Dartも生成できる
    • TypeScriptの生成も様々なタイプができる Angular, Aurelia, Axios, Fetch, Inversify, jQuery, Nestjs, Node, redux-query, Rxjs)

生成コマンド(axios対応のTypeScriptの場合)

> openapi-generator-cli generate -i \\
  ./YOUR_PATH/openapi.yaml -g typescript-axios -o openapi/types \\
  --additional-properties=modelPropertyNaming=camelCase,supportsES6=true

自動生成されたコード例

    async getShops() {
      state.isLoading = true
      const { data, error }: any = await new ApiV1ShopApi(
        undefined,
        $config.baseURL,
        axios as AxiosInstance
      )
        .apiV1ShopsGet(1, 1000)
        .catch((error) => {
          return { error }
        })
      state.isLoading = false
      if (error) {
        state.error = error
        return
      }
      state.shops = data.shops
    },

各ライブラリの特徴と要件を満たすか

RailsでOpenAPIでググるとこの3つよく出る

  • Grape
  • committee-rails
  • rspec-openapi

※ 以降生成されるOpenAPIのファイルopenapi.yamlを OpenAPI定義ファイル表記します

Grape

RailsのAPIモードが出る前まで、RailsでAPIといえばこれ

現在ではあまり人気のないDSL形式でAPIを作成できる OpenAPIもRailsが動的に作成してくれる

Grapeのメリット、デメリット

メリット

  • grape-entity
    • レスポンスで返すモデルで表示させる属性を明示的にできる
    • OpenAPIのレスポンスのコンポーネントを自動で作成してくれる
  • grape-swagger
    • OpenAPI定義ファイルを動的に生成、さらにSwaggerUIもついてくるオールインワン
  • 先人の日本語情報が比較的多い

Grapeのメリット、デメリット

デメリット

  • DSLのため学習コストがかかる
  • スキーマを先に作って実装する駆動開発ができない
  • grape-swaggerがOpenAPIのv2にしか対応されておらずv3のブランチがずっと本体にされていない

Grapeが要件を満たすか

  • メンテナブルなAPI設計書をつくりたい
    • 🔺 SwaggerUIもv2なので機能が少ない
  • モックサーバを使ってスキーマ駆動開発がしたい
    • ⭕ PrismはOpenAPI v2にも対応
  • TypeScriptのAPIアクセス部分を自動生成したい
    • 🔺 未確認、v2なのでだんだんメンテされなくなるだろう

committee-rails

committeeをrailsで組み込みやすくしてくれるgem committeeはOpenAPI定義ファイルは自分で書く必要があるが、 その定義通りのリクエスト・レスポンスをテストできる assert_schema_conform が便利

committee-railsのメリット、デメリット

メリット

  • 純粋なスキーマ駆動開発ができる
  • Prismでモックを動かせるのでRails側とフロント側で同時並行に実装できる
  • 他2つと違ってRailsで入出力だけ作るなどからめ手が不要
  • OpenAPIの定義どおりにRailsを実装できたかテストできる

committee-railsのメリット、デメリット

デメリット

  • OpenAPI定義ファイルを手作業で書かなければならい
  • OpenAPI定義ファイルだけ先に作るので他2つよりも実装に不安がある
  • 特に初期は項目の追加など手戻りが頻発する状態でフロントの担当に共有していいものか悩ましい

OpenAPI定義ファイルを手作業で書かなければならい問題は

を使ってGUIで開発するとちょっと楽

committee-railsが要件を満たすか

  • メンテナブルなAPI設計書をつくりたい
    • ⭕ OpenAPIも最新のv3に対応
  • モックサーバを使ってスキーマ駆動開発がしたい
    • ⭕ PrismはOpenAPI v3に対応
  • TypeScriptのAPIアクセス部分を自動生成したい
    • ⭕ 正しいOpenAPIファイルなのできれいに生成する

rspec-openapi

Rspecのリクエストスペックを書けばOpenAPI定義ファイルを出力してくれる テスト駆動開発大好きな人に朗報

rspec-openapiのメリット、デメリット

メリット

  • request specを書けば定義ファイルを作成してくれる
  • 各APIのレスポンス例のexampleもテスト結果から作成してくれる(他2つにはない)

request spec例

it "hogehoge" do の箇所がOpenAPIのdescriptionにこの分が反映されるのでrspecのいつもの書き方をすると違和感が出やすいので注意

RSpec.describe 'Tables', type: :request do
  describe '#index' do
    it 'テーブルの配列を取得する' do
      get '/tables', params: { page: '1', per: '10' }, headers: { authorization: 'k0kubun' }
      expect(response.status).to eq(200)
    end

    it 'does not return tables if unauthorized' do
      get '/tables'
      expect(response.status).to eq(401)
    end
  end

  # ...
end

生成されるOpenAPI定義ファイル例

openapi: 3.0.3
info:
  title: rspec-openapi
paths:
  "/tables":
    get:
      summary: index
      tags:
      - Table
      parameters:
      - name: page
        in: query
        schema:
          type: integer
        example: 1
      - name: per
        in: query
        schema:
          type: integer
        example: 10
      responses:
        '200':
          description: returns a list of tables
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: integer
                    name:
                      type: string
                    # ...

rspec-openapiのメリット、デメリット

デメリット

  • 仕様を満たすOpenAPI定義ファイルを作るには、手動で対応するワークアラウンドが結構ある

※ 今年ぶつかった問題なので急に具体的になります

rspec-openapiのワークアラウンド

ログイン必須の場合は各メソッドの定義に手動で追記する必要がある

devise_token_authで認証用のgemを利用した場合 client, access_token, uidをヘッダにつける必要あり

各メソッドにつける

paths:
  "/api/v1/shops":
    get:
      security:
      - client: []
        access_token: []
        uid: []

認証情報の定義

components:
  securitySchemes:
    client:
      type: apiKey
      description: ClientID
      in: header
      name: client
    access_token:
      type: apiKey
      description: access-token
      in: header
      name: access-token
    uid:
      type: apiKey
      description: uid
      in: header
      name: uid

rspec-openapiのワークアラウンド

レスポンスがヘッダだけの場合不要な情報が付与されるので削除する必要がある

responses:
 '204':
   description: 商品カテゴリを更新
   # これ以降を削除する
   content:
    '':
      schema:
        type: string
      example: ''
responses:
  '204':
  description: 商品カテゴリを更新

rspec-openapiのワークアラウンド

specを実行時にIDがインクリメントされるとパラメタが追加されてしまう

letでidを指定してレコードを保存して実行すると、idがインクリメントされてparametersの内容が増えてしまうため自動生成時に同じ入力パラメータが重複してエラーになる

let(:category) { create(:category, name: '名前') }

IDを指定してやる

let(:category) { create(:category, id:1, name: '名前') }

rspec-openapiのワークアラウンド

コンポーネントを自分で作らなければならない

自動生成されたもの

responses:
    '200':
        description: 製品情報のリストを取得
        content:
        application/json:
            schema:
            properties:
                products:
                type: array
                items:
                    type: object
                    properties:
                    id:
                        type: integer
                    name:
                        type: string

この状態だとコンポーネントを使わないとこのようなよろしくないクラス名になる

/**
 *
 * @export
 * @interface InlineResponse2006
 */
export interface InlineResponse2006 {
    /**
     *
     * @type {number}
     * @memberof Shop
     */
    id: number;
    ...
}

こういう意味の分かる名前のクラスを生成したい

/**
 *
 * @export
 * @interface Shop
 */
export interface Shop {
    /**
     *
     * @type {number}
     * @memberof Shop
     */
    id: number;
    ...
}

そのためOpenAPI定義ファイルに

responses:
    '200':
        description: お店のリスト取得する
        content:
        application/json:
            schema:
            properties:
                products:
                type: array
                items:
                    "$ref": "#/components/schemas/Shop"

OpenAPI定義ファイルのコンポーネントの定義

components:
  schemas:
    Shop:
      nullable: true
      type: object
      properties:
        id:
          type: integer
        name:
          type: string

レスポンスをコンポーネントに移動したあと 新しいメソッドを追加し、OpenAPI定義ファイルを自動生成すると コンポーネントと、レスポンスのオブジェクトが2つ存在してしまう。

つまりここうなってしまう

responses:
    '200':
        description: お店のリストを取得
        content:
        application/json:
            schema:
            properties:
                products:
                type: array
                items:
                    "$ref": "#/components/schemas/Shop"
                    # せっかくコンポーネントを作ったのに下記の余分なものがついてしまう
                    type: object
                    properties:
                    id:
                        type: integer
                    name:
                        type: string

OpenAPI generatorでTypeScriptを生成する際にエラーになるため都度消す必要がある。

※ SwaggerUIやPrismを動かす分には先に書いてあるコンポーネントを利用するため気にならない

rspec-openapiが要件を満たすか

  • メンテナブルなAPI設計書をつくりたい
    • 🔺 最新のOpenAPI v3に対応するが、ワークアラウンドが多くAPIの本数が増えるとどんどん辛くなる
  • モックサーバを使ってスキーマ駆動開発がしたい
    • ⭕ PrismはOpenAPI v3に対応
  • TypeScriptのAPIアクセス部分を自動生成したい
    • 🔺 特にコンポーネント周りのワークアラウンドを実施しないと、自動で命名されたTypeScriptのクラスになってしまう

まとめ

今回の要件要件

  • メンテナブルなAPI設計書をつくりたい
  • モックサーバを使ってスキーマ駆動開発がしたい
  • TypeScripのAPIアクセス部分を自動生成したい

これらすべての要件を満たす⭕のはcommittee-rails

まとめ

ただし開発の初期段階(1-3週間ぐらい)で、仕様が変わりやすい段階でcommittee-railsを使うと rspec-openapiで動作するRailsでOpenAPIを作成しつつ、 徐々に仕様が固まってAPIの本数が増えてきたらcommittee-railsに切り替えるのが良いのでないか

まとめ

実行時のOpenAPIの厳密さは右に行くほど適当な書き方だとエラーになる確率が上がる(体感) Swagger UI > Prism > Openapi Generator

そのためOpenAPIのファイルをコミットする場合は自動生成が通ってからコミットするのがコツ

PR

UZUMAKIではRailsエンジニアを絶賛募集中です。

↓の記事を読んでご興味を持っていただいた方は、

是非応募宜しくおねがいします!