카테고리 없음

Spring REST Docs로 APIDocs 자동화 하기

혹등고래1호기 2023. 3. 30. 17:01

백엔드와 프론트엔드의 원활한 협업을 위해서는 REST API명세에 대한 문서화가 잘 되어 있어야 할 것이다. 구글 독스, 노션 등을 사용해서 직접 사용할 수 있지만 API문서 작성을 도와주는 자동화 도구들이 많이 개발되어 있으니 사용해 보도록 하자.

 

먼저 APIDocs가 무엇인지 부터 ARABOZA

 

APIDocs란? 

API Docs(documents)는 API 사용 방법을 사용자에게 알려주는 문서이다. 본인 같은 경우 프로젝트를 할 떄 카카오 로그인이나, 카카오 페이 API를 사용을 했는데 카카오의 공식문서를 참고하여 구현을 했는데 그 공식문서도 일종의 APIDocs로도 구분을 할 수 있는 것 같다. 

 

이렇듯 정보들을 제공해주는 문서가 잘 설명되어 있어야 사용자들이 API를 잘 사용할 수 있다. 

  • 요청 URL
  • 해당 API에 대한 설명
  • 헤더 정보
  • 요청 파라미터
  • 응답 데이터
  • 요청 파라미터와 응답 데이터에 대한 설명
  • API 호출/응답 샘플

 

APIDocs의 종류

Spring Boot에서 APIDocs의 종류에는 크게 보면 Swagger와 Spring Rest Docs 2가지 종류를 많은 사람들이 사용을 하고 있다. 

 

Swagger VS Spring REST Docs

Swagger

Swagger를 Spring 프로젝트에서 사용하기 위해서는 컨트롤러에 어노테이션을 통해서 API를 기술한다. 

@Api(value = "방명록", tags = "방명록 관련 API")
@RestController
public class ArticleController {
    private final ArticleService articleService;

    public ArticleController(ArticleService articleService) {
        this.articleService = articleService;
    }

    @Operation(summary = "특정 유저에 방명록 생성", description = "특정 유저에 대한 방명록을 생성합니다.")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "ownerId", value = "방명록 주인 닉네임, 영문 대소문자 및 숫자로만 구성될 수 있음."),
            @ApiImplicitParam(name = "articleRequestDto", value = "생성할 방명록 내용. 제목(title)과 내용(body)를 JSON 형태로 HTTP Body에 실어 전송 해야함.")
    })
    @PostMapping("/{ownerId}/articles")
    public ResponseEntity<ArticleResponseDto> createArticle(@PathVariable String ownerId,
                                                            @RequestBody ArticleRequestDto articleRequestDto) {
        Long id = articleService.createArticle(new OwnerId(ownerId), articleRequestDto);
        return ResponseEntity.created(URI.create("/" + ownerId + "/articles/" + id)).build();
    }

 

어노테이션으로 API문서를 생성해주는 편리한 도구이다 하지만 스웨거에는 분명한 단점이 존재한다.

 

1.  문서화에 대한 코드가 포함되기 떄문에 가독성이 떨어진다.(사실상 어노테이션 떡칠을 해야한다 그리고 실제 코드에 적용을 해야하기 떄문에 꺼려진다.)

2. API스펙이 변화가 되더라도 API문서의 내용이 수정되지 않는다. 어노테이션을 수정을 해야 문서의 내용이 수정이 되기 때문에 문서와 실제 API스펙이 일치 된다고 보장할 수 없다. 

 

Spring REST Docs

Spring REST Docs는 스프링 프레임워크에서 제공하는 API문서 자동화 도구이다. Spring REST Docs는 문서화를 위한 코드를 

프로덕션 코드에 추가하지 않아도 된다는 장점이 있다.

 

프로덕션 코드에 따로 무언가를 추가하지 않고 테스트 코드를 작성하여서 API를 명세할 수 있다. 스프링 통합 테스트 혹은 컨트롤러 슬라이스 테스트)가 실행되고 해당 테스트가 성공하면, 그 테스트에 대한 asciidoc 스니펫이 생성된다.

 

Spring REST Docs는 테스트코드를 기반으로 문서가 생성되다보니 테스트가 실패하면 문서를 생성할 수 없다는 점도 있지만 이 말은 

API스펙과 항상 일치하는 문서를 작성할 수 있다 라는 것이다.

 

그래서 본인 같은 경우는 이번에 Spring REST Docs를 사용하기로 했다.

 

 

Spring REST Docs적용하기

build.gradle 설정하기

plugins {
    id 'org.springframework.boot' version '2.5.5'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
    // Asciidoctor 플러그인 적용
    // gradle 7.0 이상부터는 jvm 사용
    id "org.asciidoctor.jvm.convert" version "3.3.2" 

}

group = 'com.example'
version = '1.0'
sourceCompatibility = '11'

configurations {
    asciidoctorExtensions // dependencies 에서 적용한 것 추가
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}


dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok' // 추가
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    // build/generated-snippets 에 생긴 .adoc 조각들을 프로젝트 내의 .adoc 파일에서 읽어들일 수 있도록 연동해줍니다.
    // 이 덕분에 .adoc 파일에서 operation 같은 매크로를 사용하여 스니펫 조각들을 연동할 수 있는 것입니다.
    // 그리고 최종적으로 .adoc 파일을 HTML로 만들어 export 해줍니다.
    asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor' 

    // restdocs-mockmvc의 testCompile 구성 -> mockMvc를 사용해서 snippets 조각들을 뽑아낼 수 있게 된다.
    // MockMvc 대신 WebTestClient을 사용하려면 spring-restdocs-webtestclient 추가
    // MockMvc 대신 REST Assured를 사용하려면 spring-restdocs-restassured 를 추가
    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' 
}

