🔖

フロントエンドとOpenAPI定義ファイルの置き場のTIPS

2024/12/11に公開

本記事は 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定義ファイルを取得する

定義ファイルが別リポジトリで管理されるため、なんらかの手段でフロントエンドのリポジトリからそれを読み込めるようにする必要があります。

ファイルに変更がある度にコピーしてきても良いのですが、より簡単に扱えるように工夫がしてあります。

フロントエンドのリポジトリ上では以下のスクリプトが用意されています。

中身の処理は以下のようになっています。

dl-swagger.js: OpenAPI定義ファイルをダウンロードするスクリプト

dl-swagger.js
/* 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を使わなくてもよさそうですが、現在使っている原文ママ

package.json
{
  "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"
  },
  ...
}

フロントエンドエンジニアの実作業としては以下のようになります。

  1. APIの定義をバックエンドエンジニアと議論
  2. APIの定義が決まったらpackage.json 内のOpenAPI定義ファイルのコミットハッシュを更新(PR中のコミットハッシュでも可)
  3. pnpm prepare:api の実行 (定義ファイルのダウンロード + 定義ファイルから型生成)

package.json内にOpenAPIのリポジトリのハッシュ値が記述されることで以下が実現できます。

  • OpenAPI定義ファイルの差分をPRに大きく含めない
  • フロントエンドの実装が依存しているAPI定義の状態をgit上に表現できる

またGitHubのトークン(上記のSWAGGER_REPO_TOKEN)は .swagger-repo-token として別ファイルでつくることを前提にしています。(コミットされないように .gitignore に記述されている) .env の中身に入れるにはアプリケーションに関係がない値ですし、個別で認証を通すのも大変、かといってハードコーディングもしたくなかったのでこのような対応をとっています。

ファイルとして扱うのは仕様としても扱いやすいため、こちらも有志のエンジニアがAWS Secret Managerにアクセスしファイルを生成するツールをつくってくれました。(感謝)

余談

内容としては大したことはやっていないのですが、責務をちゃんと切っておくとみんなが動きやすくなり、ソフトウェアのレバレッジが働きやすいんだなと体感しました。

Discussion