티스토리 뷰

GitHub: https://github.com/ohksj77/api-gateway

 

GitHub - ohksj77/api-gateway: 직접 라우팅을 구현하며 api-gateway를 만들어보자

직접 라우팅을 구현하며 api-gateway를 만들어보자. Contribute to ohksj77/api-gateway development by creating an account on GitHub.

github.com

전체 코드는 위 Repository에서 확인할 수 있습니다. 이 포스트에서 전체 구현을 다루지는 않습니다.

 

들어가며

마이크로서비스 아키텍처에서 API Gateway는 모든 클라이언트 요청의 진입점 역할을 하며, 라우팅, 로드 밸런싱, 인증, 모니터링 등 다양한 기능을 제공합니다. 이번 포스트에서는 Spring WebFlux와 Kotlin을 사용하여 API Gateway의 핵심 기능을 직접 구현한 프로젝트를 소개하고, 그 설계와 구현 방식을 분석해보겠습니다.

 

 

* I/O 작업을 적은 스레드 수로 효율적으로 처리하는 이점을 가져가고자 WebFlux를 채택했습니다.

* WebFlux에 아직 익숙하지 않지만 API GW를 만드는데 사용되는 인터페이스 정도는 다룰 수 있었습니다. 아직 많은 학습이 필요합니다.

 

프로젝트 구조

api-gateway/
├── api-gateway/          # API Gateway 서버 (포트: 8080)
├── module1/             # 백엔드 서비스 1 (포트: 8081)
└── module2/             # 백엔드 서비스 2 (포트: 8082)

 

module1, module2 는 api-gateway 의 동작을 확인하기 위해 간단히 추가한 서버입니다.

 

기술 스택

  • 언어: Kotlin
  • 프레임워크: Spring Boot 3.4.4, Spring WebFlux
  • Java 버전: 21

 

핵심 기능 분석

1. 설정 기반 라우팅

API Gateway는 YAML 설정 파일을 통해 라우팅 규칙을 정의합니다:

 

위 설정을 읽어와 설정대로 라우팅이 가능하도록 구현했습니다.

 

2. 데이터 모델 설계

class App(
    val port: Int,
    val version: String,
    val name: String,
    val http: Http
) {
    fun createUrl(): String {
        return http.baseUrl + ":" + port
    }
}

class Http(
    val baseUrl: String,
    val routes: List<Route>
)

class Route(
    val method: String,
    val path: String,
    val header: Map<String, String>?
)

 

이 데이터 모델은 설정 파일의 구조를 반영하며, createUrl() 메서드를 통해 백엔드 서비스의 전체 URL을 생성합니다.

 

3. 리액티브 라우터 구현

class Router(
    private val routerHandler: RouterHandler,
    private val app: App
) {
    private var webClient: WebClient = WebClient.builder()
        .baseUrl(app.createUrl())
        .build()

    fun route(): RouterFunction<ServerResponse> {
        val routes = app.http.routes.map { router ->
            when (router.method) {
                "GET" -> RouterFunctions.route(
                    RequestPredicates.GET(router.path),
                    routerHandler.get(router, webClient)
                )
                "POST" -> RouterFunctions.route(
                    RequestPredicates.POST(router.path),
                    routerHandler.post(router, webClient)
                )
                // ... 다른 HTTP 메서드들
            }
        }
        return routes.reduce { acc, next ->
            acc.and(next)
        }
    }
}

 

이 구현의 특징:

  • Spring WebFlux RouterFunction 사용: 함수형 엔드포인트 정의
  • WebClient: 리액티브 HTTP 클라이언트로 백엔드 서비스와 통신
  • 동적 라우팅: 설정 파일 기반으로 런타임에 라우트 생성

 

4. 서킷 브레이커 패턴

@Component
class RouterHandler(
    private val circuitBreaker: CircuitBreaker,
) {
    fun get(route: Route, webClient: WebClient): (ServerRequest) -> Mono<ServerResponse> = { request ->
        // ... 요청 처리 로직
        requestBuilder.retrieve()
            .bodyToMono(String::class.java)
            .transformDeferred(CircuitBreakerOperator.of(circuitBreaker))
            .flatMap { ServerResponse.ok().bodyValue(it) }
    }
}

 

Resilience4j를 사용한 서킷 브레이커 설정:

resilience4j:
  circuitbreaker:
    configs:
      default:
        slidingWindowSize: 10
        failureRateThreshold: 50.0
        waitDurationInOpenState: 5s
        permittedNumberOfCallsInHalfOpenState: 5

 

다음과 같은 동작을 합니다:

  • slidingWindowSize: 10개의 요청을 윈도우로 사용
  • failureRateThreshold: 50% 실패율 시 서킷 브레이커 활성화
  • waitDurationInOpenState: 5초간 서킷 브레이커 열린 상태 유지
  • permittedNumberOfCallsInHalfOpenState: 반열린 상태에서 5개 요청 허용

 

  • 위 설정들은 임의로 설정한 값으로, 실 상황에서 사용한다면 테스트를 통해 세밀하게 설정되어야 합니다.

 

5. 요청 처리 핸들러

fun get(route: Route, webClient: WebClient): (ServerRequest) -> Mono<ServerResponse> = { request ->
    logger.info("[REQUEST] get")
    val uri = buildUri(route.path, request)
    val queryParams = request.queryParams().toSingleValueMap()

    val requestBuilder = webClient.get()
        .uri { builder ->
            builder.path(uri)
            queryParams.forEach { (key, value) -> builder.queryParam(key, value) }
            builder.build()
        }

    route.header?.forEach { (key, value) -> requestBuilder.header(key, value) }

    requestBuilder.retrieve()
        .bodyToMono(String::class.java)
        .transformDeferred(CircuitBreakerOperator.of(circuitBreaker))
        .flatMap { ServerResponse.ok().bodyValue(it) }
}

 

핵심 기능들:

  • Path Variable 처리: {id} 형태의 경로 변수를 실제 값으로 치환
  • Query Parameter 전달: 클라이언트의 쿼리 파라미터를 백엔드로 전달
  • 헤더 설정: 라우트별로 정의된 헤더 정보 적용
  • 리액티브 스트림: Mono를 사용한 비동기 처리

 

개선 가능한 부분

  1. 인증/인가: 현재 구현되지 않은 보안 기능
  2. 로드 밸런싱: 단일 인스턴스만 지원
  3. 모니터링/로깅: 상세한 메트릭 수집 부족
  4. 캐싱: 응답 캐싱 메커니즘 부재

 

후기

이러한 직접 구현을 통해 API Gateway의 내부 동작 원리를 알아볼 수 있었습니다. 프로토타입 정도로 구현했기에 부족한 기능들을 추후 추가해보고 싶습니다. 또한, 활용했던 WebFlux 에 대해 더 학습해보고 싶습니다.

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
TAG
more
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함