인터넷에 많이 공개된 Spring Rest Docs를 공부하고 적용해봤다.



* Spring Rest Docs가 Swagger2에 비해 갖는 장점

- 내가 원하는 형태로 문서화할 수 있다. (대신 단점도 됨)

- 테스트를 무조건 만들어야 하고 문서화를 진행해야 한다. (안전)

- Spring Controller의 지저분한 annotation이 깔끔해짐 (코드의 간결성)

- Swagger2는 2018년 중반 이후부터 더 이상 개발되지 않고 있다. 



* Spring Rest Docs가 Swagger2에 비해 갖는 단점

- 처음에 손이 많이 간다. 

  asciidoc로 문서화를 수행해야 한다. 처음에는 디폴트가 먼저도 모름. 헤맴. 그리고 만들어진 html 문서를 resource static 문서로 복사해야 한다. gradle, maven 자동화가 필요하다.

- UI를 못하거나 문서화를 자동으로 해주는 swagger2가 적당한 선이 아직까지는 내게 어울림

- swagger2의 try it out 기능이 없는 것 같다(또는 어디에 있는지 못 찾겠다.) 

   swagger2의 try it out은 api 연동의 핵심이라.. 의사소통을 많이 줄여준다. 


-> (개인적으로) 아직은 Swagger2가 훨씬 편한 듯 하다. 그러나 Spring Rest Docs의 철학이 아무 매력적이다. '테스트 없는 문서화가 의미가 있나?' 테스트를 강제하는 형태가 좋아보였다. 



Spring Rest Docs를 사용하면 아래와 비슷하게 나온다. 귀찮아서 정리를 못했지만 손이 조금 간다. 꼼꼼한 사람들에게는 좋을 것 같다. 







나머지는 코드이다. 


https://github.com/knight76/springboot2-restdocs


build.gradle


buildscript {
repositories {
mavenCentral()
jcenter()
}

dependencies {
classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.9'
}
}


plugins {
id 'org.springframework.boot' version '2.1.6.RELEASE'
id "org.asciidoctor.convert" version "1.5.9"
}

apply plugin: 'org.asciidoctor.convert'
apply plugin : 'java'
apply plugin : 'io.spring.dependency-management'
apply plugin : 'org.asciidoctor.convert'

ext {
lombokVersion = "1.18.8"
swagger2Version = '2.9.2'
oldSwagger2Version = '1.5.21'
}


group = 'com.google.knight76'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'


ext {
snippetsDir = file('build/generated-snippets')
}

test {
outputs.dir snippetsDir
}

asciidoctor {
inputs.dir snippetsDir
dependsOn test
}

bootJar {
dependsOn asciidoctor
from ("${asciidoctor.outputDir}/html5") {
into 'static'
}
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation('org.springframework.boot:spring-boot-starter-web') {
exclude module: "spring-boot-starter-tomcat"
}
implementation "org.projectlombok:lombok:${lombokVersion}"
implementation 'org.springframework.boot:spring-boot-starter-undertow'

asciidoctor 'org.springframework.restdocs:spring-restdocs-asciidoctor'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation('org.springframework.restdocs:spring-restdocs-mockmvc')
}


src/doc/asciidoc/api-docs.adoc

:sectnums:
:sectnumlevels: 5
:toc: left
:toclevels: 3
:page-layout: docs


= Hello World

== Hello World (Plain)

include::{snippets}/hello-world-test/hello-world/http-request.adoc[]
include::{snippets}/hello-world-test/hello-world/curl-request.adoc[]
include::{snippets}/hello-world-test/hello-world/http-response.adoc[]
include::{snippets}/hello-world-test/hello-world/request-body.adoc[]
include::{snippets}/hello-world-test/hello-world/response-body.adoc[]
include::{snippets}/hello-world-test/hello-world/httpie-request.adoc[]


== Hello World (Json)

include::{snippets}/hello-world-test/hello-world-json/http-request.adoc[]
include::{snippets}/hello-world-test/hello-world-json/curl-request.adoc[]
include::{snippets}/hello-world-test/hello-world-json/http-response.adoc[]
include::{snippets}/hello-world-test/hello-world-json/request-body.adoc[]
include::{snippets}/hello-world-test/hello-world-json/response-body.adoc[]
include::{snippets}/hello-world-test/hello-world-json/response-fields.adoc[]
include::{snippets}/hello-world-test/hello-world-json/httpie-request.adoc[]
include::{snippets}/hello-world-test/hello-world-json/request-parameters.adoc[]

== City (add-city)

include::{snippets}/cities-controller-test/add-city/http-request.adoc[]
include::{snippets}/cities-controller-test/add-city/curl-request.adoc[]
include::{snippets}/cities-controller-test/add-city/http-response.adoc[]
include::{snippets}/cities-controller-test/add-city/request-body.adoc[]
include::{snippets}/cities-controller-test/add-city/response-body.adoc[]
include::{snippets}/cities-controller-test/add-city/response-fields.adoc[]


== City (delete-city)

include::{snippets}/cities-controller-test/delete-city/http-request.adoc[]
include::{snippets}/cities-controller-test/delete-city/curl-request.adoc[]
include::{snippets}/cities-controller-test/delete-city/http-response.adoc[]
include::{snippets}/cities-controller-test/delete-city/request-body.adoc[]
include::{snippets}/cities-controller-test/delete-city/response-body.adoc[]
include::{snippets}/cities-controller-test/delete-city/response-fields.adoc[]

== City (get-cities)

include::{snippets}/cities-controller-test/get-cities/http-request.adoc[]
include::{snippets}/cities-controller-test/get-cities/curl-request.adoc[]
include::{snippets}/cities-controller-test/get-cities/http-response.adoc[]
include::{snippets}/cities-controller-test/get-cities/request-body.adoc[]
include::{snippets}/cities-controller-test/get-cities/response-body.adoc[]
include::{snippets}/cities-controller-test/get-cities/response-fields.adoc[]


== City (get-city)

include::{snippets}/cities-controller-test/get-city/http-request.adoc[]
include::{snippets}/cities-controller-test/get-city/curl-request.adoc[]
include::{snippets}/cities-controller-test/get-city/http-response.adoc[]
include::{snippets}/cities-controller-test/get-city/request-body.adoc[]
include::{snippets}/cities-controller-test/get-city/response-body.adoc[]
include::{snippets}/cities-controller-test/get-city/path-parameters.adoc[]
include::{snippets}/cities-controller-test/get-city/response-fields.adoc[]


예시

@Test
public void getCity() throws Exception {
// given
String response = "{'id': 1,'name':'Bratislava','population':432000}";

// when
when(cityService.get(1L)).thenReturn( Optional.of(new City(1L, "Bratislava", 432000)));

ResultActions result =
mockMvc.perform(get("/api/1/city/{id}", 1L)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andDo(print());

// then
result.andExpect(status().isOk())
.andDo(document.document(
pathParameters(
parameterWithName("id").description("도시의 id")),
responseFields(
fieldWithPath("id").description("도시의 id"),
fieldWithPath("name").description("도시 이름"),
fieldWithPath("population").description("도시 인구")
)))
.andExpect(jsonPath("id", is(notNullValue())))
.andExpect(content().json(response));
}






참고 

http://woowabros.github.io/experience/2018/12/28/spring-rest-docs.html

https://docs.spring.io/spring-restdocs/docs/current/reference/html5/


Posted by '김용환'
,