본문 바로가기
  • 개발하는 곰돌이
Development/Spring & Spring Boot

[Spring] Spring REST Docs를 사용해서 API 명세서를 작성해보자

by 개발하는 곰돌이 2024. 7. 8.

들어가기 전에

기존에 진행했던 프로젝트들의 API 명세서를 작성할 때는 어노테이션만으로 API 요청 및 응답 정보를 생성할 수 있고, Postman 등의 툴 없이 API를 실행할 수 있다는 장점이 있어서 OpenApi에서 제공하는 Swagger-UI를 사용하고 있었습니다.

 

하지만 스웨거의 경우 어노테이션을 통해 API 문서를 생성하다보니 API가 변경되었을 때 어노테이션의 내용도 수정하지 않으면 실제 API 사양과 스웨거 문서 상의 API 사양이 일치하지 않는 문제가 있어 API 문서를 완전히 자동으로 생성할 수는 방법이 필요했습니다.

 

그렇게 자동으로 API 문서를 생성하는 방법에 대해 찾아보던 중 스프링에서 제공하는 Spring REST Docs라는 도구가 있다는 사실을 알게되어 해당 방법을 도입해보기로 했습니다.

Spring REST Docs?

Spring REST Docs는 이름에서부터 알 수 있듯이 스프링에서 RESTful 서비스 문서 작성을 도와주는 도구입니다.

 

스웨거와 달리 테스트 코드를 기반으로 API 문서를 생성하기 때문에 프로덕션 코드에 영향을 주지 않고 API에 대한 다양한 정보를 제공할 수 있습니다.

 

테스트 코드를 기반으로 한다는 것은 API 문서를 생성하려면 반드시 테스트 코드를 작성해서 테스트에 성공해야 한다는 뜻이고, API 문서가 생성되었다는 것은 모든 테스트를 통과했다는 뜻이 됩니다. 즉 서비스의 신뢰도가 상승하는 것과 같습니다.

 

다만 스웨거와 달리 페이지 내에서 바로 API를 실행해볼 수 없기 때문에 API를 실행하려면 포스트맨이나 curl 등을 사용해야 하는 단점도 있습니다.

 

스웨거와 Spring REST Docs를 간단히 비교해보면 아래처럼 정리되겠네요.

  Spring REST Docs Swagger-UI
장점 테스트 코드를 기반으로 하기 때문에 신뢰도가 높다. 페이지 내에서 바로 API를 실행해볼 수 있다.
프로덕션 코드에 영향을 주지 않는다. 간단하게 적용해서 사용할 수 있다.
자동으로 생성되기 때문에 변경된 API 사양에 바로 동기화가 된다.  
단점 적용하는 과정이 복잡하다. API에 대해 다양한 정보를 제공하려면 여러 어노테이션을 사용해야 하기 때문에 프로덕션 코드가 지저분해진다.
페이지 내에서 바로 API를 실행할 수 없다. 어노테이션으로 직접 정보를 입력하기 때문에 API 사양이 변경되면 어노테이션의 정보도 직접 수정해야 한다.

Spring REST Docs를 적용할 준비를 하자!

프로젝트 환경

Spring REST Docs를 적용한 프로젝트의 환경은 아래 환경을 사용했습니다.

  • SpringBoot 3.1.11
  • Gradle 8.7
  • Kotlin 1.9.22(JDK 17)
  • JUnit5

Gradle에 관련 설정을 추가

스프링부트 프로젝트에 Spring REST Docs를 적용하려면 build.gradle 또는 build.gradle.kts 파일에 아래 내용들을 추가해줘야 합니다.

 

build.gradle

plugins {
    id 'org.asciidoctor.jvm.convert' version '3.3.2'
}

dependencies {
    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
}

ext {
    set('snippetsDir', file("build/generated-snippets"))
}

tasks.named('test') {
    outputs.dir snippetsDir
}

tasks.named('asciidoctor') {
    inputs.dir snippetsDir
    dependsOn test
}

build.gradle.kts

plugins {
    id("org.asciidoctor.jvm.convert") version "3.3.2"
}

dependencies {
    testImplementation("org.springframework.restdocs:spring-restdocs-mockmvc")
}

extra["snippetsDir"] = file("build/generated-snippets")

tasks.bootBuildImage {
    builder.set("paketobuildpacks/builder-jammy-base:latest")
}

tasks.test {
    outputs.dir(project.extra["snippetsDir"]!!)
}

