[Swagger] 스프링 3.x Swagger 적용(With. SpringDocs)
Develop/Spring 2024. 1. 31. 22:08

[Swagger] 스프링 3.x Swagger 적용(With. SpringDocs)

@Beemo9
목차
스프링 버전이 3.x대로 넘어오면서 기존에 사용하던 Springfox가 호환이 되지않는 문제가 있습니다.
해결을 위한 SpringDocs 사용법에 대한 가이드 포스팅

 

예전 스프링 2.x대에서 사용하던 Springfox 라이브러리가 메인테이너되지 않는 문제가 발생하여 레퍼런스를 참조하던 중 3.x에서 Swagger를 적용하기 위해서는 SpringDocs 라이브러리를 활용하여 구축하여야 한다는 솔루션을 찾게 되었습니다.

이번 포스팅은 처음 써보는 SpringDocs에 대한 기본 작성 가이드를 구축하기 위한 포스팅이며, 오타/오역/오류가 있다면 언제든지 말씀하여 주시면 감사하겠습니다.

 

Swagger 구축 환경

SpringBoot : 3.2.2
JDK : 17
build Tools : gradle
Editor : InteliJ

 

 


build.gradle 의존성 추가

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'	//Swagger

 

Springboot 3.x 버전에서 스웨거 적용을 위한 의존성을 한 줄 추가해줍니다.

 


Config 클래스 파일 작성

Swagger 사용을 위한 설정 파일을 작성해줍니다.

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration    // 스프링 실행시 설정파일 읽어드리기 위한 어노테이션
public class SwaggerConfig {

    @Bean
    public OpenAPI openAPI() {
        return new OpenAPI()
                .components(new Components())
                .info(apiInfo());
    }

    private Info apiInfo() {
        return new Info()
                .title("CodeArena Swagger")
                .description("CodeArena 유저 및 인증 , ps, 알림에 관한 REST API")
                .version("1.0.0");
    }
}

 

@Configuration으로 빈 등록과 동시에 설정파일임을 알립니다.

apiInfo 메서드의 경우 아래 그림과 같이 제목, 설명, 버전과 같이 Swagger에서 보여줄 API 정보를 설정하는 부분입니다.

 

혹은 application.yml이나 application.properties 설정으로 대신할 수 있습니다.

위는 기본적인 설정이며, 자세한 설정은 다음 공식문서를 참조하시면 좋을 것 같습니다.

https://springdoc.org/#properties

 

OpenAPI 3 Library for spring-boot

Library for OpenAPI 3 with spring boot projects. Is based on swagger-ui, to display the OpenAPI description.Generates automatically the OpenAPI file.

springdoc.org

 

 


GUI Swagger 접속

 

http://서버주소:포트번호/swagger-ui/index.html#/

해당 URL로 접속을 하면 상단 그림처럼 접속 후 설정된 Swagger를 확인할 수 있습니다.

 

혹시라도 접속이 안된다면 application 설정에서 아래와 같은 context path를 변경하는 구문이 없는지 확인합시다.

server.servlet.context-path=/api

만약 context Path가 설정되어 있다면 URL에 추가하여 접속하시면 됩니다.

 

혹은 임의의 경로로 Swagger 접속 URL을 변경하고 싶다면 아래와 같은 속성을 추가하면 됩니다.

springdoc.swagger-ui.path=/swagger-ui.html

위 설정 적용 시 최종 URL : 서버주소:포트/swagger-ui.html

 


Annotation

직접적인 적용에 앞서 자주 사용하는 각 태그에 대해 설명하겠습니다.

 

@Tag

API Endpoint가 어떤 그룹에 속하는지 알려주는 그룹핑 어노테이션입니다.

> Controller단에 설정하면 해당 Controller의 정보를 나타낼 수 있습니다.

 

다음으로는 @Tag를 Controller에 적용했을 경우의 과정입니다.

@RestController
@RequiredArgsConstructor
@Tag(name = "알림 API", description = "컨트롤러에 대한 설명입니다.")
@RequestMapping("/alarm")
public class AlarmController implements AlarmControllerDocs{

	...
    ...
}

 

아래는 Controller 상단 어노테이션으로 위와 같이 @Tag를 설정해주었을 때의 Swagger 문서 모습입니다.

 


@Operation

현재 REST API Endpoint에 대한 요약, 설명 등을 지정하는 용도로 사용됩니다.
아래는 사용 가능한 옵션 목록입니다.

 

✴︎ operationId: Endpoint 아이디
✴︎ summary: 간단한 설명으로 Swagger-ui의 Endpoint 상단에 노출됩니다.
✴︎ description: 엔드포인트 상세 설명입니다.
✴︎ tags: 현재 Endpoint가 어떠한 tag 그룹에 속한지 알려주는 속성입니다.
✴︎ response: 응답코드, 응답 타입 등을 지정합니다.
✴︎ security: 보안방법에 대한 설정을 지정합니다.

 

