Spring

Spring REST Docs📝를 사용해보자

집관리사 2020. 11. 15. 14:26

github.com/beemiel/Spring-REST-Docs

 

beemiel/Spring-REST-Docs

Spring-REST-Docs을 사용해보자. Contribute to beemiel/Spring-REST-Docs development by creating an account on GitHub.

github.com

소스코드는 여기☝

 

개발을 하다보니 느낀건데.. 개발은 개발+문서화이다. 체계적인 문서화가 개발과 협업에 미치는 영향을 몸소 느껴보다 보니 그렇다. 그래서 그런지 API 문서화를 돕는 툴도 여러가지가 있다. 내가 찾아본 건 두 가지다.

 

 

Spring REST Docs VS Swagger

 

Spring REST Docs Swagger 
컨트롤러의 테스트 코드를 통과해야 문서화 가능
(관점에 따라 장점이기도, 단점이기도 함)
컨트롤러의 메서드만으로 가능
문서화에 특화 문서화 후 테스트가 가능하므로 테스트가 용이
REST Docs에서 제공하는 메서드로 필요한 설정 추가 컨트롤러에 어노테이션으로 문서에 필요한 설정 추가

 

내가 생각한 장점은 

- 테스트 코드를 강제한다.

- 문서화에 특화되어 있다.

이다.

 

테스트 코드를 강제하면서 시간은 좀 더 걸리겠지만 테스트 코드를 작성하면서 얻는 이점을 얻을 수 있으며, 문서화에 특화되어 있으므로 더 세세하게 문서를 작성할 수 있다.

사실 문서의 레이아웃이 REST Docs가 더 마음에 든다는 이유도 있었다🤭🤭

 

1. 프로젝트 세팅

다른 항목들은 일반적인 프로젝트 세팅과 동일하고 Spring Web과 Spring REST Docs만 필수로 넣어주면 된다.

 

2. build.gradle 설정

plugins {
	id 'org.springframework.boot' version '2.3.0.RELEASE'
	id 'io.spring.dependency-management' version '1.0.9.RELEASE'
	id 'org.asciidoctor.convert' version '1.5.8' //asciiDoc -> html로 변환하는 플러그인
	id 'java'
}

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

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

ext {
	set('snippetsDir', file("build/generated-snippets")) //문서 조각인 스니펫이 생성되는 경로
}

bootJar {
	dependsOn asciidoctor
	copy { //build/asciidoc/html5에 있는 컨버팅 된 html을 jar로 복붙
		from "build/asciidoc/html5"
		into 'src/main/resources/static/docs/'
	}
}

혹은 

bootJar {
	dependsOn asciidoctor
	from ("${asciidoctor.outputDir}/html5") {
		into 'static/docs'
	}
}
이렇게도 됨

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'mysql:mysql-connector-java'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
	testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' //mockmvc 를 restdocs 에 사용하게 돕는 라이브러리
	asciidoctor 'org.springframework.restdocs:spring-restdocs-asciidoctor:2.0.1.RELEASE'
}

test {
	outputs.dir snippetsDir
	useJUnitPlatform()
}

asciidoctor {
	inputs.dir snippetsDir
	dependsOn test
}

문서들의 기본 경로가 build/~ 인 이유는 버전 관리가 필요하지 않기 때문이라고 한다. 

html 문서가 이동하는 시점도 jar 파일이 생성되는 시점.

 

📌경로에 대한 주의사항

Maven과 Gradle 경로가 다름! 주의할 것

경로를 다르게 지정하면 docs와 html이 생기지 않는다.

 

3. 프로덕션 코드 작성

import lombok.*;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Member {

    private Long id;
    private String name;
    private int age;

}
import com.beemiel.springrestdocsexample.entity.Member;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/test")
public class MemberController {

    @GetMapping("/list")
    public List<Member> listTest() {
        ~~
    }

    @GetMapping("/list/{id}")
    public Member test(@PathVariable Long id) {
        ~~
    }

    @PostMapping("/post")
    public Member postTest(@RequestBody Member member) {
        ~~
    }

}

Get과 Post를 문서화하겠다.

 

4. 테스트 코드 작성

테스트 코드는 기존의 테스트 코드에 문서 부분만 추가하는 것이다.

테스트 클래스 위에 어노테이션을 추가해줘야 한다.

MockMvc도 추가하고 나면 테스트 코드를 작성하자.

Service가 없는 경우

MemberController MockBean은 특정 메서드가 호출되었을 때 가짜 값을 넣어주기 위한 것

만약 Service에서 받아오도록 설계했다면 Controller가 아니라 Service를 MockBean으로 만들어서 가짜 값을 반환하도록 해야 함

Service가 있는 경우

 

when부분은 컨트롤러가 이미 제대로 작성되어 있으면 필요하지 않다.

when().thenReturn()을 사용하여 내가 원하는 가짜 값을 넣어줄 수 있다.

 

그리고 중요한 것은 mockMvc 부분이다.

.perform은 요청을 보낼 url을 지정하고

.andExpect는 반환될 예상 값을 넣어준다. 물론 상태 코드뿐만 아니라 response의 상세 내용까지도 체크할 수 있다.