tasks.asciidoctor {
    inputs.dir(project.extra["snippetsDir"]!!)
    dependsOn(tasks.test)
}

스프링 이니셜라이저로 스프링부트 프로젝트를 신규 생성할 때 Testing 탭의 Spring REST Docs를 의존성에 추가한다면 위 내용들이 자동으로 작성됩니다.

Gradle 추가 설정

기본 설정은 위와 같이만 해줘도 API 문서 자동 생성이 가능하지만 아래 예시처럼 추가로 더 상세한 설정을 할 수도 있습니다.

 

build.gradle

ext {
    set('snippetsDir', file("build/generated-snippets"))
    set('docsOutputDir', file("build/docs/asciidoc"))	// API 문서가 생성되는 경로
    set('projectDocsDir', file("src/main/resources/static/docs"))	// 생성된 API 문서를 복사할 경로
}

tasks.named('bootJar') {
    dependsOn 'asciidoctor'	// asciidoctor 수행 후 bootJar 수행
}

tasks.named('test') {
    doFirst { delete(snippetsDir) }	// test 수행 전 실행
    outputs.dir snippetsDir
}

tasks.named('asciidoctor') {
    doFirst {	// asciidoctor 수행 전 실행
        delete(docsOutputDir)	// 기존 생성된 API 문서 삭제
        delete(projectDocsDir)	// 기존에 복사된 API 문서 삭제
    }
    inputs.dir snippetsDir
    dependsOn test
    doLast {	// asciidoctor 수행 완료 후 실행
        copy {	// 생성된 API 문서를 지정된 경로에 복사
            from docsOutputDir
            into projectDocsDir
        }
    }
}

build.gradle.kts

extra["snippetsDir"] = file("build/generated-snippets")
extra["docsOutputDir"] = file("build/docs/asciidoc")	// API 문서가 생성되는 경로
extra["projectDocsDir"] = file("src/main/resources/static/docs")	// 생성된 API 문서를 복사할 경로

tasks.bootJar {
    dependsOn(tasks.asciidoctor)	// asciidoctor 수행 후 bootJar 수행
}

tasks.test {
    doFirst { delete(project.extra["snippetsDir"]!!) }	// test 수행 전 실행
    outputs.dir(project.extra["snippetsDir"]!!)
}

tasks.asciidoctor {
    doFirst {	// asciidoctor 수행 전 실행
        delete(project.extra["docsOutputDir"]!!)	// 기존 API 문서 삭제
        delete(project.extra["projectDocsDir"]!!)	// 기존에 복사된 API 문서 삭제
    }
    inputs.dir(project.extra["snippetsDir"]!!)
    dependsOn(tasks.test)
    doLast {	// asciidoctor 수행 완료 후 실행
        copy {	// 생성된 API 문서를 지정된 경로에 복사
            from(project.extra["docsOutputDir"]!!)
            into(project.extra["projectDocsDir"]!!)
        }
    }
}

위 내용은 bootJar로 생성된 .jar 파일에 API 문서를 담기 위해 몇가지 조정을 한 예시입니다.

 

dependsOn으로 asciidoctor가 수행된 이후에 bootJar가 수행되도록 해줍니다.

 

test 단계에서는 테스트 수행 전에 스니펫이 생성되는 디렉토리를 삭제하도록 했습니다. 기본적으로는 테스트 코드에서 지정한 디렉토리의 스니펫은 자동으로 삭제되고 재생성되지만, 테스트 코드에서 지정한 디렉토리를 변경할 경우에는 제대로 삭제되지 않습니다.

 

가령, member-info 디렉토리에 스니펫을 생성하도록 테스트 코드를 작성했다가 이 경로를 member/info로 변경하고 테스트 코드를 실행하면 기존에 생성되어 있던 member-info 내부의 스니펫이 그대로 남게 됩니다. 이를 방지하기 위한 설정입니다.

 

asciidoctor 단계에서는 API 문서가 생성되기 전에 기존에 생성된 API 문서들을 삭제하고, API 문서가 생성된 이후엔 build 디렉토리 내에 생성된 API 문서를 프로젝트의 resources/static으로 복사하여 서버에 접속하여 바로 확인할 수 있도록 하였습니다.

API 문서 만들기

Spring REST Docs로 API 문서를 생성하려면 테스트 코드 외에도 추가적인 설정이 필요합니다. 우선은 테스트 코드부터 작성해봅시다.

테스트 코드를 작성해보자!

