Spring Boot + Swagger2.0の触り心地を確かめてみる

https://start.spring.io/ からwebのみを選択してパパッと作成
Swaggerはなかったので、Maven Repositoryから持ってきた
versionなどは `build.gradle` 参照してもらいたい

サンプルコード
https://github.com/ririkku/swagger-demo

# 試せる最小構成

### コード全量

```build.gradle
plugins {
	id 'org.springframework.boot' version '2.2.1.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'

	// Swaggerで必要な最低限
	implementation "io.springfox:springfox-swagger2:2.9.2"
	implementation "io.springfox:springfox-swagger-ui:2.9.2"
	
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

test {
	useJUnitPlatform()
}
```

```SwaggerDemoApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SwaggerDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SwaggerDemoApplication.class, args);
    }
}
```

```SwaggerDemoConfiguration.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2 // swagger2.0を使用
public class SwaggerDemoConfiguration {

    @Bean
    public Docket petApi() {
        return new Docket(DocumentationType.SWAGGER_2) // Swagger2.0を使用します宣言
                .select()
                .paths(PathSelectors.ant("/apis/**"))
                .build();
    }
}
```

```SwaggerDemoRestController.java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("apis")
public class SwaggerDemoRestController {

    @GetMapping
    public String get() {
        return "get";
    }
}
```

### 確認

普段はIntellij IDEAから起動しているけど、その環境じゃない人もいるかもだから Gradleで起動する
`./gradlew bootRun`
ローカルホストでアクセス
`http://localhost:8080/swagger-ui.html`
(入りきらなかったので2分割)
<img width="1477" alt="スクリーンショット 2019-12-03 0.04.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/2af7fa85-5d61-b8d8-48ca-17f1b1cdb910.png">
<img width="1466" alt="スクリーンショット 2019-12-03 0.05.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/ec0a65ce-0073-1ae4-73f5-fb082d620e2b.png">

### 雑感
UIいいじゃん！！！
コードもそんなに書いてないし、実用的かも
でも色々お節介な感じはする、ちょっとカスタマイズしてみよう

# Headerっぽいところをカスタマイズ

### コード(変更ファイル)

```SwaggerDemoConfiguration.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2 // swagger2.0を使用
public class SwaggerDemoConfiguration {

    @Bean
    public Docket petApi() {
        return new Docket(DocumentationType.SWAGGER_2) // Swagger2.0を使用します宣言
                .select()
                .paths(PathSelectors.ant("/apis/**"))
                .build()
                .apiInfo(new ApiInfoBuilder()
                        .title("Customise Title Swagger Demo Application")
                        .description("自分好みにカスタマイズしてたよ")
                        .contact(new Contact("customise-name", "http://customise-contact", "customise-email"))
                        .version("1.0")
                        .termsOfServiceUrl("http://customise.com")
                        .license("Customise License").licenseUrl("http://customise-license-url") // licenseのみだとテキスト、licenseUrl設定するとリンクになる
                        .build());
    }
}
```

### 確認
<img width="1459" alt="スクリーンショット 2019-12-03 0.27.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/dba5f5c1-e45f-c958-3bfc-0ec29603028a.png">

