ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • MSA) Spring Cloud 기반의 MSA 구조에서 Swagger 통합하기 + FastAPI의 Swagger까지
    SpringBoot 2024. 6. 17. 21:51

     

     

    들어가며

     

    저번 시간에 FastAPI 서버를 Spring Cloud Eureka에 Client로 등록하는 방법에 이어서,

    이번에는 각 서비스마다 Swagger를 설정하여 이를 한 곳에서 통합하여 볼 수 있게끔 적용하려고 합니다.

     

    MSA 구조에서 프로젝트를 진행하면, 각 마이크로서비스들이 각각의 애플리케이션에서 돌아가기 때문에

    모놀리식 구조와 다르게 API 문서들을 관리하는데 어려움이 존재합니다.

     

     

    쉽게 설명해보자면

    모놀리식 구조의 경우 하나의 애플리케이션으로 운영 되므로

    애플리케이션에 Swagger를 하나만 띄우면 되지만

     

    MSA 구조는 서비스들이 독립적으로 분리되어있기 때문에 서비스마다 Swagger를 띄워줘야합니다.

    이러한 상황에서 Swagger를 통합해주지 않는다면,

    API를 받아쓰는 프론트 입장에서는 호출하고 싶은 API를 테스트하거나 확인해보고 싶을 때마다

    API가 해당하는 서비스쪽의 Swagger로 접속해야하는 번거로움이 존재합니다. 

     

     

    그래서 이번에는 각 마이크로서비스에서 설정한 Swagger들을

    API Gateway쪽에 통합하여 위 문제들을 해결해보고자 합니다.

     

     

     

    백엔드 환경

    • Java 17
    • Spring Boot 3.2.4
    • Python 3.12.3
    • FastAPI 0.111.0

     

     

     

    Spring Cloud API Gateway

    1.  의존성 추가하기 (gradle)

    // swagger
    implementation 'org.springdoc:springdoc-openapi-starter-webflux-ui:2.5.0'

     

    참고로 Spring Boot 3버전 이상은 springdoc-openapi v2 버전부터 사용해야합니다.

    자세한 내용은 아래 공식 문서를 참고해주세요!

    https://springdoc.org/

     

     

    또한 API Gateway 쪽에는 위처럼 webflux로 의존성을 추가해줍니다.

    그 이유가 중요합니다!!

    Spring Cloud Gateway는 내부적으로 WebFlux를 사용하기 때문에,

    이를 기반으로 하는 애플리케이션은 WebFlux의 특성을 따릅니다.

    따라서, WebFlux와 호환되는 라이브러리를 사용해야 합니다.

    만약에 WebFlux가 아닌 WebMVC 기반인

    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'로 의존성을 추가하면 오류가 발생합니다.

     

     

    WebFlux vs WebMVC

    Spring WebMVC는 전통적인 동기 처리 모델에 기반하며, 각 요청마다 하나의 스레드를 할당하는 방식입니다.

    Spring WebFlux는 비동기적이고 논블로킹 방식으로 요청을 처리하며, 요청과 스레드가 N:1입니다. 
    높은 확장성과 병렬 처리를 필요로 하는 애플리케이션에 적합합니다.

     

     

     

    2.  yml 파일 설정하기

    springdoc:
      swagger-ui:
        urls[0]:
          name: 회원 서비스
          url: http://localhost:8000/user-service/v3/api-docs
        urls[1]:
          name: 분석 서비스
          url: http://localhost:8000/analysis-service/v3/api-docs
        urls[2]:
          name: 상품 서비스
          url: http://localhost:8000/product-service/v3/api-docs
        urls[3]:
          name: 알림 서비스
          url: http://localhost:8000/notification-service/v3/api-docs

     

    위와 같이 작성해주면 나중에 통합된 Swagger에서

    아래 사진에 빨간색 박스로 표시한 부분처럼 원하는 서비스의 API 문서를 확인할 수 있습니다.

    서비스를 클릭했을 때, 해당 서비스의 api-docs를 불러와서 API 문서를 보여주는 방식입니다.

     

    미리보는 완성된 통합본

     

    참고로 저는 아래와 같이 rewritepath를 적용하고 있습니다.

          routes:
            - id: user-service
              uri: lb://USER-SERVICE
              predicates:
                - Path=/user-service/**
              filters:
                - RewritePath=/user-service/(?<segment>.*), /$\{segment}

     

     

    또한 Gateway로 접속했을 때,

    바로 Swagger로 접속하고 싶다면 아래와 같이 use-root-path: true로 설정해주면 됩니다.

    ex) localhost:8000 -> localhost:8000/webjars/swagger-ui/index.html로 접속됨

     

    springdoc:
      swagger-ui:
        use-root-path: true

     

    그러나 이러한 방식은 보안상으로 좋은 방법은 아닌 것 같아서 true로 설정하지는 않았습니다. (default가 false)

     

    use-root-path: true를 하지 않는다면

    localhost:8000으로 접속시 Whitelabel Error Page로 접속되므로

    통합된 Swagger를 보고 싶다면 localhost:8000/swagger-ui.html로 접속해줘야 합니다.

     

    /swagger-ui.html 경로를 바꾸고 싶다면 아래와 같이 경로를 수정해주면 됩니다.

    springdoc:
      swagger-ui:
        path: {경로 설정} -> default가 /swagger-ui.html
        enabled: true

     

     

     

     

    Microservice (SpringBoot)

    1.  의존성 추가하기 (gradle)

    // swagger
    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'

     

    Gateway에서 설정했던 것처럼 springdoc을 추가해주는데

    저의 경우 webmvc로 설정했습니다.

     

     

    2.  yml 파일 설정하기

    springdoc:
      api-docs:
        version: openapi_3_1
        enabled: true
      enable-spring-security: true
      default-consumes-media-type: application/json
      default-produces-media-type: application/json

     

    api-docs 사용 여부와 버전을 설정해주고 Spring Security와 통합된 형태로 API 문서를 제공하며,

    기본적으로 JSON 형식의 요청과 응답을 처리하도록 설정해주었습니다.

     

    참고로 api-docs의 default 경로는 /v3/api-docs입니다.

    그래서 별도의 경로 수정을 하지 않았습니다.

     

     

    3. SwaggerConfig 코드 작성

    api 요청할 때 개발 서버와 로컬 서버로 나누는 url들을 application.yml에서 관리하였고,

    @Value를 통해 값을 가져오는 방식으로 구현했습니다.

    import io.swagger.v3.oas.annotations.OpenAPIDefinition;
    import io.swagger.v3.oas.annotations.info.Info;
    import io.swagger.v3.oas.models.Components;
    import io.swagger.v3.oas.models.OpenAPI;
    import io.swagger.v3.oas.models.security.SecurityRequirement;
    import io.swagger.v3.oas.models.security.SecurityScheme;
    import io.swagger.v3.oas.models.servers.Server;
    import lombok.RequiredArgsConstructor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.Collections;
    
    @Configuration
    @RequiredArgsConstructor
    @OpenAPIDefinition(
            info = @Info(title = "API Document",
                    description = "USER SERVICE 명세서",
                    version = "v1.0.0"
            )
    )
    public class SwaggerConfig {
    
        @Value("${server.url.development}")
        private String developmentServerUrl;
    
        @Value("${server.url.local}")
        private String localServerUrl;
    
        @Bean
        public OpenAPI openAPI() {
            SecurityScheme securityScheme = new SecurityScheme()
                    .type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT")
                    .in(SecurityScheme.In.HEADER).name("Authorization");
            SecurityRequirement securityRequirement = new SecurityRequirement().addList("bearerAuth");
    
            return new OpenAPI()
                    .components(new Components().addSecuritySchemes("bearerAuth", securityScheme))
                    .security(Collections.singletonList(securityRequirement))
                    .addServersItem(new Server().url(developmentServerUrl).description("개발 서버"))
                    .addServersItem(new Server().url(localServerUrl).description("로컬 서버"));
        }
    
    }

     

     

    Microservice (FastAPI)

    FastAPI는 그냥 뭐 없습니다...!

    자체적으로 Swagger 설정이 되어있고, 저는 간단한 작업만 해줬습니다.

     

    OpenAPI Specification(OAS) 경로만, 위 API Gateway의 yml 파일에서 설정한 경로인 /v3/api-docs로 변경해주었습니다.

    default는 /openapi.json이라고 하는데, 저는 /v3/api-docs로 통일하고 싶어서 수정하려고합니다.

     

    api 요청할 때 개발 서버와 로컬 서버로 나누는 url들을 dotenv에서 관리하였고,

    필요할 때 코드 상에서 dotenv의 키를 통해 해당하는 값을 불러오도록 구현했습니다.

    from fastapi import FastAPI
    from contextlib import asynccontextmanager
    from dotenv import load_dotenv
    import py_eureka_client.eureka_client as eureka_client
    import uvicorn
    import random
    import os
    
    ...
    
    app = FastAPI(lifespan=lifespan,
                  title = "API Document",
                  description = "ANALYSIS SERVICE 명세서",
                  version = "v1.0.0",
                  openapi_url = "/v3/api-docs",
                  servers=[
                      {"url": os.getenv('DEVELOPMENT_SERVER_URL'), "description": "개발 서버"},
                      {"url": os.getenv('LOCAL_SERVER_URL'), "description": "로컬 서버"}
                  ]
    )
    
    ...

     

    설정이라고 말하기도 좀 그렇지만

    FastAPI 설정은 끝..!

     

     

     

    확인

    아래 사진과 같이 각 서비스의 Swagger들이 잘 통합된 것을 확인할 수 있습니다!

Designed by Tistory.