Swagger에서 SpringDoc으로: Springfox가 죽었다, API 문서는 어떻게 하지

📖 17min read

“이 API 파라미터가 뭐예요?”

프론트엔드 개발자, 외부 업체, 또는 미래의 나 자신이 가장 자주 하는 질문이다.

“이 API에 뭘 보내야 하고, 뭐가 돌아와요?”

처음에는 메신저로 설명했다. “POST /api/projects에 name, status, region 넣으면 돼요.” 그런데 API가 30개를 넘어가면서, 파라미터 하나 물어볼 때마다 코드를 열어서 확인하고 답하는 루프가 끝없이 반복됐다.

‘API 문서가 자동으로 만들어지면 좋겠다.’ 그래서 Swagger를 도입했다. Springfox 라이브러리를 추가하니, 코드에서 API 문서가 자동으로 생성되고 브라우저에서 바로 확인하고 테스트까지 할 수 있었다. 한동안은 잘 돌아갔다.

그런데 어느 순간부터 문제가 생기기 시작했다.

API 문서가 있으면 “이 파라미터 뭐예요?”를 더 이상 채팅으로 안 물어봐도 된다.

왜 바꾸나: Springfox의 한계

Springfox는 오랫동안 Spring Boot에서 Swagger UI를 사용하는 사실상 표준이었다. 하지만 두 가지 큰 문제가 있다.

첫째, ‘Springfox는 사실상 개발이 멈췄다.’ 마지막 릴리스(3.0.0)가 2020년 7월이다. 그 이후로 6년 가까이 업데이트가 없다. Spring Boot 2.6 이상에서는 경로 매칭 방식 변경으로 호환 문제가 발생하고, Boot 3에서는 아예 동작하지 않는다.

둘째, ‘Swagger 2 스펙 기반이다.’ 현재 API 문서화의 표준은 OpenAPI 3이다. Springfox는 Swagger 2(OpenAPI 2) 기반이라, 최신 스펙의 기능을 활용할 수 없다.

여기서 용어를 짧게 정리하자.

용어설명
Swagger원래 API 문서화 도구/스펙의 이름. 현재는 도구 이름으로만 사용
OpenAPISwagger에서 이름이 바뀐 API 명세 표준. 현재 버전은 3.x
SpringfoxSpring에서 Swagger 2/OpenAPI 2 문서를 생성하는 라이브러리. EOL 상태
SpringDocSpring에서 OpenAPI 3 문서를 생성하는 라이브러리. 현재 표준
Swagger UIAPI 문서를 브라우저에서 볼 수 있는 웹 인터페이스. SpringDoc도 동일 UI 사용

요약하면: Springfox(죽음) → SpringDoc(대체). Swagger 2(구) → OpenAPI 3(현). Swagger UI는 둘 다 사용.


SpringDoc으로 전환하기

1단계: 의존성 교체

// build.gradle

// 제거
implementation 'io.springfox:springfox-boot-starter:3.0.0'

// 추가
implementation 'org.springdoc:springdoc-openapi-ui:1.7.0'

Spring Boot 3을 쓴다면 springdoc-openapi-starter-webmvc-ui:2.x를 사용한다. 이 글에서는 Boot 2.4 기준(1.x)으로 설명한다.

이것만으로 /swagger-ui.html 또는 /swagger-ui/index.html에서 API 문서가 자동 생성된다. Springfox처럼 별도의 Docket 설정 클래스가 필요 없다.

2단계: 어노테이션 변경

Springfox와 SpringDoc은 사용하는 어노테이션이 다르다. 주요 변경점을 정리하면 이렇다.

Springfox (Swagger 2)SpringDoc (OpenAPI 3)위치
@Api@TagController 클래스
@ApiOperation@Operation메서드
@ApiParam@Parameter파라미터
@ApiModel@SchemaDTO 클래스
@ApiModelProperty@SchemaDTO 필드
@ApiResponse@ApiResponse (패키지 변경)메서드

Springfox의 어노테이션은 io.swagger.annotations 패키지이고, SpringDoc의 어노테이션은 io.swagger.v3.oas.annotations 패키지다.

변경 전 (Springfox):

@Api(tags = "프로젝트 API")
@RestController
@RequestMapping("/api/projects")
public class ProjectController {

    @ApiOperation(value = "프로젝트 목록 조회", notes = "조건에 맞는 프로젝트 목록을 반환합니다.")
    @GetMapping
    public List<ProjectDto> getProjects(
            @ApiParam(value = "상태 필터", example = "ACTIVE")
            @RequestParam(required = false) String status) {
        return projectService.search(status);
    }
}

변경 후 (SpringDoc):

@Tag(name = "프로젝트 API", description = "프로젝트 CRUD 및 검색")
@RestController
@RequestMapping("/api/projects")
public class ProjectController {

    @Operation(summary = "프로젝트 목록 조회", description = "조건에 맞는 프로젝트 목록을 반환합니다.")
    @GetMapping
    public List<ProjectDto> getProjects(
            @Parameter(description = "상태 필터", example = "ACTIVE")
            @RequestParam(required = false) String status) {
        return projectService.search(status);
    }
}

DTO도 마찬가지다.

// 변경 전
@ApiModel(description = "프로젝트 생성 요청")
public class ProjectCreateDto {
    @ApiModelProperty(value = "프로젝트 이름", required = true, example = "스마트농장")
    private String name;
}

// 변경 후
@Schema(description = "프로젝트 생성 요청")
public class ProjectCreateDto {
    @Schema(description = "프로젝트 이름", requiredMode = Schema.RequiredMode.REQUIRED, example = "스마트농장")
    private String name;
}

3단계: Springfox 설정 클래스 제거

Springfox에서는 Docket Bean을 직접 만들어야 했다. SpringDoc에서는 이게 필요 없다.

