SwaggerでJavaのドキュメントを作ってみる

# はじめに

大きな開発をする際に開発内容の説明するのって難しいですよね。
もしかすると世間にはいい感じのドキュメントの作り方があるかもしれないし、
なんなら自分好みのドキュメント生成用アプリなんかを作るのも1つの手だと思います。

ただできるだけ素早く手軽に手間も抑えたい！
という気持ちでたまたま最近使い始めていたSwaggerを使っていい感じにドキュメントが作成できないかと考えたので
今回はJavaの開発内容をSwaggerを使ってドキュメント化してみたいと思います。

# Swaggerについて

Swaggerについては公式ドキュメントやほかにもいろいろな記事があるので詳細は省きますが、
簡単にまとめるとOpenAPI仕様のAPIについて設計やドキュメントの作成などが可能なツールのセットとなります。

https://swagger.io/docs/specification/about/

ツールのセットということで実際どういったツールがあるかというと下記のようなツールがあるようです。
|名称 |概要　|
|:------------|:-------------|
|Swagger Editor |OpenAPI定義を作成できるブラウザベースのエディター  |
|Swagger UI  |OpenAPIの定義をブラウザで見やすく表示するツール |
|Swagger Codegen |OpenAPI定義から実際のコードを生成するツール |
|Swagger Editor Next |OpenAPIとAsyncAPIの定義を作成したりレビューできるブラウザベースのエディター  |
|Swagger Core  |OpenAPI定義の作成、操作することができるJavaのライブラリ |
|Swagger Parser  |OpenAPI 定義を解析するためのスタンドアロン・ライブラリ |
|Swagger APIDom |APIの言語(OpenAMIやRAMLなど)、シリアライズ形式(xmlやyamlなど)によらず形式を統一した構造を提供する  |

今回は主にSwagger EditorとSwagger UIを使うことになります。

# 定義の作成

ドキュメントの作成にはSwagger EditorとSwagger UIを使用するためにセットアップが必要になります。
具体的なセットアップ手順は下記が参考になるかと思います。

http://itbibo.php.xdomain.jp/swagger-editor-install/

セットアップが完了すると下記のような画面になり、ドキュメントの作成が可能になります。

![swagger-setup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/877698/95abef22-4c5d-8390-987a-541d7a7f4d75.png)

最初の起動時には公式で用意されている例のようなドキュメントが表示されているので、
まずはこの例を参考に書いていくのがいいと思います。
もう一度この例の状態に戻したい場合は、Edit > Load PetStore OAS 3.0 を実行することで実現可能です。

次に実際にドキュメントを書くステップに移りますが、
今回は下記のような条件を例にしてまずドキュメントを作成します。
1. 会員情報を管理するクラス
1. データの登録、データの取得、データの更新、データの削除を実装する

実際に必要な情報を埋めていったものが下記になります。

<details><summary>実際のswagger定義</summary><div>

```member.yaml
openapi: 3.0.3
info:
  title: Memberクラス
  description: |-
    メンバーの管理をするクラス
  termsOfService: http://swagger.io/terms/
  contact:
    email: apiteam@swagger.io
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.0
externalDocs:
  description: Find out more about Swagger
  url: http://swagger.io
servers:
  - url: http://localhost:8080
paths:
  /register:
    post:
      tags:
      - Member
      summary: メンバーの登録処理
      description: 登録に必要なデータを引数に渡すことでメンバーの登録が実行されます。
      operationId: register
      parameters:
      - name: name
        in: query
        description: メンバーの名前
        required: true
        explode: true
        schema:
          type: string
        example: swagger name
      - name: age
        in: query
        description: メンバーの年齢
        required: true
        explode: true
        schema:
          type: integer
        example: 25
      - name: adress
        in: query
        description: メンバーの住所
        required: true
        explode: true
        schema:
          type: string
        example: 東京都〇〇区 ××
      - name: gender
        in: query
        description: メンバーの性別
        required: true
        explode: true
        schema:
          type: string
        example: 男
      - name: mail
        in: query
        description: メンバーのメールアドレス
        required: true
        explode: true
        schema:
          type: string
        example: qiitaro.xxx.xxx
      responses:
        200:
          description: コンソール出力
          content:
            application/json:
              schema:
                type: object
                properties:
                  登録者ID:
                    example: m12345
  /getMember:
    get:
      tags:
      - Member
      summary: メンバーの取得処理
      description: 登録者IDを引数に渡すことで該当するのメンバー情報を取得します。
      operationId: getMember
      parameters:
      - name: id
        in: query
        description: 登録者ID
        required: true
        explode: true
        schema:
          type: string
        example: m12345
      responses:
        200:
          description: メンバー情報 Map<String>
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                    example: swagger name
                  age: 
                    type: integer
                    example: 25
                  adress: 
                    type: string
                    example: 東京都〇〇区 ××
                  gender: 
                    type: string
                    example: 男
                  email: 
                    type: string
                    example: qiitaro.xxx.xxx
  /updateMember:
    put:
      tags:
      - Member
      summary: メンバーの更新処理
      description: 登録者IDを引数に渡すことで該当するのメンバー情報を更新します。
      operationId: updateMember
      parameters:
      - name: id
        in: query
        description: 登録者ID
        required: true
        explode: true
        schema:
          type: string
        example: m12345
      - name: columnNm
        in: query
        description: 更新対象のカラム名
        required: true
        explode: true
        schema:
          type: string
        example: adress
      - name: updateData
        in: query
        description: 更新後のデータ
        required: true
        explode: true
        schema:
          type: string
        example: 埼玉県〇〇市 ××
      responses:
        200:
          description: コンソール出力
          content:
            application/json:
              schema:
                type: object
                properties:
                  更新データ:
                    example: adress:埼玉県〇〇市 ××
  /deleteMember:
    delete:
      tags:
      - Member
      summary: メンバーの削除処理
      description: 登録者IDを引数に渡すことで該当するのメンバー情報を削除します。
      operationId: deleteMember
      parameters:
      - name: id
        in: query
        description: 登録者ID
        required: true
        explode: true
        schema:
          type: string
        example: m12345
      responses:
        200:
          description: コンソール出力
          content:
            application/json:
              schema:
                type: object
                properties:
                  削除完了:
                    example: m12345
```
</div></details>

