SpringBoot 2.0とapiDocを連携させてみた
SpringFrameworkでAPIドキュメントを準備する手段としてSpringFoxやSpring REST Docsなどが候補にあがる。
両者とも導入敷居が低くSpringFoxはコントローラクラスやレスポンスモデルにアノテーションをつけていきSpringを起動すればドキュメントエンドポイントが生まれる。APIの開発序盤からDocsを整理していけるスピード感がある。対してSpring REST DocsはテストコードにDocs生成手段を仕込みHttpStatus:200 OK
やHttpStatus:404 NotFound
などレスポンス毎にDocsを整理できるので網羅的なドキュメントを整理することができる。
Spring Boot 2.0でも同様にSpringFoxやSpring REST Docsを使いAPIドキュメントを整理したかったが現時点では未対応な状況である。(2017/09/29)
何か別の手段でと思いを馳せて居たところで同僚にapiDocを教えてもらった。
apiDoc - Inline Documentation for RESTful web APIs
apiDocはJavadoc-Styleやコメントコードを駆使してAPIドキュメントをまとめapidoc
のコマンドを叩けばドキュメント一式のHTMLが生成される。詳細は公式を確認いただきたい。
コメントコードが生成できればドキュメントHTMLが生成される仕組みなのでSpring REST DocsのようにテストコードにapiDoc-Params(コメントコード)を生成する仕組みを取り入れてみた。
ここからは開発したテストコードを元にドキュメントHTMLが生成されるまでをまとめていきたい。
apiDocのコメントコードを吐くテストコード
/** * @api {GET} /api/task/:taskId タスクの取得 * @apiName GetTask * @apiGroup Task * @apiVersion 1.0.0 * * @apiUse GetTaskOk * @apiUse GetTaskNotFound * */ @Test fun `GET Task`() { val define = DefineBuilder({ version { "1.0.0" } name { "GetTaskOk" } }) // @apiParamのコメントコードを吐く define.param { ApiParam("taskId", "タスクID", "23445", false, "7") } // mock `when`(taskHandler.fetchByTaskId(any())).thenReturn(ok().json().body(Mono.just(mockModel))) client.get().uri("/api/task/1") .accept(MediaType.APPLICATION_JSON_UTF8) .exchange().expectStatus().isOk .expectBody() .consumeWith { val actual: TaskModel = mapper.readValue(it.responseBody) actual.id shouldBe 1L actual.title shouldBe "task title" actual.createdAt shouldBe "2017-06-13T16:22:52Z" actual.updatedAt shouldBe "2017-06-13T16:22:52Z" // @apiSuccessのコメントコードを吐く define.success { actual } // @apiSuccessExampleのコメントコードを吐く define.successExample { ApiSuccessExample("Success", HttpStatus.OK, actual) } } define.genDoc() }
タスクを1件取得するエンドポイントのテストコードに仕込んだ。最初のコメントコードでapiDocの仕様に基づきAPIの概要をまとめている。@apiUse
については後述する。
テストコードのブロック内ではDefineBuilder
のインスタンスからDocをビルドアップしている。このDefineBuilder
をお手製でつくってみた。
define.success { actual }
このsuccessに渡るインスタンスクラスのプロパティに次のような@ApiDocProperty
がついていれば@apiSuccess {String} firstname Firstname of the User.
のようなコメントコードが吐かれるようにした。
data class TaskModel( @ApiDocProperty(value = "タスクID", example = "23445") val id: Long, @ApiDocProperty(value = "タイトル", example = "牛乳を買う") val title: String, @ApiDocProperty(value = "完了日時", example = "yyyy-MM-dd'T'HH:mm:ss'Z'") val finishedAt: String?, @ApiDocProperty(value = "作成日時", example = "yyyy-MM-dd'T'HH:mm:ss'Z'", nullable = true) val createdAt: String, @ApiDocProperty(value = "更新日時", example = "yyyy-MM-dd'T'HH:mm:ss'Z'") val updatedAt: String)
上記のインスタンスは次のようなコメントコードを吐く。JSONレスポンスの各項目にも名称やサンプル値などが整理できる。
@apiSuccess {String} createdAt 作成日時, Nullable:TRUE, Example:yyyy-MM-dd'T'HH:mm:ss'Z' @apiSuccess {String} finishedAt 完了日時, Nullable:FALSE, Example:yyyy-MM-dd'T'HH:mm:ss'Z' @apiSuccess {long} id タスクID, Nullable:FALSE, Example:23445 @apiSuccess {String} title タイトル, Nullable:FALSE, Example:牛乳を買う @apiSuccess {String} updatedAt 更新日時, Nullable:FALSE, Example:yyyy-MM-dd'T'HH:mm:ss'Z'
そして最後に@apiUse
である。冒頭のコメントコードは次のようになっていた。
@apiUse GetTaskOk @apiUse GetTaskNotFound
apiDocの特徴的な機能で@apiUse
に宣言された変数は@apiDefine
に宣言された変数と紐づくことになる。GetTaskOk
は正常系のテストコードに紐づき、GetTaskNotFound
は404エラーになるテストコードに紐付いている。404エラーのテストコードは次のようになっている。
fun `GET Task NotFound`() { val define = DefineBuilder({ version { "1.0.0" } name { "GetTaskNotFound" } // `@apiDefine GetTaskNotFound`を吐く }) // mock `when`(taskHandler.fetchByTaskId(any())).thenThrow(WebAppException.NotFoundException("task notfound.")) client.get().uri("/api/task/1") .accept(MediaType.APPLICATION_JSON_UTF8) .exchange().expectStatus().isNotFound .expectBody() .consumeWith { val actual: ErrorItem = mapper.readValue(it.responseBody) define.error { actual } define.errorExample { ApiErrorExample("BadRequest", HttpStatus.BAD_REQUEST, actual) } } define.genDoc() }
@apiUse
と@apiDefine
の仕組みをつかえば複数のAPIレスポンスをドキュメント化することができる。
エンドポイントのあらゆる異常系を書き@apiUse
と@apiDefine
で紐付けることでドキュメントが整備されていく仕掛けとなる。Spring REST Docsのエッセンス
を上手く取り入れられた。
まとめ
apiDocが生成したドキュメントの画面キャプチャは次のようになった。
正常系
エラー
SpringFoxやSpring REST DocのSpring Boot 2.0.0(Spring5)のサポートを待つ間の暫定対応としてapiDocを連携させてみたが、思いの外ドキュメントにする手間もなくドキュメント化が実現できた。
サポートが完了したSpringFoxやSpring REST Docの新しい機能に期待をしながら、今回つくったDefineBuilderに対応していないapiDoc-Paramを追加していきライブラリとして成熟させていきたい。
コード
今回紹介したコードはgithubに置いていますので良ければ参照してください。
テストコードはこちらです。