본문 바로가기
프로젝트/DailycluB

Springdoc Swagger로 API 문서 작업하기 + 이슈 핸들링

by 넬준 2022. 9. 16.

 저번 Pre-Project 때 API 문서 작업에 애를 먹었었다. 

 회고에도 적었지만 처음 배운 API 문서 툴이 Spring Rest Docs라서 Pre 때는 이를 이용했다. 굉장히 좋은 툴인 것은 맞다. 하지만 테스트 코드 작성이 익숙치 않아서 초반에 시간을 많이 잡아 먹었고, 또 API 스펙에 변화가 있을 때 즉각적으로 반영하기가 힘들었다. 그래서 후반에 가서는 API 문서가 제 기능을 하지 못했었다. 

 

 그래서 이번 프로젝트에서는 Swagger를 이용해서 API 문서 작업을 진행하기로 했다.

 Spring에서 Swagger 기능을 제공하는 라이브러리는 Springfox, Springdoc 이렇게 2가지가 있다. 그 중 Springdoc 라이브러리를 사용했다. 

 

 API 문서 작업하면서 겪은 이슈를 정리하고자 한다.

 

1. Response Body에 Json으로 넘겨줄 객체의 자료형에 관한 이슈 (with Generic)

 리스트를 조회할 때 페이징 처리를 위해 응답 데이터 리스트를 MultiResponseDto<T> 클래스로 감싸서 넘겨준다. 

@Getter
public class MultiResponseDto<T> {
    private List<T> data; //응답 데이터 리스트
    private PageInfo pageInfo; //페이지 정보

    public MultiResponseDto(List<T> data, Page page) {
        this.data = data;
        this.pageInfo = new PageInfo(page.getNumber() +1, page.getSize(), page.getTotalElements(), page.getTotalPages());
    }

    public MultiResponseDto(List<T> data) {
        this.data = data;
    }
}

 

 @ApiResponse의 "content" property에서 implementation class에 class literal을 적어야 하기 때문에 generic을 가지고 있다면 Swagger에서 자료형을 특정할 수 없기 때문에 값을 덜 채워준다. 

 

data 리스트의 BookmarkDto.ResonseWIthProgram 자료형을 읽어올 swagger가 읽어올 수 없다.

 

이를 해결하기 위해, "content" property를 작성하지 않고 대신에 리턴자료형을 Generic을 채워서 작성해주면 Swagger가 자료형을 특정할 수 있어서 Api에 전부 포함되어서 나온다.

 

data 내부가 채워진 것을 볼 수 있다.

 

 

2. 서로 다른 엔티티의 Schema가 같게 나오는 이슈

 

프로그램 등록 요청 API 스펙
북마크 등록 요청 API 스펙

 

 서로 다른 엔티티의 API에서 각자의 스펙이 적용되지 않고 하나로 중복되어 나오는 이슈가 있었다. 위에서는 프로그램 등록 요청 API 스펙과 북마크 등록 요청 API 스펙이 서로 같게 나오는 상황이다.

 

 이는 Schema를 보면 이유를 알 수 있다.

 

 

 Schema에서 보면 해당 요청 API 스펙에 대한 URI가 서로 같기 때문이다. 

 현재 클래스의 구조를 보면, Program 관련 dto 클래스를 하나로 묶는 PrgramDto 클래스가 있고 내부 클래스로 Post 클래스가 있고, Bookmark도 마찬가지로 BookmarkDto 클래스 내부에 Post 클래스가 있다. 클래스 명을 URI에 그대로 사용하기 때문에 서로 다른 Post 클래스가 같은 URI을 가지게 된 상황이다. 

 

 이를 해결하기 위해서는 @Schema annotation의 "name" property를 서로 다르게 작성해주면 된다.

 

//프로그램
public class ProgramDto {
   @Schema(name = "프로그램 등록 요청 API 스펙", title = "프로그램 등록 요청 API 스펙")
   public static class Post {
   ...
   }

//북마크
public class BookmarkDto {
   @Schema(name = "북마크 등록 요청 API 스펙", title = "북마크 등록 요청 API 스펙")
   public static class Post {
   ...
   }

 

 

 name을 서로 다르게 지정해주면 자신에게 맞는 API 스펙으로 바뀌는 것을 볼 수 있다. 요청/응답 등에서 중복해서 나오는 API 스펙이 있다면 @Schema에 "name" property를 알맞게 줘보자.

 

3. @ModelAttribute 붙은 파라미터 DTO 클래스의 필드 정보를 표시하지 않는 이슈

 프로그램 리스트 조회할 때 Query Parameter로 받을 검색/필터링 조건이 여러 개라서 SearchFilterDto라는 클래스를 만들었다.

@Schema(name = "프로그램 검색/필터 API 스펙", title = "프로그램 검색/필터 API 스펙")
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SearchFilterDto {
    @Schema(description = "검색 키워드")
    private String keyword;

    @Schema(description = "지역 번호", name = "location-id")
    private Long locationId;

    @Schema(description = "프로그램 시작 날짜", name = "program-date", pattern = "yyyy-MM-dd", example = "2022-09-18")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate programDate;

    @Schema(description = "프로그램 상태", allowableValues = {"POSSIBLE", "IMMINENT"}, name = "program-status")
    private String programStatus;
}

 

 ProgramController에서 프로그램 리스트 조회하는 핸들러 메서드에서 해당 파라미터들을 SearchFilterDto로 매핑하려고 했다.

 

@GetMapping
public 리턴자료형 getPrograms(@Parameter(description = "페이지 번호") @RequestParam int page,
                             @Parameter(description = "한 페이지당 프로그램 수") @RequestParam int size,
                             @ModelAttribute SearchFilterDto searchFilterDto) {
...
}

 

 하지만 Swagger에서는 SearchFilterDto 클래스의 각 파라미터(필드)를 보여주지 않고 searchFilterDto로 묶어서 보여줬다.

 

SearchFilterDto 필드를 하나씩 보고 싶은건데...

 

 이를 해결하기 위해서는, @ModelAttribute 앞에 @ParameterObject를 붙여주면 된다.

@GetMapping
public 리턴자료형 getPrograms(@Parameter(description = "페이지 번호") @RequestParam int page,
                             @Parameter(description = "한 페이지당 프로그램 수") @RequestParam int size,
                             @ParameterObject @ModelAttribute SearchFilterDto searchFilterDto) {
...
}

 

 


 

참고

 

https://blog.jiniworld.me/91

https://dev-youngjun.tistory.com/258

https://oingdaddy.tistory.com/272?category=824422 

https://www.dariawan.com/tutorials/spring/documenting-spring-boot-rest-api-springdoc-openapi-3/

 

 

댓글