Spring REST Docs를 사용하기 위한 테스트 코드는 기본적으로는 컨트롤러 테스트코드와 비슷합니다. 여기서는 MockMvc를 사용하여 테스트 코드를 작성했습니다.

 

Kotlin

@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs
class AdminControllerTest @Autowired constructor(
    private val tokenProvider: TokenProvider,
    private val memberRepository: MemberRepository,
    private val mockMvc: MockMvc
) {
    @BeforeEach
    fun clear() {
        memberRepository.deleteAllInBatch()
    }

    @Test
    fun `모든 사용자 찾기`() {
        val token = "Bearer ${tokenProvider.createAccessToken("admin:ADMIN")}"
        // given
        val uuids = memberRepository.saveAll(
            listOf(
                Member("colabear754", "1234", "콜라곰", type = MemberType.USER),
                Member("ciderbear754", "1234", "사이다곰", type = MemberType.USER),
                Member("fantabear754", "1234", "환타곰", type = MemberType.USER)
            )
        ).map { it.id.toString() }
        // when
        val actionsDsl = mockMvc.get("/admin/members") { header(HttpHeaders.AUTHORIZATION, token) }
        // then
        actionsDsl.andExpect {
            status { isOk() }
            jsonPath("$.status", Matchers.`is`(ApiStatus.SUCCESS.name))
            jsonPath("$.message", Matchers.nullValue())
            jsonPath("$.data", Matchers.hasSize<Any>(3))
            jsonPath("$.data[*].id", Matchers.containsInAnyOrder(uuids[0], uuids[1], uuids[2]))
            jsonPath("$.data[*].name", Matchers.containsInAnyOrder("콜라곰", "사이다곰", "환타곰"))
            jsonPath("$.data[*].type", Matchers.containsInAnyOrder("USER", "USER", "USER"))
        }

        // Spring REST Docs
        actionsDsl.andDo { handle(
            document(
                "admin/members",	// identifier
                preprocessRequest(	// requestPreprocessor
                    modifyUris()
                        .scheme("https")
                        .host("api.demo.com")
                        .removePort(),
                    modifyHeaders()
                        .set(HttpHeaders.AUTHORIZATION, "Bearer {access_token}")
                ),
                preprocessResponse(prettyPrint()),	// responsePreprocessor
                requestHeaders(headerWithName(HttpHeaders.AUTHORIZATION).description("인증 토큰")),	// snippets
                responseFields(
                    fieldWithPath("status").description("API 상태"),
                    fieldWithPath("message").description("API 메시지").type(JsonFieldType.STRING).optional(),
                    fieldWithPath("data").description("응답 데이터"),
                    fieldWithPath("data[].id").description("사용자 ID"),
                    fieldWithPath("data[].account").description("계정"),
                    fieldWithPath("data[].name").description("이름"),
                    fieldWithPath("data[].age").description("나이").type(JsonFieldType.NUMBER).optional(),
                    fieldWithPath("data[].type").description("사용자 유형"),
                    fieldWithPath("data[].createdAt").description("가입일")
                )
            )
        ) }
    }
}

Java

@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs
public class AdminControllerTest {
    private final TokenProvider tokenProvider;
    private final MemberRepository memberRepository;
    private final MockMvc mockMvc;

    @Autowired
    public AdminControllerTest(TokenProvider tokenProvider, MemberRepository memberRepository, MockMvc mockMvc) {
        this.tokenProvider = tokenProvider;
        this.memberRepository = memberRepository;
        this.mockMvc = mockMvc;
    }

