안녕하세요! 이번 포스팅에서는 Spring Rest Docs를 적용하고 사용하는 방법에 대해 알아보겠습니다.
전체 코드는 Github에서 확인이 가능합니다. ✍️
Spring Rest Docs는 API들을 자동으로 문서화할 수 있게 도와주는 도구입니다. 비슷한 서비스로는 Swagger가 있습니다. 저도 초반에는 Swagger를 사용했는데, 사용하면서 아래와 같은 불편함을 느꼈습니다.
1. 서비스 코드에 Swagger 관련 어노테이션을 추가해줘야한다.
@Api
@Controller
public class AccountController {
@ApiOperation("회원정보 가져오기")
public ResponseEntity<RestResponse> changeInfo() throws BizCheckedException {
// ...
}
@ApiIgnore
public String list() {
// ...
}
}
2. 테스트는 용이하나 Swagger 문서만으로 연동 시스템을 만들기 어렵다.
위와 같은 이유들로 API 문서화를 바꿔야하나 생각하던 중 Spring Rest Docs를 알게 되었고, 적용하면서 삽질한 내용을 공유하려고 합니다. 👀
📚 개념 정리 & 사전준비
1. Swagger vs Spring Rest Docs
2. 사전 준비
Spring Rest Docs는 빌드 도구, 테스트, 방식에 따라 사용법이 조금씩 다릅니다. 제가 Spring Rest Docs를 적용한 환경은 아래와 같습니다.
- 빌드 도구
- gradle
- 테스트
- JUnit4
- MockMvc
- Spring Rest Docs 방식
- asciidoc
- 의존성 추가
- spring-boot-starter-web
- spring-boot-starter-test
💻 구현
1. build.gradle 설정
Spring Rest Docs를 사용하기 위해서는 build.gradle을 설정해주어야합니다. (maven인 경우)
build.gradle
plugins {
id 'org.asciidoctor.convert' version "1.5.3" // (1)
}
ext {
snippetsDir = file('build/generated-snippets') // (2)
}
asciidoctor { // (3)
inputs.dir snippetsDir
dependsOn test
}
dependencies {
asciidoctor 'org.springframework.restdocs:spring-restdocs-asciidoctor:1.2.6.RELEASE' // (4)
testCompile group: 'org.springframework.restdocs', name: 'spring-restdocs-mockmvc', version: '2.0.2.RELEASE' // (5)
}
// (6)
//bootJar {
// dependsOn asciidoctor
// from ("${asciidoctor.outputDir}/html5") {
// into "static/docs"
// }
//}
task copyDocument(type: Copy) {
dependsOn asciidoctor
from file("build/asciidoc/html5/")
into file("src/main/resources/static/docs")
}
build {
dependsOn copyDocument
}
- Asciidoctor 플러그인을 적용하기 위해 추가해준다.
- 생성된 스니펫의 출력 위치를 정의한다. (build/generated-snippets)
- 2번에서 정의한 출력위치로 스니펫을 넣어주고, 문서가 작성되기 전에 테스트가 실행되도록 한다.
- .adoc 파일에서 빌드, 스니펫 생성을 자동으로 구성되기 위해 추가하는 의존성이다.
- restdocs에서 MockMvc를 사용할 때 사용하는 의존성이다.
- 자동으로 생성된 html 파일을 static 폴더로 복사해주는 구성이다.
- 라고 공식문서에 나와있는데 저는 이 구성을 추가해주어도 static에 html에 복사가 안되는 현상이 발생하여 아래 구성으로 대체했습니다.
- 빌드에서 copyDocumet라는 파일을 복사해주는 task를 실행시켜주는 구성입니다.
2. 컨트롤러 생성
HelloController.java
@RestController
public class HelloController {
@GetMapping("/hello/{name}")
public ResponseEntity<Person> hello(@PathVariable String name) {
return ResponseEntity.ok()
.body(new Person(name, "안녕하세요!"));
}
}
Person.java
public class Person {
private String name;
private String message;
public Person(String name, String message) {
this.name = name;
this.message = message;
}
public String getName() {
return name;
}
public String getMessage() {
return message;
}
}
/hello/{name}으로 접근을 하면 아래와 같은 응답을 주는 컨트롤러를 간단하게 구성했습니다.
{
"name": {name},
"message": "안녕하세요!"
}
3. 테스트 생성
HelloControllerTest.java
@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloControllerTest {
@Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); // (1)
private MockMvc mockMvc;
@Autowired
WebApplicationContext context;
@Before
public void setUp() { // (2)
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.build();
}
@Test
public void Hello_테스트() throws Exception {
// given
String name = "칩앤데일";
// when
mockMvc.perform(get("/hello/" + name)
.characterEncoding("utf-8")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) // then
.andExpect(jsonPath("$.name").value(name))
.andDo(document("hello")); // (3)
}
}
- 프로젝트의 빌드 도구(build.gradle)를 기반으로 디렉토리로 자동 구성을 하는 역할을 한다.
- Maven : target/generated-snippets
- Gradle : build/generated-snippets
documentationConfiguration()
에서 인스턴스를 얻어 MockMovc를 구성한다.- 구성된 디렉토리 아래에 hello라는 디렉토리를 만들어 스니펫을 만든다. (스니펫은 RestDocumentationResultHandler에 의해 작성된다.)
테스트까지 작성을 완료하고 나면, 테스트를 실행해줍니다.
테스트에 성공하면 build - generated-snippets-hello에 총 6개의 기본 스니펫이 생긴 것을 확인할 수 있습니다. 이제 이 스니펫들을 가지고 문서화를 해보도록 하겠습니다.
4. .adoc 파일 만들기
src/docs/asciidoc/index.adoc 파일을 만들어줍니다. adoc의 파일 이름은 index가 아닌 다른 것이어도 상관없습니다.
intelliJ라면 AsciiDoc 플러그인을 다운로드 받으면 .adoc 파일에서 편하게 작업이 가능합니다!
index.adoc
== 인사하기
=== Request
CURL:
include::{snippets}/hello/curl-request.adoc[]
Request Parameters:
include::{snippets}/hello/http-request.adoc[]
Request HTTP Example:
include::{snippets}/hello/http-request.adoc[]
=== Response
Response:
include::{snippets}/hello/http-response.adoc[]
Asciidoc 기본 사용법은 잘 정리된 글이 있으니 참고하시면 좋을 것 같습니다.
스니펫을 가져오고 싶을 때는 include::{snippets}/경로/curl-request.adoc[]
로 가져올 수 있습니다.
이제 열심히 작성한 index.adoc을 우리가 직접 볼 수 있게 html로 뽑아내보겠습니다.
./gradlew build
를 통해 gradle을 빌드를 해줍니다.
빌드가 성공적으로 종료되면 build/asciidoc/html5와 src/main/resource/static/docs에 index.html이 생긴 것을 확인할 수 있습니다. (만약 src/main/resource/static/docs에 index.html이 생기지 않는다면 build.gradle 설정에서 build를 bootJar (boot 버전이 1.4 이하라면 jar)로 변경해보시길 바랍니다.)
static에 있는 파일은 매핑 컨트롤러가 없어도 경로를 쓰면 접근이 가능하기 때문에, 어플리케이션을 실행하고 localhost:8080/docs/index.html에 접속하면
잘 나오는 것을 확인할 수 있습니다!
✨ 정리
기본 스니펫 6개 이외에도 추가적인 스니펫을 설정할 수 있는데, 그 부분은 다음 포스팅에서 알아보도록 하겠습니다! 저는 개인적으로 Swagger보다 Spring Rest Docs를 사용하는 방법이 더 깔끔하고 마음에 들어서 적용 과정을 포스팅으로 정리해보았습니다.
혹시 글을 읽으면서 잘못된 내용이 있으면 댓글로 알려주시면 감사하겠습니다! 읽어주셔서 감사합니다! 😊
👏 참고
'Backend > SpringBoot' 카테고리의 다른 글
Spring Boot에서 이벤트 사용하기 (6) | 2020.08.27 |
---|---|
Spring Boot에서 CORS 적용해보기 (7) | 2020.08.05 |
Spring Boot Custom Annotation 만들기 (4) | 2020.07.08 |
Spring Security Error Message 커스텀하기 (9) | 2020.06.02 |
Spring Security로 로그인/회원가입 프로젝트 (66) | 2020.05.26 |