📚

【OpenAPI】Stoplight Studioを活用して快適&高速にAPI定義を書く方法|Offers Tech Blog

2022/05/30に公開
2

概要

Offers を運営している株式会社 overflow の磯崎です。弊社は新規プロダクト開発でスキーマ駆動開発を取り入れており、API 定義とは楽しくお付き合いさせていただいております。その全体像については、以下の記事でまとめておりますので、是非ご一読ください。今回は、ポチポチいじるだけで誰でも簡単に API 定義できる神ツール「Stoplight Studio」を活用した API 定義について紹介していますので、ぜひ参考にしてください。

https://zenn.dev/offers/articles/20220411-open-api-schema

Stoplight Studio とは?

Stoplight Studio とは、 OpenAPI 定義ファイルの作成と管理ができる GUI エディタです。これだと少々分かりづらいので、簡単に一言で表すと「ポチポチと誰でも簡単に API 定義ができてしまうツール」です。Stoplight Studio は、GUI で直感的な操作ができるため、高速に API 仕様を記述できます。これで冗長な yml とにらめっこしなくて良くなります。

最終的に管理するファイルは yml なので、Stoplight で操作していく上で「こりゃ yml いじったほうが早いね」となった際は yml を直接編集可能です。したがって、大枠はポチポチと GUI で定義していき、そこで表現できない細かいものを直接 yml をいじって実現といった編集方法になります。

https://stoplight.io/studio

API document がすぐさまできあがります

stoplight.io でアカウント作成して、そこで git 連携することでその repository にある yml を元に document を閲覧できるようになります。特別な操作は一切必要なく、言わわれるがままに設定をしていくと document みれるようになります。なにも考えず従うだけでパっと API document をみれるようになるのはすごく楽です。

公開範囲だけ気をつける必要があります。誤って「public」で作成してしまうと、それが全世界に公開されるので、無料プランであれば、「internal」で作成する必要があります。

また、ブラウザ上でも API 定義をいじることができるのですが、自分たちはここではいじってはおりません。気を抜くと develop に変更 push されるので、いつもの慣れ親しんだ方法で、ローカル環境でブランチを切って、それを push して PR 作成してという方法で API を定義しております。

自前の環境に hosting する際も簡単にできます

公式の demo

document 通りではありますが、下記の記述で簡単に API document を表示できます。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />
    <title>Elements in HTML</title>

    <script src="https://unpkg.com/@stoplight/elements/web-components.min.js"></script>
    <link
      rel="stylesheet"
      href="https://unpkg.com/@stoplight/elements/styles.min.css"
    />
  </head>
  <body>
    <elements-api apiDescriptionUrl="tictactoe.yaml" router="hash" />
  </body>
</html>

超絶 easy ですね。数秒で完成です。

mock server が簡単に立ち上がります

同じく、Stoplight が提供している Prism を使用すると、爆速で mock server を立ち上げることができます。

起動までの手順

npm or yarn で project に突っ込んだ後、それぞれ下記コマンドぶっ叩くだけで、定義した yml を元に mock server が立ち上がります。

通常起動

prism mock <path to api yml>

これで立ち上がるので、特に port いじっていなければ、
Prism is listening on http://127.0.0.1:4010
で立ち上がります。あとはここめがけて request を送って、実際に開発していくという流れになります。

こんな感じの reponse が返ってきます。

{ "name": "string", "some_boolean": true }

これだと string であれば string が、integer であれば integer が返ってくるので、サービス固有の値などこちらで定義した response を返したい場合は example に定義していきます。

docker 使いたい場合は、下記を用意しておいて、docker-compose up するだけで OK です。

Dockerfile
FROM <お好きなnode.js>

WORKDIR /app
ENV HOST 0.0.0.0

COPY package.json package-lock.json ./
RUN npm install
docker-compose.yml
version: '3'
services:
  <name>:
    build: .
    tty: true
    command: ash -c "npm install && npm run start:docker"
    volumes:
      - .:/app:delegated
    ports:
      - "4010:4010"