    @Test
    void 모든_사용자_찾기() throws Exception {
        // given
        String token = "Bearer " + tokenProvider.createAccessToken("admin:ADMIN");
        List<String> uuids = memberRepository.saveAll(List.of(
                        Member.builder()
                                .account("colabear754")
                                .password("1234")
                                .name("콜라곰")
                                .type(MemberType.USER)
                                .build(),
                        Member.builder()
                                .account("ciderbear754")
                                .password("1234")
                                .name("사이다곰")
                                .type(MemberType.USER)
                                .build(),
                        Member.builder()
                                .account("fantabear754")
                                .password("1234")
                                .name("환타곰")
                                .type(MemberType.USER)
                                .build()
                )).stream()
                .map(member -> member.getId().toString())
                .toList();
        // when
        ResultActions action = mockMvc.perform(get("/admin/members").header(HttpHeaders.AUTHORIZATION, token));
        // then
        action.andExpect(status().isOk())
                .andExpect(jsonPath("$.status", Matchers.is(ApiStatus.SUCCESS.name())))
                .andExpect(jsonPath("$.message", Matchers.nullValue()))
                .andExpect(jsonPath("$.data", Matchers.hasSize(3)))
                .andExpect(jsonPath("$.data[*].id", Matchers.containsInAnyOrder(uuids.get(0), uuids.get(1), uuids.get(2))))
                .andExpect(jsonPath("$.data[*].name", Matchers.containsInAnyOrder("콜라곰", "사이다곰", "환타곰")))
                .andExpect(jsonPath("$.data[*].type", Matchers.containsInAnyOrder("USER", "USER", "USER")));
        
        // Spring REST Docs
        action.andDo(document(
                "admin/members",	// identifier
                preprocessRequest(	// requestPreprocessor
                        modifyUris()
                                .scheme("https")
                                .host("api.demo.com")
                                .removePort(),
                        modifyHeaders()
                                .set(HttpHeaders.AUTHORIZATION, "Bearer {access_token}")
                        ),
                preprocessResponse(prettyPrint()),	// responsePreprocessor
                requestHeaders(headerWithName(HttpHeaders.AUTHORIZATION).description("인증 토큰")),	// snippets
                responseFields(
                        fieldWithPath("status").description("API 상태"),
                        fieldWithPath("message").description("API 메시지").type(JsonFieldType.STRING).optional(),
                        fieldWithPath("data").description("응답 데이터"),
                        fieldWithPath("data[].id").description("사용자 ID"),
                        fieldWithPath("data[].account").description("계정"),
                        fieldWithPath("data[].name").description("이름"),
                        fieldWithPath("data[].age").description("나이").type(JsonFieldType.NUMBER).optional(),
                        fieldWithPath("data[].type").description("사용자 유형"),
                        fieldWithPath("data[].createdAt").description("가입일")
                )
        ));
    }
}

다른 의존성을 사용하지 않는 테스트 코드를 작성하는 경우라면 @SpringBootTest@AutoConfigureMockMvc 대신 @WebMockMvc를 사용할 수도 있습니다.

 

@AutoConfigureRestDocs는 Spring REST Docs를 활성화하고 자동 설정을 적용하는 어노테이션입니다. 테스트 클래스에 해당 어노테이션을 적용해야 테스트 코드 실행 시 자동으로 스니펫이 작성됩니다.

테스트 코드를 실행하면 자동으로 스니펫이 작성됩니다.

actionDsl.andExpect까지는 기존의 컨트롤러 테스트와 동일합니다.

 

이후 actionDsl.andDo부분에서 자동으로 작성되는 스니펫에 대해 설정합니다.

 

첫번째 파라미터인 identifier는 위의 그래들 설정에서 snippetsDir로 설정한 경로 내에 API 문서가 저장될 경로를 지정합니다. 위 테스트 코드에서는 build/generated-snippets/admin/members 디렉토리 내부에 /admin/members API 해당하는 .adoc 파일이 생성됩니다.

 

두번째 파라미터인 requestPreprocessor는 API의 요청 파라미터를 문서화하기 전에 전처리를 진행할 내용을 넣어줍니다. 위 테스트 코드에서는 API의 호스트를 변경하고, 요청 헤더의 Authorization을 변경했습니다. modifyUris()를 설정하지 않으면 요청 문서의 호스트가 기본값인 localhost:8080으로 작성됩니다.

 

스키마와 호스트, 포트 정보의 경우 아래와 같이 테스트 클래스의 @AutoConfigureRestDocs에서 공통으로 처리할 수도 있습니다 uriPort값을 0으로 주면 포트 정보를 제거하고, 그 외에는 호스트 뒤에 포트번호가 작성됩니다.

@AutoConfigureRestDocs(uriScheme = "https", uriHost = "api.demo.com", uriPort = 0)

작성된 요청 정보

 

세번째 파라미터인 responsePreprocessor는 응답 파라미터를 문서화하기 전에 전처리를 진행할 내용을 넣어줍니다. 위 테스트 코드에서 인자로 넣어준 preprocessResponse(prettyPrint())는 응답 JSON 데이터를 JSON 형식에 맞게 정렬해주는 전처리 프로세서입니다.

preprocessResponse(prettyPrint())를 사용하지 않으면 응답 JSON 데이터가 한줄로 작성됩니다.
preprocessResponse(prettyPrint())를 인자로 넣어주면 익숙한 JSON 포맷으로 정렬되어 작성됩니다.

