프로그래밍/Spring Boot

[SpringBoot] 프로젝트에 Swagger 적용하기

카프카뮈 2021. 9. 6. 02:34

오늘 글은 다음 목적을 바탕으로 작성되었다.

  • SpringBoot 프로젝트의 문서화를 위해 Swagger를 적용한다.
  • Swagger의 다양한 구성과 설정 방법에 대해 알아본다.
  • Swagger를 통해 컨트롤러, 모델, 요청, 응답을 문서화한다.

Swagger

Swagger란 개발한 REST API를 편리하게 문서화 해주고, 이를 통해서 편리하게 API를 호출해보고 테스트할 수 있는 프로젝트이다. 기본 swagger2 프로젝트 뿐 아니라 다양한 구성을 가지고 있다. MVNRepository에 검색해보자.

이렇게 많은 프로젝트가 있다니!

본 글에서 다룰 주요 구성은 다음과 같다.

  • SpringFox Swagger 2는 적용된 프로젝트의 응답, 요청, 예시 등의 정보를 JSON 쌍으로 만들어준다.
  • SpringFox UI는 Swagger 2를 통해 만든 JSON 쌍을 상호작용하기 좋은 웹페이지로 만들어준다.
  • SpringFox Bean Validator를 적용할 경우, Swagger가 문서화를 진행하면서 Bean Validation Annotation이 적용된 내용에 대한 정보를 추가로 저장한다.
  • SpringFox Data REST는 @Entity와 @Repository를 찾아 엔티티에 대한 사양 정보를 추가로 문서화한다.

그러나, 위의 구성은 모두 하나의 구성을 통해 정리할 수 있다.

// https://mvnrepository.com/artifact/io.springfox/springfox-boot-starter
implementation group: 'io.springfox', name: 'springfox-boot-starter', version: '3.0.0'
  • SpringFox Boot Starter를 gradle에 추가해주면, 이를 통해 기본적으로 SpringFox Swagger 2와 UI를 추가해준다.
  • 원래는 추가로 작성해야 하는 Config 파일을 제외해도 작동된다. 단 Spring Boot를 사용하지 않는다면, 기존처럼 별도의 설정 파일을 추가해야 한다.
  • 만약 classpath에서 spring-boot-starter-data-rest 를 발견하면, SpringFox Data REST를 추가한다.
  • SpringFox Bean Validator 역시 자동으로 추가해준다.

결론적으로, 만약 특정 구성만 사용해야 하는 경우는 각각의 구성에 대한 의존성을 추가하면 된다.

그러나 종합적인 구성을 간편하게 추가하고 Configuration 파일 없이도 간결하게 프로젝트를 관리하고 싶다면,

SpringFox Boot Starter에 대한 의존성을 추가해주면 된다.


Swagger 적용

위에서 언급한 대로, SpringFox Boot Starter를 의존성에 추가해 주었다.

이후 프로그램을 실행한 뒤, uri 뒤에 "/swagger-ui/"를 붙여 실행하면 해당 문서를 확인할 수 있다!

* 주소 끝에 "/"를 안붙이면 404 오류가 발생할 수 있다.

페이지의 시작시 모습. 엄청 이쁘게 만들어진다!!
SpringFox Data REST까지 포함한 탓에, Entity 구성도 추가되었다.
주요 목적인 Controller의 문서화. 각각의 요소들에 대해 테스트해 볼 수 있다.

놀랍게도, 위의 페이지가 gradle에 의존성 하나 추가한 것으로 툭 튀어나온다!!

  • Entity의 경우, 보유한 속성들과 Repository의 구성을 바탕으로 생성된다.
  • Controller의 경우, 함수의 Request/Response 인자를 분석해서 문서화를 진행한다.
  • 현재 별도의 작업을 하지 않아 설명은 없지만, 이것만으로도 충분히 프로젝트를 테스트할 수 있다.

Swagger 커스터마이징

지금의 페이지도 만족스럽지만, 그래도 더 추가해보고 싶은 기능들이 있다.

  • 메인 페이지의 이름과 설명을 추가해주고 싶다.
  • Controller와 그 아래 세부 메소드에 대해 설명을 추가해주고 싶다.
  • 보여주기 싫은 메소드는 감추고 싶다.

