
Intro
이전에 Spring RestDocs를 처음 사용하시는 분들을 위하여 Spring Rest Docs 예제 코드로 학습하기 라는 포스팅을 작성했습니다. 회사에서도 도입 이후 꽤나 만족하면서 잘 사용하고 있었지만, 아래와 같은 몇가지 불편한 부분이 있었습니다.
1. 기본 스니펫 템플릿 사용 시 필수 여부에 대하여 작성하기 힘들다.
2. Enum에 대한 값을 수동으로 작성해주어야 한다.
3. Enum의 값이 많을 경우 문서 상 설명 란이 너무 길어진다.
위 불편사항에 대하여 우아한 형제들 기술블로그의 Spring REST Docs에 날개를... (feat: Popup) 글을 참고하여 기존의 불편사항을 개선하고 실제로 적용한 내용을 예제와 함께 1편과 2편으로 나누어서 소개해드리려고 합니다.
모든 예제 코드는 깃허브에서 확인하실 수 있으며, 여기에서 개선된 문서 체험을 해보실 수 있습니다.
Optional 값 표기 커스텀 스니펫 템플릿 적용
예시로 회원가입 API를 개발한다고 했을 때 "한 줄 소개"와 같은 값은 회원가입에 필수적으로 필요한 값이 아닐 수 있습니다. 문서에 이런 Optional에 대한 부분이 명시가 되어있지 않다면 프론트엔드 개발자는 API를 호출할 때 해당 값을 필수로 보내주어야 하는지 아닌지에 대하여 백엔드 개발자에게 물어보거나, 직접 한땀한땀 API를 여러 상황에 대하여 호출해봐야 알 수 있습니다.
Spring RestDocs의 기본 스니펫 템플릿을 사용하면 아래 사진과 같이 Path, Type, Description 컬럼만 존재하여 Optional 여부에 대하여 명시를 해주기 위해서는 Description 부분에 적는 수 밖에 없습니다.

하지만 설명 컬럼에 Optional 여부에 대하여 매번 명시해주는 것은 꽤나 번거로운 작업이였고, 필수값이 아니였다가 필수값이 된 경우에 문서를 수정하지 않아 실제 API 스펙과 달라질 수도 있다는 우려가 있었습니다.
커스텀 스니펫 템플릿을 적용하면 이런 부분을 어느정도 해결할 수 있었고, 적용 방법은 아래와 같습니다.
커스텀 스니펫 템플릿 적용 방법

