안녕하세요, Lune 입니다.
이번엔 Spring REST Docs를 기술 세미나 주제로 가져와봤는데요.
Spring REST Docs를 프로젝트에서 사용하게 된 이유부터 적용기까지 공유해보려고 합니다.
1. API 문서의 필요성
조금 뻔한 얘기로 시작해볼까요?
개발을 하면서 API 문서들이 필요한 이유는 무엇일까요? 제가 생각해봤을 때, 그리고 검색을 해봤을 때 아래와 같은 이유들이 있었습니다.
- API 사용법 이해
- 개발자 간의 효율적인 협업
- 빠른 문제 해결 및 버그 대응
- 개발 생산성 향상
개인적으로는 일단 2번이 제일 와닿습니다. 협업할 때 같은 API 문서를 보며 얘기하면 소통이 더 잘된다고 느낀 적이 많았거든요.
2. API 문서를 만드는 여러 방법들
자, 그럼 이번에는 API 문서를 만들 수 있는 방법들에 대해 이야기해볼게요.
- 구글 공유 문서 (Docs / Sheets)
- Notion
- GitBook
- Swagger
- Spring REST Docs
위에 리스트 말고도 더 많은 방법들이 있겠지만, 제가 사용해봤거나 사용사례를 본 적 있는 것 위주로 작성해봤습니다.
구글 공유 문서 / Notion / GitBook
코드베이스가 아니라 수동으로 직접 문서를 작성하는 방식입니다. 장단점을 알아보자면 다음과 같습니다.
장점 | 단점 |
---|---|
쉬운 협업 및 공유 | API 문서 작성 기능 제한적 |
커스텀 스타일 적용 쉬운 편 | 비용 문제 발생 가능 |
사용자 친화적인 UI/UX | API 문서 자동화 어려움 |
위 내용이 3가지 모두에 동일하게 적용된다고 할 수는 없지만, 보편적인 장단점 위주로 설명해보려고 했으니 조금 안 맞다고 생각이 들어도 이해해주세요 👐
Swagger / Spring REST Docs
코드 내에 API 문서화를 위한 작업을 진행하는 방식입니다. 장단점을 알아보자면 다음과 같습니다.
장점 | 단점 |
---|---|
자동 문서 생성 | 러닝커브 존재 및 설정 복잡 |
일관된 형식과 스타일 유지 | 커스터마이제이션 한계 |
실시간 업데이트 | 의존성 및 업그레이드 어려움 |
여기서 커스터마이제이션 한계 란 문서의 외관이나 기능을 개발자가 원하는 대로 조정하는 데에 한계가 있다는 것을 의미합니다.
3. Spring REST Docs를 선택한 이유
현재 진행 중인 프로젝트에서는 왜 Spring REST Docs를 선택하게 되었을까요?
우선 첫 시작은 GitBook 이었답니다. 기능 개발이 들어가지 않은 상태에서 프론트엔드 개발자와의 빠른 협업을 위해 GitBook으로 API 문서를 만들기 시작했었죠. 그런데 초반에 잘 알아보지 않은 탓에 얼마 지나지 않아 무료 버전의 한계를 맞닥뜨리게 되었습니다. 무료 버전에서는 여러 사람이 문서 편집을 할 수 없어 공동 작업이 더 이상 불가능하게 되어버렸거든요 🥲
그 다음으로 생각한 건 API 문서 자동화가 가능한 Swagger와 Spring REST Docs였습니다. 그래서 그 둘을 비교해봤습니다.
Swagger
장점 | 단점 |
---|---|
어노테이션 기반 문서 생성 | 프로덕션 코드에 작업 필요 |
화면에서 API 테스트 | 프로덕션 코드와 동기화 안 되어 있을 수 있음 |
비교적 쉬운 적용 |
Spring REST Docs
장점 | 단점 |
---|---|
프로덕션 코드에 영향 없음 | 적용이 어려운 편 |
테스트 성공 시 문서 생성 | 테스트 코드 양이 늘어남 |
API 문서 최신 상태 유지 |
(제목에 드러나있듯이) 결과적으로 Spring REST Docs를 사용하기로 결정을 했습니다. 이번 프로젝트에서는 테스트 코드를 작성하기로 했었고, 프로덕션 코드에 영향이 없고 늘 현행화가 가능하다는 점에 이끌렸거든요.
4. Spring REST Docs 적용기
이제부터는 Spring REST Docs를 적용했던 과정을 하나씩 설명해보겠습니다.
참고로 저는 Asciidoctor & MockMvc 방식을 사용했는데요. 공식 문서에 따르면 문서 작성을 위해 Asciidoctor와 Markdown을 지원하고 있고 MockMvc, WebTestClient, REST Assured 방식의 테스트 예시를 보여주고 있습니다.
1) build.gradle 설정
build.gradle에 추가되어야 하는 내용 및 설명은 아래 코드로 대체합니다.
plugins {
// Asciidoctor 플러그인 적용
id "org.asciidoctor.jvm.convert" version "3.3.2"
}
ext {
// 생성된 snippets 출력 위치를 정의하는 snippetsDir 속성을 구성
snippetsDir = file('build/generated-snippets')
}
configurations {
// asciidoctorExt 구성을 선언
asciidoctorExt
}
dependencies {
// asciidoctorExt 구성에서 spring-restdocs-asciidoctor에 대한 종속성을 추가
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
// MockMvc 테스트 방식을 사용
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
}
tasks.named('test') {
// 테스트 작업을 실행하면 출력이 snippetsDir에 기록된다는 것을 Gradle이 인식하도록 함
outputs.dir snippetsDir
}
asciidoctor {
// asciidoctor 작업을 구성
dependsOn test
configurations 'asciidoctorExt'
baseDirFollowsSourceFile()
inputs.dir snippetsDir
}
asciidoctor.doFirst {
// asciidoctor 작업이 수행될 때 가장 먼저 수행
delete file('src/main/resources/static/docs')
}
task copyDocument(type: Copy) {
// html 파일 복사
dependsOn asciidoctor
from file("build/docs/asciidoc")
into file("src/main/resources/static/docs")
}
bootJar {
dependsOn copyDocument
}
2) 테스트 코드 작성
첫 번째 테스트 코드 외에는 구현 방식에 따라 다를 수 있으므로 가볍게 봐주시면 됩니다.
/** 테스트 코드 **/
result.andExpect(status().isOk())
.andDo(document(
"commoncode/get-common-codes",
getDocumentRequest(),
getDocumentResponse(),
// pathParameters, queryParameters, requestFields, responseFields는 필요 시 각각 작성
pathParameters(
parameterWithName("codeName").description("코드명")
),
responseFields(beneathPath("value").withSubsectionId("value"),
fieldWithPath("codeNo").type(JsonFieldType.NUMBER).description("코드번호"),
fieldWithPath("codeName").type(JsonFieldType.STRING).description("코드명"),
fieldWithPath("description").type(JsonFieldType.STRING).description("설명").optional()
)
));
/** Utils 만들어 사용 **/
public interface RestDocumentUtils {
static OperationRequestPreprocessor getDocumentRequest() {
return preprocessRequest(modifyUris().scheme("http")
.host("spring.restdocs.test") // 실제 host 아님
.removePort(), prettyPrint());
}
static OperationResponsePreprocessor getDocumentResponse() {
return preprocessResponse(prettyPrint());
}
}
/** 추상 클래스 작성 **/
@WebMvcTest({
CommonCodeController.class,
})
@AutoConfigureRestDocs // 통합 테스트에서 Spring REST Docs를 활성화하고 구성하는 데 사용
public abstract class ControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired
protected ObjectMapper objectMapper;
@MockBean
protected CommonCodeService commonCodeService;
}
// 아래와 같이 상속받아 사용
// class CommonCodeControllerRestDocsTest extends ControllerTest {
3) 테스트 성공
위와 같이 작성한 테스트 코드가 통과하게 된다면 build/generated-snippets 경로 하위에 adoc 확장자 파일들이 여러 개 생성된 것을 확인할 수 있습니다. 파일을 하나씩 선택해서 보면 Asciidoc 문법에 맞게 작성된 내용과 미리보기를 확인할 수 있답니다.
4) API 문서 틀 작성
위에서 본 문서 조각들(snippets)을 그대로 활용할 수 있다면 좋겠지만, HTML 형태의 API 문서로 만들어 주기 위해서는 아직 추가적인 작업이 남아있습니다. 한 데 모아주는 작업을 해줘야 하는데요. 우선 저는 index.adoc 이라는 파일을 만들어 API별 adoc 파일을 include 하는 구조를 사용했습니다. 참고로 index.adoc 파일은 src/docs/asciidoc 하위 에 만들어주었는데 이 경로가 gradle을 사용할 경우의 기본 경로랍니다.
// index.adoc
= API Document
// Asciidoc 문서의 구조와 스타일 정의
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 2
:sectlinks:
// adoc 파일 include
include::overview.adoc[]
include::sample-api.adoc[]
// sample-api.adoc
== 샘플 API
[[공통코드-조회]]
=== 공통코드 조회
==== Request
include::{snippets}/commoncode/get-common-codes/path-parameters.adoc[]
===== HTTP Request 예시
include::{snippets}/commoncode/get-common-codes/http-request.adoc[]
==== Response
include::{snippets}/commoncode/get-common-codes/response-fields-value.adoc[]
===== HTTP Response 예시
include::{snippets}/commoncode/get-common-codes/http-response.adoc[]
5) API 문서 생성
이제 거의 마무리 단계인데요. gradle의 bootJar 작업을 실행시키면 build.gradle에 작성한 copyDocument 작업을 거쳐서 미리 지정한 resources/static/docs 경로 하위에 build 내에 생성되어 있던 html 파일이 복사되어 들어오고, 서버를 띄웠을 때 도메인 하위 /docs/index.html 경로로 접속해 API 문서를 확인할 수 있게 된답니다.
6) API 문서 살펴보기
최종적으로 만들어진 문서의 형태는 다음과 같습니다. 좌측에 ToC가 있어 원하는 API로의 이동이 간편하고 화면도 깔끔하지 않나요?
5. 정리
지금까지 저와 함께 API 문서부터 Spring REST Docs 적용기까지 살펴봤는데요. 읽기에 어떠셨을지 궁금합니다 😄
Spring REST Docs를 적용하면서 여러 차례 시행착오를 거치며 보낸 시간이 생각나네요. 처음 접하는 부분이 많아 공식 문서, 영상, 블로그 등을 참고했는데 그 과정에서 조금씩 이해하게 되고 원하는 결과를 만들어낼 수 있어서 개인적으로 뿌듯하고 좋았던 경험이었습니다.
아 그리고 이번 세미나를 준비하면서 추가적인 정보를 찾다가 알게 된 건데 Swagger와 Spring REST Docs의 장점만 취해서 API 문서화를 할 수 있는 방법도 있다고 해요. 가능하다면 다음에는 이 방식을 사용하거나 시간이 될 때 변경해볼 수 있으면 좋을 것 같다는 생각이 드네요.