# サーバー作成

Swagger定義の記述が完了したので次はこれを参照しやすいようにdockerコンテナ化します。

### サーバーファイル作成

まずはSwagger定義からnode.js用サーバーファイルを出力します。
下記画像のようにGenerate Server > nodejs-serverから出力可能です。
![nodejs-server.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/877698/cc990e81-0e82-764e-0970-4f9d7e6fd2b2.png)

サーバーファイルの出力が完了するとzipファイルがダウンロードされるので、
このzipファイルを任意のディレクトリに解凍します。
解凍後のディレクトリ構成としては下記のようになっています。

```
nodejs-server-server-generated
├ .swagger-codegen
├ api
├ controllers
├ service
├ utils
├ .swagger-codegen-ignore
├ index.js
├ package.json
└README.md
```

### Dockerfileの作成

サーバーファイルの作成まで完了したので実際にサーバーをたてていきます。
今回はnode.jsになるため詳細な手順は下記のページが参考になります。

https://nodejs.org/ja/docs/guides/nodejs-docker-webapp

上記手順を通してDockerfileと.dockerignoreファイルの作成が完了した後は、
これらを先ほど解凍したサーバーファイルのディレクトリに配置します。
最終的なのディレクトリ構成としては下記のようになっています。

```
nodejs-server-server-generated
├ .swagger-codegen
├ api
├ controllers
├ service
├ utils
├ .dockerignore
├ .swagger-codegen-ignore
├ Dockerfile
├ index.js
├ package.json
└README.md
```

### サーバーの作成

ここまででnode.jsサーバーの作成準備が整ったのでサーバーの作成をしていきます。
下記コマンドを実行していくことでサーバーの作成ができます。

```
$ cd /nodejs-server-server-generated　作成したサーバーファイルのディレクトリ

$ docker build . -t {任意のタグ}
$ docker run -name {任意の名前} -p {使用するポート}:{使用するポート} -d {任意のタグ}

例)
$ docker build . -t member-doc
$ docker run --name member -p 8080:8080 -d member-doc
```

サーバーの起動確認後ブラウザからに下記にアクセスして画像ようなページが開けば完了です。
http://localhost:8080/docs/ 

![member.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/877698/09288ef5-026a-a1e2-4f7f-fe9dd83cc8ca.png)

Swaggerを利用したことによる特徴として、POSTやGETなどで色分けした部分が処理と対応していると視覚的にもわかりやすい状態になっていたりします。

POST：データの登録
GET：データの取得
PUT：データの更新
DELETE：データの削除

各メソッドについても下記画像のように展開することで必要な引数についてや実行結果について確認できるようになっています。
![register.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/877698/0b4a1e9b-23eb-bc0e-3754-e179ff949576.png)



また今回Swagger定義を作成したクラスを実際のソースに起こすと下記のようなイメージになります。

```Member.java

package jp.test.Member;

public class Member {
    
    public void register( String name, int age, String adress, String gender, String mail ) {
        String memberId;
        // 登録処理
        System.out.println("登録者ID：" + memberId);
    }
    
    
    public HashMap<String> getMember(String sql) {
        HashMap<String> memberMap = new HashMap<String>();
        // データ取得処理
        return memberMap;
    }
    
    public void updateMember(String id, String columnNm, String updateData) {
        // データ更新処理
        System.out.println("更新データ：" + columnNm + "=" + updateData);
    }

    public void deleteMember(String id) {
        // データ削除処理
        System.out.println("削除完了：" + id);
    }
}
```

# メリットとデメリット

今回Swaggeを利用してJavaのクラスをドキュメント化してみて個人的に感じたメリットとデメリットを下記にまとめてみました。

### メリット

1. 実装内容をある程度整理してかつそれを説明資料に残せる
1. メソッドを網羅しつつ確認したいメソッドにフォーカスできるドキュメントになる
1. Swagger自体の勉強にもなる

### デメリット

1. 書き方が縛られる(引数の型やresponse部分など)
1. Swaggerを普段から使う場合混同する可能性がある
1. 複雑なクラスやメソッドの場合はわかりづらい

# おわりに

今回はSwaggerを使ってJavaのドキュメントを作成してみましたが、
個人的にはソースに説明を書くよりも説明には使いやすいように感じたのと、ある種フォーマットが決まっていることで表記ゆれのようなものもなくなりドキュメント化の1つとしてありなように感じました。

またSwagger自体オープンソースなので、それこそドキュメント用にカスタマイズしたものを作ってみたりとか、
あとはyamlで書く形式なのでjavadocからSwagger定義を生成するツールとかも作れなくはないんじゃないかと思ったりといろいろと試してみる価値があるように思います。