ext {
    // 아래서 사용할 변수 선언
    snippetsDir = file('build/generated-snippets') 
}


test {
    // 위에서 작성한 snippetsDir 디렉토리를 test의 output으로 구성하는 설정 -> 스니펫 조각들이 build/generated-snippets로 출력
    outputs.dir snippetsDir 
    useJUnitPlatform()
}

asciidoctor { // asciidoctor 작업 구성
    dependsOn test // test 작업 이후에 작동하도록 하는 설정
    configurations 'asciidoctorExtensions' // 위에서 작성한 configuration 적용
    inputs.dir snippetsDir // snippetsDir 를 입력으로 구성

    // source가 없으면 .adoc파일을 전부 html로 만들어버림
    // source 지정시 특정 adoc만 HTML로 만든다.
    sources{
        include("**/index.adoc","**/common/*.adoc")
    }    

    // 특정 .adoc에 다른 adoc 파일을 가져와서(include) 사용하고 싶을 경우 경로를 baseDir로 맞춰주는 설정입니다.
    // 개별 adoc으로 운영한다면 필요 없는 옵션입니다.
    baseDirFollowsSourceFile()
}

// static/docs 폴더 비우기
asciidoctor.doFirst {
    delete file('src/main/resources/static/docs')
}

// asccidoctor 작업 이후 생성된 HTML 파일을 static/docs 로 copy
task copyDocument(type: Copy) {
    dependsOn asciidoctor
    from file("build/docs/asciidoc")
    into file("src/main/resources/static/docs")
}

// build 의 의존작업 명시
build {
    dependsOn copyDocument
}


// 참고사항 //
// 공식 문서에서는 위의 ascidoctor.doFirst부터 아래 내용은 없고 이와 같은 내용만 있습니다.
// 이렇게 하면 jar로 만들어 질때 옮겨지는 것으로 IDE로 돌릴 때는 build 폴더에서만 확인이 가능합니다.
// 위 방법을 사용하면 IDE에서도 static으로 옮겨진 것을 확인할 수 있습니다.
// 위에 방법을 사용하든 아래 방법을 사용하든 편한 선택지를 사용하시면 됩니다.
bootJar {
	dependsOn asciidoctor 
	from ("${asciidoctor.outputDir}/html5") { 
		into 'static/docs'
	}
}

 

위와 같이 세팅을 해주게 되면 ./gradew build로 빌드를 진행하면 test -> asciidoctor -> copyDocument -> build 순서로 진행되게 됩니다.
블로그마다 설정들이 전부 다른데 정확한 설정은 공식문서를 참고하시는 것을 권장합니다.

 

 

테스트 코드

 

   mockMvc.perform(MockMvcRequestBuilders.get("/products/1"))
            .andExpect(status().isOk())
            .andExpect(content().string(containsString(
                "5000"
            )))
            .andExpect(content().string(containsString(
                "1000"
            )))
            .andExpect(content().string(containsString(
                "3000"
            )))
            .andDo(MockMvcResultHandlers.print())
            .andDo(document("products/productDetail"
                , Preprocessors.preprocessRequest(prettyPrint()),
                Preprocessors.preprocessResponse(prettyPrint())));
    }
@AutoConfigureRestDocs

테스트 코드 파일에 위의 어노테이션을 반드시 적용시켜주어야 한다. 

 

Preprocessors.preprocessXXX 를 사용해주면, JSON등을 깔끔하게 포맷팅하여 문서를 생성해준다.

 

테스트 코드에 파일에 넣을 정보들을 세팅을 해주었다면 이제 테스트를 실행시켜주자 테스트가 성공을 했다면 

테스트 결과를 담은 파일들이 있을 것 이다. 기본적으로 다음과 같은 조각들이 default로 생성된다.

  • curl-request.adoc
  • http-request.adoc
  • httpie-request.adoc
  • http-response.adoc
  • request body
  • response body

 

테스트 코드에 따라 추가적인 조각이 생성될 수 있습니다.

  • response-fields.adoc
  • request-parameters.adoc
  • request-parts.adoc
  • path-parameters.adoc
  • request-parts.adoc

이제 이 조각들로 문서를 작성해야 합니다.

 

우선 adoc 파일 작성의 편의를 위해서 AsciiDOc플러그인을 설치를 해줍시다. 맥  기준으로 Command + , 를 누르면 바로 Settings로 넘어갈 수 있습니다. 

 

이제 테스트로 만들어준 파일들을 이용해서 문서를 만들어야 한다.

그 전에 우선 main/resources/static/docs 디렉토리를 만들어줍니다. 앞서 gradle 설정에 의해 이곳으로 html 파일이 복사되어 이곳으로 옮겨집니다.
그리고 src/docs/asciidoc 디렉토리를 만들고 안에 index.adoc 파일을 만들어줍니다.

 

Index.adoc파일에 문서화 하고 싶은 API 스펙을 작성을 하면 됩니다.

= Product API 문서
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 3

== 상품 정보 가져오기

=== Request

include::{snippets}/products/productDetail/http-request.adoc[]

=== Response

include::{snippets}/products/productDetail/http-response.adoc[]

간단하게 상품 정보에 대해서 request와 response정보 정도를 문서화 해보도록 하겠습니다. 

 

그 다음 ./gradlew build를 통해서 빌드를 해주게 되면 

index.html파일이 생기게 됩니다. 그러면 옆에 브라우저 아이콘들이 생기는데 해당 아이콘을 누르면 index.html의 내용이 브라우저를 통해서 띄워지게 됩니다. 

 

 

레퍼런스 https://backtony.github.io/spring/2021-10-15-spring-test-3/

 

Spring REST Docs 적용 및 최적화 하기

Java, JPA, Spring을 주로 다루고 공유합니다.

backtony.github.io