티스토리 뷰
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를 사용한 비동기 처리
개선 가능한 부분
- 인증/인가: 현재 구현되지 않은 보안 기능
- 로드 밸런싱: 단일 인스턴스만 지원
- 모니터링/로깅: 상세한 메트릭 수집 부족
- 캐싱: 응답 캐싱 메커니즘 부재
후기
이러한 직접 구현을 통해 API Gateway의 내부 동작 원리를 알아볼 수 있었습니다. 프로토타입 정도로 구현했기에 부족한 기능들을 추후 추가해보고 싶습니다. 또한, 활용했던 WebFlux 에 대해 더 학습해보고 싶습니다.
'오픈소스-직접-구현' 카테고리의 다른 글
| Kotlin으로 Git 따라 만들기: KGit (0) | 2025.07.06 |
|---|---|
| Kotlin으로 MySQL 따라 만들기: KMySQL (0) | 2025.07.06 |