Nội dung bài viết
Video học lập trình mỗi ngày
Đây là "Microservices Architecture" được gói gọn bởi Springboot. Nó đơn giản đến mức ở level nào cũng có thể hiểu được. Rất nhiều khái niệm được đưa ra nhằm giải thích về "Functions of an API Gateway". Với tôi chỉ cẩn hiểu 7 yếu tố này là xem như đã thành công!
API Gateway - Nên đơn giản
Tóm lại là "Functions of an API Gateway" nếu thời gian của bạn là uý giá thì 6 tính năng cần chú ý, chứ trên internet nhiều và mông lung quá:
Monitoring & Logging: Theo dõi hiệu năng, lỗi và mức sử dụng API.
Caching: Lưu tạm phản hồi thường dùng để giảm tải và tăng tốc.
Response Transformation: Chỉnh sửa phản hồi cho phù hợp với client/UI.
Request Transformation: Điều chỉnh yêu cầu để tương thích backend.
Rate Limiting: Giới hạn số yêu cầu nhằm ngăn lạm dụng.
Authentication & Authorization: Xác thực người dùng và kiểm tra quyền truy cập.
Request Routing: Định tuyến yêu cầu đến đúng dịch vụ backend.
Còn nếu bạn có thời gian như tôi thì chúng ta đi dạo một vòng về các tính năng được liệt kê trên...
API Gateway - (1) Monitoring & Logging
Vì mọi request đều đi qua Gateway, nên đây là nơi lý tưởng nhất để thu thập dữ liệu:
- API nào được gọi nhiều nhất?
- Thời gian phản hồi trung bình là bao nhiêu?
- Tỷ lệ lỗi là bao nhiêu?
- Request từ đâu tới?
Tất cả những thông tin này cực kỳ quý giá để theo dõi sức khỏe hệ thống, debug lỗi và thậm chí là cho cả team business phân tích hành vi người dùng. Chúng ta có thể log mọi request/response hoặc tích hợp với các công cụ như Prometheus, Grafana, ELK Stack...
Về thực hành thì đây: Giám sát hệ thống bán vé tàu tết với Prometheus, Grafana, ELK Stack
API Gateway - (2) Caching
Với những request mà dữ liệu ít thay đổi (ví dụ: danh sách sản phẩm hot, danh mục...), việc lần nào cũng hỏi xuống backend là một sự lãng phí. Gateway có thể cache lại response của những request này. Lần sau client hỏi, nếu có trong cache thì trả về luôn, không cần làm phiền đến backend nữa. Việc này giúp giảm tải cho hệ thống và tăng tốc độ phản hồi đáng kể.
Thực hành với Go: Tại sao hệ thống bán vé api cần LOCAL CACHE khi đã có DISTRIBUTED CACHE?
API Gateway - (3 vs 4) Request & Response Transformation
Đôi khi, "ngôn ngữ" của client và backend không khớp nhau.
- Request Transformation: Client gửi lên một request với format A, nhưng service backend lại chỉ hiểu format B. Gateway có thể "dịch" lại request này trước khi gửi đi. Ví dụ đơn giản là thêm một cái header tracking chẳng hạn.
- Response Transformation: Ngược lại, service backend trả về dữ liệu thô, nhưng UI/client cần một cấu trúc JSON gọn gàng hơn. Gateway lại ra tay "mông má" lại response trước khi trả về cho client.
Ví dụ, ta thêm một X-Request-ID
vào mỗi request để dễ dàng tracing log:
// Trong một GatewayFilter...
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String requestId = UUID.randomUUID().toString();
ServerHttpRequest modifiedRequest = exchange.getRequest().mutate()
.header("X-Request-ID", requestId)
.build();
// Gửi request đã được chỉnh sửa đi tiếp
return chain.filter(exchange.mutate().request(modifiedRequest).build());
}
API Gateway - (5) Rate Limiting
Hệ thống của mình không phải là cái máy vô hạn. Nếu một client nào đó "lỡ tay" gọi API liên tục cả ngàn lần một giây, các service backend của mình sẽ "ngủm" ngay. Rate Limiting sinh ra để giải quyết việc này, nó giới hạn số lượng request mà một client có thể gọi trong một khoảng thời gian nhất định.
Spring Cloud Gateway cũng hỗ trợ cái này rất tốt, thường là kết hợp với Redis để lưu trữ số lượng request.
Cấu hình trong application.yml
:
spring:
cloud:
gateway:
routes:
- id: some_route
uri: lb://some-service
predicates:
- Path=/api/sensitive/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 10 request mỗi giây
redis-rate-limiter.burstCapacity: 20 # Tối đa cho phép 20 request dồn dập
key-resolver: "#{@userKeyResolver}" # Dùng IP hoặc user ID để phân biệt client
Với cấu hình trên, mỗi client chỉ được gọi các API /api/sensitive/**
tối đa 10 lần/giây. Cố tình gọi thêm? Gateway sẽ trả về lỗi 429 Too Many Requests
.
Thực hành về: Circuit Breaker vs RateLimiter - Tuyến phòng thủ đầu tiên cho DDD (bán vé trực tuyến)
PI Gateway - (6) Authentication & Authorization (AuthN & AuthZ)
Ok, đã biết đường rồi, nhưng vào được hay không lại là chuyện khác. Tầng này chính là lúc gã bảo vệ hỏi: "Anh là ai? (Authentication) và Anh có quyền vào đây không? (Authorization)".
Đây là một trong những lý do lớn nhất người ta dùng API Gateway. Thay vì mỗi service phải tự đi implement logic xác thực token (JWT chẳng hạn), thì cứ để Gateway làm hết. Nó sẽ check token, nếu hợp lệ thì mới cho đi tiếp, thậm chí nó còn có thể "đính kèm" thông tin user (ví dụ: user-id
) vào header để các service phía sau chỉ việc dùng, không cần hỏi lại.
Với Spring Cloud Gateway, ta có thể viết một GlobalFilter
để làm việc này:
@Component
public class AuthenticationFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// Bỏ qua các endpoint không cần xác thực (ví dụ: /login, /register)
if (isPublicEndpoint(request)) {
return chain.filter(exchange);
}
// 1. Lấy token từ header "Authorization"
String authHeader = request.getHeaders().getFirst("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
// Trả về lỗi 401 Unauthorized
return this.onError(exchange, "Missing Authorization Header", HttpStatus.UNAUTHORIZED);
}
String token = authHeader.substring(7);
// 2. Validate token (dùng thư viện jwt của bạn)
if (!jwtUtil.isTokenValid(token)) {
return this.onError(exchange, "Invalid Token", HttpStatus.UNAUTHORIZED);
}
// 3. (Optional) Lấy thông tin user và gắn vào header cho các service phía sau
String userId = jwtUtil.getUserIdFromToken(token);
ServerHttpRequest modifiedRequest = request.mutate()
.header("X-User-Id", userId)
.build();
return chain.filter(exchange.mutate().request(modifiedRequest).build());
}
// ... các method helper khác ...
}
Thấy không? Mọi logic xác thực tập trung một chỗ. Các service backend chỉ cần tin tưởng vào header X-User-Id
mà Gateway gửi xuống là được. Quá khỏe!
Thực hành tại: SENIOR hỏi INTERN: Bạn đã làm qua dự án Microservice? Cách authentication trong Microservice?
API Gateway - (7) Request Routing
Đây là nhiệm vụ cơ bản và sơ đẳng nhất: nhận request và biết phải đẩy nó cho service nào. Giống như lễ tân chỉ đường cho khách vậy: "Anh muốn gặp phòng Kinh doanh à? Đi thẳng rẽ trái nhé."
Nó sẽ nhìn vào path, header, hay bất cứ thứ gì trong request để quyết định "số phận" của request đó. Với anh em nhà Java, đặc biệt là team Spring Boot, thì Spring Cloud Gateway là một lựa chọn quá quen thuộc. Cấu hình routing của nó cực kỳ đơn giản, nhìn phát hiểu liền.
Ví dụ, cấu hình trong file application.yml
:
spring:
cloud:
gateway:
routes:
- id: user_service_route
uri: lb://user-service # lb là load balancer, chỉ đến service tên là "user-service"
predicates:
- Path=/api/users/** # Bất kỳ request nào có path /api/users/...
- id: product_service_route
uri: lb://product-service
predicates:
- Path=/api/products/** # Sẽ được chuyển đến product-service
Chỉ cần vài dòng YAML là xong, quá gọn gàng!
Kết Lại
Tóm lại, API Gateway không chỉ đơn giản là một cái router. Nó là một con dao đa năng Thụy Sĩ, giải quyết hàng loạt các "cross-cutting concerns" (những vấn đề chung như security, logging, rate limiting...). Nhờ có nó, team backend có thể tập trung hoàn toàn vào việc xây dựng business logic cho service của mình mà không cần bận tâm đến những thứ râu ria xung quanh.
Đối với anh em dev Java, việc nắm vững một công cụ như Spring Cloud Gateway thực sự là một lợi thế lớn khi xây dựng các hệ thống microservices hiện đại. Nó giúp hệ thống của chúng ta trở nên an toàn hơn, ổn định hơn và dễ quản lý hơn rất nhiều.