위의 세 가지를 이제부터 진행해 보자.

그 전에, 요약삼아 주요 어노테이션 목록을 작성해 보았다.

Annotation 설명
@Api 클래스를 스웨거의 리소스로 표시
@ApiOperation 특정 경로의 오퍼레이션 HTTP 메소드 설명
@ApiParam 오퍼레이션 파라미터에 메타 데이터 설명
@ApiResponse 오퍼레이션의 응답 지정
@ApiModelProperty 모델의 속성 데이터를 설명
@ApiImplicitParam/@ApiImplicitParams 메소드 단위의 오퍼레이션 파라미터를 설명.
@ApiImplicitParams 내에 @ApiImplicitParam를 포함함
@ApiIgnore 메소드나 컨트롤러를 스웨거 문서화에서 제외

SwaggerConfig

일단 SwaggerConfig를 만들어서, Api 문서의 이름과 설명을 추가해보자.

SpringFox Swagger2와 SpringFox Swagger UI를 사용했다면 내용을 더 기재해야 한다.

그러나 우리는 SpringFox Boot Starter를 통해 Configuration을 자동으로 추가했으므로, 프로필 설정만 추가해주자.

package com.umjjal.global.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    private static final String API_NAME = "UMJJAL Project API";
    private static final String API_VERSION = "0.0.1";
    private static final String API_DESCRIPTION = "UMJJAL 프로젝트 명세서";

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.any())              
                .paths(PathSelectors.any())
                .build();
    }

    public ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title(API_NAME)
                .version(API_VERSION)
                .description(API_DESCRIPTION)
                .contact(new Contact("KAFKA", "https://kafcamus.tistory.com/", "dlrjsgml42@naver.com"))
                .build();
    }
}

api 메소드는 Swagger 설정을 저장하는 Docket 빈으로 운영된다.

여기서 apiInfo를 등록해 주고, api와 path를 등록해준다.

위처럼 any로 등록할 경우, 모든 로직에 대해 Swagger 문서화가 수행된다.

만약 특정한 경로에 대해서만 문서화를 하고 싶다면, 이 설정을 수정하는 것을 추천한다.

 

아래의 apiInfo 메소드에서는 ApiInfoBuilder를 통해 ApiInfo 데이터를 생성하여 전달한다.

위에서는 title, version, description 설정을 등록했고, contact에서 나의 이름, url, mail 정보를 등록했다.

위의 설정 적용 이후, 위처럼 타이틀의 설명이 바뀐 것을 알 수 있었다!


API 설명 추가하기

이제 API에 대해 더 자세하게 정보를 전달해 보자!

아래 컨트롤러 코드를 먼저 보자.

@Api(tags = {"게시글 API 정보를 제공하는 Controller"})
@RequestMapping("/api")
@RequiredArgsConstructor
@RestController
public class PostController {
    private final PostService postService;

    @ApiOperation(value = "조건에 맞는 게시글 목록을 반환하는 메소드")
    @ApiImplicitParam(name = "tagIds", value = "검색할 태그 ID를 담은 리스트", dataType = "list")
    @GetMapping("/posts")
    public ResponseEntity<List<PostResponseDto>> getPosts(@RequestParam(required = false) List<Long> tagIds) {
        final List<PostResponseDto> responses = postService.getPosts(tagIds);

        return ResponseEntity.ok()
                .body(responses);
    }

    @ApiOperation(value = "ID가 일치하는 게시글을 반환하는 메소드")
    @ApiImplicitParam(name = "postId", value = "조회할 게시물 ID", required = true, dataType = "long")
    @GetMapping("/posts/{postId}")
    public ResponseEntity<PostResponseDto> getPost(@PathVariable final Long postId) {
        final PostResponseDto response = postService.getPost(postId);

        return ResponseEntity.ok()
                .body(response);
    }
}
  • @Api는 컨트롤러를 지정한다. tags를 통해 설명을 달아줄 수 있다.
  • @ApiOperation은 메소드를 지정한다. value를 통해 설명을 써줄 수 있다.
  • @ApiParam은 메소드 인자에 직접 지정한다. value로 설명을 써 준다. (위 코드에서는 사용 X)
  • @ApiImplicitParam은 @ApiParam때문에 코드가 지저분해 지는 것을 방지한다. 메소드 위에 달아주고, 이름을 통해 매치한다.
    • @ApiImplicitParams를 쓴 뒤, 내부에 여러 개의 @ApiImplicitParam을 써줄 수 있다.
    • 속성은 다양하다. dataType, paramType, name, value, required, defaultValue 등을 제공한다.

