APIドキュメントをRedocで一新しました

    APIドキュメントをRedocで一新しました

    目次

      概要

      AQに入社して2年目、デジタルエンジニアリング部デジタルエンジニアリング1課の松浦です。

      本記事は、私が携わったとある案件で利用していたAPIドキュメントを整備した際の記録です。
      APIドキュメントの管理に困っている方の参考になればと思います。

      導入経緯

      もともとスプレッドシートで管理していたのですが、スプレッドシートの管理だと、差分を見るのが大変だったり、他社に利用してもらいにくいなど不便な点がありました。
      またOpenAPIの仕様に合わせた記述をしたいという要望があったため、YAML + OpenAPIでの管理へ移行することとなりました。

      導入ライブラリの選定基準

      OpenAPIへの記述へ移行するに当たり、YAMLで書いた仕様書を何かしらの方法でレンダリングする必要がありました。候補としては、Redoc、Swagger UIが上がり、当初はSwagger UIを導入する予定だったのですが、いくつかの理由がありRedocを採用する形となりました。

      Swagger UIではなくRedocを選んだ理由

      今回の要件として、静的ファイル(HTML, CSS, JavaScript)をAmazon S3に配置して、参照したいという要望がありました。
      しかし、Swagger UIは基本的にサーバーありきで実装する形となっており、静的ファイルとして出力する場合はSwagger UIの出力ファイルであるswagger-ui-distをダウンロードしてきて、書き換える必要がありました。

      参考:実装手順
      参考:Swagger UI Repo

      この直接ファイルを修正するという作業があるとswagger-ui-distが更新された場合に、自身がファイルを修正して再アップロードする必要があり、非常に困っておりました。

      そこで色々と探していたところRedocというライブラリを発見しました。

      参考:Redoc

      Redocでは、コマンド一発でYAMLから単一HTML(出力されるHTML内にCSS、JavaScriptが梱包されている) への変換が可能なので、swagger-ui-distと比べてHTMLファイルを直接触る必要がありませんでした。

      他にもRedocはswagger-ui-distと比べたとき、

      • UIがキレイで見やすい
      • 静的ファイルとして出力される前提なのでサーバーコストを低く抑えられる
        • swagger-uiと比べての話で、swagger-ui-distを利用した場合は同じ

      といったメリットもあり、今回はRedocを採用することとなりました。

      CD用のサービスについて

      Amazon S3に設置するとのことだったので、CDを行うためのサービス候補としては、下記の2つがありました。

      • GitHub Action
      • AWS CodePipeline

      メインのプロジェクトでAWS CodePipelineを利用して使い慣れていたことと、Amazon S3との相性が良いことから、今回はAWS CodePipelineを採用しました。

      導入

      構成としては

      techblog_redocimage_intro-2

      の形にしました。
      動作は以下の順番で行われます。

      1. AWS CodePipeline経由でGitHubからソースコードを取得する
      2. AWS CodeBuildでRedocコマンドを実行する
      3. 出力されたHTMLファイルをAmazon S3へアップロードする

      APIドキュメントを確認する際には、そのままAmazon S3の公開URLを提供する形にしました。

      リポジトリのフォルダ構成

      Redoc側ではファイル構成が決まっていないため、以下のような構成にしました。src内のデータをビルドして、outに出力する構成です。掲載する画像に関しては、参照がズレないようoutと同じパスに設置しています。

      Redocがnpmパッケージで管理されていたので、npmで管理、npm scriptsで出力するようにしました。

      references
      ├── aws
      │ └── buildspec.yaml
      ├── README.md
      ├── out
      │ ├── output.html
      │ └── images
      │ ├── img1.png
      │ └── img2.png
      ├── package-lock.json
      ├── package.json
      └── src
      ├── src.yaml
      └── images
      ├── img1.png
      └── img2.png

      AWS CodeBuild用スクリプト

      AWS CodePipelineから処理を受け取った後、リポジトリ内にあるaws/buildspec.yamlをAWS CodeBuildが参照します。
      YAML構成は、

      version: 0.2

      phases:
      install:
      runtime-versions:
      nodejs: latest
      pre_build:
      commands:
      - cd references
      - npm install
      build:
      commands:
      - npm run build
      - cd ${CODEBUILD_SRC_DIR}
      artifacts:
      type: zip
      files:
      - "**/*"
      base-directory: references/out

      となっています。

      buildspec.yamlでは、phase内にある動作を順番に実行します。

      • install
        • 実行環境を整えるためのPhase
        • Nodejsランタイム環境のインストールを実施しています
      • pre_build
        • Build前に動作するPhase
        • build実行フォルダへの移動およびパッケージのインストールを実施しています
      • build
        • 実際にBuildするPhase
        • 事前にnpm scriptsで定義しているbuildコマンドを実施しています

      最後のartifactsでは、ビルド後にどうファイルを出力するかの指定ができます。
      今回の場合は、references/out以下のファイルをzipファイルで固めた後、AWS Code Pipelineへ処理にしています。

      他にも項目はあるので、詳細は公式リファレンスを参照してください。

      参考:公式リファレンス

      npm run build の内容

      AWS CodeBuildのbuildフェーズで動作させているコマンドは、npm scriptsで指定しているbuildコマンドで、OpenAPIの仕様に則ったYAMLファイルをHTMLファイルへ変換しています。

      ポイントとしてnpm scriptsではprebuildpostbuildコマンドを定義しておくと、buildの前後で実施してくれます。build内にワンライナーで書いてもよいですが、大量のコマンドを並べる必要がある場合は、分離したほうが分かりやすいと思います。

      他コマンドについてはbuild内の一部コマンドとして利用しています。

      本案件では、開発者の開発環境がWindows / Mac環境でした。

      なので、削除コマンド、コピーコマンドに関しては以下のライブラリを利用して、プラットフォーム間の差異を吸収しています。

      • rimraf
        • Win/Mac/Linux間の削除コマンド差分を吸収しています。
      • cpx
        • Win/Mac/Linux間のコピーコマンド差分を吸収しています。

      以下がpackage.jsonの中身となっております。

      "scripts": {
          "prebuild": "npm run clean",
          "build": "npm run _bundle-yaml-from-source && npm run _create-redoc",
          "postbuild": "npm run _copy-binaries",
          "clean": "rimraf out && mkdir out && echo $null > ./out/.gitkeep",
          "lint": "npx openapi lint ./src/src.yaml",
          "preview": "npx openapi preview-docs ./src/src.yaml",
          "_bundle-yaml-from-source": "npx openapi bundle src/src.yaml --output ./out/out.yaml ",
          "_create-redoc": "npx redoc-cli bundle ./out/bundled.yaml -o out/output.html --options.theme.colors.primary.main=red && rimraf ./out/out.yaml",
          "_copy-binaries": "npx cpx ./src/images/**/* ./out/images/"
      }
      

      Build後の動作

      AWS CodeBuildから渡されたzipファイルをAmazon S3へ渡すだけなのですが、注意点が2つあります。

       

      ファイル抽出を選択しておく

      AWS CodeBuild側からはzipファイルとしてデータが送られ来ているので、Amazon S3にzipファイルを解凍した状態で設置したい場合「デプロイする前にファイルを抽出する」にチェックを入れる必要があります。

      このチェックを入れなかった場合、zipファイルがAmazon S3に直接アップロードされるだけになります。

       

      権限を設定しておく

      Amazon S3側の権限でAWS CodePipelineから触れるようにしておかないと、Amazon S3アップロード時に弾かれます。

      また、特定の利用者にのみ公開したいという要望があったため、閲覧制限も設ける必要がありました。
      今回のケースだと実装工数の関係で、IP制限を設けて対応することになりました。

      参考:実装手順

      実施してみて

      上記設定を行うことで、GitHubからAmazon S3へのHTMLデプロイまで、1通り実施することができました。ただし、2点ほどうまく行かなかった点があったので、そこは次回以降の注意点として気をつけて行きたいと考えています。

      Amazon S3の権限でのやらかし

      Amazon S3でIP制限を設けたのは良かったのですが、AWS CodePipelineにだけ権限を上げればよいのでは?

      と考え、権限をAWS CodePipelineからのアクセスのみにしたところ、自分が締め出されました…😱

      これ以降、自分の権限ではアクセスできなくなってしまったので、運用保守チームへ連絡して対応してもらうことになりました。権限を絞る時は自分が継続して触れる権限になっているかの確認を行いましょう。

      CI周りの整備がしっかりできなかった

      本来であればGitHubのリポジトリPush時にOpenAPIのlintをかけて、不正なYAML構造である場合にリジェクトする処理を追加で入れ込みたかったのですが、時間の関係でできませんでした。

      ただし、GitHubのissueとして切り出しておいたので、対応自体は今後の改修でしてもらえそうな感じになっています。間に合わないけどやっておいたほうが良い作業はissueに切り出すの、大事。

      さいごに

      今回は、CDの流れを1通り自分で実装できたことがとても良かったと感じております。

      GitHub Actionsを利用してCDを構築したことは経験がありましたが、AWS CodePipelineの構築(利用はしていた)は初めてだったので、サービス間の違いが知れて良かったです。
      書き方はもちろんですが、AWS CodePipelineだとArtifact(受け渡し用zipファイル)を利用してStep間のデータ受け渡しを行う点が大きく違うと感じました。

      PJとしては、運用保守チームに自ら志願して、今回の対応を任せてもらえたことが良かったです。対応に困ったときにも弊社メンバーが積極的にフォローをしてくれました。改めて、メンバー間のフォローはPJにおいて大切だと感じました。
      ちょっとしたお願いからつながった話なので、面白そうな話があれば自分から聞いてみることが、一番大事かもしれないですね。