Swagger を利用したマイクロサービスアーキテクチャの高速開発（Spring-Boot 編）

##はじめに
マイクロサービスアーキテクチャ（以下 MSA）は、モノリスでは一つであった機能や処理を小さなサービス（マイクロサービス）として独立させ、サービスが互いに連動し合いながら処理を実現するアーキテクチャです。MSA は、サービス間のインターフェース設計が、モノリスに比べて多く発生する特徴があります。本記事は、Sprint-Boot ベースの MSA システム開発において、Swagger を利用して、サービス間のインターフェースの設計、構築、文書化を効率化させ、開発の品質と生産性を向上させる手法を紹介します。

##Swagger
Swagger は、OpenAPI 仕様を中心に構築されたオープンソースのツール群で、REST API の設計、構築、文書化、利用に役立ちます。主な Swagger ツールには以下のものがあります。

|No  |ツール名  |概要  |
|---|---|---|
|1  |Swagger Editor  |ブラウザベースのエディタで、OpenAPI 仕様を書くことができます。  |
|2  |Swagger UI  |OpenAPI 仕様をインタラクティブな API ドキュメントとしてレンダリングします。  |
|3  |Swagger Codegen  |OpenAPI 仕様からサーバースタブやクライアントライブラリを生成します。  |

##OpenAPI
OpenAPI 仕様[^1]（旧 Swagger 仕様）は、REST API のための API 記述フォーマットです。OpenAPI ファイルでは、以下のような API 情報を記述することができます。

- 利用可能なエンドポイント（/users）と各エンドポイントでの操作（GET /users、POST /users等）
- 処理パラメータ 各処理の入力と出力
- 認証方法
- 連絡先、ライセンス、使用条件、その他の情報

API 仕様は YAML または JSON で記述できます。このフォーマットは人間が読みやすいため、習得が容易です。完全な [OpenAPI 3.0 仕様書](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md)は GitHub で見ることができます。


[^1]: OpenAPI 仕様は、OAS とも呼ばれます。

##システム構成 サンプル
システムの基本的な骨格は下図になります。
各コンポーネント間は REST で通信を行います。
フロントエンドからマイクロサービスは直接通信せず、BFF ( Backend for frontend ) を配置し、フロントエンドとマイクロサービスの通信を一元的に制御します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408709/0b840f86-e416-d0ae-f608-ef74b39bad6d.png)
フロントエンドは React、サーバーサイドは Spring-Boot アプリケーション、Kubernetes 上で実行するSPA 構成になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408709/556f0a97-431a-de72-cc1d-27734232aa67.png)

Swagger を利用して、各コンポーネント間のインターフェースを生成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408709/b9bdf3c6-605d-470c-8c0d-1250490614d8.png)

##インターフェースの生成の流れ
OpenAPI 仕様に従って API を定義した YAML ファイル（以降、OAS ファイル）を元に、OpenAPI Generator ツールを通して、コントローラークラスとモデルクラス、WebClientSDK クラスとモデルクラスを生成する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408709/4a34e79b-4264-28cb-1a9f-277023b6c651.png)



##ディレクトリ構成
Spring-Bootプロジェクトのディレクトリ構成（抜粋）を示す。
OpenAPI Generator に関連するディレクトリに限定しています。

