フロントエンドとOpenAPI定義ファイルの置き場のTIPS
本記事は Timee Advent Calendar 2024 11日目の記事です。
はじめに
ニンテンドーサウンドクロック Alarmo が当たって嬉しい @redshoga です。
本記事では株式会社タイミーで扱っているフロントエンドのリポジトリとOpenAPIの定義ファイルの関係について紹介します。OpenAPIの置き場に迷っている、困っている方の参考になれば幸いです。
TL;DR
- フロントエンドとバックエンドとOpenAPI定義ファイル置き場をそれぞれのリポジトリにする
- エンジニアの関心ごとにまとめられ、認知負荷が下げられる
- CI/CD等のエコシステムが組みやすい
- フロントエンドのリポジトリからfetchしてくるスクリプトを用意する
- package.jsonなどにcommit hashを入れておくと楽
前提知識
タイミーでは事業者が働き手を募集したりメッセージをやり取りするための店舗用管理画面があります。今回はこの店舗用管理画面の話が主題となります。
店舗用管理画面はNext.js(PagesRouter, SSR無し)で構成されています。APIはRailsのAPIサーバがあります。大きく関係するリポジトリは以下のようになっています。
- バックエンド(Rails API)のリポジトリ
- フロントエンド(Next.js)のリポジトリ
- 店舗用管理画面で扱うAPIをまとめたOpenAPI定義ファイルのリポジトリ
OpenAPI定義ファイルをどこに置くか
OpenAPI定義ファイルのリポジトリは特に関心を持つバックエンドのリポジトリにあってもよいですが、以下の理由から分離しています。
- 責任範囲を明確化: OpenAPI定義ファイルは、バックエンドエンジニアだけでなくフロントエンドエンジニアも関心を持ち、どちらからも議論が必要なことが多い。そのため独立したリポジトリとして管理することで、よりどちらからもアクセスをしやすいようにするため。
- 変更の追跡性: コード変更に埋もれず管理がしやすいようにするため。
またOpenAPI定義ファイルだけを置くことで周辺ツールの管理が容易になる環境圧がつくれたと考えています。実際問題OpenAPI定義のlinterの追加やビュワーの整備などの改善が有志により行われていました。(感謝)
OpenAPI定義ファイルを取得する
定義ファイルが別リポジトリで管理されるため、なんらかの手段でフロントエンドのリポジトリからそれを読み込めるようにする必要があります。
ファイルに変更がある度にコピーしてきても良いのですが、より簡単に扱えるように工夫がしてあります。
フロントエンドのリポジトリ上では以下のスクリプトが用意されています。
中身の処理は以下のようになっています。
-
package.json内に記述されたOpenAPI定義ファイルのURLを取得する。
-
GitHubのトークン(
SWAGGER_REPO_TOKEN
)を用いてURLの宛先をダウンロードする。※GitHubのトークンを用いると
https://raw.githubusercontent.com/{組織名}/{リポジトリ名}/{ハッシュ値}/{ファイルパス}
の指定で特定のファイルを取得できます。
dl-swagger.js
: OpenAPI定義ファイルをダウンロードするスクリプト
/* eslint-disable */
const SWAGGER_REPO_TOKEN_ENV_NAME = "SWAGGER_REPO_TOKEN";
const SWAGGER_REPO_TOKEN_FILE_NAME = ".swagger-repo-token";
const axios = require('axios');
const package = require('./package.json');
const fs = require("fs");
(async () => {
const token = (await (async () => {
if (process.env[SWAGGER_REPO_TOKEN_ENV_NAME]) return process.env[SWAGGER_REPO_TOKEN_ENV_NAME];
return fs.readFileSync(SWAGGER_REPO_TOKEN_FILE_NAME, "utf-8")
})()).trim()
const { data } = await axios.get(package.swagger.url, { headers: { "Authorization": `token ${token}` } });
fs.writeFileSync(package.swagger.fileName, data, { encoding: "utf8" });
})();
※axiosを使わなくてもよさそうですが、現在使っている原文ママ
{
"name": "timee-client-web",
"version": "1.0.0",
"private": true,
"license": "UNLICENSED",
"scripts": {
...
"prepare:api": "node dl-swagger && shx rm -rf './src/apis/Autogen*' && openapi2aspida --build",
},
"swagger": {
"url": "https://raw.githubusercontent.com/Taimee/timee-client-web-api-swagger/7d505c4f4ff8f70a8f4581ccf491aa66ffb4436e/swagger.yml",
"fileName": "timee-api.swagger.yml"
},
...
}
フロントエンドエンジニアの実作業としては以下のようになります。
- APIの定義をバックエンドエンジニアと議論
- APIの定義が決まったら
package.json
内のOpenAPI定義ファイルのコミットハッシュを更新(PR中のコミットハッシュでも可) -
pnpm prepare:api
の実行 (定義ファイルのダウンロード + 定義ファイルから型生成)
package.json内にOpenAPIのリポジトリのハッシュ値が記述されることで以下が実現できます。
- OpenAPI定義ファイルの差分をPRに大きく含めない
- フロントエンドの実装が依存しているAPI定義の状態をgit上に表現できる
またGitHubのトークン(上記のSWAGGER_REPO_TOKEN
)は .swagger-repo-token
として別ファイルでつくることを前提にしています。(コミットされないように .gitignore
に記述されている) .env
の中身に入れるにはアプリケーションに関係がない値ですし、個別で認証を通すのも大変、かといってハードコーディングもしたくなかったのでこのような対応をとっています。
ファイルとして扱うのは仕様としても扱いやすいため、こちらも有志のエンジニアがAWS Secret Managerにアクセスしファイルを生成するツールをつくってくれました。(感謝)
余談
内容としては大したことはやっていないのですが、責務をちゃんと切っておくとみんなが動きやすくなり、ソフトウェアのレバレッジが働きやすいんだなと体感しました。
Discussion