// 삭제: Springfox 설정 클래스 전체
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.example"))
            .paths(PathSelectors.any())
            .build();
    }
}

SpringDoc에서 동일한 설정은 application.yml로 할 수 있다.

# application.yml
springdoc:
  swagger-ui:
    path: /swagger-ui.html    # Swagger UI 경로
    groups-order: ASC          # 그룹 정렬
  api-docs:
    path: /v3/api-docs         # OpenAPI JSON 경로
  packages-to-scan: com.example.controller   # 스캔 대상 패키지
Springfox는 문을 닫았지만, Swagger UI는 SpringDoc으로 그대로 이어진다.

실전 활용: 더 유용한 문서 만들기

SpringDoc을 설치하면 API 문서가 자동으로 생성되지만, 기본 상태로는 부족하다. 실무에서 정말 유용한 문서를 만들려면 몇 가지 추가 설정이 필요하다.

API 그룹 분리

API가 많아지면 전부 한 페이지에 나열되어 찾기 어렵다. 그룹으로 나누면 가독성이 올라간다.

@Configuration
public class SpringDocConfig {

    @Bean
    public GroupedOpenApi projectApi() {
        return GroupedOpenApi.builder()
            .group("프로젝트")
            .pathsToMatch("/api/projects/**")
            .build();
    }

    @Bean
    public GroupedOpenApi memberApi() {
        return GroupedOpenApi.builder()
            .group("회원")
            .pathsToMatch("/api/members/**")
            .build();
    }
}

JWT 인증 헤더 추가

이전 글(Spring Security)에서 JWT를 다뤘다. Swagger UI에서 API를 테스트하려면 JWT 토큰을 입력할 수 있어야 한다.

@Configuration
public class SpringDocConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
            .info(new Info()
                .title("프로젝트 관리 API")
                .version("1.0")
                .description("프로젝트 관리 시스템 API 문서"))
            .addSecurityItem(new SecurityRequirement().addList("JWT"))
            .components(new Components()
                .addSecuritySchemes("JWT", new SecurityScheme()
                    .name("Authorization")
                    .type(SecurityScheme.Type.HTTP)
                    .scheme("bearer")
                    .bearerFormat("JWT")));
    }
}

이렇게 설정하면 Swagger UI 상단에 “Authorize” 버튼이 생기고, JWT 토큰을 입력하면 모든 요청에 Authorization: Bearer 헤더가 자동으로 추가된다.

응답 코드별 설명

@Operation(summary = "프로젝트 상세 조회")
@ApiResponses(value = {
    @ApiResponse(responseCode = "200", description = "조회 성공"),
    @ApiResponse(responseCode = "404", description = "프로젝트를 찾을 수 없음"),
    @ApiResponse(responseCode = "403", description = "접근 권한 없음")
})
@GetMapping("/{id}")
public ProjectDto getProject(@PathVariable Long id) {
    return projectService.findById(id);
}

실무 조언

1. 어노테이션 없이도 기본 문서는 생성된다

SpringDoc은 @Operation, @Schema 같은 어노테이션이 없어도 컨트롤러의 메서드 시그니처만으로 기본 문서를 자동 생성한다. 처음부터 모든 API에 어노테이션을 다 붙이려 하지 말고, 우선 설치만 해서 기본 문서를 확인하고, 설명이 필요한 곳에만 어노테이션을 추가하는 것이 현실적이다.

2. 문서를 믿을 수 있게 유지하라

API 문서의 가장 큰 함정은 ‘코드는 바뀌었는데 문서는 안 바뀌는 것’이다. SpringDoc은 코드에서 문서를 생성하므로 이 문제가 적지만, 어노테이션의 description이나 example이 실제 동작과 다르면 오히려 혼란을 준다. description은 “무엇을 하는가”만 쓰고, 세부 사항은 코드가 말하게 두는 것이 유지보수에 유리하다.

3. 운영 환경에서는 비활성화

Swagger UI가 운영 환경에 노출되면 보안 위험이 있다. API 구조가 외부에 그대로 드러나기 때문이다.

# application-prod.yml
springdoc:
  api-docs:
    enabled: false
  swagger-ui:
    enabled: false

4. 전환 체크리스트

항목확인
Springfox 의존성 제거build.gradle에서 삭제
SpringDoc 의존성 추가springdoc-openapi-ui
Docket 설정 클래스 삭제@EnableSwagger2 포함
어노테이션 패키지 변경io.swagger.annotations → io.swagger.v3.oas.annotations
@ApiOperation → @Operationsummary, description 매핑
@ApiModelProperty → @Schemadescription, example 매핑
Swagger UI 접속 확인/swagger-ui/index.html
JWT 인증 헤더 설정SecurityScheme 추가
운영 환경 비활성화springdoc.enabled=false

마치며: 문서는 코드의 일부다

API 문서화를 ‘나중에 하면 되는 것’으로 미루다 보면, 결국 아무도 안 하게 된다. SpringDoc을 쓰면 코드를 작성하는 것 자체가 문서를 만드는 것이 된다. 어노테이션 몇 개만 추가하면, 코드 변경이 곧 문서 변경이다.

Springfox에서 SpringDoc으로 전환하는 것은 어렵지 않다. 의존성 교체, 어노테이션 패키지 변경, 설정 클래스 삭제. 대부분 기계적인 작업이다. 미루고 있었다면 지금이 바꿀 때다.

다음 글에서는 DB 스키마 변경을 안전하게 관리하는 방법을 다룬다. 테이블에 컬럼을 추가하거나 이름을 바꿀 때, “ALTER TABLE 한 줄이면 되지”가 아닌 이유. Flyway를 사용한 DB 마이그레이션 관리를 알아보겠다.

댓글 남기기