다음으로는 @Operation를 /alarm/receive이라는 Endpoint에 적용했을 경우의 과정입니다.

@Operation(summary = "수신함 리스트", description = "파라미터로 받은 유저가 수신한 알림 목록을 최신순으로 정렬하여 전달")
@GetMapping("/receive")
    public ResponseEntity<AlarmResultDto> receive(@RequestParam String userId) {
    	...
    }

 

아래는 어노테이션 적용 시 모습입니다.


@Parameter

해당하는 Endpoint의 파라미터의 타입과 입력에 대해 설명을 지정합니다.

아래는 사용 가능한 옵션 목록입니다.

✴︎  name: 파리미터 이름

✴︎  description: 파리미터 설명

✴︎  required: 필수/선택 여부 (true이면 필수, false이면 선택입니다.)

✴︎  in: 파리미터의 타입을 지정합니다.

    ✴︎  ParameterIn.QUERY: 요청 쿼리 파라미터입니다.

    ✴︎  ParameterIn.HEADER: 요청 헤더에 전달되는 파라미터입니다.

    ✴︎  ParameterIn.PATH: PathVariable 에 속하는 파라미터입니다.

    ✴︎  값없음: RequestBody에 해당하는 객체 타입의 파라미터를 나타냅니다.

 

다음으로는 @Parameter를 코드에 적용했을 경우의 과정입니다.

@Operation(summary = "알림 송신", description = "필요 파라미터 : 알림타입, 수신자ID, 송신자ID, 알림내용 || 알림타입이 1 또는 2라면 status=요청 대기, 3,4라면 status=처리 완료")
    @Parameter(name = "alarmType", description = "보내는 알림의 타입")
    @Parameter(name = "toId", description = "받는 유저의 아이디")
    @Parameter(name = "fromId", description = "보내는 유저의 아이디")
    @Parameter(name = "alarmMsg", description = "알림의 내용")
    @Parameter(name = "alarmStatus", description = "알림의 처리상태, Required = false || 요청 시 보내지 않아도 자동 처리됨.")
    @PostMapping("/send")
    public ResponseEntity<?> send(@RequestBody AlarmSendDto alarmSendDto) {

        ...
    }

 

메서드 상단 어노테이션도 가능하지만 매개변수 단에도 @Parameter가 적용됩니다.

 

아래는 어노테이션 적용 시 모습입니다.

 


@Response

해당하는 Endpoint의 파라미터의 응답 구조를 나타냅니다.

아래는 사용 가능한 옵션 목록입니다.

✴︎  responseCode : HTTP 상태 코드입니다.

✴︎  description : 응답 결과 구조에 대한 설명입니다.

✴︎  content : 응답 페이로드 구조입니다.

✴︎  schema : 페이로드에서 사용하는 객체 스키마입니다.

 

다음으로는 @Response를 코드에 적용했을 경우의 과정입니다.

	@Operation(summary = "알림 송신", description = "필요 파라미터 : 알림타입, 수신자ID, 송신자ID, 알림내용 || 알림타입이 1 또는 2라면 status=요청 대기, 3,4라면 status=처리 완료")
    @ApiResponse(responseCode = "200", description = "수신함 조회 성공", content = @Content(schema = @Schema(implementation = AlarmReceiveDto.class)))
    @PostMapping("/send")
    public ResponseEntity<?> send(@RequestBody AlarmSendDto alarmSendDto) {

        ...
    }

 

responseCode, description 옵션 외에도 schema를 content로 활용도 한번 해보았습니다.

 

아래는 어노테이션 적용 시 모습입니다.

 


 

@Schema

DTO 객체를 설명을 추가하는 어노테이션입니다.

아래는 사용 가능한 옵션 목록입니다.

✴︎  description : 멤버변수에 대한 설명입니다.
✴︎  defaultValue : 기본값을 설정하는 옵션입니다.

 

다음으로는 @Schema를 코드에 적용했을 경우의 과정입니다.

 

첫번째로 DTO에 대한 @Schema 설명만 추가했을 경우의 모습입니다.

@Data
@Schema(description = "알림 송신 DTO")
public class AlarmSendDto {
    private int alarmType;
    private int toId;
    private int fromId;
    private String alarmMsg;
    private String alarmStatus;
}

 

 

 

 

이제 각 멤버변수에도 @Schema를 달아준 뒤의 모습을 보겠습니다.

@Data
@Schema(description = "알림 송신 DTO")
public class AlarmSendDto {
    
    @Schema(description = "알림 타입 >> 1 : 문제 생성 요청, 2 : 문제 수정 요청, 3 : 게임 초대, 4 : 공지사항")
    private int alarmType;
    
