[Spring] Rest Docs와 Swagger UI개발/기능 개발2023. 4. 11. 23:39
Table of Contents
기존 Swagger 사용의 단점
- 비즈니스 로직에 문서화 코드가 들어가야한다.
- 검증과정을 거치지 않았기 때문에 불안정하다.
- MSA와 같이 분산된 서비스에서 각각의 문서가 전부 다른 주소를 갖기 때문에 일일이 찾아봐야한다.
해결 방안
- Rest Docs를 통해 테스트 코드에서 문서화 코드를 작성하여, 프로덕션 코드와 분리하고 검증된 문서 생성
- Rest Docs에 의해 생성된 open api spec 추출하여 Swagger UI로 api spec을 보내 마이크로서비스들의 API 문서 통합
적용
Rest Docs Gradle 의존성 & 빌드 스크립트 추가
//(1)
plugins {
id "org.asciidoctor.jvm.convert" version "3.3.2"
}
//(2)
configurations {
asciidoctorExt
}
//(3)
dependencies {
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
}
//(4)
ext {
snippetsDir = file('build/generated-snippets')
}
//(5)
test {
outputs.dir snippetsDir
}
//(6)
asciidoctor {
inputs.dir snippetsDir
configurations 'asciidoctorExt'
dependsOn test
}
//(7)
asciidoctor.doFirst{
delete file('src/main/resources/static/docs')
}
//(8)
task copyDocument(type: Copy){
dependsOn asciidoctor
from file("build/docs/asciidoc")
into file("src/main/resources/static/docs")
}
//(9)
bootJar {
dependsOn copyDocument
from ("${asciidoctor.outputDir}/html5") {
into 'static/docs'
}
}
테스트코드 작성
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs
class OrderServiceApplicationTests {
@Autowired
private MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
@Test
@DisplayName("사전 검증")
void prepareVerificationTest() throws Exception{
//given
int amount = 9900;
//when
mockMvc.perform(post("/payment/prepare/{amount}",amount)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
//then
.andExpect(status().isOk())
// rest docs 문서화
.andDo(document("prepareVerification",
responseFields(
fieldWithPath("code").type(JsonFieldType.NUMBER).description("응답 코드"),
fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메세지").optional(),
fieldWithPath("response").type(JsonFieldType.OBJECT).description("응답 데이터"),
fieldWithPath("response.merchant_uid").type(JsonFieldType.STRING).description("결제 아이디"),
fieldWithPath("response.amount").type(JsonFieldType.NUMBER).description("금액"),
fieldWithPath("response.token").type(JsonFieldType.STRING).description("토큰")
)
));
}
@Test
@DisplayName("사후 검증")
void completeVerificationTest() throws Exception{
//given
RequestPayment request = new RequestPayment(
"imp_442601173622",
"imp464605542023-04-05T16:43:08.675766",
"5b5825a581c67c7c345811f74b2b27be216b91a0",
"ABC@Email.com",
1,
9900
);
//when
mockMvc.perform(post("/payment/completion")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
//then
.andExpect(status().isOk())
.andDo(document("completionVerification",
requestFields(
fieldWithPath("imp_uid").type(JsonFieldType.STRING).description("포트원 고유번호"),
fieldWithPath("merchant_uid").type(JsonFieldType.STRING).description("결제 고유번호"),
fieldWithPath("token").type(JsonFieldType.STRING).description("엑세스 토큰"),
fieldWithPath("userEmail").type(JsonFieldType.STRING).description("유저 이메일"),
fieldWithPath("itemId").type(JsonFieldType.NUMBER).description("아이템 아이디"),
fieldWithPath("amount").type(JsonFieldType.NUMBER).description("총액")
),
responseFields(
fieldWithPath("status").type(JsonFieldType.STRING).description("결제 상태"),
fieldWithPath("message").type(JsonFieldType.STRING).description("메세지").optional()
)
));
}
@Test
@DisplayName("환불")
void refund() throws Exception{
//given
RequestRefund request = new RequestRefund(
"ABC@Email.com",
"imp464605542023-04-05T16:43:08.675766",
9900,
"단순 변심"
);
//when
mockMvc.perform(post("/payment/cancel")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
//then
.andExpect(status().isBadRequest())
.andDo(document("refund",
requestFields(
fieldWithPath("userEmail").type(JsonFieldType.STRING).description("유저 이메일"),
fieldWithPath("merchant_uid").type(JsonFieldType.STRING).description("결제 고유번호"),
fieldWithPath("cancel_request_amount").type(JsonFieldType.NUMBER).description("환불 금액"),
fieldWithPath("reason").type(JsonFieldType.STRING).description("환불 사유")
),
responseFields(
fieldWithPath("message").type(JsonFieldType.STRING).description("환불 결과 메세지")
)
));
}
}
문서화 하기
테스트에 성공하면 아래와 같이 build/generated-snippets 폴더에 테스트 코드의 .andDo에 지정한 document이름으로 폴더가 들어있고 그 안에 adoc 파일들이 생성되어 있다.
위 사진처럼 src/docs/asciidoc 폴더 생성 후 adoc 파일을 생성한 다음 문서구조를 작성해준다.
[[api-completionVerification]]
== 사후 검증 서비스(Completion Verification API)
====== 요청 형식
include::{snippets}/completionVerification/http-request.adoc[]
====== Request Body
include::{snippets}/completionVerification/request-body.adoc[]
====== 응답 형식
include::{snippets}/completionVerification/http-response.adoc[]
include::{snippets}/completionVerification/response-fields.adoc[]
====== Response Body
include::{snippets}/completionVerification/response-body.adoc[]
====== Try with curl
include::{snippets}/completionVerification/curl-request.adoc[]
작성이 끝난 후 빌드하면 html 파일이 생성된다.
./gradlew clean bootJar
open api spec 추출
Gradle 의존성 & 빌드 스크립트 추가
plugins {
id 'com.epages.restdocs-api-spec' version '0.16.2'
}
dependencies {
testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.16.2'
}
openapi3 {
server = 'https://localhost:8080'
title = 'Octo Dream'
description = 'OctoDream API Specification'
version = '0.0.1'
format = 'yml'
outputFileNamePrefix = 'payment-service'
outputDirectory = 'src/main/resources/static/docs'
}
테스트 코드에서 import 수정
//import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
//위 코드를 아래 코드로 수정
import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document;
open api 추출
./gradlew openapi3
빌드 성공 시 yml 파일이 생성된다.
Swagger ui로 문서 통합
https://swagger.io/docs/open-source-tools/swagger-ui/usage/installation/
문서 화면
생성된 api spec swagger ui서버에 올리는 방법
docker pull swaggerapi/swagger-ui
sudo docker run -d -p 80:8080 \
--name swagger \
-e URLS_PRIMARY_NAME=user-service \
-e URLS="[{ url: 'docs/user-service.yml', name: 'user-service' }, { url: 'docs/payment-service.yml', name: 'payment-service' }]" \
-v /home/ubuntu/docs:/usr/share/nginx/html/docs/ \
swaggerapi/swagger-ui
'개발 > 기능 개발' 카테고리의 다른 글
[Spring] Filter & Interceptor & AOP (0) | 2023.05.29 |
---|---|
[Spring] IoC & DI (0) | 2023.05.24 |
[인증] JWT(JSON Web Token) (0) | 2023.04.19 |
[Spring] Microservice 관리를 위한 Eureka Server 와 Spring Cloud Gateway (0) | 2023.02.26 |
@뽀글뽀글 개발자 :: 뽀글뽀글 개발 일지
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!