Swagger (1) - SpringDoc OpenApi 뜯어보기

강지혁·2022년 7월 25일
0

Swagger

목록 보기
1/2

스웨거를 통한 API 문서 자동화

Swagger는 OpenAPI 퍼블릭 인터페이스 스펙을 바탕으로 API 문서 자동화를 지원해주는 오픈 소스 도구입니다.

스프링 프로젝트에서는 아주 간단하게 스웨거 문서를 이용할 수 있습니다.
스프링 부트 애플리케이션을 작성하고, 아래 두 의존성을 설정해주기만 하면,

	implementation("org.springdoc:springdoc-openapi-ui:1.6.9")
	implementation("org.springdoc:springdoc-openapi-kotlin:1.6.9")

/swagger-ui.html 경로에서 바로 아래와 같은 화면을 만날 수 있습니다.
(마법 같은 일이죠?)

우리가 작성한 모든 API를, 스웨거는 어떻게 물어올까요?

스웨거가 화면을 렌더링 하기까지

사실 무슨 대단한 일이 일어나지는 않습니다.
springdoc-openapi-ui 라이브러리 내부에, swagger-ui 경로에 대한 @RequestMapping이 준비되어 있습니다.

package org.springdoc.webmvc.ui;

@Controller
public class SwaggerWelcomeWebMvc extends SwaggerWelcomeCommon { 
    @Operation(hidden = true)
	@GetMapping(SWAGGER_UI_PATH)
	@Override
	public ResponseEntity<Void> redirectToUi(HttpServletRequest request) {
		return super.redirectToUi(request);
	}
}

위 코드로 인해 우리는 Swagger UI의 기본적인 정적 파일을 내려받을 수 있도록 리다이렉트 됩니다.
네트워크 탭을 열어보면, swagger-config이란 이름의 API도 한번 호출하는 것을 볼 수 있습니다.
응답의 url: "/v3/api-docs" 부분이 흥미로운데요, 스웨거 UI는 곧바로 이 경로로 API를 한번 더 호출합니다.

그리고 이 경로에는, 비로소 우리가 작성한 애플리케이션 API 정보를 불러오는 코드가 존재합니다.

package org.springdoc.webmvc.api

@RestController
public class OpenApiWebMvcResource extends OpenApiResource {
	@Operation(hidden = true)
	@GetMapping(value = API_DOCS_URL, produces = MediaType.APPLICATION_JSON_VALUE)
	@Override
	public String openapiJson(HttpServletRequest request, @Value(API_DOCS_URL) String apiDocsUrl, Locale locale)
			throws JsonProcessingException {
            
        // 아래 함수 블록으로 이동 
		return super.openapiJson(request, apiDocsUrl, locale);
	}   
}

public abstract class OpenApiResource {
    public String openapiJson(HttpServletRequest request,
			String apiDocsUrl, Locale locale)
			throws JsonProcessingException {
		calculateServerUrl(request, apiDocsUrl, locale);
        
        // 우리 (사용자) 애플리케이션 서버 정보를 담은 OpenAPI 객체 리턴
		OpenAPI openAPI = this.getOpenApi(locale);
		return writeJsonValue(openAPI);
	}
}

OpenApiWebMvcResourceOpenApiResource 추상 클래스를 상속하고, OpenApiResource에는 getOpenApi 함수가 존재하는데요, 이 곳에서 스웨거는 OpenAPI 스펙에 맞추어 서버의 정보를 보여주는 OpenApi 객체를 생성해 브라우저에게 다시 내려줍니다.

어떻게 그런 일이 가능할까요?

스프링 빈으로 하여금 ApplicationContextAware 인터페이스를 상속받게 하면, 해당 객체는 애플리케이션에서 동작하는 모든 Bean의 정보를 간접적으로 확인할 수 있습니다.

API 문서를 만들 때, 우리가 관심 있는 것이 뭘까요?
Request Mapping이 일어나는 Controller 계층이겠죠?

스웨거도 이 사실을 바탕으로, 스프링 컨테이너에 떠 있는 모든 Controller 계층 컴포넌트를 불러옵니다.