    @Schema(description = "수신자 ID")
    private int toId;
    
    @Schema(description = "송신자 ID")
    private int fromId;
    
    @Schema(description = "보내는 내용")
    private String alarmMsg;
    
    @Schema(description = "알림 상태", defaultValue = "요청 대기")
    private String alarmStatus;
}

 

확실히 이전 보다 더 알아보기 쉬운 문서로 바뀐걸 확인할 수 있습니다.

 

 


최적화

위에서 알아본 어노테이션들을 활용하면 좀 더 알아보기 쉬운 Swagger 문서를 작성할 수 있습니다.

하지만 Controller단에 덕지덕지 어노테이션을 활용하여 작성하게 된다면 아무래도 코드의 가독성을 떨어뜨릴 수 있다는 단점이 있습니다.

 

해당 부분을 해결할 수 있도록 추가적인 Swagger 전용 인터페이스를 추가하여 Controller는 해당 인터페이스를 상속받게만 한다면 Controller의 가독성을 헤치지 않으면서 수많은 어노테이션을 포함한 읽기 쉬운 Swagger 문서를 작성할 수 있습니다.

 

 

다음은 기존 Controller에서 어노테이션 매핑을 한 상태입니다.

	@Operation(summary = "알림 송신", description = "필요 파라미터 : 알림타입, 수신자ID, 송신자ID, 알림내용 || 알림타입이 1 또는 2라면 status=요청 대기, 3,4라면 status=처리 완료")
    @Parameter(name = "alarmType", description = "보내는 알림의 타입")
    @Parameter(name = "toId", description = "받는 유저의 아이디")
    @Parameter(name = "fromId", description = "보내는 유저의 아이디")
    @Parameter(name = "alarmMsg", description = "알림의 내용")
    @Parameter(name = "alarmStatus", description = "알림의 처리상태, Required = false || 요청 시 보내지 않아도 자동 처리됨.")
    @PostMapping("/send")
    public ResponseEntity<?> send(@RequestBody AlarmSendDto alarmSendDto) {

        return new ResponseEntity<AlarmResultDto>(alarmService.send(alarmSendDto), HttpStatus.OK);
    }

메서드 내부로직은 단 한줄인데 어노테이션만 한가득 있는 모습이 충분히 Controller의 가독성을 방해하고 있다는 걸 알 수 있습니다.

 


이제는 인터페이스를 활용하여 Swagger와 가독성을 모두 챙겨 보도록 하겠습니다.

AlarmControllerDocs.Interface

@Tag(name = "알림 API", description = "알림에 관한 Controller **알림 타입 = 1 : 문제 생성 요청, 2 : 문제 수정 요청, 3 : 게임 초대, 4 : 공지사항 **")
public interface AlarmControllerDocs {

    @Parameters(value = {
            @Parameter(name = "alarmType", description = "보내는 알림의 타입"),
            @Parameter(name = "toId", description = "받는 유저의 아이디"),
            @Parameter(name = "fromId", description = "보내는 유저의 아이디"),
            @Parameter(name = "alarmMsg", description = "알림의 내용"),
            @Parameter(name = "alarmStatus", description = "알림의 처리상태, Required = false || 요청 시 보내지 않아도 자동 처리됨.")
    })
    @Operation(summary = "알림 송신", description = "필요 파라미터 : 알림타입, 수신자ID, 송신자ID, 알림내용 || 알림타입이 1 또는 2라면 status=요청 대기, 3,4라면 status=처리 완료")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "201", description = "알림 송신 완료", content = @Content(schema = @Schema(implementation = AlarmSendDto.class)))
    })
    public ResponseEntity<?> send(AlarmSendDto alarmSendDto);
    
}

인터페이스에서 send 메서드에 대한 Swagger 설정을 똑같이 완료했습니다.

 

이제 Controller로 돌아가서 해당 인터페이스를 상속 후 Swagger-ui를 확인해보겠습니다.

AlarmController

@RestController
@RequiredArgsConstructor
@RequestMapping("/alarm")
public class AlarmController implements AlarmControllerDocs{

	@PostMapping("/send")
    public ResponseEntity<?> send(@RequestBody AlarmSendDto alarmSendDto) {
		...
    }
    
}

 

Swagger-ui.html

 

정상적으로 Swagger 문서가 작성된 걸 확인할 수 있습니다.

이런 방식으로 인터페이스를 활용하여 Controller 가독성을 지키며, 깔끔한 Swagger문서를 작성할 수 있습니다.

 

추가로 위 인터페이스에서 사용한 것처럼 @Parameters 및 @ApiResponses 어노테이션을 활용하면 N개의 @Parameter, @ApiResponse를 묶어서 표현할 수 있으니 참고하면 좋을 것 같습니다.

Beemo9
@Beemo9
개발 기술 블로그, Dev 포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!
image