巨大なOpenAPIを分割するための工夫 —— 1万8千行のファイルをどうする? – SmartHR Tech Blog

こんにちは、SmartHR の人事評価の開発を担当しているプロダクトエンジニアの 70snow です。

先日、担当しているプロダクトに存在していた巨大なOpenAPIファイルを分割し、管理しやすくする改善を行いました。その際にチーム開発を妨げないように行った工夫についてご紹介したいと思います。

1万8千行の巨大なOpenAPIファイルがありました……

人事評価プロダクトでは、OpenAPIのファイルは1つのファイルで管理していました。長年の追記が積み重なり、1万8千行もある巨大なものとなっていました。
この巨大なファイルには2つの大きな問題がありました。1つ目は、ファイルサイズが大きすぎて正しくメンテナンスすることが困難な状況となっており、機能開発にも影響を及ぼしていたことです。2つ目は、openapi-typescript(OpenAPIからTypeScript型定義を自動生成するツール)での利用が主で、committee(APIテスト用gem)などでの活用が限定的だったことです。

そのため、OpenAPIのファイルを分割し、メンテナンスしやすくし、より活用できる状況を整えることにしました。分割作業は個人ミッションとして対応し、メインの機能開発と並行して行ったため、機能開発を妨げないことを特に意識しました。

OpenAPIの分割ルール

今回は以下のように、pathsとcomponents/schemasのディレクトリを作り、分割したファイルを置いていく形で分割することにしました。

openapi
├── components/schemas
│   ├── user.yaml
│   └── admin.yaml
└── paths/
    ├── user.yaml
    └── admin.yaml

「OpenAPIの分割」で検索すると、pathとcomponentsのディレクトリを作り、componentsを更にexamplesとschemasに分ける、といった方法をよく見かけます。

openapi/
├── components/
│   ├── examples/
│   │   ├── user.yaml
│   │   └── admin.yaml
│   └── schemas/
│       ├── user.yaml
│       └── admin.yaml
└── paths/
    ├── user.yaml
    └── admin.yaml

今回は以下の理由からexamplesのフォルダはつくらずに必要最低限の分割に留めることにしました。

  • ルールを増やすことで逆に複雑性が増し、生産性が落ちる懸念があった
  • 細かく分割することは後からできるが、逆は難しい
  • 今回の分割作業で使用したredocly/cli split(OpenAPIを分割するコマンド)で分割される単位と一致し、再現性のある作業ができる

特に最後の点は、日々エンドポイントが増える環境では重要な要素でした。
なお、AIを用いた分割も検討しましたが、作業の再現性を重視し、このタイミングでは採用しませんでした。

このルールで分割した結果、1万8千行のファイルが252ファイルに分割されました。

分割作業の進め方

今回の分割作業を進める上で、redocly/cli splitの使用には注意点があります。
redocly/cli splitで分割を行うと、componentsへの参照はメインのOpenAPIファイルには記載されません。

具体例として、以下のようなOpenAPIファイルを分割する場合を考えてみます。

paths:
  /api/users:
    get:
      response:
        200:
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/User'
  /api/admin:
    get:
      response:
        200:
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/Admin'
components:
  Admin:
    type: object
    properties:
      id:
        type: string
      name:
        type: string
  User:
    type: object
    properties:
      id:
        type: string
      name:
        type: string

redocly/cli split で分割を行うとメインのOpenAPIファイルにはpathsの参照だけが記述されます。

paths:
  /api/users:
    $ref: paths/user.yaml
  /api/admin:
    $ref: paths/admin.yaml

この状態でopenapi-typescriptでTypeScriptのスキーマファイルを生成すると、componentsが各pathから参照された順番に書き出されるため、OpenAPIの分割前後でTypeScriptのスキーマファイルに大きな差分が発生してしまいました。
解決策として、一旦メインのOpenAPIファイルにcomponentsへの参照も記載することでTypeScriptのスキーマファイルにcomponentsが定義順に書き出されるようなり、差分をほぼ無くすことができました。

paths:
  /api/users:
    $ref: paths/user.yaml
  /api/admin:
    $ref: paths/admin.yaml