### 雑感
`Customise`のサフィックスをつけた場所が書き換えてみた箇所
`Base URL`と`http//localhost:8080/v2/api-docs`の部分は変えられないのかな？って感想
あと、`ApiInfoBuilder`クラスには`extensions`が設定できるよう
独自に何かプラグインを作成したい時とか使用するのかな？(https://swagger.io/docs/specification/2-0/swagger-extensions/)
そういやHttpメソッドってどんな感じに出るんだろう

# HTTPメソッドのUI確認

### コード(変更ファイル)
```SwaggerDemoRestController.java
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("apis")
public class SwaggerDemoRestController {

    @GetMapping
    public String get() {
        return "get";
    }

    @PostMapping
    public void post() {
    }

    @DeleteMapping
    public void delete() {
    }

    @PutMapping
    public void put() {
    }

    @PatchMapping
    public void patch() {
    }
}
```

### 確認
さっきはGET見たから次は気分でPOST
<img width="1461" alt="スクリーンショット 2019-12-03 0.38.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/beac1f94-83c7-84de-2ad3-fb8c49d727fd.png">
<img width="1440" alt="スクリーンショット 2019-12-03 0.41.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/c6904f01-e8ca-b587-1db2-fc6c17f004c8.png">


### 雑感
カラフルで見やすい(PATCHとか使わんやろ)
APIの詳細は色々勝手に考慮してくれる模様(ステータスコードとか)
次は詳細部分をカスタマイズしてみよう


# エンドポイントのカスタマイズ

心機一転、新しいクラスを作成した
`@PathVariable`はクラスにバインドしたり、`@RequestParam`はStringにバインドしたりはわざと！

### コード
```Identifier.java
public class Identifier {

    private String value;

    public Identifier(String value) {
        this.value = value;
    }

    public String value() {
        if (value == null) return "";
        return value;
    }
}
```

```SwaggerDemoCustomiseRestController.java
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("apis/customise")
public class SwaggerDemoCustomiseRestController {

    @GetMapping("{identifier}")
    public String detail(@RequestHeader("X-Customise-Header") String customiseHeader,
                         @PathVariable("identifier") Identifier identifier,
                         @RequestParam(value = "name", required = false) String name,
                         @RequestParam("limit") int limit) {
        return identifier.value();
    }
}
```

### 確認

`@RequestHeader`、`@PathVariable`、`@RequestParam`を入れてみた
必須はものには、`required`が付くようになった、便利だね

<img width="1467" alt="スクリーンショット 2019-12-03 0.56.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/fb44bedd-4f9b-32cb-abc0-2175246a11fa.png">

ちなみに、 `Try it out` ボタンを押すと以下の表示なって値を入れて検証できるようになった！

<img width="1452" alt="スクリーンショット 2019-12-03 1.00.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/8990f98f-5c26-2fca-8c8d-f973548845f7.png">

`Execute` を押すと、 `curl`、`Request URL`、`ResponseCode`、`ResponseBody`、`ResponseHeaders`が返ってくる！

<img width="1478" alt="スクリーンショット 2019-12-03 1.04.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/888aba85-6001-1bed-c0ba-9aa24987319b.png">

<img width="1448" alt="スクリーンショット 2019-12-03 1.05.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/00f737e0-c4c0-225a-a7e0-387a92bff543.png">

### 雑感
だいぶ便利に見える
次は勝手に生成されるレスポンスステータスを自分が欲しいものだけにしてみよう


# レスポンスステータスの指定

`useDefaultResponseMessages` を設定することで、デフォルトが`200`のみ設定されるよう

## コード(変更ファイル)
```SwaggerDemoConfiguration.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2 // swagger2.0を使用
public class SwaggerDemoConfiguration {

    @Bean
    public Docket petApi() {
        return new Docket(DocumentationType.SWAGGER_2) // Swagger2.0を使用します宣言
                .select()
                .paths(PathSelectors.ant("/apis/**"))
                .build()
                .useDefaultResponseMessages(false) // <- 追加
                .apiInfo(new ApiInfoBuilder()
                        .title("Customise Title Swagger Demo Application")
                        .description("自分好みにカスタマイズしてたよ")
                        .contact(new Contact("customise-name", "http://customise-contact", "customise-email"))
                        .version("1.0")
                        .termsOfServiceUrl("http://customise.com")
                        .license("Customise License").licenseUrl("http://customise-license-url") // licenseのみだとテキスト、licenseUrl設定するとリンクになる
                        .build());
    }
}
```

### 確認

<img width="1480" alt="スクリーンショット 2019-12-03 1.11.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/aa82652c-842b-c208-fa45-c44c0caacb2d.png">

### 雑感
お節介が消えてくれた
次は実際に使いそうなケースのAPIを全部盛りで定義してみよう


# 実用的なエンドポイント定義

Swaggerでみたいだけだから、処理内容は適当of適当

### コード
```LessonIdentifier.java
class LessonIdentifier {

    private Integer value;

    LessonIdentifier(String value) {
        this.value = Integer.valueOf(value);
    }

    LessonIdentifier(int value) {
        this.value = value;
    }

    Integer value() {
        if (value == null) return 0;
        return value;
    }
}
```
```LessonRequest.java
public class LessonRequest {

    private String studentName;
    private String tutorName;

    public LessonRequest(String studentName, String tutorName) {
        this.studentName = studentName;
        this.tutorName = tutorName;
    }

    public String getStudentName() {
        return studentName;
    }

    public String getTutorName() {
        return tutorName;
    }
}
```
```LessonResponse.java
public class LessonResponse {

    private int id;
    private String studentName;
    private String tutorName;

    LessonResponse(int id, String studentName, String tutorName) {
        this.id = id;
        this.studentName = studentName;
        this.tutorName = tutorName;
    }

    public int getId() {
        return id;
    }

    public String getStudentName() {
        return studentName;
    }

    public String getTutorName() {
        return tutorName;
    }
}
```
```LessonIdentifierResponse.java
public class LessonIdentifierResponse {

    private int value;

    LessonIdentifierResponse(LessonIdentifier lessonIdentifier) {
        this.value = lessonIdentifier.value();
    }

    public int getValue() {
        return value;
    }
}
```
```ErrorResponse.java
public class ErrorResponse {

    private String message;

    public ErrorResponse(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}
```
```LessonController.java
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("apis/lessons")
public class LessonController {

    @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class)
    @GetMapping
    @ResponseStatus(HttpStatus.OK)
    public List<LessonResponse> list() {
        // 取得処理
        return Arrays.asList(
                new LessonResponse(1, "studentName1", "tutorName1"),
                new LessonResponse(2, "studentName2", "tutorName2"),
                new LessonResponse(3, "studentName3", "tutorName3"));
    }

    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @GetMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.OK)
    public LessonResponse detail(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier) {
        // 取得処理
        return new LessonResponse(1, "studentName1", "tutorName1");
    }

    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public LessonIdentifierResponse add(@RequestBody LessonRequest lessonRequest) {
        // 追加処理
        return new LessonIdentifierResponse(new LessonIdentifier(4));
    }

    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @DeleteMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier) {
        // 削除処理
    }

    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @PutMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.OK)
    public LessonResponse edit(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier,
                               @RequestBody LessonRequest lessonRequest) {
        // 編集処理
        return new LessonResponse(1, "EditStudentName1", "EditTutorName1");
    }
}
```

### 確認
たくさんあるので、一部だけ
`Example Value` がいい感じに出るようになった
あと `ResponseStatus` が指定したもののみ！

<img width="1453" alt="スクリーンショット 2019-12-03 1.42.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/77166c12-80b1-e80a-08d4-51c706b1d319.png">

<img width="1450" alt="スクリーンショット 2019-12-03 1.43.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/6063518e-3f24-3354-b4d5-2a46f6572b53.png">
<img width="1460" alt="スクリーンショット 2019-12-03 1.44.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/5370c8a0-27bc-1060-cc56-c35475fc2929.png">


### 雑感
いい感じに使えそうなところまできた
APIの説明とか書きたくね？となったので、書いてみる


# エンドポイントの説明を詳細化

`@ApiOperation` を追加してみた

### コード(変更ファイル)
```LessonController.java
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("apis/lessons")
public class LessonController {

    @ApiOperation(value = "レッスンのリストを取得します", notes = "検索条件なしで全件取ることで、あなたのブラウザの時を止めます。")
    @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class)
    @GetMapping
    @ResponseStatus(HttpStatus.OK)
    public List<LessonResponse> list() {
        // 取得処理
        return Arrays.asList(
                new LessonResponse(1, "studentName1", "tutorName1"),
                new LessonResponse(2, "studentName2", "tutorName2"),
                new LessonResponse(3, "studentName3", "tutorName3"));
    }

    @ApiOperation(value = "レッスンを取得します", notes = "ID指定したレッスンを取得します")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @GetMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.OK)
    public LessonResponse detail(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier) {
        // 取得処理
        return new LessonResponse(1, "studentName1", "tutorName1");
    }

    @ApiOperation(value = "レッスンを作成します", notes = "作成した後はIDを返します！")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public LessonIdentifierResponse add(@RequestBody LessonRequest lessonRequest) {
        // 追加処理
        return new LessonIdentifierResponse(new LessonIdentifier(4));
    }

    @ApiOperation(value = "レッスンを削除します", notes = "何も返さないよ")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @DeleteMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier) {
        // 削除処理
    }

    @ApiOperation(value = "レッスンを編集します", notes = "編集後のレッスンを返します!")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @PutMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.OK)
    public LessonResponse edit(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier,
                               @RequestBody LessonRequest lessonRequest) {
        // 編集処理
        return new LessonResponse(1, "EditStudentName1", "EditTutorName1");
    }
}
```

### 確認

詳細が出るようになった！

<img width="1480" alt="スクリーンショット 2019-12-03 1.52.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/23829510-bb97-31df-6aa0-d06cceb55a3f.png">

<img width="1473" alt="スクリーンショット 2019-12-03 1.52.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/2cd6322d-3948-3c3a-4ec9-83b19c868d1b.png">

### 雑感
けど、 `@ApiOperation`とか`@ApiResponse`とかに書いてるメッセージとか長くなってしまうの嫌すぎるな。。。
別ファイルにできないだろうかってことで試してみる


## 別ファイルにメッセージを定義する

### コード
```application.properties
LessonController.list.value=レッスンのリストを取得します
LessonController.list.notes=検索条件なしで全件取ることで、あなたのブラウザの時を止めます

LessonController.detail.value=レッスンを取得します
LessonController.detail.notes=ID指定したレッスンを取得します

LessonController.add.value=レッスンを作成します
LessonController.add.notes=作成した後はIDを返します！

LessonController.delete.value=レッスンを削除します
LessonController.delete.notes=何も返さないよ

LessonController.edit.value=レッスンを編集します
LessonController.edit.notes=編集後のレッスンを返します!
```
```LessonController.java
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("apis/lessons")
public class LessonController {

    @ApiOperation(value = "${LessonController.list.value}", notes = "${LessonController.list.notes}")
    @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class)
    @GetMapping
    @ResponseStatus(HttpStatus.OK)
    public List<LessonResponse> list() {
        // 取得処理
        return Arrays.asList(
                new LessonResponse(1, "studentName1", "tutorName1"),
                new LessonResponse(2, "studentName2", "tutorName2"),
                new LessonResponse(3, "studentName3", "tutorName3"));
    }

    @ApiOperation(value = "${LessonController.detail.value}", notes = "${LessonController.detail.notes}")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @GetMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.OK)
    public LessonResponse detail(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier) {
        // 取得処理
        return new LessonResponse(1, "studentName1", "tutorName1");
    }

    @ApiOperation(value = "${LessonController.add.value}", notes = "${LessonController.add.notes}")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public LessonIdentifierResponse add(@RequestBody LessonRequest lessonRequest) {
        // 追加処理
        return new LessonIdentifierResponse(new LessonIdentifier(4));
    }

    @ApiOperation(value = "${LessonController.delelte.value}", notes = "${LessonController.delete.notes}")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @DeleteMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier) {
        // 削除処理
    }

    @ApiOperation(value = "${LessonController.edit.value}", notes = "${LessonController.edit.notes}")
    @ApiResponses(value = {
            @ApiResponse(code = 400, message = "Bad Request", response = ErrorResponse.class),
            @ApiResponse(code = 404, message = "Not Found", response = ErrorResponse.class)})
    @PutMapping("{lessonIdentifier}")
    @ResponseStatus(HttpStatus.OK)
    public LessonResponse edit(@PathVariable("lessonIdentifier") LessonIdentifier lessonIdentifier,
                               @RequestBody LessonRequest lessonRequest) {
        // 編集処理
        return new LessonResponse(1, "EditStudentName1", "EditTutorName1");
    }
}
```

### 確認

問題なく出てそう！

<img width="1479" alt="スクリーンショット 2019-12-03 2.16.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401549/e51b9d88-25e3-e975-b82a-a67f439e897b.png">


### 雑感
メッセージを1ファイルにまとめたいときはすごく便利だな
ただ、やりすぎないようにしないとよくある複雑なメッセージファイルになりがちだから、気をつけて使おう


# まとめ
とりあえず便利そうだというのはわかった
けど、Build後とか、本番環境にswagger-uiへアクセスして欲しくない場合とか微妙に調べきれてない箇所があるので後々調べてみる


# 参考
https://springfox.github.io/springfox/docs/current/