package.json
"scripts": {
  "start:docker": "prism mock -h 0.0.0.0 <path to yml>",
},

動的な response での起動

prism mock -d reference/offers-manager-API.v1.yaml

-d option 付与するだけで、動的な response が返ってきます。簡単ですね。

{"name":"qui amet elit (ランダムな文字列)", "some_boolean": true or false

また、返り値が配列の場合も、動的に数を変化させて response を返してくれます。

API 定義で最低限守るルールとその理由

必須プロパティには required を必ずつける

これを定義することによって、レスポンスに

  • key が存在している
  • null を許容しない

というルールを定められるため、認識合わせた上で開発を進めることができます。

OK: 下記が返ってくること想定

{ "email": "test@hogehoge" }

OK: 空文字も

{ "email": "" }

NG: null や key 自体存在しないものは返ってこない

{ "email": null }

弊社では API 定義を元に、フロントエンドの TypeScript を自動生成しています。自動生成された関数を呼ぶだけで api 叩くことができ、その response や body の型も自動生成されたものを使えば良いので、定義された API を神として、それに従って開発をすすめることができます。その上で開発しやすくするためにも、しっかりと制約はつけておくべきです。

tag は必ずつける

document 化した際に、グルーピングされます。これつけないと後々カオスになります。また、上述したフロントエンドのコードを自動生成した際に DefaultApi という名前がついているようでついていない class が誕生し、それをフロント側から呼ぶことになるので不格好になります。

description は命名からその意図が予測できないものに関しては書く

基本的にはいちいち説明を書かなくても良いくらい命名をしっかりせよという話ではあるのですが、とはいえ説明が必要なものは多々ありますね。API 定義者と実装者が違うパターンも有り、API 定義を元に実装完了した段階で認識のずれが発生して出戻りするのは最悪な体験なので、これを防ぐためにも説明はしっかり書きましょうということです。
ただ、全てに説明を書いていくのは少々めんどくさかったりするので、良い塩梅でやっていくのが良いですね。

operationId は必ずつける

こちら、stoplight を使って編集していると特に忘れがちなのですが、これが前述したフロントエンドのコードを自動生成した時の関数名になります。なので、しっかりと命名する必要があります。

additionalProperties: false もつける

これは stoplight 上では定義できないので、直接 yml をいじることになります。これがあることによって、定義した key 以外は入ってこないよという明示になります。デフォでつけても良いんじゃないかレベルです。

スキーマ駆動開発においては、バックエンド ⇔ API Schema においてズレがないかをテストでチェックする必要があります。弊社はバックエンドで Ruby on Rails を使用しているので、「committee-rails」を使って整合性チェックをしていますが、これが抜けていると余計な key が混じっていてもそのまま普通にテスト通過します。侮れません。

nullable: true は基本使用しない

書いて字のごとく null 許容です。また key 自体が存在しないことも許容します。
基本的にはこの辺のハンドリングをフロントエンドに押し付ける形なので、基本的には返却値は絞った上で開発をしていくのが良いですね。

弊社での使用例

なんら特別なはことしておりませんが、具体的な使用例を記述します。

上記ルール気をつけながらポチポチと定義

さあ、新しい機能開発するぞとなった場合はまず Stoplight Studio を立ち上げて、該当の Project を開いた上であとはポチポチしていくだけです。

ディレクトリ構成

https://meta.stoplight.io/docs/platform/58bf119ddff88-work-with-files
こちらに default のディレクトリ構造が書いてあるので、そのまま使用しております。

まずは path を定義する

api 定義されている yml を選択して、そこで「New Path」をクリックして path を追加します。

pathの追加

tag, operationId を忘れないうちにまず入力する

あとでやろうとすると忘れるので、忘れないうちに呼吸するように定義します。

tag, operationIdの追加

model を定義する

User などの model を定義していきます。
すべてのカラムを記述する必要はなく、あくまで返り値として使用する値をここに定義していきます。

tag, operationIdの追加

query params, body, response を定義する

画面に従って定義していくだけで OK なのですが、一点注意点があります。
API 定義を元にフロントエンドのコードを自動生成している場合は、body や response を独立した型として吐き出してそれを使いたいケースがほとんどだと思います。
このまま画面に従ってポチポチと定義していくとそれができず、InlineObject という名前で吐き出されます。
しっかりと名付けを行った上でフロントのコードを自動生成したい場合は、別ファイルに切り分けて response ではそれを $ref したものを定義すると作成したファイル名で型の生成が行われます。

reponseの定義

$ref で分割しまくったファイルを 1 枚にまとめる

このまま $ref を乱用した yml を使っていってもいいのですが、たまーに $ref しているファイルでは使用できないツール郡があります。
$ref しているものを 1 枚のファイルにマージしたものを用意しておくと、どこでも使いまわすことができるので、swagger-merger を使用して、1 枚にまとめております。

swagger-merger -i <base api yaml file path> -o <export api yaml file path>

上記実行するだけで簡単にまとめることができるので、これを package.json にいれておいて、適宜呼び出して使っています。

毎回人間が叩いて運用していくのは辛いので、husky を入れて、Git hooks で pre-commit 時に自動生成&git add をして巻き込んで push しています。

こちらで生成したファイルは基本的には人がいじることはないファイルなので、git 管理対象には含めないという選択肢もありだと思います。どのような意図で使っていくかによって、適宜良い感じの方法をとっていきます。
どのような管理方法であれ、前提として生成した 1 枚の yml は基本的には人間がいじることはなく、$ref を用いて定義されたものから生成していくファイルになります。

CI 上で validate

少なくとも、おかしい箇所ないかだけチェックしています。
swagger-cli を使用して、validate しています。

swagger-cli validate <path to api yml>

使っていて良かったこと

やはり、ポチポチと定義していけるので API 定義は楽です。また間違いにも気づきやすいです。
そして誰でも慣れれば簡単にいじれるので、時間の節約にもなります。

使っていて辛かったこと

これはこのツールに限らずですが、考慮すべきことが多くて面倒です。これは Open APi 定義していく上では避けては通れないので、これからもなるべく人間のリソースを食わない形で運用していけるような方法を模索していきます。

そして、一番つらかったのは、api を定義した yml のコンフリクトです。実際に悪夢として夢にでてきました。
日々チームで開発をしていくと、複数機能を並行して開発をすることは多々ありますが、default branch にマージ前の api 定義がたくさん並ぶ → フロント・バックエンド共に機能出来上がった段階でマージと行っていくと、それはまあコンフリクトします。

そしてその解消作業は非常に苦痛です。いつものコードのコンフリクトはわけが違うインデントで管理されたファイルのコンフリクト作業になります。また、params など他と同じようなものが定義されていると、どっちがどっちだかわけがわからなくなります。

解決方法としては、こまめにマージするか、api yml 自体を分割するアプローチがパット浮かびます。
api yml 自体を分割するアプローチに関しては、これを行うと Stoplight Studio 上で定義した API が表示されなくなることがわかりました。

上記は最早 stoplight を使用しての辛かったことではなく、Open API 定義をしていく上で辛かったことですね。
この辺り、stoplight 使いながら引き続き良い感じの管理方法を模索中です。

まとめ

結論、Stoplight 入れてよかったです!定義楽です!
辛いこともありますがそれは Stoplight のせいではなく、どちらかというと Open API の管理によるもので、色々と工夫しながらストレスのない運用を模索しながらこれからも続けていきます。

関連記事

https://zenn.dev/offers/articles/20220411-open-api-schema
https://zenn.dev/offers/articles/20220523-component-design-best-practice
https://zenn.dev/offers/articles/20220418-what-is-bff-architecture

Offers Tech Blog

Discussion