```yaml
root/
　├ openapi/    #(*1)
　└ src/main/java/com/ksper/swagger/bff/common/
　 　                                       ├config/   
　 　                                       ├controller/    #(*2)
　 　                                       ├model/    #(*3)
　 　                                       ├generated/    #(*4)
　 　                                               ├ server/
　 　                                               │    └ controller/    #(*5)
　 　                                               │    └ model/    #(*6)
　 　                                               └ client/
　 　                                                    └ client/    #(*7)
　 　                                                    └ model/    #(*8)
```
- (*1) OAS ファイル、[インターフェース生成シェル](https://qiita.com/ksper/private/87abb20b78f79db013be#インターフェース生成シェル)を配置
- (*2) OpenAPI Generator で生成されたコントローラーの Interface ファイルの実装クラスを配置
- (*3) OpenAPI Generator で生成されたモデルクラス以外で必要なモデルクラスを配置
- (*4) OpenAPI Generator で生成されたクラスを下層のディレクトリに配置
- (*5) OpenAPI Generator で生成されたコントローラーの Interface ファイルを配置
- (*6) OpenAPI Generator で生成されたコントローラー用のモデルクラスファイルを配置
- (*7) OpenAPI Generator で生成された WebClientSDK ファイルを配置
- (*8) OpenAPI Generator で生成された WebClientSDK 用のモデルクラスファイルを配置


##OAS ファイルと生成されるコントローラークラス
サンプル OAS ファイルは以下の通りとする。
OAS ファイルの記述ルールは、[OpenAPI 3.0 仕様書](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md)を参照してください。

```yaml:BFF.yaml
openapi: 3.0.0
info:
  title: BFF
  version: '1.0'
servers:
  - url: 'http://localhost:3000'
paths:
  '/users/{userId}':
    parameters:
      - schema:
          type: integer
        name: userId
        in: path
        required: true
        description: Id of an existing user.
    get:
      summary: Get User Info by User ID
      tags: []
      responses:
        '200':
          description: User Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
              examples:
                Get User Alice Smith:
                  value:
                    id: 142
                    firstName: Alice
                    lastName: Smith
                    email: alice.smith@gmail.com
                    dateOfBirth: '1997-10-31'
                    emailVerified: true
                    signUpDate: '2019-08-24'
        '404':
          description: User Not Found
      operationId: get-users-userId
      description: Retrieve the information of the user with the matching user ID.
#    （省略）
components:
  schemas:
    User:
      title: User
      type: object
      description: ''
      x-examples:
#    （省略）
      properties:
        id:
          type: integer
          description: Unique identifier for the given user.
        firstName:
          type: string
        lastName:
          type: string
        email:
          type: string
          format: email
        dateOfBirth:
          type: string
          format: date
          example: '1997-10-31'
        emailVerified:
          type: boolean
          description: Set to true if the user's email has been verified.
        createDate:
          type: string
          format: date
          description: The date that the user was created.
      required:
        - id
        - firstName
        - lastName
        - email
        - emailVerified
  securitySchemes: {}

```
サンプル OAS ファイルから生成されるコントローラークラス（Interface ファイル）

```java:UsersAPI.java 
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2021-05-01T22:27:12.174376+09:00[Asia/Tokyo]")
@Validated
@Api(value = "users", description = "the users API")
public interface UsersApi {

    default Optional<NativeWebRequest> getRequest() {
        return Optional.empty();
    }

    /**
     * GET /users/{userId} : Get User Info by User ID
     * Retrieve the information of the user with the matching user ID.
     *
     * @param userId Id of an existing user. (required)
     * @return User Found (status code 200)
     *         or User Not Found (status code 404)
     */
    @ApiOperation(value = "Get User Info by User ID", nickname = "getUsersUserId", notes = "Retrieve the information of the user with the matching user ID.", response = User.class, tags={  })
    @ApiResponses(value = { 
        @ApiResponse(code = 200, message = "User Found", response = User.class),
        @ApiResponse(code = 404, message = "User Not Found") })
    @GetMapping(
        value = "/users/{userId}",
        produces = { "application/json" }
    )
    default ResponseEntity<User> getUsersUserId(@ApiParam(value = "Id of an existing user.",required=true) @PathVariable("userId") Integer userId) {
        getRequest().ifPresent(request -> {
            for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
                if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
                    String exampleString = "{ \"firstName\" : \"firstName\", \"lastName\" : \"lastName\", \"emailVerified\" : true, \"dateOfBirth\" : \"1997-10-31T00:00:00.000+0000\", \"id\" : 0, \"email\" : \"email\", \"createDate\" : \"2000-01-23\" }";
                    ApiUtil.setExampleResponse(request, "application/json", exampleString);
                    break;
                }
            }
        });
        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);

    }

//  （省略）

}

```

###コントローラークラス作成の注意点
- コントローラークラスのメソッドの名前は、OAS ファイルの operationId で指定する。
- URL の第一階層名の種類数分のコントローラークラスを生成する。（ /users/{userId}' の users の部分）
- 作成されるのは、Interface クラスであるため、必ず実装クラスを作成する。

##インターフェース生成シェル
マイクロサービスのインターフェースが頻繁に変わることが想定される場合は、OpenAPI Generator に必要な引数を与えて起動するシェルスクリプトを準備することが望ましい。以下にサンプルを記載する。

#### OAS ファイルからコントローラークラスを生成するスクリプト
```bash
#!/bin/sh

OUT=.

PGM=../openapi-generator-cli-5.0.0.jar

java -jar ${PGM} generate \
    -i ../ksper-frontend-common/BFF.yaml \
    -o ${OUT}/ \
    --api-package com.ksper.swagger.bff.common.generated.server.controller \
    --model-package com.ksper.swagger.bff.common.generated.server.model \
    -g spring \
    --library spring-boot \
    -p interfaceOnly=true,dateLibrary=java8

```

##OAS ファイルの運用
複数チームで開発する場合は、OAS ファイルと生成されたソースコードの置き場所が、生産性と品質に関係します。私のプロジェクトで実践した運用イメージを下図に示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1408709/23db7c18-471d-dd60-bef4-b58e98ae4735.png)

- マイクロサービスの OAS ファイルは、各マイクロサービスのリポジトリで管理
- BFF の OAS ファイルは、フロントエンドの共通リポジトリで管理

####運用上の注意点
OAS ファイルを編集した場合、APIの呼び出し元側と呼び出し先側の双方に影響が出ます。OAS ファイルの編集時は、コミュニケーションツールなどを活用し、関連チームに編集することを小まめに伝えることが重要です。

##おわりに
マイクロサービスアーキテクチャのシステムは、メンテナンス性や拡張性に優れる反面、コンポーネントの細分化によってインターフェースが増えます。そのため、インターフェス設計書のドキュメント作成稼働や、各チーム同士が意識を合わせなければならない事項の増加、結合試験時のバグの増加等が起こります。Swaggerは、そういったマイクロサービスの課題を解決する支援をしてくれます。

記事は以上になります。
本記事では、WebClientSDK側の生成について触れておりませんので、今後余裕があれば、そちらも加筆していきます。
読んでいただきありがとうございました。

## 参考文献
https://swagger.io/specification/
https://stoplight.io/studio/