실제 실행되는 예시는 다음과 같다. 이전보다 훨씬 가독성이 좋아졌다!

요청과 응답 설명 추가하기

위에서 Controller에 어노테이션을 추가해 설명을 달았다.

그러나, 이것만으로는 부족하다. 요청으로 들어오는 객체에 대해 어떻게 문서화를 할 수 있을까? 그리고 응답값은 어떻게 문서화할 수 있을까?

이를 위해, Swagger는 다양한 어노테이션을 제공한다. 아래 코드를 먼저 보자.

@ApiModel(value = "태그 생성 정보")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class TagCreateRequestDto {
    @ApiModelProperty(value="태그의 이름", example = "이름입니다.", required = true)
    @NotBlank(message = "태그 이름이 입력되지 않았습니다.")
    private String name;
}
  • @ApiModel은 모델 객체를 지정한다. 설명을 description으로 추가할 수 있다.
  • @ApiModelProperty는 모델 객체의 세부 사항에 대한 설명을 추가한다.
    • value로 이름을 지정하고, example로 예제를 넣을 수 있다. 그 외에도 여러 옵션이 존재한다.

결과적으로, 위 이미지처럼 모델이 모여서 정리된다!!

또한 아래 이미지처럼 메소드의 요청과 반환 파트도 더 깔끔하게 바뀌는 것을 알 수 있다.

(모델 설정을 하지 않은 경우, 예시도 출력되지 않고 단순 JSON으로 출력된다)

그렇다면 응답은 어떻게 문서화할까? 아래 코드를 보자.

@ApiResponses({
        @ApiResponse(code=204, message="삭제 완료"),
        @ApiResponse(code=401, message="삭제 권한 없음. 로그인 필요")
})
@ApiOperation(value = "게시글을 삭제하는 메소드")
@ApiImplicitParam(name = "postId", value = "조회할 게시물 ID", required = true, dataType = "long")
@DeleteMapping("/posts/{postId}")
public ResponseEntity<Void> deletePost(@PathVariable final Long postId,
                                       @LoginUser final User user) throws AccessDeniedException {
    postService.deletePost(postId, user);

    return ResponseEntity.noContent().build();
}
  • @ApiResponse는 반환값 코드와 그에 맞는 메시지를 지정한다.
  • @ApiResponses는 여러 개의 @ApiResponse를 포함한다.

위 코드를 실행하면, 아래처럼 지정된 코드에 설명이 추가된다.

API 숨기기

만약 어떤 API를 숨기고 싶다면, 해당 컨트롤러나 메소드에 @ApiIgnore 어노테이션을 추가해주면 된다!

일단 코드를 한 번 추가해 보자!! 컨트롤러 위에 @ApiIgnore 어노테이션을 추가하면...

요렇게 추가해준다
요렇게 PostController가 쏙 빠진다!

유사한 방식으로 컨트롤러 내의 메소드에도 해당 어노테이션을 달 수 있다! 그러면 그 메소드만 빠진다.


마무리

Swagger는 이번에 처음 써봤는데, REST Docs보다 훨씬 쉽구 간단해서 즐거웠다!!

협업을 하거나 프로젝트 테스트를 해보기에도 좋다는 생각이 들었다.

다만 아래의 사항은 이번에 공부해보지 못했는데, 추후 포스팅해보고 싶다.

  • OAuth를 프로젝트에 사용하고 있는데, 로그인 정보에 대해 일괄적으로 처리할 방법을 알고 싶다.

 

많이 부족한 글인지라...혹시 잘못된 부분이 있다면 댓글 부탁드립니다.

참고한 링크

Baeldung의 Swagger 기본 설정법 : 링크

Baeldung의 @ApiIgnore 사용법 : 링크

 

반응형