Swaggerのファイルを分割してソース管理を運用する
Swaggerとは
Swaggerとは、RESTful APIを構築するためのオープンソースのフレームワークのことを指します。 2015年より「Google」,「Microsoft」,「IBM」, etc が、「Open API Initiative」という団体を結成し、RESTful APIのインターフェイスを記述をするための標準化を推進しています。その標準フォーマットが「Swagger」です。
以前はフレームワークの一部でしたが、2016年に「Open API Initiative」が統括する独立プロジェクトとなりました。
主な仕様については、こちらから確認できます。
競合するサービスはSwaggerの他にもいくつかありますが、Swaggerが独立プロジェクトとなった2016年から2023年現在までの動向を見る限り、現在もトレンドのようです(※Google Trends)
Swaggerドキュメントの例
下記に記述するyamlやjsonをSwagger Editor へ貼付することでAPIドキュメントとしてグラフィカルに表示できます。
openapi: 3.0.3
info:
version: 1.0.0
title: Swagger sample @zenn article
tags:
- name: common
description: 共通領域
- name: order
description: 申込領域
paths:
/common/GetSystemDateTime:
get:
tags:
- common
summary: システム日時取得API
description: |-
[Overview]
サーバ内システム日時を[yyyy-MM-dd HH:mm:ss](JST)形式で返却する
[Details]
1.システム日時を返却する
- 1-1. 設定ファイルに保存されている値が存在する場合、その日時を返却する
operationId: GetSystemDateTime
responses:
'200':
description: 成功時
content:
application/json:
schema:
type: object
properties:
SystemDateTime:
type: string
description: システム日時
format: date-time
example: '2023-09-22 09:22:22'
'500':
description: 異常時
content:
application/json:
schema:
required:
- StatusCode
- Message
type: object
properties:
StatusCode:
title: StatucCode
type: integer
format: int32
example: 500
Message:
title: statuscode of name
type: string
example: Internal Server Error.
ValidationErrors:
type: array
description: ValidationErrors
items:
type: object
properties:
Code:
title: メッセージコード
type: string
example: null
Message:
title: メッセージ
type: string
example: null
Attribute:
title: 項目
type: string
example: null
BusinessErrors:
type: array
description: BusinessErrors
items:
type: object
properties:
Code:
title: メッセージコード
type: string
example: null
Message:
title: メッセージ
type: string
example: null
Attribute:
title: 項目
type: string
example: null
/order/PostCreateOrder:
post:
tags:
- order
summary: 申込作成API
description: >-
[Overview]
申込情報を作成し外部APIをCallする
[Details]
1.requestBodyから「リクエスト項目」を取得する。
2.requestBodyをリクエスト項目として、外部APIをコールする。
operationId: PostCreateOrder
requestBody:
description: リクエスト項目を指定
required: true
content:
application/json:
schema:
type: object
properties:
orderid:
title: 申込ID
type: string
example: Order0001
userid:
title: ユーザID
type: string
example: User0001
responses:
'200':
description: 成功時
content:
application/json:
schema:
type: object
properties:
processResult:
type: object
description: 外部処理結果用モデル
properties:
returnCode:
type: string
description: リターンコード
example: Success
resultInfos:
type: array
items:
type: object
description: 結果情報リスト
properties:
resultCategory:
type: string
description: 結果区分
example: Error
resultCode:
type: string
description: 結果コード
example: XXXXX
resultMessage:
type: string
description: 結果メッセージ
example: XXXXX
'500':
description: 異常時
content:
application/json:
schema:
required:
- StatusCode
- Message
type: object
properties:
StatusCode:
title: StatucCode
type: integer
format: int32
example: 500
Message:
title: statuscode of name
type: string
example: Internal Server Error.
ValidationErrors:
type: array
description: ValidationErrors
items:
type: object
properties:
Code:
title: メッセージコード
type: string
example: null
Message:
title: メッセージ
type: string
example: null
Attribute:
title: 項目
type: string
example: null
BusinessErrors:
type: array
description: BusinessErrors
items:
type: object
properties:
Code:
title: メッセージコード
type: string
example: null
Message:
title: メッセージ
type: string
example: null
Attribute:
title: 項目
type: string
example: null
「swagger.yml」を元に生成されたドキュメントは下記のように表示されます。
Swagger定義ファイルを分割する理由
当初はAPI定義ファイルである「swagger.yml」ファイルへAPIが増えるたびに記述を追加していました。
結果として、下記のような問題が発生しました。
- ファイルサイズの肥大化
- 複数人での並行作業が困難
- コンフリクトの頻発
- 編集すべき記述を素早く見つけられない
これらの問題に対処するために「swagger.yml」の分割をおこなうことにしました。
ファイル分割の考え方と記載内容
さきほどの 「swagger.yml」を分割して記述してみます。
├── index.yaml
├── paths
│ └── index.yml
│ └── GetSystemDateTime.yml
│ └── PostCreateOrder.yml
└── components
└── index.yml
└── ProcessResultModel.yml
└── InternalServerErrorResponse.yml
└── ErrorModel.yml
最上位階層の「index.yml」には、下記の情報を定義しています。
- info: titleやdescriptionなどの情報を定義します。
- servers: 定義したAPIを実行する基底エンドポイントを定義します。 ※今回のサンプルでは未定義です。
- tags: APIをカテゴリごとに分けたい場合などに定義します。
- paths: ファイル分割されたAPIのファイルパスを示す子階層の「index.yaml」を指定します。
- components: ファイル分割されたAPI内で参照する共通のスキーマを定義するように設計しました。pathsと同様に、子階層の「index.yaml」を指定します。
openapi: 3.0.3
info:
version: 1.0.0
title: Swagger sample @zenn article
tags:
- name: common
description: 共通領域
- name: order
description: 申込領域
paths:
$ref: './paths/index.yml'
components:
$ref: './components/index.yml'
paths階層の「index.yml」には、分割したAPIの情報が記載されたファイルをパスを定義していきます。
#【共通】システム日時取得API
GetSystemDateTime:
$ref: 'GetSystemDateTime.yml'
#【申込】申込作成API
PostCreateOrder:
$ref: 'PostCreateOrder.yml'
それぞれ個別に分割して定義していきます。
# /common/GetSystemDateTime:
get:
tags:
- common
summary: システム日時取得API
description: |-
[Overview]
サーバ内システム日時を[yyyy-MM-dd HH:mm:ss](JST)形式で返却する
[Details]
1.システム日時を返却する
- 1-1. 設定ファイルに保存されている値が存在する場合、その日時を返却する
operationId: GetSystemDateTime
responses:
'200':
description: 成功時
content:
application/json:
schema:
type: object
properties:
SystemDateTime:
type: string
description: システム日時
format: date-time
example: '2023-09-22 09:22:22'
'500':
$ref: '#/components/InternalServerErrorResponse'
# /PostCreateOrder:
post:
tags:
- order
summary: 申込作成API
description: >-
[Overview]
申込情報を作成し外部APIをCallする
[Details]
1.requestBodyから「リクエスト項目」を取得する。
2.requestBodyをリクエスト項目として、外部APIをコールする。
operationId: PostCreateOrder
requestBody:
description: リクエスト項目を指定
required: true
content:
application/json:
schema:
type: object
properties:
orderid:
title: 申込ID
type: string
example: Order0001
userid:
title: ユーザID
type: string
example: User0001
responses:
'200':
description: 成功時
content:
application/json:
schema:
type: object
properties:
processResult:
$ref: '#/components/ProcessResultModel'
'500':
$ref: '#/components/InternalServerErrorResponse'
components階層の「index.yml」には、分割したAPI内で参照する共通のスキーマを定義していきます。
ProcessResultModel:
$ref: 'ProcessResultModel.yml'
ErrorModel:
$ref: 'ErrorModel.yml'
InternalServerErrorResponse:
$ref: 'InternalServerErrorResponse.yml'
こちらもそれぞれ個別に分割して定義していきます。
# ProcessResult:
type: object
description: 外部IF処理結果用モデル
properties:
returnCode:
type: string
description: リターンコード
example: Success
resultInfos:
type: array
items:
type: object
description: 結果情報リスト
properties:
resultCategory:
type: string
description: 結果区分
example: Error
resultCode:
type: string
description: 結果コード
example: XXXXX
resultMessage:
type: string
description: 結果メッセージ
example: XXXXX
# ErrorModel
description: Response Error Model.
required:
- StatusCode
- Message
type: object
properties:
StatusCode:
title: StatucCode
type: integer
format: int32
example: 500
Message:
title: statuscode of name
type: string
example: Internal Server Error.
ValidationErrors:
type: array
description: ValidationErrors
items:
type: object
properties:
Code:
title: メッセージコード
type: string
example: null
Message:
title: メッセージ
type: string
example: null
Attribute:
title: 項目
type: string
example: null
BusinessErrors:
type: array
description: BusinessErrors
items:
type: object
properties:
Code:
title: メッセージコード
type: string
example: null
Message:
title: メッセージ
type: string
example: null
Attribute:
title: 項目
type: string
example: null
# InternalServerErrorResponse
description: |-
Internal Server Error.
content:
application/json:
schema:
allOf:
- $ref: '#/components/ErrorModel'
properties:
StatusCode:
example: 500
Message:
example: Internal Server Error.
分割したファイルの統合
ファイル分割することはできましたが、小分けにしたSwaggerファイルを最終的に「swagger.yml」へ統合する必要があります。
下記のようにやり方はいくつかあるようです。
今回はファイルを編集・追加・削除などをした際に自動的に「swagger.yml」へ統合したいのと、Swagger UIへも同時に反映していきたかったので、Node.js で実装したプログラムをContainer Image化していきます。
Docker Containerとして起動することで、ホストOS側のディレクトリを監視し、変更のキャッチと同時にファイル統合を行います。
こちらのソースコードはGitに公開しています。
アーキテクチャは下図のイメージです。
- swagger-uiイメージは8005ポートフォワードし、ホストOS側のdocsディレクトリをマウントします。
- swagger-watchイメージはコンテナ側でミラーリングしているディレクトリを監視します。
version: '3'
services:
swagger-ui:
image: swaggerapi/swagger-ui
ports:
- 8005:8080
volumes:
- ./docs:/usr/share/nginx/html
swagger-watch:
build: ./swagger-watch
volumes:
- ./swagger-watch:/app
- /app/node_modules
- ./src:/src
- ./docs/swagger.yml:/docs/swagger.yml
working_dir: /app
command: 'node index.js'
イメージ作成からコンテナの起動
Gitリポジトリよりファイル統合ツールをPullします。直接ダウンロードしてもOKです。
git clone https://github.com/yutaka-art/swagger-detect-merger.git C:\Work\swagger-detect-merger
ファイル統合ツールをContainer Image化します。※初回だけ必要です。
cd C:\Work\swagger-detect-merger
docker-compose build
Image化したContainerを起動します。
docker-compose up -d
Docker Desktopを見ると下記のように、Containerが起動状態となっていることを確認します。
起動しているSwagger UIへ接続します。
→
試しに分割したファイルを編集してみます。
起動しているSwagger UIをリロードすると、統合されていることが確認できます。
まとめ
このようにファイル分割をすることで、APIや共通リソースなどコンポーネント単位によるソース管理が可能となります。
ファイル分割の方法や考え方については様々な記事がありますが、自動ファイル統合については詳細な記事が少なかったため書いてみました。ファイルをGitへPushした際に「swagger.yml」を統合し、「Azure Static Web Apps」などへ公開するCI/CDができると更なる効率化が見込めると思います。それはまた別途記事にします。
Discussion