현재 프로젝트는 RestDocs 기반의 테스트 기반으로 만들어진 API 문서화를 진행했습니다.
rest docs 는 매우 치명적인 문제가 있는데 예쁘지가 않습니다.
물론 신뢰성이 있다는 장점이 있긴 하지만 그래도 아쉬운 부분이라
계속 신경 쓰고 있었는데.
마침 프로젝트 개선 과정을 시작하게 되서 같이 진행하게 되었습니다.
이 주제는 레퍼런스가 매우 많아서 진행하실분들은 참고하시면 좋을것 같습니다.
컬리 블로그 Spring REST Docs
SwaggerUI + Spring REST Docs 함께 사용하기(feat. Rest Assured)
우아한형제들 - Spring Rest Docs 적용
참조 레퍼런스들도 매우 많고 저도 이 레퍼런스들을 참고해서 진행했습니다.
워낙 좋은 자료들이 많아 적용하실 분들은 제 글이 아닌 다른 레퍼런스를 참조해서 하시는걸 추천 드리고
저의 경우의 글을 정리하겠습니다.
현재 프로젝트의 문서 정리의 기본적인 맥락은 매우 간단합니다
Restdocs 기반의 문서화는 테스트 후 → asciidoc 스닙핏 파일 생성 → 스닙핏 취항 정리 → html 파일 생성 → html 파일을 웹에 띄워서 확인
이러한 흐름으로 진행됩니다.
여기서 asciidoc 파일이 아닌 openAPI를 활용해 yaml 형태의 데이터를 추출하고
이를 활용해 swagger-ui에 적용하는 방식으로 진행하였습니다.
plugins {
id "org.asciidoctor.jvm.convert" version "3.3.2"
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
asciidoctorExt
}
dependencies {
// RestDocs
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
}
ext { // 전역 변수
snippetsDir = file('build/generated-snippets')
}
test {
outputs.dir snippetsDir
}
asciidoctor {
inputs.dir snippetsDir
configurations 'asciidoctorExt'
sources { // 특정 파일만 html로 만든다.
include("**/index.adoc")
}
baseDirFollowsSourceFile() // 다른 adoc 파일을 include 할 때 경로를 baseDir로 맞춘다.
dependsOn test
}
bootJar {
dependsOn asciidoctor
from("${asciidoctor.outputDir}") {
into 'static/docs'
}
}
tasks.register('cleanDocs', Delete) {
delete 'docs'
}
tasks.register('copyRestDocs', Copy) {
dependsOn asciidoctor, cleanDocs
from file("build/docs/asciidoc/index.html") // 복사할 파일 위치
into 'docs' // 경로 수정
}
build {
dependsOn copyRestDocs
}
주석도 달아놓았지만 간단하게 설명하자면
asciidoc 파일을 html 파일로 변환하는 과정을 진행합니다.
또한 저는 github-pages를 통한 별도로 문서를 관리하고 있어서
build.gradle에 github-pages 관련된 내용도 있습니다.
${rootDir}/docs 폴더에 html 파일이 생성되고 github에 올라가면 자동적으로 배포되게 됩니다.
dependsOn 과 같은 문법은 gradle의 task 관련 문법입니다.
선행 task가 끝나야만 진행되는 task를 의미합니다.
예를 들어 test task가 끝나야만 asciidoctor task가 진행되고
asciidoctor task가 끝나야만 bootJar task가 진행됩니다.
plugins {
id 'com.epages.restdocs-api-spec' version '0.18.2' //rest-docs-openapi3
id 'org.hidetake.swagger.generator' version '2.18.2' //swagger - ui
}
swaggerSources {
sample {
setInputFile(file("docs/openapi3.yaml"))
}
}
dependencies {
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' // mockmvc
testImplementation "org.springdoc:springdoc-openapi-ui:1.6.11" // restdocs-openapi3
testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.18.2' // restdocs-openapi3
swaggerUI 'org.webjars:swagger-ui:5.9.0'
}
openapi3 {
server = 'http://localhost:8080'
title = 'Petlink API'
description = 'Petlink API description'
version = '0.1.0'
format = 'yaml'
outputDirectory = "docs"
}
tasks.withType(GenerateSwaggerUI).configureEach {
dependsOn 'openapi3'
doFirst {
def swaggerUIFile = file("${openapi3.outputDirectory}/openapi3.yaml")
def securitySchemesContent = " securitySchemes:\n" + \
" APIKey:\n" + \
" type: apiKey\n" + \
" name: Authorization\n" + \
" in: header\n" + \
"security:\n" +
" - APIKey: [] # Apply the security scheme here"
swaggerUIFile.append securitySchemesContent
}
}
// 생성된 openapi3 스펙을 기반으로 SwaggerUISample 생성 및 static/docs 패키지에 복사
bootJar {
dependsOn generateSwaggerUISample
doLast {
copy {
from "${generateSwaggerUISample.outputDir}" // 'swagger-ui-sample' 디렉토리
into "${project.rootDir}/docs" // 프로젝트 루트의 'docs' 디렉토리로 복사
}
}
println("Files copied from ${generateSwaggerUISample.outputDir} to ${project.rootDir}/docs")
}
변경된 내용입니다. 기본적으로 이전에 asciidoc 파일을 html 파일로 변환하는 과정을 전부 제거 후
openapi3를 활용해 yaml 파일을 생성하고 이를 기반으로 swagger-ui를 생성하는 방식으로 변경하였습니다.
이 과정중 매우 헷갈리는 부분이 경로였는데 저는 깃허브 페이지의 경로를 맞추기 위해
openapi3의 outputDirectory를 docs로 변경하였습니다.
이 경로는 저의 경우에는 ${rootDir}/docs로 설정되어 있습니다.
여러분도 하다가 뭔가 꼬이면 경로를 주의깊게 보는 것도 좋습니다.
별도로 테스트 코드경우 wapper를 활용해 선언부분만 변경하면 되어 큰 문제 없이 진행되었습니다.
관련해서 소스코드가 필요하신 분들은 아래의 링크를 참조하시면 됩니다.
petlink/pr/50 : https://github.com/f-lab-edu/petLink/tree/pr/50/restdocs-openapi3
성공적으로 예쁜 api docs를 만들었습니다.
아쉬운 점은 swagger-ui를 활용해 만든 api docs는 커스텀에 제약이 좀 많아 아쉬운것 같습니다.
그래도 테스트 기반의 신뢰성과 테스트까지 가능한 장점이 있어서
여러분들도 이 방식을 고려해보시는걸 추천드립니다.