.andExpect(jsonPath("name").value("게스트하우스"));

이렇게 jsonPath를 이용할 수도 있다.

.andDo의 document가 REST Docs를 만드는 부분이라고 할 수 있는데, REST Docs의 snippets 를 생성하기 위한 작업을 한다.

"{class-name}/{method-name}" 는 스니펫의 경로를 지정해줄 수 있다. 그냥 디렉토리 명 하나만 작성해도 되긴 한다.

아래와 같이 경로가 지정된다.

preprocessResponse/preprocessRequest는 선행처리자로 해당 응답 혹은 요청을 어떻게 처리할 것인지를 설정할 수 있다. 해당 메서드를 통해서 내가 원하는 형태로 만들 수 있다.

prettyPrint()는 말 그대로 예쁘게 출력해 달라는 것이다.

                        preprocessRequest(modifyUris()
                                .scheme(CommonString.SCHEMA)
                                .host(CommonString.HOST), prettyPrint()),

위처럼 요청시 스키마와 호스트를 변경할 수도 있다.

 

필드마다 더 상세한 내용을 작성하고 싶다면 아래의 메서드들을 이용하자.

pathParameters : {id} -> 이걸 사용하려면 그냥 get이 아니라 RestDocumentationRequestBuilders를 사용해야함

responseFields : response field 값들을 설명

requestFields : request Body 값 설명

requestParameters : requestPram 설명

fieldWithPath : 각 필드들의 이름

description : 필드 설명

type : type을 사용하여 원하는 타입을 지정할 수 있다.

 

위의 메서드들을 이용하면 표 형태의 스니펫이 생긴다.

생선된 스니펫

 

배열의 경우에는?

배열임을 나타내는 문자를 추가해줘야 한다.

[] : 배열임을 나타낸다.

errors 는 배열 자체를 나타내는 것이다.

 

5. 테스트 실행

테스트를 실행하고 완료되면 build 디렉토리에 snippets가 생긴다.

디렉토리 구조

 

6.  Snippets을 이용하여 .adoc 작성

이제 스니펫들을 이용해서 문서를 작성하는 작업만 남았다. 좀 손이 많이 가는 것 같긴하다^^...

 

📌해당 프로젝트는 gradle이므로 꼭 src/docs/asciidoc 에다가 만들어야함

 

이제 asciidoc의 문법을 또 익혀야 하나..

asciidoctor.org/docs/asciidoc-writers-guide/

 

AsciiDoc Writer’s Guide | Asciidoctor

AsciiDoc provides a nice set of components for including non-paragraph text—​such as block quotes, source code listings, sidebars and tables—​in your document. These components are referred to as delimited blocks because they are surrounded by deli

asciidoctor.org

싶지만 일단 필요한 것만 써보자.

 

내가 주로 사용하는 형식은 다음과 같다.

= new API Docs //큰 제목
Lynn<qnswwrn@gmail.com> //작성자
1.0.0, 09/06/2020 //버전, 날짜
:toc: left //왼쪽 테이블
:sectnums:

//이렇게 하면 표를 만들 수 있다.
|===
| Method | Content

| `GET`
| 읽기

| `POST`
| 추가

| `PATCH`
| 수정

| `DELETE`
| 삭제
|===

== GET member list 모든 회원 가져오기 //api제목 

=== HTTP Request
include::{snippets}/member-controller-test/test-list/http-request.adoc[]
//여기서 스니펫을 이용해서 어떻게 작성하면 되는지 보여줌
// {snippets} 는 스니펫이 있는 경로로 build/generated-snippets 를 의미

📌snippets의 경로를 찾지 못하는 경우📌
🤣{snippets}대신 ../../../build/generated-snippets 경로를 직접 써줌🤣

=== HTTP Response
include::{snippets}/member-controller-test/test-member/http-response.adoc[]

=== Path-parameters
include::{snippets}/member-controller-test/test-member/path-parameters.adoc[]

=== Response Fields
include::{snippets}/member-controller-test/test-member/response-fields.adoc[]

마크다운처럼
- 이 표시와
```
블럭으로 감쌀 수도 있다.
```

 

7. 빌드 후 확인

사실 테스트 작성과 문서 작성이 제일 귀찮다. 그게 다지만^^..

6번까지 끝나면 이제 jar build 후 확인만 해보면 된다.

build나 bootJar를 해서 asciidoctor가 .adoc을 이용해서 html을 만들도록 하자.

빌드!

 

그러면 이렇게 복사된 걸 확인할 수 있다.

 

그리고 참고로

📌static/docs/에 html은 jar파일 안에 복사가 되는 것이다.

나는 이 부분을 착각해서 삽질을 좀 했다. 바로 눈에 보이는 것이 아니라 jar에 이미 복사됨!

 

서버를 구동한 뒤 static/docs/문서.html 경로로 들어가보면 문서를 확인할 수 있다.

예) http://localhost:8080/docs/new-api-docs.html

 

 

참고

www.youtube.com/watch?v=-phOCB65an8

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

jaehun2841.github.io/2019/08/04/2019-08-04-spring-rest-docs/#%EA%B2%B0%EA%B3%BC