먼저 위 사진과 같이 테스트의 resources 디렉토리 하위로 `org.springframework.restdocs.templates`라는 경로에 `request-fields.snippet` 파일과 `response-fields.snippet` 파일을 생성하고 아래와 같이 내용을 작성해줍니다.
※ 오타가 있을 경우 동작하지 않으므로 주의합니다.
request-fiedls.snippet
|===
|필드명|타입|필수값|설명
{{#fields}}
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}}
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{#optional}}Optional{{/optional}}{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/fields}}
|===
response-fields.snippet
|===
|필드명|타입|필수값|설명
{{#fields}}
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}}
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{#optional}}Optional{{/optional}}{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/fields}}
|===
Spring RestDocs의 스니펫 템플릿에서는 mustache 문법을 지원합니다. 따라서 커스텀해줄 스니펫 템플릿에도 Mustache 문법을 이용하여 필수값에 대하여 명시할 수 있도록 해주었고, 기존의 컬럼명들도 전부 한글로 변경해주었습니다.
Mustache 문법에 대한 내용은 이번 포스팅의 범위에 벗어나므로 생략하겠습니다.
놀랍게도 커스텀 스니펫 템플릿을 적용하는 방법은 이게 끝입니다. 이제 테스트코드에서 문서 작성 부분에 .optional()을 붙여주기만 하면 문서에 자동으로 반영이 됩니다.


예제에서는 커스텀 스니펫 템플릿을 Request/Response 필드만 적용하였지만, 필요에 따라서 Query Parameters/Path Variable에도 입맛에 맞게 적용하여 사용할 수 있습니다.
Enum 표기 및 확인에 대한 불편함
아래 사진은 개선하기 전 Enum 값에 대하여 문서에 표기하는 방법 예시입니다.

사진처럼 필드가 Enum타입인 경우 수기로 상수 값들에 대한 설명을 작성해주었는데, 중간에 값이 추가되거나 삭제된 경우 문서에 반영되지 않아 누락이 생기는 일이 많았고, 여러 API 스펙에서 사용하는 Enum인 경우 수정이 발생하면 문서에서 수정해야 할 부분이 많아 번거로움이 있었습니다.
또한 Enum 값이 많을 경우 설명 칸이 너무 길어져 가독성에도 좋지 않아 문서를 보는 입장에서도 불편하다는 피드백이 있었습니다.
이러한 불편함을 개선하기 위하여 아래와 같이 두가지 목표를 세우고 개선 작업을 진행하였습니다.
1. 한 눈에 값을 파악할 수 있도록 문서 UX 개선
2. 작성의 번거로움 개선
팝업 형태로 Enum값 확인 UX 개선하기
위에서도 언급한 Spring REST Docs에 날개를... (feat: Popup) 아티클을 통하여 팝업을 통하여 Enum 값에 대하여 표기가 가능하다는 것을 알게 되었고, 참고하여 한번 예제에 적용해보겠습니다.

위 사진과 같이 docinfo.html 파일을 만들어 주고 자바스크립트 코드를 작성해줍니다.
<script>
function ready(callbackFunc) {
if (document.readyState !== 'loading') {
callbackFunc();
} else if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', callbackFunc);
} else {
document.attachEvent('onreadystatechange', function () {
if (document.readyState === 'complete') {
callbackFunc();
}
});
}
}
function openPopup(event) {
const target = event.target;
if (target.className !== "popup") {
return;
}
event.preventDefault();
const popupWidth = 500;
const popupHeight = 600;
const left = (window.innerWidth - popupWidth) / 2 + window.screenX;
const top = (window.innerHeight - popupHeight) / 2 + window.screenY;
window.open(target.href, target.text, `left=${left}, top=${top}, width=${popupWidth}, height=${popupHeight}, status=no, menubar=no, toolbar=no, resizable=no`);
}
ready(function () {
const el = document.getElementById("content");
el.addEventListener("click", event => openPopup(event), false);
});
</script>
위 코드에 대하여 간단하게 설명하자면, 클래스 이름이 "popup"인 요소의 클릭 이벤트를 잡아 지정한 사이즈의 팝업을 띄우는 코드입니다.
다음으로는 팝업을 사용할 문서에 docinfo에 대한 옵션을 추가해줍니다.

저의 경우 여러개의 문서가 아닌 include를 통하여 index.adoc 파일 하나로 API 문서를 관리하고 있었기에 예제에서도 index.adoc 파일에 해당 옵션을 추가해주었습니다.
이렇게하면 html 파일로 변환 시 공통적으로 작성한 자바스크립트 코드를 공통 헤더로 가질 수 있게 됩니다.
Asciidoc에서는 링크를 걸 수 있는 문법을 지원해주는데, Enum에 대한 adoc 파일을 작성하고, 테스트 코드 작성 시 Enum 필드의 description() 부분에 링크를 걸어주면 팝업을 통해 띄울 수 있게 됩니다.
무슨말인지 감이 잘 안오실 수 있을 것 같아 예제를 준비했습니다.
Asciidoc 링크 문법
// Asciidoc 링크 문법
link:${HTML경로}[${보여질 텍스트},role="popup"]
// 예제에 사용할 링크
link:enums/ExampleType.html[샘플 타입,role="popup"]
Enum에 대한 .adoc 파일 작성

예제에서 ExampleType이라는 Enum에 대하여 예제를 작성할 것이기에, ExampleType.adoc 파일을 생성해줍니다.
저는 다른 문서들과 분리하기 위하여 enums 라는 디렉토리 하위에 생성해주었습니다.
ExampleType.adoc
ifndef::snippets[]
:snippets: build/generated-snippets
endif::[]
= ExampleType
:doctype: book
ExampleType
|===
|이름|설명
|`+TYPE1+`
|샘플 타입1
|`+TYPE2+`
|샘플 타입2
|===

생성한 ExampleType.adoc 파일에 위와 같이 Enum에 대하여 설명을 작성해줍니다.
테스트코드에서 description 부분에 asciidoc 링크 적용

빌드 이후에 ExampleType.adoc 파일이 ExampleType.html파일로 변환될 것이고, 테스트코드에 해당 파일의 경로와 링크의 대체텍스트 "샘플 타입"이라고 지정해주었습니다. role 부분은 스니펫이 html로 변환될 때 class 이름으로 변경되며, 위에서 class 이름이 popup으로 되어있는 요소에 대하여 클릭이벤트를 처리해주었기에 role="popup"이라고 명시해줍니다.
문서 상 적용

여기까지 잘 따라오셨다면, 위 사진처럼 문서에서 팝업 형태로 Enum에 대한 값을 확인할 수 있습니다.
어찌저찌 팝업 형태로 띄우는 것 까지는 적용했지만, 팀에 적용하기에는 아래와 같은 이유로 어려운 상황입니다.
1. Enum마다 adoc 파일을 만들어주어야 하기에 기본적으로 Asciidoc 문법에 대한 이해가 필요하다.
2. 문서 작성 시 description()에 링크를 문자열로 수동 작성해야하기에 오탈자와 같은 실수가 있을 수 있다.
3. 문서 하나를 작성하기 위해서 너무 많은 리소스가 든다.
결국 위 3가지의 문제를 해결하지 못한다면 오히려 적용하지 않는것만 못하다고 생각했고. 2편에서는 이러한 문제점들을 해결한 내용을 소개해드리겠습니다.
'개발 > Spring' 카테고리의 다른 글
Spring RestDocs 개선기(2) - 리플렉션을 이용한 Enum 문서 작성 자동화 (1) | 2025.01.29 |
---|---|
Inner Class를 Bean으로 등록할 수 있는 경우와 없는 경우 (1) | 2024.11.13 |
예제 코드로 알아보는 Spring RestDocs (6) | 2024.11.13 |
로그추적기 - 3. 필드동기화 (0) | 2023.08.17 |
로그추적기 - 2. 파라미터로 동기화 (0) | 2023.08.17 |
개발을 하며 만났던 문제들과 해결 과정, 공부한 내용 등을 기록합니다.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!