백앤드 이야기/JAVA&Spring

[Spring] Spring REST Docs 활용하여 API 문서 만들기

한희성 2021. 6. 30.
반응형

안녕하세요 깐지와꾼지파파 입니다 !!! 그동안 글을 너~~~무 쓰지 않아.. 오랜만에 작성하네요..^^

오늘은 api 문서를 어떻게 아름답게 공유하면 좋을까 고민하다가 restdocs 를 발견하게 관련하여 글을 써 보려고 합니다.

 

작성에 앞서 자바 api 문서 자동화 라이브러리로는 주로 두가지를 많이 사용하는데요

Swagger, Spring Rest Docs 두 가지가 대표적입니다 !

 

1. 고민해야 할 이슈

1. RESTful 서비스에 대해 정확하고 읽기 쉬운 문서 생성 및 제공
2. 클라이언트 작업자와 서버 작업자 사이의 API 문서 버전 동기화
3. 테스트를 통하여 API 문서 자동 생성

2. Rest Docs 을 선택한 이유

1. 테스트를 성공해야만 문서가 만들어 진다.

2. 소스 자체에 영향이 없다

3. Spring REST Docs 의 장점

1. 별도 html, pdf 파일로 api 문서 자동생성 (호스팅 가능)
2. req/res 테스트를 통하여 api 문서가 생성되므로 신뢰성 증가
3. api 변경에 따른 api 문서 최신화 보장

 

4. Rest Docs 작성 버전

1. spring boot 2.5.1

2. gradle 7.1

3. java 11

4. jUnit5

5. MockMvc

6. asciidoctor 3.3.2

 

5. 예제 소스

build.gradle

plugins {
    id 'org.springframework.boot' version '2.5.1'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
    id "org.asciidoctor.jvm.convert" version "3.3.2" //1
}

group = 'site.heeseong'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

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

//3
asciidoctor {
    dependsOn test
    attributes 'snippets': snippetsDir
    inputs.dir snippetsDir
}