components:
  Admin:
    $ref: components/schemas/admin.yaml
  User:
    $ref: components/schemas/user.yaml

本番に適用しても問題ないと判断できる状態になったため、チームとしても安心して変更を取り込むことができました。

追記したcomponentsのセクションは本来不要であるため、mainブランチへの取り込み後、レビューしやすいよう差分が200行程度になるようにPull Requestを分けながら記述を削除していきました。
これもレビューコストを抑えるための工夫の1つです。ただ、componentsが120個近く存在したため最終的に15個ものPull Requestが出来上がってしまいました。
このような定型作業にはAIが非常に効果的でした。下記のようなプロンプトでほぼAIにコードを書かせたため、実際に手を動かした時間はわずかでした。

# 目的:TypeScriptスキーマファイルの差分を200行程度に抑えながら、
# 不要なcomponents参照を段階的に削除する

タスク1
./openapi/openapi.yamlのcomponentsの定義を先頭から5つ削除してください
その後、OpenAPIのビルドを実行してください
その後タスク2に進んでください
タスク2
更新された TypeScriptのスキーマファイルの差分が200行未満であればタスク3を実行してください
差分が200行以上であれば終了してください
タスク3
./openapi/openapi.yamlのcomponentsの定義を先頭から1つ削除してください
その後、OpenAPIのビルドを実行してください
その後タスク2を再度実行してください

ビルド忘れのチェック機構の導入

OpenAPIの分割作業の結果、committeeが分割されたファイル構造を認識できず、テストが失敗するようになりました。参照のパスの記載方法を修正することで対応することもできましたが、工数がかなりかかることが予想されたため、1つのOpenAPIにビルドする手順を追加する対応を行いました。

分割されたファイル構造への対応により、新たにOpenAPIに変更を加える際には、path/componentsの編集の後にビルドの手順が必要になりました。しかし、ビルド作業を忘れるケースが散見されました(自分でも何度かやりました)。
そこで、CIの中でOpenAPIのビルドを忘れていないかをチェックするステップを追加しました。

ビルド忘れを検出するために、以下のコマンドをCIで実行しています(実際には丁寧にメッセージを出しています)。

# OpenAPIファイルをビルドして単一ファイルに統合
npx @redocly/cli bundle openapi/openapi.yaml -o openapi/api_schema.yaml
if git diff --exit-code openapi/api_schema.yaml
  echo "ok"
  exit 0
else
  echo "ng"
  exit 1
fi

このコマンドは、OpenAPIファイルをビルドして差分をチェックし、ビルド忘れを検出します。

CIでのチェック機構により、ビルド忘れを機械的にチェックすることができるようになりました(自分も何度も助けられました)。

分割の結果、快適なスキーマ駆動開発になりました

レビューしてくれる人、他の開発者のことを考えながらプロセスを考えた結果、チーム開発を妨げることなくOpenAPIの分割を実施することができました。
OpenAPIが扱いやすくなったことで、スキーマ駆動開発を効率よく推進することができるようになりました。具体的には以下のような改善が見られました。

  • フロントエンドとバックエンドの開発を並行して進められるようになった
  • AIにOpenAPIを記述してもらうのが容易になった
  • RSpecでのcommitteeの活用が進んだ
  • redocly/cli lintを導入した文法チェックなど、プロセスの改善も進んだ

一番大きなメリットとしては、OpenAPIの整備が進んだことによりOrval(OpenAPIからTypeScript型定義を自動生成するツール)による型生成を導入できたことです。これにより、フロントエンド開発でのAPI型定義の手動作成が不要になり、開発コストの軽減が実現できています。
まだ一部のエンドポイントでは記述が不完全だったり、コードの変更にOpenAPIが追いついていない状況があるため、AIを活用しながら引き続きOpenAPIの改善を行っていきたいと思っています。

We Are Hiring!

SmartHR では一緒に SmartHR を作りあげていく仲間を募集中です!

少しでも興味を持っていただけたら、カジュアル面談でざっくばらんにお話ししましょう!

hello-world.smarthr.co.jp


Source link

関連記事

コメント

この記事へのコメントはありません。