public OpenAPI build(Locale locale) {
	// ...

    // Set default mappings
	this.mappingsMap.putAll(context.getBeansWithAnnotation(RestController.class));
	this.mappingsMap.putAll(context.getBeansWithAnnotation(RequestMapping.class));
	this.mappingsMap.putAll(context.getBeansWithAnnotation(Controller.class));  
    
    // ...
}

그 다음 작업이 어떻게 이루어질지도 예측 가능합니다.

  1. 컨트롤러에 존재하는 모든 RequestMapping에 대해
  2. API 스펙을 정의하고 (경로, 파라미터, 응답 등..) , OpenAPI 객체에 저장한다.

물론 아주 복잡하고 정교한 많은 과정이 더 필요하겠지만, 큰 틀에서는 이게 전부이겠죠!?


OpenApi 객체 살펴보기

앞서 소개해드린 과정을 통해 만들어내는 OpenApi 객체를 조금 뜯어보고, 다음 포스팅에서 실제로 협업에 사용할 때 어떤 문제들을 마주칠 수 있는지 다뤄보도록 하겠습니다.

다음은 OpenApi 객체 일부입니다.

public class OpenAPI {

    private String openapi = "3.0.1";
    private Info info = null;
    private ExternalDocumentation externalDocs = null;
    private List<Server> servers = null;
    private List<SecurityRequirement> security = null;
    private List<Tag> tags = null;
    private Paths paths = null;
    private Components components = null;
    private java.util.Map<String, Object> extensions = null;
    
    // ...
}

Paths 프로퍼티는, 앞서 우리가 예측했던 대로, 각 경로들에 대한 논리적 매핑임을 쉽게 예측해볼 수 있습니다.

OpenApi 객체는 이외에도 본 포스팅에서 다루지 않는 아주 많은 정보들이 들어가고 있네요.

이 가운데, 마지막으로 Components 프로퍼티에 대해서 알아봅시다.

public class Components {

    /**
     * @since 2.1.6
     */
    public static final String COMPONENTS_SCHEMAS_REF = "#/components/schemas/";
    private Map<String, Schema> schemas = null;
    private Map<String, ApiResponse> responses = null;
    private Map<String, Parameter> parameters = null;
    private Map<String, Example> examples = null;
    private Map<String, RequestBody> requestBodies = null;
    private Map<String, Header> headers = null;
    private Map<String, SecurityScheme> securitySchemes = null;
    private Map<String, Link> links = null;
    private Map<String, Callback> callbacks = null;
    private java.util.Map<String, Object> extensions = null;
    
    // ...
}

뭔지 모르겠을 때는 항상 공식문서를 보는 습관을 길러야 합니다.

공식문서에서 Component에 대해 아래와 같이 설명하고 있네요.

Holds a set of reusable objects for different aspects of the OAS. All objects defined within the components object will have no effect on the API unless they are explicitly referenced from properties outside the components object.

수많은 프로퍼티 가운데, Schema 객체에만 (찐막으로) 포커스를 옮겨봅시다.
(선택적 포커싱은 다음 포스팅을 위한 떡밥..)

Schema 오브젝트란, API에 관여하는 데이터 타입 을 의미하는 객체들입니다.
(DTO라고 생각하면 되겠죠?)

우리가 스웨거 설정을 하게 되면, 하단 footer 영역에 스웨거가 찾은 스키마들이 쭉 나열되는 것을 확인할 수 있습니다.


떡밥과 마무리

협업 과정에서 API spec을 빠르게 주고받고 싶을 때, 스웨거를 이용하면 협업 과정에서 언제나 최신 기준의 API 문서를 가지고 소통할 수 있으며, 유지 비용도 비교적 저렴하다는 장점이 있습니다.

그러나 실무에서 Swagger만을 사용해 협업을 해나가려는 경우, 다양한 방면에서 크고작은 문제점(고민거리)들이 튀어나옵니다.

다음 포스팅에서는 제가 협업 과정에서 겪었던 문제점, 그리고 그것에 대한 해결 과정에 대해 담아보도록 하겠습니다.

0개의 댓글