//4
bootJar {
    dependsOn asciidoctor
    copy {
        from "${asciidoctor.outputDir}"
        into 'src/main/resources/static/docs'
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'mysql:mysql-connector-java'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1'

    //5
    // https://mvnrepository.com/artifact/org.springframework.restdocs/spring-restdocs-mockmvc
    testImplementation group: 'org.springframework.restdocs', name: 'spring-restdocs-mockmvc', version: '2.0.5.RELEASE'
}

test {
    useJUnitPlatform()
}

1 : AsciiDoc 파일을 변환해주고 build 폴더 위치에 복사하기 위한 플러그인, gradle 7.x 부터는 jvm 플러그인을 사용하지 않으면 build시 아래와 같은 오류가 나온다

- 'org.asciidoctor.convert' is deprecated. When you have time please switch over to 'org.asciidoctor.jvm.convert'.

 

2 : ext 블록 gradle의 모든 task 에서 사용할 수 있는 전역 변수 영역, 스니핏의 디렉토리 설정

3 : asciidoctor 설정, build 시 test > asciidoctor  순으로 실행

4 : build 시 asciidoctor > bootJar 순으로 실행

- jvm 플러그인 버전부터 asciidoctor의 스니핏 파일 생성 경로가 html -> build/docs/asciidoc 로 변경 되었다.

- 스니핏 파일들을 모아 /static/docs/ 폴더로 복사 하여 호스팅을 가능하게 만듬

 

5 : MockMvc 를 사용하여 Rest Docs 을 가능하게 해주는 라이브러리

 

test/**/ApiDocumentUtils

package site.heeseong.restdocs;

import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;

import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;

public interface ApiDocumentUtils {

    static OperationRequestPreprocessor getDocumentRequest() {
        return preprocessRequest(
                modifyUris() // 1
                        .scheme("https")
                        .host("docs.api.com")
                        .removePort(),
                prettyPrint()); //2
    }

    // 3
    static OperationResponsePreprocessor getDocumentResponse() {
        return preprocessResponse(prettyPrint());
    }
}

1: 문서상에 표기되는 기본값 변경

2: request 를 예쁘게 출력

3. reponse 를 예쁘게 출력

test/**/UserApiController

package site.heeseong.restdocs;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import site.heeseong.restdocs.service.UserApiService;

import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static site.heeseong.restdocs.ApiDocumentUtils.getDocumentRequest;
import static site.heeseong.restdocs.ApiDocumentUtils.getDocumentResponse;


@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs(uriScheme = "https", uriHost = "docs.api.com")
public class UserApiController {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private UserApiService userApiService;

    @BeforeEach
    public void setUpData(){
        userApiService.saveTestUser();
    }

    @Test
    public void selectUser() throws Exception{

        ResultActions result = this.mockMvc.perform(
                RestDocumentationRequestBuilders.get("/users/{idx}", 1L)
        );

        result.andExpect(status().isOk())
                .andDo(
                        document("select-user"
                                , getDocumentRequest()
                                , getDocumentResponse()
                                , pathParameters(
                                        parameterWithName("idx").description("고유 번호")
                                )
                                , responseFields(
                                        fieldWithPath("idx").type(JsonFieldType.NUMBER).description("고유 번호")
                                        , fieldWithPath("userId").type(JsonFieldType.STRING).description("아이디")
                                        , fieldWithPath("email").type(JsonFieldType.STRING).description("이메일")
                                        , fieldWithPath("phoneNumber").type(JsonFieldType.STRING).description("휴대폰 번호")
                                )
                        ))
                .andDo(print());
    }

    @Test
    public void saveUser() throws Exception{
        mockMvc.perform(
                    post("/users")
                )
                //.andDo(print())
                .andExpect(status().isOk());
    }

    @Test
    public void updateUser() throws Exception{
//        //given
//        User user = User.builder()
//                        .userId("테스트수정")
//                        .email("테스트 email 수정")
//                        .phoneNumber("010수정")
//                        .build();
//
//
//        mockMvc.perform(
//                    put("/users", user)
//                )
//                .andExpect(status().isOk());
    }

    @Test
    public void deleteUser() throws Exception{
//        mockMvc.perform(
//                    delete("/users")
//                )
//                .andExpect(status().isOk());
    }
}

 

테스트 수행시 build/generated-snippets/ 하위에 지정한 문자열의 폴더와 문서가 생성

 

위 생성된 파일들을 하나로 묶어 html 로 생성하기 위해서 asciidoc 셋팅을 해주어야하는데요

gradle 경우 src/docs/asciidoc 경로에 만들어 주어야 기능이 정상적으로 동작합니다.

 

https://github.com/hhsung0120/restdocs

 

hhsung0120/restdocs

Contribute to hhsung0120/restdocs development by creating an account on GitHub.

github.com

필수 값 사용 여부는 다음 시간에 작성 하도록 하겠습니다 ~~~

 

 

참고 1 : https://asciidoctor.github.io/asciidoctor-gradle-plugin/master/user-guide/

 

Asciidoctor Gradle Plugin Suite

This collection of plugins requires at least Gradle 4.9, JDK 8.0 and AsciidoctorJ 2.0.0 to run. If you need prior Gradle, JDK or AsciidoctorJ support please use a plugin from the 1.5.x or 1.6.x release series.

asciidoctor.github.io

참고 2 : https://techblog.woowahan.com/2597/

 

Spring Rest Docs 적용 | 우아한형제들 기술블로그

{{item.name}} 안녕하세요? 우아한형제들에서 정산시스템을 개발하고 있는 이호진입니다. 지금부터 정산시스템 API 문서를 wiki 에서 Spring Rest Docs 로 전환한 이야기를 해보려고 합니다. 1. 전환하는

techblog.woowahan.com

참고 3 : https://narusas.github.io/2018/03/21/Asciidoc-basic.html

 

Asciidoc 기본 사용법

Asciidoc의 기본 문법을 설명한다

narusas.github.io

 

반응형

'백앤드 이야기 > JAVA&Spring' 카테고리의 다른 글

[JAVA&기타] 네이밍 규칙  (0) 2022.03.11
[JAVA] Optional 기능 메모  (0) 2021.07.13
[JPA] 메모  (0) 2021.06.14
[JAVA] stream API 사용하기  (0) 2021.06.09
[Gradle] gradle 명령어  (0) 2021.04.21

댓글

💲 추천 글