
SpringCloudGateway는 마이크로서비스 아키텍처에서 요청을 적절한 서비스로 전달하기 위한 비동기 API 게이트웨이 역할을 수행한다. 기존 서블릿 기반 게이트웨이와 달리 논블로킹 I/O와 이벤트 루프 기반 구조를 채택하여 높은 동시성과 확장성을 제공한다. 이 글에서는 SpringCloudGateway를 구성하는 핵심 요소 중 라우트 설정에 집중하여 의존성 구성부터 내부 동작 방식, Route Predicate Factory와 RouteLocator 설계 방식까지 차근차근 정리한다.
SpringCloudGateway WebFlux Netty
SpringCloudGateway는 Spring WebFlux와 Netty를 기반으로 동작한다. WebFlux는 Spring5에서 도입된 리액티브 웹 프레임워크로, 비동기·논블로킹 방식의 요청 처리를 기본 철학으로 삼는다. 기존의 동기 서블릿 모델에서는 하나의 요청이 하나의 스레드를 점유한 채 응답이 끝날 때까지 블로킹되었다. 반면 WebFlux는 이벤트 루프 기반으로 동작하며, I/O 작업이 대기 상태에 들어가면 스레드는 즉시 반환되어 다른 요청을 처리할 수 있다.
이러한 구조는 적은 스레드로도 높은 동시성을 처리할 수 있게 해주며, 대규모 트래픽 환경에서 리소스 효율을 크게 개선한다. SpringCloudGateway는 이러한 WebFlux 위에서 동작하며, 서블릿 컨테이너 대신 Netty 런타임을 사용한다. Netty는 비동기 이벤트 드리븐 네트워크 프레임워크로, 모든 네트워크 I/O를 논블로킹 방식으로 처리한다. SpringCloudGateway가 고성능 게이트웨이로 분류되는 이유는 이러한 기술 스택 조합에 있다.
의존성 구성 또한 이를 반영한다. spring-cloud-starter-gateway는 내부적으로 WebFlux와 Netty를 포함하고 있으며, 필요에 따라 WebFlux 의존성을 명시적으로 추가할 수 있다. Mac 환경에서는 Netty DNS 이슈를 피하기 위해 플랫폼 전용 resolver 의존성을 추가하는 경우도 있다. 게이트웨이 기능이 필요 없는 환경에서는 spring.cloud.gateway.enabled=false 설정으로 비활성화할 수 있다.
SpringCloudGateway Route Predicate
SpringCloudGateway의 라우팅 핵심은 Route Predicate Factory에 있다. 라우트는 단순한 경로 매핑이 아니라, 요청이 특정 조건을 만족할 때만 매칭되도록 설계된다. 이 조건을 정의하는 것이 Predicate이며, 각 Predicate는 ServerWebExchange를 입력으로 받아 true 또는 false를 반환하는 함수형 인터페이스다.
SpringCloudGateway는 Path, Method, Header, Query 등 다양한 기본 Predicate Factory를 제공한다. Path Predicate는 요청 URL 패턴을 기준으로 라우트를 결정하고, Method Predicate는 HTTP 메서드(GET, POST 등)에 따라 요청을 분기한다. Header Predicate는 특정 헤더 값이 존재하거나 일치하는지를 기준으로 판단하며, Query Predicate는 쿼리 파라미터를 기준으로 라우트를 구성할 수 있다. 이러한 Predicate 조합을 통해 매우 세밀한 요청 분기가 가능하다.
Route Predicate 설정은 Java 클래스 방식과 YAML 방식 두 가지가 있다. YAML 방식은 설정이 직관적이지만 컴파일 타임 검증이 불가능하다. 반면 Java DSL 방식은 컴파일러의 도움을 받을 수 있고, IDE 자동 완성과 리팩토링이 가능하다는 장점이 있다. 또한 AbstractRoutePredicateFactory를 상속하면 사용자 정의 Predicate를 직접 구현할 수 있어 도메인 특화 라우팅 조건을 만들 수 있다.
SpringCloudGateway RouteLocator 설계
SpringCloudGateway에서 실제 라우트 정보는 RouteLocator를 통해 관리된다. RoutePredicateHandlerMapping은 요청이 들어오면 RouteLocator로부터 모든 라우트를 조회하고, Predicate를 통해 매칭되는 라우트를 찾는다. 매칭된 라우트가 발견되면 해당 라우트에 정의된 필터 체인을 실행한다.
기본 예제에서는 하나의 클래스에서 RouteLocator를 구성하지만, 실무에서는 도메인별로 라우트를 분리하는 것이 유지보수에 유리하다. 이를 위해 RouteLocatorBuilderProvider와 같은 인터페이스를 정의하고, 각 도메인별 구현체를 스프링 빈으로 등록하는 구조를 사용할 수 있다. 중앙 RouteConfig에서는 모든 Provider를 수집하여 하나의 RouteLocator로 통합한다.
이 구조의 장점은 라우트 설정이 도메인 단위로 분리되어 가독성과 변경 용이성이 높아진다는 점이다. 또한 필터 적용 여부, URI 변경, 인증 필터 추가 등을 도메인별로 독립적으로 관리할 수 있다. 라우트 순서 또한 매우 중요하다. 보다 범위가 넓은 경로가 먼저 선언되면 구체적인 경로는 매칭되지 않는다. 이는 Spring Security 필터 체인과 유사한 개념으로, 라우트 ID 중복과 순서 누락에 각별한 주의가 필요하다.
SpringCloudGateway 라우트 코드
SpringCloudGateway 라우트 설정은 코드 기반으로 작성할 때 가독성과 안정성이 크게 향상된다. 아래는 RouteLocatorBuilderProvider를 활용한 도메인별 라우트 설정 예시다.
@Service
@RequiredArgsConstructor
public class OrdersRouteLocatorBuilderProvider
implements RouteLocatorBuilderProvider {
private static final String ORDERS_URI = "http://localhost:8091";
private final L2Filter l2Filter;
@Override
public void build(RouteLocatorBuilder.Builder routes) {
routes.route("orders-jwt", r -> r.path("/orders/jwt")
.filters(f -> f
.rewritePath("/orders/(?<path>.*)", "/${path}")
.filter(l2Filter.apply(new L2Filter.Config(true, true)))
)
.uri(ORDERS_URI));
routes.route("orders-default", r -> r.path("/orders/**")
.filters(f -> f
.rewritePath("/orders/(?<path>.*)", "/${path}")
)
.uri(ORDERS_URI));
}
}
이 구조에서는 라우트 ID, 경로 매칭 조건, 필터 구성, 목적지 URI가 명확히 분리된다. 특정 엔드포인트에만 JWT 검증 필터를 적용하고, 그 외 요청은 기본 라우트로 처리하는 식의 설계가 가능하다. 라우트가 많아질 경우 메서드 단위로 쪼개어 API 문서처럼 구성하는 것도 좋은 방법이다.
YAML 방식에서도 동일한 기능을 구현할 수 있지만, 복잡한 RewritePath나 필터 조합이 늘어날수록 오타와 설정 오류를 발견하기 어려워진다. 따라서 규모가 커질수록 코드 기반 RouteLocator 설계가 운영 안정성과 유지보수 측면에서 더 적합하다.