📑

Swaggerのファイルを分割してソース管理を運用する

2023/09/16に公開

Swaggerとは

Swaggerとは、RESTful APIを構築するためのオープンソースのフレームワークのことを指します。 2015年より「Google」,「Microsoft」,「IBM」, etc が、「Open API Initiative」という団体を結成し、RESTful APIのインターフェイスを記述をするための標準化を推進しています。その標準フォーマットが「Swagger」です。

以前はフレームワークの一部でしたが、2016年に「Open API Initiative」が統括する独立プロジェクトとなりました。

主な仕様については、こちらから確認できます。
https://swagger.io/specification/

競合するサービスはSwaggerの他にもいくつかありますが、Swaggerが独立プロジェクトとなった2016年から2023年現在までの動向を見る限り、現在もトレンドのようです(※Google Trends)

Swaggerドキュメントの例

下記に記述するyamlやjsonをSwagger Editor へ貼付することでAPIドキュメントとしてグラフィカルに表示できます。
https://editor.swagger.io/

swagger.yml
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」を指定します。
index.yml
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の情報が記載されたファイルをパスを定義していきます。

paths\index.yaml
#【共通】システム日時取得API
GetSystemDateTime:
  $ref: 'GetSystemDateTime.yml'
#【申込】申込作成API
PostCreateOrder:
  $ref: 'PostCreateOrder.yml'

それぞれ個別に分割して定義していきます。

paths\GetSystemDateTime.yaml
#  /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'
paths\PostCreateOrder.yaml
#  /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内で参照する共通のスキーマを定義していきます。

components\index.yml
ProcessResultModel:
  $ref: 'ProcessResultModel.yml'
ErrorModel:
  $ref: 'ErrorModel.yml'
InternalServerErrorResponse:
  $ref: 'InternalServerErrorResponse.yml'

こちらもそれぞれ個別に分割して定義していきます。

components\ProcessResultModel.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
components\ErrorModel.yml
#   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
components\InternalServerErrorResponse.yml
#   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イメージはコンテナ側でミラーリングしているディレクトリを監視します。
docker-compose.yml
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'

イメージ作成からコンテナの起動

https://www.docker.com/products/docker-desktop/
https://git-scm.com/download

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ができると更なる効率化が見込めると思います。それはまた別途記事にします。

GitHubで編集を提案

Discussion