この記事は every Tech Blog Advent Calendar 2024 1 日目の記事です。
はじめに
こんにちは、トモニテ開発部ソフトウェアエンジニア兼、CTO 室 Dev Enable グループの rymiyamoto です。
Advent Calendar のトップバッターを務めさせていただきます!
今回は OpenAPI でスキーマ駆動開発をしていく上での定義ファイルの管理方法についてお話できればと思います。
元々別の新規プロダクトで採用されていた分割方法をトモニテでも取り入れてみたので、その知見を共有します。
現状の管理方法からの問題点
過去の記事にてご紹介しましたが、トモニテのあるプロジェクトで oapi-codegen
という OpenAPI の定義(YAML ファイル)からコードを生成できるツールを使ってドキュメント駆動開発を行っています。
最初のうちは YAML は大した大きさではなかったのですが、開発が進むにつれて YAML ファイルの行数が数千行に達し、特定の定義を探すのに時間がかかるようになってきてしまいました。
ある程度コンポーネントを定義してはいましたが、特定のレスポンスを修正するには 1 ファイル内をソース検索するのが都度手間になります。
そのため、各定義を細かくファイルに分割することができないか、メンバーが試行錯誤していました。
分割の手段
今回採用したのは redocly-cli
というドキュメントを生成・管理するためのコマンドラインツールを使って、分割した定義ファイルを結合する方法です。
定義自体は現状以下のようにまとめています。
イメージ
openapi/ ├── common # 共通の定義 │ ├── parameters.yml │ ├── responses.yml │ ├── schemas.yml │ └── securitySchemes.yml ├── web # サービスごとの定義 │ ├── endpoints │ │ ├── auth.yml │ │ └── user.yml │ ├── main.yml │ ├── parameters │ │ └── user.yml │ ├── properties │ │ └── auth.yml │ ├── requestBodies │ │ └── auth.yml │ ├── responses │ │ ├── auth.yml │ │ └── user.yml │ └── schemas │ ├── auth.yml │ └── user.yml └── gen └── web.yml
common
には共通の定義をまとめ、web
にはサービスごとの定義をまとめています。
各ファイルは main.yml
から参照される形になっています。
# main.yml openapi: 3.1.0 paths: /auth: $ref: "endpoints/auth.yml#/paths/~1auth" /users: $ref: "endpoints/user.yml#/paths/~1users" components: securitySchemes: Bearer: $ref: "../common/securitySchemes.yml#/components/securitySchemes/Bearer" # endpoint/user.yml paths: /users: get: operationId: GetUsers description: ユーザー一覧を返すAPI tags: - user parameters: - $ref: "../parameters/user.yml#/components/parameters/StatesUserParam" - $ref: "../../common/parameters.yml#/components/parameters/PageParam" - $ref: "../../common/parameters.yml#/components/parameters/PerPageParam" responses: "200": $ref: "../responses/user.yml#/components/responses/GetUsersResponse" "400": $ref: "../../common/responses.yml#/components/responses/ErrorResponse" # 以下略 # parameters/user.yml components: parameters: StatesUserParam: in: query name: states description: "状態" required: false schema: type: array items: type: boolean example: true example: true # responses/user.yml components: responses: GetUsersResponse: description: ユーザー一覧 content: application/json: schema: type: object additionalProperties: false properties: users: type: array description: ユーザーの配列 items: $ref: "../schemas/user.yml#/components/schemas/User" pagination: $ref: "../../common/schemas.yml#/components/schemas/Pagination" required: - users - pagination # schemas/user.yml components: schemas: User: type: object properties: id: type: integer format: uint64 x-go-name: "ID" description: "ID" example: 1 email: type: string description: "メールアドレス" example: "test@every.tv" created_at: type: string description: "作成日時" example: "2023-01-01T00:00:00Z" updated_at: type: string description: "更新日時" example: "2023-01-01T00:00:00Z" deleted_at: type: string description: "削除日時" example: "2023-01-01T00:00:00Z" required: - id - email - created_at - updated_at
この定義上で redocly-cli
を使って結合を行い、gen
ディレクトリに結合した定義を出力することで、定義の管理を行っています。
docker run --rm -v $${PWD}/openapi:/spec redocly/cli:1.25.14 bundle web/main.yml -o gen/web.yml
あとは生成された gen/web.yml
を oapi-codegen
でコード生成することで、コードとドキュメントを生成することができます。
また、POST や PUT などのフォームデータを送る場合の定義も同様に分割しています。
# endpoint/auth.yml paths: /auth: post: operationId: Login description: ログインAPI tags: - auth requestBody: $ref: "../requestBodies/auth.yml#/components/requestBodies/LoginRequest" responses: "200": $ref: "../responses/auth.yml#/components/responses/LoginResponse" "400": $ref: "../../common/responses.yml#/components/responses/ErrorResponse" # 以下略 # requestBodies/auth.yml components: requestBodies: LoginRequest: required: true content: application/json: schema: type: object description: ログイン properties: email: $ref: "../properties/auth.yml#/components/properties/LoginEmail" password: $ref: "../properties/auth.yml#/components/properties/LoginPassword" required: - email - password # properties/auth.yml components: properties: LoginEmail: name: email type: string description: "メールアドレス" example: "example@every.tv" x-oapi-codegen-extra-tags: validate: required,email,max=100 # go-playground/validator のタグを指定
分割によるメリット・デメリット
もちろん分割により各ドメイン単位でファイルが分けられるため、かなり見通しが良くなりました。
そのため、レビューでも差分の確認がしやすいかと思います。
ただ、今回極力分割をしてみましたが、新しい定義を追加するときには記載する箇所が多くなるため、人によってはかえってやりづらいと感じることもあると思います。
分割粒度は任意で決められるので、程よいポイントを見つけることが重要かと思います。
まとめ
今回は OpenAPI の定義ファイルを分割して管理する方法についてご紹介しました。
気づいたら肥大化してそのままになっている定義ファイルを分割することで、見通しを良くし、レビューもしやすくなりました。
一例に過ぎないので、各自のプロジェクトに合わせて適切な分割方法を見つけていただければと思います。
また、この方法を見つけてくれたメンバーに感謝 🙏 です。
最後に
エブリーでは、ともに働く仲間を募集しています。
テックブログを読んで少しでもエブリーに興味を持っていただけた方は、ぜひ一度カジュアル面談にお越しください!
最後までお読みいただき、ありがとうございました!