こんにちは,For School事業部のid:atomiyamaです.
現在Studyplus for Schoolはサービスのフルリニューアルを行っています.
弊サービスはこれまでRailsでslimを使いViewを提供してきましたが,今後より良い体験をユーザーへ届けるためにリニューアルを行いサーバーサイドとクライアントサイドを分離しました.
リニューアルに向けて現在サーバーサイドはRailsでJSON APIサーバーの開発を行っており,その中で導入したスキーマ駆動開発の話をします.
TL;DR
- 技術スタックはOpenAPI3.0, swaggerUI,committee
- クライアントサイド開発者と連携してJSONスキーマを仮決定する.
- サーバーサイドの開発者はRailsコントローラにそのJSONをべた書きした仮実装を行う.
- クライアントサイド,サーバーサイドの開発者が互いにフィードバックしながら並行して実装を進める.
スキーマ駆動開発とは
スキーマ駆動開発とは,
「スキーマをはじめに定義し,スキーマを元にAPI開発者,利用者が同時に開発を進めていくこと」です.
スキーマ駆動開発を導入するモチベーション
今回スキーマ駆動開発を導入した背景は以下の2つの問題により思ったようなスケジュールで開発が進行してしなかったことです.
- サーバーサイドの実装が完了するまでクライアントサイド開発者が実装に着手できない.
- 一度実装したAPIにクライアントサイドから変更の依頼が入り手戻りが頻発する.
今回のフルリニューアルのような長期にわたるプロジェクトでは特に既に開発を終えたタスクに対してな手戻りが頻発することは当初のスケジュール通りにプロジェクトを進めていく上で大きな問題になります.
またクライアントサイドにおいてはAPIで提供されるデータの仕様が不明確なままでは開発を進めることができませんからサーバーサイドの進捗から強く影響を受けてしまいます.
そこでサーバーサイド,クライアントサイドが並行して独立して開発できるようにスキーマ駆動開発を導入しました.
スキーマ駆動開発
私達のチームではスキーマ駆動開発を進める上で以下のようなステップにそって開発を進行しています.
- APIのスキーマを利用者の合意のもと決定する
- APIスタブサーバを提供する
- 本実装を行う
APIドキュメンテーション
APIのスキーマを決定しドキュメント化するために弊社ではOpenAPI3.0を使っています.
OpenAPI3.0に従ってyamlで定義を書くんですがサービスがある程度の規模になってくると一つのyamlファイルにすべてのエンドポイントの仕様を書くのは辛くなってきます😭
そこで以下の用にエンドポイントURIのパスと対応するレベルでファイルを分割し,Rakeタスクでファイルを結合してswaggerUIが読み込み可能な形に吐き出しています.
├── app ├── bin ├── config ├── db ├── dockerfiles ├── lib ├── log ├── public ├── spec ├── swagger │ └── v1 │ ├── main.yaml │ ├── paths.yaml │ ├── components │ │ └─ user.yml │ └── paths │ └─ users.yml ├── tmp └── vendor
# swagger/main.yml openapi: 3.0.0 info: title: SampleAPIApp version: 0.0.1 servers: - url: https://example.com/v1 paths: $ref: ./paths.yaml components: schemas: $ref: ./components/schemas.yaml # swagger/paths.yaml /v1/users: $ref: ./paths/users.yaml # swagger/path/users.yaml get: summary: ユーザー一覧 tags: - ユーザー responses: 200: description: OK content: application/json: schema: type: object required: - users properties: users: type: array items: type: object required: - name - age - email properties: name: type: string description: 名前 example: スタプラタロウ age: type: integer description: 年齢 example: 20 email: type: string description: メールアドレス example: sutapura.tarou@example.com
APIスタブサーバの提供
前項でスキーマが決定しswaggerUIリーダブルな形に変換できたのでswaggerUIの公式イメージを立ち上げ出力したファイルを読み込むことで下のようにAPIのスキーマが閲覧可能になりました.
ここから実際にRailsの実装を行っていくのですが,いきなり本実装をはじめてしまうと完了するまでクライアント開発者は開発に着手できなくなってしまうので最低限の実装でAPIスタブを実装します.
実際にはswaggerUIのResponsesのExample valueにあるオブジェクトをコピーして,Railsのコントローラにそのまま貼り付けます.
# app/controllers/v1/users_controller.rb class V1::UsersController < ApplicationController def index return json: { "users": [ { "name": "スタプラタロウ", "age": 20, "email": "sutapura.tarou@example.com" } ] } end end
テストも最低限書いておきます.テストではcommitteeを使っています.
committeeを使ってテストをすることでOpenAPI3.0に則って定義したスキーマと差分の無いレスポンスが返却されることをテストすることができます.
committeeの導入についてはこちらを確認してください.
require 'rails_helper' RSpec.describe V1::UsersController, type: :request do describe "GET /v1/users" do subject { get "/v1/users" } # 最低限のテスト it "200" do subject expect(response).to have_http_status(200) assert_schema_conform end end end
ここまでをやったら一度PullRequestを作成してデプロイしてしまいます.
これらの作業は2,3時間もあれば終わってしまう作業ですし,ここまで完了すればサーバーから仮のデータが提供されるようになっているためクライアントサイドの開発者も開発を進めることができます.
本実装
残すはクライアント,サーバーともに前項までにやった仮の実装を本実装していくだけです.
ひたすらにスキーマ通りにデータを返せるようにコーディングします.
まとめ
スキーマ駆動開発を導入することにより早い段階で決定されたAPIスキーマ(APIスタブも含む)を元に,クライアントサイド,サーバーサイド互いが自身の作業に専念できるようになりました.
個人的にいいなと思った点は,最初のスキーマを決定する際にクライアントサイド開発者のフィードバックを受けるので互いの仕様への理解をすり合わせる空気感が自然とできることです.
またほぼ同時に本実装が進むため,もしどちらかの実装の都合によりスキーマに仕様変更が入っても完成前なので大きな手戻りにはなりにくいためスケジュール遅延リスクも最小限に抑えることができます.
是非これからAPI開発に取り組む方はスキーマ駆動開発取り入れてみてはどうでしょう.