마지막으로 snippets은 요청, 응답 데이터의 내용을 정의한 스니펫을 전달받는 가변 인자입니다. snippets로 요청 필드나 응답 필드를 정의한 스니펫을 전달하면 각각 request-fields.adoc 또는 response-fields.adoc 파일이 추가로 생성됩니다.

스니펫을 document()의 인자로 전달하면 response-fields.adoc가 추가로 생성됩니다.

각 필드의 타입은 요청/응답 필드의 타입에 맞춰 자동으로 지정되는데 null인 응답은 별도로 타입을 지정하지 않으면 Null 타입으로 지정됩니다. 이 때 optional()을 설정하지 않으면 테스트에 실패하여 문서가 작성되지 않습니다.

테스트 결과가 null인 필드에 optional()을 지정하지 않으면 예외가 발생하여 테스트에 실패합니다.

requestFields로 요청 필드를 정의하거나 responseFields로 응답 필드를 정의할 때는 하나라도 누락된 필드가 있다면 예외가 발생하여 테스트에 실패합니다. 원하는 필드만 정의를 하려면 relaxedRequestFieldsrelaxedResponseFields를 사용해야 합니다.

정의를 누락한 필드가 있다면 테스트에 실패합니다.

요청/응답 필드 스니펫을 커스터마이징하고 싶다!

현재 자동으로 생성된 요청/응답 필드 스니펫에는 필드명, 타입, 설명만 적혀있습니다. 요청 필드의 필수값 여부나 응답 필드의 Nullable 여부까지 표시하고 싶습니다.

 

인텔리제이 프로젝트 탐색기에서 External Libraries를 펼쳐서 쭉 내려가다보면 Gradle: org.springframework.restdocs:spring-restdocs-core라는 라이브러리가 있습니다.

이 라이브러리를 다시 펼쳐서 org.springframework.restdocs 안의 templates/asciidoctor 디렉토리를 펼쳐보면 수많은 .snippet 파일이 존재하는 것을 볼 수 있습니다.

.snippet 파일들이 테스트 코드를 실행하면 자동으로 생성되는 .adoc 파일들의 포맷이 됩니다.

 

요청과 응답 필드 스니펫의 포맷을 변경할거니까 src/test/resources 디렉토리 내부에 org/springframework/restdocs/templates 디렉토리를 만들어서 default-request-fields.snippet의 내용을 복사한 request-fields.snippetdefault-response-fields.snippet의 내용을 복사한 response-fields.snippet을 만들어서 내용을 추가해줍니다.

 

request-fields.snippet

response-fields.snippet

노란 음영 부분이 변경된 부분입니다.

 

공통적으로 Path를 Name으로 바꾸고 요청 스니펫에는 Required를, 응답 스니펫에는 Nullable을 추가했습니다.

 

