Spring Cloud Gateway Mülakatı: Routing, Filtreler ve Load Balancing
Teknik mülakatlar için Spring Cloud Gateway'i öğrenin: routing, filtreler, load balancing ve API Gateway pattern'leri üzerine kod örnekleriyle 12 soru.

Spring Cloud Gateway, Spring mikroservis mimarisinde API Gateway uygulamak için referans çözümdür. Teknik mülakatlar; routing yapılandırma, özel filtreler oluşturma ve load balancing'i etkin biçimde yönetme yetkinliğini değerlendirir.
İşe alım uzmanları Gateway pattern'lerinin anlaşılmasını ölçer: merkezi kimlik doğrulama, rate limiting ve circuit breaker. Spring Cloud Gateway'in alternatiflere göre neden tercih edildiğini açıklayabilmek fark yaratır.
Spring Cloud Gateway mimarisi ve temelleri
Soru 1: Spring Cloud Gateway nedir ve neden kullanılmalı?
Spring Cloud Gateway, Spring WebFlux ve Project Reactor üzerine kurulu reaktif bir API Gateway'dir. Mikroservislere yönelen tüm isteklerin tek giriş noktası olarak çalışır ve routing, filtreleme ve load balancing yetenekleri sunar.
// Spring Cloud Gateway'in temel yapılandırması
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
// Reaktif Netty sunucusunu başlatır (Tomcat değil)
SpringApplication.run(GatewayApplication.class, args);
}
}# application.yml
# Minimum gateway yapılandırması
spring:
cloud:
gateway:
routes:
# Kullanıcı servisine route
- id: user-service
uri: http://localhost:8081
predicates:
- Path=/api/users/**
# Sipariş servisine route
- id: order-service
uri: http://localhost:8082
predicates:
- Path=/api/orders/**Spring Cloud Gateway'in temel avantajları şunlardır: yüksek performans için bloke etmeyen mimari, Spring Cloud ekosistemiyle yerel entegrasyon ve modern reaktif pattern'ler için destek.
Soru 2: Route, Predicate ve Filter kavramlarını açıklayın
Spring Cloud Gateway'i üç temel kavram yapılandırır: Route'lar hedefleri tanımlar, Predicate'ler bir route'un ne zaman uygulanacağını belirler, Filter'lar istek ve yanıtları değiştirir.
// Programatik route yapılandırması
@Configuration
public class RouteConfiguration {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// Birden fazla predicate içeren route
.route("product-service", r -> r
// Predicate: URL yolu
.path("/api/products/**")
// Predicate: HTTP yöntemi
.and()
.method(HttpMethod.GET, HttpMethod.POST)
// Predicate: header mevcut
.and()
.header("X-Api-Version", "v2")
// Filter: yol yeniden yazma
.filters(f -> f
.rewritePath("/api/products/(?<segment>.*)", "/products/${segment}")
// Filter: header ekleme
.addRequestHeader("X-Gateway-Source", "spring-cloud-gateway")
)
// Hedef URI
.uri("http://localhost:8083"))
.build();
}
}İşleme akışı şu sırayı izler:
Gelen istek
│
▼
┌─────────────────┐
│ Predicates │ → Koşulları değerlendirir (path, yöntem, header...)
└────────┬────────┘
│ Eşleşme bulundu
▼
┌─────────────────┐
│ Pre-Filters │ → İsteği routing öncesi değiştirir
└────────┬────────┘
│
▼
┌─────────────────┐
│ HTTP Proxy │ → Hedef servise yönlendirir
└────────┬────────┘
│
▼
┌─────────────────┐
│ Post-Filters │ → Yanıtı istemciye dönmeden önce değiştirir
└────────┬────────┘
│
▼
İstemciye yanıtSoru 3: En çok kullanılan predicate'ler hangileridir?
Spring Cloud Gateway, çeşitli routing koşulları için pek çok yerleşik predicate sağlar. Birden fazla predicate'i birleştirmek karmaşık routing kuralları oluşturmayı sağlar.
# application.yml
# Yaygın predicate örnekleri
spring:
cloud:
gateway:
routes:
# Değişken yakalamalı path routing
- id: user-details
uri: http://user-service
predicates:
- Path=/users/{userId}
# HTTP yöntemine göre routing
- id: user-create
uri: http://user-service
predicates:
- Path=/users
- Method=POST
# Header tabanlı routing
- id: mobile-api
uri: http://mobile-service
predicates:
- Header=X-Client-Type, mobile
# Query parametreli routing
- id: search-api
uri: http://search-service
predicates:
- Query=q
# Host tabanlı routing
- id: admin-portal
uri: http://admin-service
predicates:
- Host=admin.example.com
# Zaman tabanlı routing
- id: maintenance-mode
uri: http://maintenance-service
predicates:
- Between=2026-03-20T02:00:00Z,2026-03-20T04:00:00Z// Özel predicate oluşturma
@Component
public class ApiKeyRoutePredicateFactory
extends AbstractRoutePredicateFactory<ApiKeyRoutePredicateFactory.Config> {
public ApiKeyRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
// API key varlığını ve geçerliliğini kontrol eder
String apiKey = exchange.getRequest()
.getHeaders()
.getFirst("X-Api-Key");
return apiKey != null && config.getValidKeys().contains(apiKey);
};
}
@Validated
public static class Config {
private List<String> validKeys = new ArrayList<>();
public List<String> getValidKeys() {
return validKeys;
}
public void setValidKeys(List<String> validKeys) {
this.validKeys = validKeys;
}
}
}Predicate sırası değerlendirmeyi etkilemez ancak route sırası önemlidir. Route'lar sırayla değerlendirilir ve ilk eşleşme kullanılır.
Filtreler ve istek dönüşümü
Soru 4: Pre ve post-processing filtreleri nasıl çalışır?
GatewayFilter filtreleri sıralı bir zincirde çalışır. "Pre" filtreler isteği routing öncesi, "post" filtreler ise yanıtı hedef servisten aldıktan sonra değiştirir.
// Global logging filtresi
@Component
@Slf4j
public class LoggingFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// PRE-FILTER: routing öncesi
String requestId = UUID.randomUUID().toString();
long startTime = System.currentTimeMillis();
log.info("Request {} started: {} {}",
requestId,
exchange.getRequest().getMethod(),
exchange.getRequest().getPath());
// İstek ID'sini header'lara ekler
ServerHttpRequest modifiedRequest = exchange.getRequest()
.mutate()
.header("X-Request-Id", requestId)
.build();
// Zinciri sürdürür ve yanıtı işler
return chain.filter(exchange.mutate().request(modifiedRequest).build())
.then(Mono.fromRunnable(() -> {
// POST-FILTER: yanıt sonrası
long duration = System.currentTimeMillis() - startTime;
HttpStatusCode status = exchange.getResponse().getStatusCode();
log.info("Request {} completed: status={}, duration={}ms",
requestId, status, duration);
}));
}
@Override
public int getOrder() {
// Negatif sıra = önce çalışır
return -1;
}
}// JWT kimlik doğrulama filtresi
@Component
@RequiredArgsConstructor
public class AuthenticationFilter implements GatewayFilter {
private final JwtTokenValidator tokenValidator;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String authHeader = exchange.getRequest()
.getHeaders()
.getFirst(HttpHeaders.AUTHORIZATION);
// Token varlığını kontrol eder
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return handleUnauthorized(exchange, "Missing or invalid Authorization header");
}
String token = authHeader.substring(7);
// Token'ı reaktif olarak doğrular
return tokenValidator.validate(token)
.flatMap(claims -> {
// İsteği kullanıcı bilgileriyle zenginleştirir
ServerHttpRequest enrichedRequest = exchange.getRequest()
.mutate()
.header("X-User-Id", claims.getSubject())
.header("X-User-Roles", String.join(",", claims.getRoles()))
.build();
return chain.filter(exchange.mutate().request(enrichedRequest).build());
})
.onErrorResume(e -> handleUnauthorized(exchange, e.getMessage()));
}
private Mono<Void> handleUnauthorized(ServerWebExchange exchange, String message) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
String body = String.format("{\"error\": \"%s\"}", message);
DataBuffer buffer = exchange.getResponse()
.bufferFactory()
.wrap(body.getBytes(StandardCharsets.UTF_8));
return exchange.getResponse().writeWith(Mono.just(buffer));
}
}Soru 5: En faydalı yerleşik filtreler hangileridir?
Spring Cloud Gateway, yaygın senaryoları kapsayan birçok yerleşik filtre sunar: URL yeniden yazma, header düzenleme, retry ve circuit breaker.
# application.yml
# Yaygın yerleşik filtreler
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
# Yol yeniden yazma
- RewritePath=/api/orders/(?<segment>.*), /orders/${segment}
# İstek header'larını ekle
- AddRequestHeader=X-Gateway-Version, 1.0
# Hassas yanıt header'larını kaldır
- RemoveResponseHeader=X-Powered-By
- RemoveResponseHeader=Server
# Yol öneki
- PrefixPath=/v2
# Önek kaldır
- StripPrefix=1
# Hatalarda otomatik retry
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE
methods: GET
backoff:
firstBackoff: 100ms
maxBackoff: 500ms
factor: 2
# Hız sınırlandırma
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@userKeyResolver}"// Kullanıcı başına rate limiter yapılandırması
@Configuration
public class RateLimiterConfiguration {
@Bean
public KeyResolver userKeyResolver() {
// Kimliği doğrulanmış kullanıcı başına sınır
return exchange -> Mono.just(
exchange.getRequest()
.getHeaders()
.getFirst("X-User-Id")
).defaultIfEmpty("anonymous");
}
@Bean
public KeyResolver ipKeyResolver() {
// IP adresi başına sınır
return exchange -> Mono.just(
Objects.requireNonNull(exchange.getRequest()
.getRemoteAddress())
.getAddress()
.getHostAddress()
);
}
}Spring Boot mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
Soru 6: Body değiştiren filtre nasıl uygulanır?
İstek veya yanıt body'sini değiştirmek; ModifyRequestBodyGatewayFilterFactory veya ModifyResponseBodyGatewayFilterFactory ile özel bir yaklaşım gerektirir.
// İstek body'sini değiştiren filtre
@Component
@RequiredArgsConstructor
public class RequestBodyModificationFilter implements GlobalFilter, Ordered {
private final ObjectMapper objectMapper;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Yalnızca JSON içeren POST/PUT isteklerini değiştirir
if (!isJsonRequest(exchange)) {
return chain.filter(exchange);
}
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
try {
// JSON'ı parse edip değiştirir
Map<String, Object> body = objectMapper.readValue(
bytes,
new TypeReference<Map<String, Object>>() {}
);
// Meta veri ekler
body.put("processedAt", Instant.now().toString());
body.put("gatewayVersion", "1.0");
byte[] modifiedBytes = objectMapper.writeValueAsBytes(body);
// Değiştirilmiş body ile yeni istek oluşturur
ServerHttpRequest modifiedRequest = new ServerHttpRequestDecorator(
exchange.getRequest()
) {
@Override
public Flux<DataBuffer> getBody() {
return Flux.just(
exchange.getResponse()
.bufferFactory()
.wrap(modifiedBytes)
);
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.putAll(super.getHeaders());
headers.setContentLength(modifiedBytes.length);
return headers;
}
};
return chain.filter(exchange.mutate().request(modifiedRequest).build());
} catch (IOException e) {
return Mono.error(new RuntimeException("Failed to parse request body", e));
}
});
}
private boolean isJsonRequest(ServerWebExchange exchange) {
MediaType contentType = exchange.getRequest().getHeaders().getContentType();
return contentType != null &&
contentType.isCompatibleWith(MediaType.APPLICATION_JSON);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}// Yanıtları değiştirmek için yapılandırma
@Configuration
public class ResponseBodyModificationConfig {
@Bean
public RouteLocator responseModifyingRoutes(
RouteLocatorBuilder builder,
ModifyResponseBodyGatewayFilterFactory modifyResponseBodyFilter
) {
return builder.routes()
.route("modify-response", r -> r
.path("/api/users/**")
.filters(f -> f.modifyResponseBody(
String.class,
String.class,
MediaType.APPLICATION_JSON_VALUE,
(exchange, responseBody) -> {
// Yanıtı standart bir biçimde sarmalar
return Mono.just(String.format(
"{\"success\": true, \"data\": %s, \"timestamp\": \"%s\"}",
responseBody,
Instant.now()
));
}
))
.uri("lb://user-service"))
.build();
}
}Load Balancing ve dayanıklılık
Soru 7: Spring Cloud LoadBalancer ile load balancing nasıl yapılandırılır?
Spring Cloud Gateway, trafiği servis örnekleri arasında dağıtmak için Spring Cloud LoadBalancer ile entegre olur. lb:// URI şeması otomatik load balancing'i etkinleştirir.
# application.yml
# Load balancing yapılandırması
spring:
cloud:
gateway:
routes:
- id: user-service
# lb:// load balancing'i etkinleştirir
uri: lb://user-service
predicates:
- Path=/api/users/**
# Load balancer yapılandırması
loadbalancer:
ribbon:
enabled: false # Spring Cloud LoadBalancer kullanır (Ribbon değil)
# Servis bazlı yapılandırma
configurations: default
# Load balancing için health check
health-check:
path:
user-service: /actuator/health
interval: 10s
# Service discovery (Eureka veya başka)
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/// Özel load balancer yapılandırması
@Configuration
@LoadBalancerClients(defaultConfiguration = CustomLoadBalancerConfig.class)
public class LoadBalancerConfiguration {
}
// CustomLoadBalancerConfig.java
// Özel load balancing stratejisi
public class CustomLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> loadBalancer(
Environment environment,
LoadBalancerClientFactory clientFactory
) {
String serviceId = environment.getProperty(
LoadBalancerClientFactory.PROPERTY_NAME
);
// Varsayılan olarak Round Robin
return new RoundRobinLoadBalancer(
clientFactory.getLazyProvider(serviceId, ServiceInstanceListSupplier.class),
serviceId
);
}
@Bean
public ServiceInstanceListSupplier serviceInstanceListSupplier(
ConfigurableApplicationContext context
) {
// Örneklere health check ekler
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withHealthChecks()
.withCaching()
.build(context);
}
}// Ağırlıklı load balancer
@Component
@RequiredArgsConstructor
public class WeightedLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final String serviceId;
private final ObjectProvider<ServiceInstanceListSupplier> supplierProvider;
private final Random random = new Random();
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return supplierProvider.getIfAvailable()
.get()
.next()
.map(instances -> {
if (instances.isEmpty()) {
return new EmptyResponse();
}
// Meta verilerden ağırlıkları hesaplar
List<WeightedInstance> weighted = instances.stream()
.map(instance -> {
int weight = Integer.parseInt(
instance.getMetadata().getOrDefault("weight", "1")
);
return new WeightedInstance(instance, weight);
})
.toList();
// Ağırlıklı seçim
int totalWeight = weighted.stream()
.mapToInt(WeightedInstance::weight)
.sum();
int randomWeight = random.nextInt(totalWeight);
int currentWeight = 0;
for (WeightedInstance wi : weighted) {
currentWeight += wi.weight();
if (randomWeight < currentWeight) {
return new DefaultResponse(wi.instance());
}
}
return new DefaultResponse(weighted.get(0).instance());
});
}
private record WeightedInstance(ServiceInstance instance, int weight) {}
}Soru 8: Gateway'de circuit breaker nasıl uygulanır?
Circuit breaker, kademeli arızalara karşı koruma sağlar. Spring Cloud Gateway, gelişmiş arıza yönetimi için Resilience4j ile entegre olur.
# application.yml
# Resilience4j Circuit Breaker yapılandırması
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
# Fallback'li circuit breaker
- name: CircuitBreaker
args:
name: orderServiceCB
fallbackUri: forward:/fallback/orders
resilience4j:
circuitbreaker:
configs:
default:
# Değerlendirilen istek sayısı
slidingWindowSize: 10
# Devreyi açmak için arıza eşiği
failureRateThreshold: 50
# Yeniden denemeden önce devre açık kalma süresi
waitDurationInOpenState: 30s
# Half-open durumunda izin verilen istekler
permittedNumberOfCallsInHalfOpenState: 3
# Otomatik geçişler
automaticTransitionFromOpenToHalfOpenEnabled: true
instances:
orderServiceCB:
baseConfig: default
failureRateThreshold: 60
timelimiter:
configs:
default:
timeoutDuration: 5s
instances:
orderServiceCB:
timeoutDuration: 3s// Fallback controller'ı
@RestController
@RequestMapping("/fallback")
@Slf4j
public class FallbackController {
@GetMapping("/orders")
public Mono<ResponseEntity<Map<String, Object>>> ordersFallback(
ServerWebExchange exchange
) {
log.warn("Circuit breaker activated for orders service");
Map<String, Object> response = Map.of(
"success", false,
"error", "Service temporarily unavailable",
"code", "SERVICE_UNAVAILABLE",
"retryAfter", 30
);
return Mono.just(ResponseEntity
.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(response));
}
@PostMapping("/orders")
public Mono<ResponseEntity<Map<String, Object>>> ordersPostFallback() {
Map<String, Object> response = Map.of(
"success", false,
"error", "Order creation temporarily unavailable",
"code", "SERVICE_UNAVAILABLE",
"message", "Please try again later"
);
return Mono.just(ResponseEntity
.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(response));
}
}// Circuit breaker olay izleme
@Component
@Slf4j
@RequiredArgsConstructor
public class CircuitBreakerEventListener {
private final MeterRegistry meterRegistry;
@EventListener
public void onCircuitBreakerStateTransition(
CircuitBreakerOnStateTransitionEvent event
) {
CircuitBreaker.StateTransition transition = event.getStateTransition();
log.info("Circuit breaker {} state changed: {} -> {}",
event.getCircuitBreakerName(),
transition.getFromState(),
transition.getToState());
// Micrometer metriği
meterRegistry.counter(
"circuit_breaker.state_transition",
"name", event.getCircuitBreakerName(),
"from", transition.getFromState().name(),
"to", transition.getToState().name()
).increment();
}
@EventListener
public void onCircuitBreakerFailure(CircuitBreakerOnErrorEvent event) {
log.error("Circuit breaker {} error: {}",
event.getCircuitBreakerName(),
event.getThrowable().getMessage());
}
}Circuit breaker ile HTTP istemcisi arasında tutarlı bir timeout yapılandırın. Çok uzun timeout thread'leri bloke ederken çok kısa olanı yanlış pozitiflere yol açar.
Soru 9: Üstel backoff'lu retry nasıl uygulanır?
Üstel backoff'lu akıllı retry, zorlanan bir servisi ezmemeyi sağlarken başarı şansını da en üst düzeye çıkarır.
# application.yml
# Backoff'lu retry yapılandırması
spring:
cloud:
gateway:
routes:
- id: payment-service
uri: lb://payment-service
predicates:
- Path=/api/payments/**
filters:
- name: Retry
args:
# Deneme sayısı
retries: 3
# Retry tetikleyen HTTP kodları
statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE,GATEWAY_TIMEOUT
# Yalnızca idempotent HTTP yöntemleri
methods: GET,PUT
# Retry tetikleyen istisnalar
exceptions:
- java.io.IOException
- java.net.ConnectException
- org.springframework.cloud.gateway.support.TimeoutException
# Üstel backoff
backoff:
firstBackoff: 100ms
maxBackoff: 2000ms
factor: 2
basedOnPreviousValue: true// İş mantığı içeren özel retry
@Component
@Slf4j
public class CustomRetryFilter implements GatewayFilter, Ordered {
private static final int MAX_RETRIES = 3;
private static final Duration INITIAL_BACKOFF = Duration.ofMillis(100);
private static final double BACKOFF_MULTIPLIER = 2.0;
private static final double JITTER_FACTOR = 0.1;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return Mono.defer(() -> attemptRequest(exchange, chain, 0));
}
private Mono<Void> attemptRequest(
ServerWebExchange exchange,
GatewayFilterChain chain,
int attempt
) {
return chain.filter(exchange)
.onErrorResume(throwable -> {
if (attempt >= MAX_RETRIES || !isRetryable(throwable, exchange)) {
return Mono.error(throwable);
}
Duration backoff = calculateBackoff(attempt);
log.warn("Retry attempt {} after {}ms for {} {}",
attempt + 1,
backoff.toMillis(),
exchange.getRequest().getMethod(),
exchange.getRequest().getPath());
return Mono.delay(backoff)
.then(Mono.defer(() ->
attemptRequest(exchange, chain, attempt + 1)
));
});
}
private boolean isRetryable(Throwable throwable, ServerWebExchange exchange) {
// Sadece idempotent yöntemleri tekrar dener
HttpMethod method = exchange.getRequest().getMethod();
if (!Set.of(HttpMethod.GET, HttpMethod.PUT, HttpMethod.DELETE)
.contains(method)) {
return false;
}
// İstisna türünü kontrol eder
return throwable instanceof ConnectException ||
throwable instanceof TimeoutException ||
throwable instanceof ServiceUnavailableException;
}
private Duration calculateBackoff(int attempt) {
// Jitter'lı üstel backoff
long baseBackoff = (long) (
INITIAL_BACKOFF.toMillis() * Math.pow(BACKOFF_MULTIPLIER, attempt)
);
// Rastgele jitter ekler (±10%)
double jitter = 1.0 + (Math.random() - 0.5) * 2 * JITTER_FACTOR;
long finalBackoff = (long) (baseBackoff * jitter);
return Duration.ofMillis(finalBackoff);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 1;
}
}Gelişmiş pattern'ler ve iyi uygulamalar
Soru 10: İstek toplaması nasıl uygulanır?
Toplama, birden fazla mikroservis çağrısını istemci için tek bir yanıtta birleştirerek gecikmeyi ve frontend karmaşıklığını azaltır.
// Çoklu servis toplaması
@RestController
@RequestMapping("/api/aggregate")
@RequiredArgsConstructor
@Slf4j
public class AggregationController {
private final WebClient.Builder webClientBuilder;
private final CircuitBreakerRegistry circuitBreakerRegistry;
@GetMapping("/user-dashboard/{userId}")
public Mono<DashboardResponse> getUserDashboard(@PathVariable Long userId) {
// Farklı servislere paralel çağrılar
Mono<UserProfile> userMono = fetchUserProfile(userId);
Mono<List<Order>> ordersMono = fetchRecentOrders(userId);
Mono<NotificationCount> notificationsMono = fetchNotificationCount(userId);
// Sonuçların toplanması
return Mono.zip(userMono, ordersMono, notificationsMono)
.map(tuple -> DashboardResponse.builder()
.user(tuple.getT1())
.recentOrders(tuple.getT2())
.notificationCount(tuple.getT3())
.generatedAt(Instant.now())
.build())
.timeout(Duration.ofSeconds(5))
.onErrorResume(this::handleAggregationError);
}
private Mono<UserProfile> fetchUserProfile(Long userId) {
return webClientBuilder.build()
.get()
.uri("lb://user-service/users/{id}", userId)
.retrieve()
.bodyToMono(UserProfile.class)
.transform(CircuitBreakerOperator.of(
circuitBreakerRegistry.circuitBreaker("user-service")
))
.onErrorReturn(new UserProfile(userId, "Unknown", null));
}
private Mono<List<Order>> fetchRecentOrders(Long userId) {
return webClientBuilder.build()
.get()
.uri("lb://order-service/orders?userId={id}&limit=5", userId)
.retrieve()
.bodyToFlux(Order.class)
.collectList()
.transform(CircuitBreakerOperator.of(
circuitBreakerRegistry.circuitBreaker("order-service")
))
.onErrorReturn(Collections.emptyList());
}
private Mono<NotificationCount> fetchNotificationCount(Long userId) {
return webClientBuilder.build()
.get()
.uri("lb://notification-service/notifications/count/{id}", userId)
.retrieve()
.bodyToMono(NotificationCount.class)
.transform(CircuitBreakerOperator.of(
circuitBreakerRegistry.circuitBreaker("notification-service")
))
.onErrorReturn(new NotificationCount(0, 0));
}
private Mono<DashboardResponse> handleAggregationError(Throwable error) {
log.error("Dashboard aggregation failed: {}", error.getMessage());
return Mono.just(DashboardResponse.builder()
.error("Partial data available")
.generatedAt(Instant.now())
.build());
}
}// Toplanmış yanıt DTO'su
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DashboardResponse {
private UserProfile user;
private List<Order> recentOrders;
private NotificationCount notificationCount;
private Instant generatedAt;
private String error;
}Soru 11: Gateway OAuth2 ile nasıl güvenli hale getirilir?
OAuth2 entegrasyonu, kimlik doğrulamayı gateway seviyesinde merkezileştirerek her mikroserviste mantığın tekrarını ortadan kaldırır.
# application.yml
# OAuth2 Resource Server yapılandırması
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.example.com/realms/myrealm
jwk-set-uri: https://auth.example.com/realms/myrealm/protocol/openid-connect/certs// Gateway güvenlik yapılandırması
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
// Stateless API için CSRF'yi devre dışı bırakır
.csrf(ServerHttpSecurity.CsrfSpec::disable)
// Yetkilendirme yapılandırması
.authorizeExchange(exchanges -> exchanges
// Genel endpoint'ler
.pathMatchers("/actuator/health", "/actuator/info").permitAll()
.pathMatchers("/api/public/**").permitAll()
.pathMatchers("/api/auth/**").permitAll()
// Role özel endpoint'ler
.pathMatchers("/api/admin/**").hasRole("ADMIN")
.pathMatchers(HttpMethod.DELETE, "/api/**").hasRole("ADMIN")
// Geri kalan her şey kimlik doğrulama gerektirir
.anyExchange().authenticated()
)
// JWT'li OAuth2 Resource Server
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
)
.build();
}
@Bean
public ReactiveJwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter =
new JwtGrantedAuthoritiesConverter();
// Rolleri "roles" claim'inden çıkarır
grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
ReactiveJwtAuthenticationConverter converter =
new ReactiveJwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(
new ReactiveJwtGrantedAuthoritiesConverterAdapter(grantedAuthoritiesConverter)
);
return converter;
}
}// Token'ı downstream servislere iletme
@Component
public class TokenRelayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.filter(auth -> auth instanceof JwtAuthenticationToken)
.cast(JwtAuthenticationToken.class)
.map(JwtAuthenticationToken::getToken)
.map(jwt -> {
// Token'ı downstream servislere iletir
ServerHttpRequest request = exchange.getRequest()
.mutate()
.header(HttpHeaders.AUTHORIZATION, "Bearer " + jwt.getTokenValue())
// Token'dan çıkarılan kullanıcı bilgilerini ekler
.header("X-User-Id", jwt.getSubject())
.header("X-User-Email", jwt.getClaimAsString("email"))
.build();
return exchange.mutate().request(request).build();
})
.defaultIfEmpty(exchange)
.flatMap(chain::filter);
}
@Override
public int getOrder() {
// Kimlik doğrulamadan sonra, routing'den önce
return SecurityWebFiltersOrder.AUTHENTICATION.getOrder() + 1;
}
}Spring Boot mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
Soru 12: İzleme ve gözlemlenebilirlikte iyi uygulamalar nelerdir?
Gateway izleme, mikroservis mimarisindeki performans ve erişilebilirlik sorunlarını tespit etmek için kritik önemdedir.
# application.yml
# Gözlemlenebilirlik yapılandırması
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,gateway
metrics:
tags:
application: api-gateway
distribution:
percentiles-histogram:
http.server.requests: true
percentiles:
http.server.requests: 0.5, 0.95, 0.99
tracing:
sampling:
probability: 1.0
spring:
cloud:
gateway:
metrics:
enabled: true
tags:
path:
enabled: true// Özel metrik filtresi
@Component
@RequiredArgsConstructor
@Slf4j
public class MetricsFilter implements GlobalFilter, Ordered {
private final MeterRegistry meterRegistry;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long startTime = System.nanoTime();
String path = exchange.getRequest().getPath().value();
String method = exchange.getRequest().getMethod().name();
// Hedef servisi route'tan çıkarır
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
String routeId = route != null ? route.getId() : "unknown";
return chain.filter(exchange)
.doOnSuccess(v -> recordMetrics(exchange, startTime, routeId, "success"))
.doOnError(e -> recordMetrics(exchange, startTime, routeId, "error"));
}
private void recordMetrics(
ServerWebExchange exchange,
long startTime,
String routeId,
String outcome
) {
long duration = System.nanoTime() - startTime;
HttpStatusCode status = exchange.getResponse().getStatusCode();
String statusCode = status != null ? String.valueOf(status.value()) : "0";
// Gecikme için timer
Timer.builder("gateway.request.duration")
.tag("route", routeId)
.tag("method", exchange.getRequest().getMethod().name())
.tag("status", statusCode)
.tag("outcome", outcome)
.register(meterRegistry)
.record(duration, TimeUnit.NANOSECONDS);
// İstek sayacı
meterRegistry.counter(
"gateway.requests.total",
"route", routeId,
"status", statusCode
).increment();
// Yavaş isteklerin loglanması
if (duration > TimeUnit.SECONDS.toNanos(1)) {
log.warn("Slow request: {} {} - {}ms (route: {})",
exchange.getRequest().getMethod(),
exchange.getRequest().getPath(),
TimeUnit.NANOSECONDS.toMillis(duration),
routeId);
}
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}// Tracing bağlamının yayılımı
@Component
@RequiredArgsConstructor
public class TracingFilter implements GlobalFilter, Ordered {
private final Tracer tracer;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Tracing span'ini oluşturur veya alır
Span span = tracer.nextSpan()
.name("gateway-request")
.tag("http.method", exchange.getRequest().getMethod().name())
.tag("http.url", exchange.getRequest().getURI().toString())
.start();
// Tracing header'larını enjekte eder
ServerHttpRequest request = exchange.getRequest()
.mutate()
.header("X-Trace-Id", span.context().traceId())
.header("X-Span-Id", span.context().spanId())
.build();
return chain.filter(exchange.mutate().request(request).build())
.doOnSuccess(v -> {
HttpStatusCode status = exchange.getResponse().getStatusCode();
span.tag("http.status_code",
status != null ? String.valueOf(status.value()) : "0");
span.end();
})
.doOnError(e -> {
span.tag("error", e.getMessage());
span.end();
});
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}Temel metrikler tablosu:
| Metrik | Açıklama |
|-----------------------------------|----------------------------------|
| gateway.request.duration | Route/durum başına gecikme |
| gateway.requests.total | İstek sayacı |
| resilience4j.circuitbreaker.state | Circuit breaker durumları |
| http.server.requests | Standart HTTP metrikleri |
| spring.cloud.gateway.routes | Aktif route'lar |Metrikleri görselleştirmek için Spring Cloud Gateway ve Resilience4j panolarıyla Grafana kullanın. P99 gecikmesi ve hata oranı üzerinde uyarılar yapılandırın.
Sonuç
Spring Cloud Gateway, modern mikroservis mimarilerinin temel bileşenlerinden biridir. Mülakatlar için akılda tutulması gereken anahtar noktalar:
Mimari ve kavramlar:
- ✅ Routes, Predicates ve Filters temel modeli oluşturur
- ✅ WebFlux ve Netty ile reaktif mimari
- ✅ Spring Cloud ekosistemiyle yerel entegrasyon
Temel özellikler:
- ✅ Çoklu kritere dayalı dinamik routing
- ✅ İstek ve yanıt dönüşümü için pre/post filtreler
- ✅ Spring Cloud LoadBalancer ile load balancing
Dayanıklılık ve güvenlik:
- ✅ Resilience4j ile circuit breaker ve fallback
- ✅ Üstel backoff ve jitter ile retry
- ✅ Merkezi OAuth2/JWT kimlik doğrulaması
Gözlemlenebilirlik:
- ✅ Route bazlı tag'li Micrometer metrikleri
- ✅ Bağlam yayılımıyla dağıtık tracing
- ✅ Health check ve actuator endpoint'leri
Spring Cloud Gateway'i kullanmayı bilmek, mikroservis pattern'leri ve ölçeklenebilirlik konularına dair derin bir anlayışı kanıtlar. Bu yetkinlikler, sağlam ve yüksek performanslı API Gateway'ler tasarlamak için vazgeçilmezdir.
Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
Etiketler
Paylaş
İlgili makaleler

Spring Kafka: dayanıklı tüketicilerle event-driven mimari
Event-driven mimariler için kapsamlı Spring Kafka rehberi. Yapılandırma, dayanıklı tüketiciler, retry politikaları, dead letter queue ve dağıtık uygulamalar için üretim kalıpları.

2026'da Spring Boot loglama: Logback ve JSON ile üretim ortamında yapılandırılmış loglar
Spring Boot yapılandırılmış loglama için kapsamlı rehber. Logback JSON yapılandırması, izleme için MDC, üretimde en iyi uygulamalar ve ELK Stack entegrasyonu.

Spring GraphQL Mülakatı: Resolver'lar, DataLoader'lar ve N+1 Problemi Çözümleri
Bu kapsamlı kılavuzla Spring GraphQL mülakatlarına hazırlanın. Resolver'lar, DataLoader'lar, N+1 problemi yönetimi, mutation'lar ve teknik sorular için en iyi uygulamalar.