고 평소 API 문서를 적용할때는 Spring Rest Docs를 많이 사용했는데
이번 기회에 Spring Docs + Swagger ui를 적용해서 둘의 장단점 비교를 해보고 싶어서 사용해봤다.
Spring Docs. + Swagger 설정 예제
개발 환경은 Spring Boot 2.6.6 버전에 Gradle을 사용하였다.
1. dependency 설정
dependencies {
...
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation 'org.springdoc:springdoc-openapi-ui:1.6.8'
}
springdoc-openapi-ui 안에는 Swagger-ui가 포함되어있다.
springdoc-openapi-ui는 2022.05.02 기준 1.6.8이 제일 최신 버전이다.
(자세한 정보는 https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-ui 참고)
2. yml 파일 설정
springdoc:
swagger-ui:
enabled: true // swagger ui 사용여부 (굳이 설정하지 않아도 default는 true)
version: 'v1' // API 문서 버전
default-consumes-media-type: application/json // 기본 consume media type
default-produces-media-type: application/json // 기본 produce media type
model-and-view-allowed: true // ModelAndView 허용
프로젝트에 필요한 정보만 설정했다. 엑셀 다운로드 기능이 있기 때문에 ModelAndView를 허용했다.
(자세한 설정 정보는 https://springdoc.org/#properties 참고)
3. Spring Docs 설정
@Configuration
public class SpringDocsConfig {
@Bean
public OpenAPI openAPI(@Value("${springdoc.version}") String version) {
Info info = new Info()
.title("Example API 문서") // 타이틀
.version(version) // 문서 버전
.description("잘못된 부분이나 오류 발생 시 바로 말씀해주세요.") // 문서 설명
.contact(new Contact() // 연락처
.name("beekei")
.email("beekei.shin@gmail.com")
.url("https://devbksheen.tistory.com/"));
// Security 스키마 설정
SecurityScheme bearerAuth = new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.in(SecurityScheme.In.HEADER)
.name(HttpHeaders.AUTHORIZATION);
// Security 요청 설정
SecurityRequirement addSecurityItem = new SecurityRequirement();
addSecurityItem.addList("JWT");
return new OpenAPI()
// Security 인증 컴포넌트 설정
.components(new Components().addSecuritySchemes("JWT", bearerAuth))
// API 마다 Security 인증 컴포넌트 설정
.addSecurityItem(addSecurityItem)
.info(info);
}
}
API 문서의 정보를 설정하고 JWT를 통한 인증을 해야 하므로 Security 설정을 추가해주었다.
위처럼 설정하면 실제 API를 요청하는 것 처럼 토큰으로 인증 후 API를 호출할 수 있다.
4. API 정보 설정
swagger2에서 swagger3로 넘어오면서 어노테이션들이 변경되었다.
공통 Response 설정
API의 반환될 수 있는 호출결과를 @ApiResponse 어노테이션으로 모두 설정해줘야 하는 번거로움이 있어서 한번에 묶은 어노테이션을 구현해 모든 API에 공통적으로 설정해주었다.
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@ApiResponses(value = {
@ApiResponse(responseCode = Success.CODE, description = "API 호출 성공"),
@ApiResponse(
responseCode = NotFound.CODE,
description = "존재하지 않는 API",
content = @Content(schema = @Schema(implementation = NotFoundResponse.class))),
@ApiResponse(
responseCode = RequestNotValid.CODE,
description = "유효성 검증 실패",
content = @Content(schema = @Schema(implementation = RequestNotValidResponse.class))),
@ApiResponse(
responseCode = RequestMethodNotSupport.CODE,
description = "잘못된 Method 요청",
content = @Content(schema = @Schema(implementation = RequestMethodNotSupportResponse.class))),
@ApiResponse(
responseCode = Unauthorized.CODE,
description = "인증 실패",
content = @Content(schema = @Schema(implementation = UnauthorizedResponse.class))),
@ApiResponse(
responseCode = ExpiredToken.CODE,
description = "토큰 유효기간 만료",
content = @Content(schema = @Schema(implementation = ExpiredTokenResponse.class))),
@ApiResponse(
responseCode = Forbidden.CODE,
description = "인가 실패(권한 없음)",
content = @Content(schema = @Schema(implementation = ForbiddenResponse.class))),
@ApiResponse(
responseCode = RegisterFailed.CODE,
description = "데이터 등록 실패",
content = @Content(schema = @Schema(implementation = RegisterFailedResponse.class))),
})
public @interface ApiDocumentResponse {
}
Controller 설정
@Tag(name = "Notice Controller", description = "공지사항 관련 컨트롤러") // Contoller 정보 설정
@RestController
@RequiredArgsConstructor
@RequestMapping(value = "notice")
public class NoticeController {
private final ModelMapper modelMapper;
private final NoticeService noticeService;
@ApiDocumentResponse // 위에서 설정한 공통 Response
@Operation(summary = "registerNotice", description = "공지사항 등록") // API 정보 설정
@PostMapping
public DataApiResponse<NoticeDTO> registerNotice(
@RequestBody @Valid RegisterNoticeRequest request) {
return new DataApiResponse<>(
noticeService.registerNotice(modelMapper.map(request, RegisterNoticeDTO.class));
);
}
@ApiDocumentResponse // 위에서 설정한 공통 Response
@Operation(summary = "getNotice", description = "공지사항 조회") // API 정보 설정
@GetMapping(value = "{noticeId}")
public DataApiResponse<NoticeInfoDTO> getNotice(
@Parameter(description = "조회할 공지사항 ID", required = true) // 파라미터 설정
@PathVariable UUID noticeId) {
return new DataApiResponse<>(noticeService.getNoticeInfo(noticeId));
}
}
@Tag 어노테이션은 컨트롤러의 정보를 기입할 수 있고, @Operation 어노테이션은 API의 정보를 기입 한다.
@Parameter 어노테이션을 RequestParam이나 PathVariable 정보를 기입할 수 있다.
Request, Response 설정
API를 호출할때 보내는 Request나 반환하는 Response 클래스에서 스키마를 설정해줘야 한다.
@Builder
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class RegisterNoticeRequest {
@Schema(description = "제목", example = "공지사항 제목입니다.", required = true)
@NotBlank
private String subject;
@Schema(description = "내용", example = "공지사항 내용입니다.", required = true)
@NotBlank
private String contents;
}
example을 설정하면 해당 API를 테스트할때 기본값으로 사용된다.
5. 결과 확인
Security 인증 컴포넌트를 클릭하여 토큰으로 인증이 가능하다.
다른 설정들도 많지만 기본적인 설정들만 사용해보았다.
Spring Rest Docs와 장단점 비교
두 라이브러리 모두 사용해본 결과
Spring Rest Docs의 장점은 테스트를 진행하고 그 결과를 기반으로 API 문서를 만들기 때문에 API 문서에 관한 관리를 따로 해주지 않아도 된다. 로직이 바뀌면 테스트도 변경되어야 하기 때문에 자동적으로 API 문서도 수정된다.
하지만 UI가 구리고(?) 실질적인 API 호출은 Postman이나 다른 방법을 사용해야 한다.
Spring Docs + Swagger UI의 장점은 간단한 설정방법과 API 호출을 직접 실행할 수 도 있다.
하지만 어노테이션으로 관리할 경우 소스코드가 지저분해지고 어노테이션을 직접 달아줘야하는 번거로움이 있고, 문서로 관리할 경우에는 API가 변경될 시 API 문서도 변경해야하는 번거로움이 생긴다.
개인적인 생각으로는 UI가 조금 구리지만 테스트와 같이 관리할 수 있는 Spring Rest Docs가 비지니스 개발에만 집중하기 좋을 것 같다. 물론 나의 무지로 인해 그렇게 생각할 수도 있다.
단점들을 해결할 수 있는 방법을 알고 계시다면 공유 부탁드립니다!
'Spring' 카테고리의 다른 글
Spring Boot + AOP + Sentry 로깅 및 오류 모니터링 하기 (0) | 2022.07.19 |
---|---|
Jenkins + Docker + Spring Cloud Config 적용기 (4) | 2022.05.24 |
스프링 배치(Spring Batch)를 이용한 데이터 마이그레이션 (2) | 2022.02.24 |
스프링 배치(Spring Batch) 활용하기 (0) | 2022.02.22 |
스프링 배치(Spring Batch) 메타 데이터(Meta-Data) 살펴보기 (0) | 2022.02.22 |