요청과 응답의 optional여부를 표시할 수 있도록 7번째 줄을 추가했습니다. 요청의 {{^optional}}optionalfalse일 때 태그 안에 있는 문자열을 표시한다는 뜻이고, 응답의 {{#optional}}optionaltrue일 때 태그 안에 있는 문자열을 표시한다는 뜻입니다.

 

이제 다시 테스트 코드를 실행해서 생성된 스니펫을 보면 커스터마이징한 형태로 작성된 것을 확인할 수 있습니다.

 

request-fields.adoc

필수값을 나타내는 Required가 추가되었습니다.

response-fields.adoc

Nullable 여부를 나타내는 Nullable이 추가되었습니다.

.snippet 파일은 Mustache라는 템플릿을 사용합니다.

 

{{ mustache }}

Logic-less templates. Available in Ruby, JavaScript, Python, Erlang, Elixir, PHP, Perl, Raku, Objective-C, Java, C#/.NET, Android, C++, CFEngine, Go, Lua, ooc, ActionScript, ColdFusion, Scala, Clojure[Script], Clojure, Fantom, CoffeeScript, D, Haskell, XQu

mustache.github.io

스니펫을 합쳐서 API 문서로 만들자!

스니펫 생성까지 완료되었다면 이제 스니펫들을 토대로 완성된 API 문서가 작성되도록 설정해줘야 합니다. 빌드 환경에 따라 아래 경로에 API 문서가 될 .adoc 파일을 작성하면 해당 파일을 토대로 같은 이름을 가진 .html 파일이 생성됩니다.

빌드 환경 원본 파일 경로 생성 파일 경로
Gradle src/docs/asciidoc/*.adoc build/asciidoc/*.html
Maven src/main/asciidoc/*.adoc target/generated-docs/*.html
공식 문서에서는 Gradle 환경에서의 html 파일 생성 경로가 build/asciidoc/html5/*.html이라고 적혀있으나 실제로는 build/ascii/*.html에 생성되고 있었습니다.

 

예를 들어 api-docs.adoc라는 파일을 작성한 후 그래들의 asciidoctor 명령을 실행하게 되면 자동으로 api-docs.html이라는 파일이 생성됩니다.

 

지정된 경로에 .adoc파일을 작성합니다.

:snippets: build/generated-snippets
:doctype: book
:source-highlighter: highlightjs
:toc: left
:toclevels: 3
:sectlinks:

[[common]]
== API 명세서
=== 공통 정보
|===
| 환경 | Host
| 개발서버 | `https://dev-api.demo.com`
| 운영서버 | `https://api.demo.com`
|===

[[admin]]
=== 관리자용 API
[[member-list]]
==== 회원 목록 조회(관리자 전용)
===== Request Header
include::{snippets}/admin/members/request-headers.adoc[]
====== Request Example
include::{snippets}/admin/members/http-request.adoc[]
===== Response Body
include::{snippets}/admin/members/response-fields.adoc[]
====== Response Example
include::{snippets}/admin/members/http-response.adoc[]
&nbsp;

1~6번째 줄은 문서의 속성을 정의하고 있습니다.

 

1번째 줄에서는 snippets라는 속성을 정의하고 있습니다. 이 값은 테스트 코드를 실행해서 생성된 스니펫의 생성 경로인 build/generated-snippets로 지정해줍니다.

 

2번째 줄에서는 생성되는 API 문서의 형식을 지정합니다. asciidoc 공식 문서 상에서는 article, book, manpage, inline을 지원한다고 하는데, inline별도의 규칙이 적용된다고 합니다. 기본값은 article입니다.

 

3번째 줄에서는 소스코드를 강조하기 위한 라이브러리를 지정하고 있습니다.

 

4번째 줄에서는 목차의 위치를 지정하고 있습니다. auto, left, right 중 하나를 사용할 수 있으며, auto는 문서의 최상단에, leftright는 각각 문서의 왼쪽과 오른쪽에 사이드바 형태로 목차가 나타납니다.

 

5번째 줄에서는 목차에 표시할 제목의 단계를 지정하고 있습니다. 위 예시에서는 3단계 제목까지 표시합니다.

 

6번째 줄에서는 각 섹션의 제목을 하이퍼링크로 설정하고 있습니다.

 

이후 include::를 사용하여 테스트 코드를 실행해서 생성된 스니펫의 내용을 API 문서에 추가해주면!

이렇게 완성된 API 문서가 작성됩니다!

 

추가설정까지 진행했다면 이렇게 작성된 API 문서가 지정한 디렉토리로 복사된 것도 확인할 수 있으며, 빌드된 jar 파일을 실행하여 해당 경로로 이동했을 때 API 문서를 열어볼 수 있습니다.

마치며

Spring REST Docs는 테스트 코드를 기반으로 API 문서가 자동으로 작성된다는 점, 프로덕션 코드에 전혀 영향이 없다는 점으로 인해 API 문서가 필요할 때 선택하기 좋은 수단이라고 생각합니다.

 

하지만 정적인 API 문서이다 보니 스웨거와 다르게 직접 API를 실행해볼 수 없다는 점이나 여러가지 설정이 복잡하다는 점은 조금 아쉽습니다.

 

본문에서는 가장 기본적인 형태에 약간의 변화만 줘서 작성했지만 그 외에도 몇가지 커스터마이징이 가능합니다. 해당 내용은 추후 별도로 다루고자 합니다.

 

전체 코드는 Github(Kotlin, Java)에서 확인하실 수 있습니다.

참조 링크

 

Spring REST Docs

Document RESTful services by combining hand-written documentation with auto-generated snippets produced with Spring MVC Test or WebTestClient.

docs.spring.io

 

AsciiDoc - AsciiDoc Language Documentation

A documentation page for AsciiDoc.

docs.asciidoctor.org

 

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

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

techblog.woowahan.com

 

내가 만든 API를 널리 알리기 - Spring REST Docs 가이드편

'추석맞이 선물하기 재개발'에 차출되어 API 문서화를 위해 도입한 Spring REST Docs 를 소개합니다.

helloworld.kurly.com

댓글