Dlaczego API Gateway to game-changer w mikrousługach?
W świecie mikrousług każdy serwis ma własny endpoint, własne zasady bezpieczeństwa i własne API. Bez centralnego punktu kontroli szybko powstaje chaos – klienty muszą znać dziesiątki URL-i, implementować własną logikę retry, a zarządzanie security staje się koszmarem.
API Gateway rozwiązuje te problemy, stając się jedynym punktem wejścia do całego ekosystemu mikrousług. To jak recepcjonista w biurowcu – wie gdzie skierować każde zapytanie i pilnuje, żeby tylko uprawnione osoby miały dostęp.
Co się nauczysz z tego artykułu:
- Jakie wzorce API Gateway są najważniejsze w praktyce
- Jak zaimplementować Backend for Frontend pattern
- Kiedy używać Circuit Breaker i jak go skonfigurować
- Praktyczne porównanie Spring Cloud Gateway vs Netflix Zuul
- Best practices dla rate limiting i security
- Jak monitorować i debugować API Gateway w produkcji
Podstawowe wzorce API Gateway
API Gateway implementuje kilka kluczowych wzorców, które rozwiązują typowe problemy w architekturze rozproszonej. Poznajmy najważniejsze z nich.
1. Gateway Router Pattern
Najprostszy i najważniejszy wzorzec – gateway odbiera request i przekierowuje go do odpowiedniego mikrousługi na podstawie URL-a, nagłówków lub innych kryteriów.
# Spring Cloud Gateway configuration spring: cloud: gateway: routes: - id: user-service uri: http://user-service:8081 predicates: - Path=/api/users/** filters: - StripPrefix=2 - id: order-service uri: http://order-service:8082 predicates: - Path=/api/orders/** filters: - StripPrefix=2
2. Backend for Frontend (BFF) Pattern
Różne typy klientów (web app, mobile app, external API) potrzebują różnych formatów danych i różnych endpoints. BFF pattern tworzy dedykowane gateway dla każdego typu klienta.
@RestController @RequestMapping("/mobile/api") public class MobileGatewayController { @Autowired private UserService userService; @Autowired private OrderService orderService; // Endpoint zoptymalizowany dla mobile - zwraca tylko potrzebne dane @GetMapping("/user/{id}/summary") public MobileUserSummary getUserSummary(@PathVariable Long id) { User user = userService.getUser(id); ListrecentOrders = orderService.getRecentOrders(id, 5); return MobileUserSummary.builder() .userName(user.getName()) .avatar(user.getAvatarUrl()) .recentOrdersCount(recentOrders.size()) .lastOrderDate(recentOrders.get(0).getCreatedAt()) .build(); } }
Circuit Breaker Pattern w API Gateway
Gdy jeden z mikrousług nie odpowiada, Circuit Breaker zapobiega kaskadowym awariom. Zamiast czekać na timeout, gateway szybko zwraca error lub fallback response.
@Component public class OrderServiceClient { @Autowired private RestTemplate restTemplate; @HystrixCommand( fallbackMethod = "getOrdersFallback", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"), @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50") } ) public ListgetOrders(Long userId) { return restTemplate.getForObject( "http://order-service/users/" + userId + "/orders", OrderList.class ).getOrders(); } public List getOrdersFallback(Long userId) { // Zwracamy cached data lub pusty wynik return cacheService.getCachedOrders(userId) .orElse(Collections.emptyList()); } }
Authentication i Authorization patterns
API Gateway to idealny punkt do centralizacji uwierzytelniania. Zamiast implementować security w każdym mikrousługi, robimy to raz w gateway.
JWT Token Validation Pattern
@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtTokenProvider tokenProvider; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = extractTokenFromRequest(request); if (token != null && tokenProvider.validateToken(token)) { // Dodaj user info do headers dla downstream services String userId = tokenProvider.getUserIdFromToken(token); String roles = tokenProvider.getRolesFromToken(token); // Forward headers to microservices request.setAttribute("X-User-Id", userId); request.setAttribute("X-User-Roles", roles); } else { response.setStatus(HttpStatus.UNAUTHORIZED.value()); return; } filterChain.doFilter(request, response); } private String extractTokenFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (bearerToken != null && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } }
Rate Limiting patterns
Rate limiting chroni przed nadmiernym ruchem i atakami DDoS. W API Gateway można implementować różne strategie w zależności od potrzeb.
Pattern | Opis | Użycie |
---|---|---|
Fixed Window | X requestów na minutę | Proste API publiczne |
Sliding Window | X requestów w ostatniej minucie | Bardziej równomierne ograniczenia |
Token Bucket | Możliwość burst traffic | API z okresowymi skokami ruchu |
Per-User Limiting | Różne limity dla różnych użytkowników | Płatne tiers API |
@Component public class RateLimitingFilter implements GlobalFilter { private final RedisTemplateredisTemplate; @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); String clientId = extractClientId(request); String key = "rate_limit:" + clientId; return checkRateLimit(key) .flatMap(allowed -> { if (allowed) { return chain.filter(exchange); } else { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS); response.getHeaders().add("X-RateLimit-Retry-After", "60"); return response.setComplete(); } }); } private Mono checkRateLimit(String key) { return Mono.fromCallable(() -> { // Sliding window algorithm with Redis long now = System.currentTimeMillis(); long windowStart = now - 60000; // 1 minute window // Remove old entries redisTemplate.opsForZSet().removeRangeByScore(key, 0, windowStart); // Count current requests Long count = redisTemplate.opsForZSet().count(key, windowStart, now); if (count < 100) { // 100 requests per minute limit // Add current request redisTemplate.opsForZSet().add(key, UUID.randomUUID().toString(), now); redisTemplate.expire(key, Duration.ofMinutes(1)); return true; } return false; }); } }
Monitoring i Observability patterns
API Gateway zbiera metryki ze wszystkich requestów, co daje doskonały wgląd w całą architekturę. Kluczowe metryki to latencja, error rate, throughput i status poszczególnych mikrousług.
Distributed Tracing
@RestController public class GatewayController { @Autowired private Tracer tracer; @GetMapping("/api/user/{id}/profile") public MonogetUserProfile(@PathVariable String id) { Span span = tracer.nextSpan() .name("gateway-get-user-profile") .tag("user.id", id) .start(); try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) { // Request propaguje trace context do mikrousług return webClient .get() .uri("http://user-service/users/" + id) .retrieve() .bodyToMono(UserProfile.class) .doOnSuccess(profile -> span.tag("user.name", profile.getName())) .doOnError(error -> span.tag("error", error.getMessage())); } finally { span.end(); } } }
Porównanie rozwiązań w 2019
Rozwiązanie | Zalety | Wady | Kiedy wybierać |
---|---|---|---|
Spring Cloud Gateway | Reactive, Performance, Spring ecosystem | Młode, mniej pluginów | Nowe projekty, Spring shops |
Netflix Zuul 1 | Stabilny, dużo dokumentacji | Blocking I/O, performance issues | Legacy systems, gdzie stability > performance |
Netflix Zuul 2 | Non-blocking, dobry performance | Nowy, mniej examples | High-traffic applications |
Kong | Feature-rich, plugins, UI | Nie-Java, licencja dla enterprise | Multi-platform, gdy potrzebujesz UI |
Production-ready deployment patterns
W produkcji API Gateway to single point of failure, więc wymaga szczególnej uwagi na wysoką dostępność i performance.
High Availability Setup
# docker-compose.yml for HA Gateway version: '3.8' services: gateway-1: image: api-gateway:latest ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod - EUREKA_INSTANCE_INSTANCE_ID=gateway-1 depends_on: - redis - eureka gateway-2: image: api-gateway:latest ports: - "8081:8080" environment: - SPRING_PROFILES_ACTIVE=prod - EUREKA_INSTANCE_INSTANCE_ID=gateway-2 depends_on: - redis - eureka load-balancer: image: nginx:alpine ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf depends_on: - gateway-1 - gateway-2
Health Checks i Graceful Shutdown
@RestController public class HealthController { @Autowired private ListhealthIndicators; @GetMapping("/health") public ResponseEntity
Częste problemy i rozwiązania
Rozwiązanie: Ustaw timeouts kaskadowo - gateway timeout > HTTP client timeout > mikrousługa timeout. Przykład: 5s > 4s > 3s.
Lepsze podejście: Loguj tylko errors, slow requests (>2s) i sample requestów (np. co 100-ty). Używaj structured logging (JSON) dla łatwego parsowania.
Security best practices
API Gateway to pierwsza linia obrony - każda luka tutaj wpływa na całą architekturę.
@Configuration public class CorsConfiguration { @Bean public CorsWebFilter corsWebFilter() { CorsConfiguration corsConfig = new CorsConfiguration(); corsConfig.setAllowCredentials(true); corsConfig.addAllowedOriginPattern("https://*.yourdomain.com"); corsConfig.addAllowedHeader("*"); corsConfig.addAllowedMethod("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfig); return new CorsWebFilter(source); } }
Tak, ale można to zminimalizować przez load balancer i multiple instances. W praktyce korzyści (centralne security, monitoring, routing) przeważają nad ryzykiem. Alternatywą jest service mesh, ale to znacznie bardziej skomplikowane.
Dobrze zoptymalizowany gateway dodaje 1-5ms latencji. Spring Cloud Gateway w benchmarkach osiąga <1ms overhead. Ważniejsze są korzyści: cache responses, circuit breaker, compression - które mogą faktycznie poprawić performance.
Jeden gateway jeśli klienty mają podobne potrzeby. BFF gdy mobile app potrzebuje innych danych niż web app, lub gdy masz external API z innymi SLA. BFF zwiększa complexity, ale daje lepsze developer experience.
Unit tests dla filters i custom logic. Integration tests z WireMock dla downstream services. Contract tests (Pact) dla API contracts. Performance tests dla throughput i latencji. Chaos engineering dla circuit breaker logic.
Zuul 1 - tylko dla legacy systems. Zuul 2 - ok dla high-performance apps, ale mało examples. Spring Cloud Gateway - najlepsza opcja dla nowych projektów Spring. Ma reactive support i aktywny development.
Header-based: route na podstawie Accept-Version header. URL-based: /v1/users vs /v2/users. Query param: /users?version=2. Header-based jest cleanest, ale URL-based jest najprostszy dla klientów.
Enable actuator metrics. Dodaj custom timers dla każdego route. Sprawdź connection pool settings dla HTTP client. Monitor GC metrics. Używaj profiler (JProfiler, async-profiler) w staging. Trace slow requests z distributed tracing.
Przydatne zasoby:
- Spring Cloud Gateway dokumentacja
- Netflix Zuul GitHub
- Kong API Gateway
- API Gateway pattern na Microservices.io
- NGINX Guide to Microservices
🚀 Zadanie dla Ciebie
Stwórz API Gateway z Spring Cloud Gateway, który będzie routować requesty do dwóch mikrousług (user-service i order-service). Dodaj JWT authentication, rate limiting (100 requests/minute), circuit breaker z Hystrix, i monitoring z Micrometer. Przetestuj, że rate limiting działa poprawnie i circuit breaker otwiera się po kilku failed requests.
Masz doświadczenie z API Gateway w produkcji? Podziel się swoimi insights w komentarzach - które wzorce sprawdziły się najlepiej w Twoich projektach?