🎯 Introduction
In modern distributed systems with dozens or hundreds of microservices, managing API traffic becomes increasingly complex. AWS API Gateway emerges as a critical component that acts as a single entry point for all client requests, solving major challenges in microservices architecture. This comprehensive guide explores API Gateway fundamentals, compares it with load balancers, and provides production-ready Java implementations.
API Gateway transforms chaotic microservices communication into organized, secure, and scalable architecture patterns that are essential for enterprise-grade applications.
🤔 Why API Gateway?
📊 Microservices Challenges Without API Gateway
graph TD
subgraph "Without API Gateway - Chaos"
C1[Mobile App] --> S1[User Service]
C1 --> S2[Order Service]
C1 --> S3[Payment Service]
C1 --> S4[Inventory Service]
C2[Web App] --> S1
C2 --> S2
C2 --> S3
C2 --> S4
C3[Partner API] --> S1
C3 --> S2
C3 --> S3
C3 --> S4
end
subgraph "Problems"
P1[Inconsistent Interfaces]
P2[Increased Latency]
P3[Security Concerns]
P4[Traffic Spikes]
P5[Cross-Cutting Logic Duplication]
end
style C1 fill:#ff6b6b
style C2 fill:#ff6b6b
style C3 fill:#ff6b6b
style P1 fill:#e74c3c
style P2 fill:#e74c3c
style P3 fill:#e74c3c
style P4 fill:#e74c3c
style P5 fill:#e74c3c
When you have dozens or hundreds of microservices, exposing each one directly to clients leads to several critical problems:
🔴 Core Problems:
- Inconsistent Interfaces: Each service may have different authentication methods, response formats, and error handling
- Increased Latency: Multiple round trips between client and services
- Security Concerns: Each service needs its own security implementation
- Traffic Spikes: Individual services can be overwhelmed without proper traffic management
- Cross-Cutting Logic Duplication: Authentication, logging, rate limiting implemented multiple times
✅ API Gateway Solution
graph TD
subgraph "With API Gateway - Organized"
C1[Mobile App] --> AG[API Gateway]
C2[Web App] --> AG
C3[Partner API] --> AG
AG --> S1[User Service]
AG --> S2[Order Service]
AG --> S3[Payment Service]
AG --> S4[Inventory Service]
end
subgraph "API Gateway Features"
F1[Single Entry Point]
F2[Authentication & Authorization]
F3[Rate Limiting & Throttling]
F4[Request/Response Transformation]
F5[Caching]
F6[Monitoring & Analytics]
F7[Load Balancing]
F8[Circuit Breaker]
end
AG -.-> F1
AG -.-> F2
AG -.-> F3
AG -.-> F4
AG -.-> F5
AG -.-> F6
AG -.-> F7
AG -.-> F8
style AG fill:#4ecdc4
style F1 fill:#2ecc71
style F2 fill:#2ecc71
style F3 fill:#2ecc71
style F4 fill:#2ecc71
style F5 fill:#2ecc71
style F6 fill:#2ecc71
style F7 fill:#2ecc71
style F8 fill:#2ecc71
An API Gateway acts as a traffic cop that directs and controls the flow of requests, solving microservices challenges by:
- Single Entry Point: Unified interface for all client interactions
- Centralized Cross-Cutting Concerns: Authentication, rate limiting, monitoring in one place
- Smart Routing: Path-based and method-based request routing
- Performance Optimization: Caching, response aggregation, connection pooling
🏗️ AWS API Gateway Architecture
🔧 How API Gateway Works
sequenceDiagram
participant C as Client
participant AG as API Gateway
participant Auth as Authentication
participant Cache as Cache Layer
participant S1 as User Service
participant S2 as Order Service
participant Mon as Monitoring
C->>AG: HTTP Request
AG->>Auth: Validate Token
Auth-->>AG: Authentication Result
alt Authenticated
AG->>Cache: Check Cache
alt Cache Miss
AG->>S1: Route to User Service
S1-->>AG: Response
AG->>Cache: Store in Cache
else Cache Hit
Cache-->>AG: Cached Response
end
AG->>Mon: Log Request Metrics
AG-->>C: HTTP Response
else Not Authenticated
AG-->>C: 401 Unauthorized
end
📋 Core API Gateway Functions
- Request Handling: Clients send all API requests to the gateway
- Routing: Gateway inspects requests and routes to appropriate backend services
- Cross-Cutting Features: Handles authentication, rate limiting, caching, logging
- Response Aggregation: Combines data from multiple services into single responses
⚖️ API Gateway vs Load Balancer: Detailed Comparison
📊 Comprehensive Comparison Table
Feature | Load Balancer | API Gateway |
---|---|---|
Primary Role | Distribute traffic across multiple backend servers | Manage and route API requests from clients to services |
OSI Layer | Network/Transport Layer (L4) or Application Layer (L7) | Application Layer (L7 only) |
Core Function | Balances traffic for availability and fault tolerance | Routes, authenticates, throttles, transforms API requests |
HTTP API Understanding | Only L7 load balancers understand HTTP | Built specifically for HTTP APIs |
Smart Routing | Basic (round-robin, IP hash, least connections) | Advanced (path-based /users , method-based POST /auth ) |
Security Features | SSL termination, basic IP filtering | Built-in auth, rate limiting, request validation, CORS |
Aggregation/Orchestration | ❌ No | ✅ Can aggregate multiple services into one response |
Request Transformation | ❌ Limited | ✅ Request/response transformation |
Caching | ❌ Basic (some L7 balancers) | ✅ Built-in response caching |
API Management | ❌ No | ✅ Versioning, documentation, SDK generation |
Monitoring & Analytics | Basic health checks | Detailed API metrics, usage analytics |
Example Tools | AWS ELB, NGINX, HAProxy, Envoy | AWS API Gateway, Kong, Apigee, Zuul |
🚗 Analogy Comparison
graph TD
subgraph "Load Balancer - Traffic Cop"
LB[Load Balancer<br/>Traffic Cop] --> L1[Lane 1<br/>Server A]
LB --> L2[Lane 2<br/>Server B]
LB --> L3[Lane 3<br/>Server C]
Note1[Distributes cars evenly<br/>across multiple lanes<br/>to prevent traffic jams]
end
subgraph "API Gateway - Concierge Desk"
AG[API Gateway<br/>Concierge] --> Check[Check ID<br/>Authentication]
Check --> Route[Decide Service<br/>Smart Routing]
Route --> Transform[Modify Request<br/>Transformation]
Transform --> Track[Track Usage<br/>Analytics]
Note2[Authenticates requests<br/>Routes intelligently<br/>Transforms data<br/>Monitors usage]
end
style LB fill:#3498db
style AG fill:#e74c3c
style Note1 fill:#f39c12
style Note2 fill:#f39c12
🎯 When to Use What
✅ Use Load Balancer When:
- Simple traffic distribution across identical servers
- High-performance, low-latency requirements
- Handling non-HTTP protocols (TCP, UDP)
- Basic high availability and fault tolerance
- Cost-sensitive scenarios (simpler = cheaper)
✅ Use API Gateway When:
- Managing multiple microservices
- Need centralized authentication/authorization
- Require request/response transformation
- API versioning and documentation needed
- Rate limiting and throttling required
- Response aggregation from multiple services
- Detailed API analytics and monitoring needed
✅ Use Both Together:
Most enterprise architectures use both - API Gateway for API management and Load Balancer for traffic distribution behind the gateway.
🛠️ AWS API Gateway Implementation
1. Java SDK Integration
Maven Dependencies:
1<dependencies>
2 <!-- AWS SDK for API Gateway -->
3 <dependency>
4 <groupId>software.amazon.awssdk</groupId>
5 <artifactId>apigateway</artifactId>
6 <version>2.21.29</version>
7 </dependency>
8
9 <!-- AWS SDK for Lambda (for backend integration) -->
10 <dependency>
11 <groupId>software.amazon.awssdk</groupId>
12 <artifactId>lambda</artifactId>
13 <version>2.21.29</version>
14 </dependency>
15
16 <!-- Spring Boot for API development -->
17 <dependency>
18 <groupId>org.springframework.boot</groupId>
19 <artifactId>spring-boot-starter-web</artifactId>
20 </dependency>
21
22 <!-- Spring Cloud AWS -->
23 <dependency>
24 <groupId>io.awspring.cloud</groupId>
25 <artifactId>spring-cloud-starter-aws</artifactId>
26 <version>2.4.4</version>
27 </dependency>
28
29 <!-- For API Gateway custom authorizers -->
30 <dependency>
31 <groupId>com.amazonaws</groupId>
32 <artifactId>aws-lambda-java-events</artifactId>
33 <version>3.11.0</version>
34 </dependency>
35
36 <!-- JSON processing -->
37 <dependency>
38 <groupId>com.fasterxml.jackson.core</groupId>
39 <artifactId>jackson-databind</artifactId>
40 </dependency>
41
42 <!-- Validation -->
43 <dependency>
44 <groupId>org.springframework.boot</groupId>
45 <artifactId>spring-boot-starter-validation</artifactId>
46 </dependency>
47</dependencies>
2. API Gateway Configuration Service
1@Service
2@Slf4j
3public class ApiGatewayService {
4
5 private final ApiGatewayClient apiGatewayClient;
6 private final String region;
7 private final String accountId;
8
9 public ApiGatewayService(@Value("${aws.region}") String region,
10 @Value("${aws.account-id}") String accountId) {
11 this.region = region;
12 this.accountId = accountId;
13 this.apiGatewayClient = ApiGatewayClient.builder()
14 .region(Region.of(region))
15 .build();
16 }
17
18 public String createRestApi(String apiName, String description) {
19 try {
20 CreateRestApiRequest request = CreateRestApiRequest.builder()
21 .name(apiName)
22 .description(description)
23 .endpointConfiguration(EndpointConfiguration.builder()
24 .types(EndpointType.REGIONAL)
25 .build())
26 .policy(createApiPolicy())
27 .build();
28
29 CreateRestApiResponse response = apiGatewayClient.createRestApi(request);
30 String restApiId = response.id();
31
32 log.info("Created REST API: {} with ID: {}", apiName, restApiId);
33 return restApiId;
34
35 } catch (Exception e) {
36 log.error("Failed to create REST API: {}", apiName, e);
37 throw new RuntimeException("API creation failed", e);
38 }
39 }
40
41 public String createResource(String restApiId, String parentId, String pathPart) {
42 try {
43 CreateResourceRequest request = CreateResourceRequest.builder()
44 .restApiId(restApiId)
45 .parentId(parentId)
46 .pathPart(pathPart)
47 .build();
48
49 CreateResourceResponse response = apiGatewayClient.createResource(request);
50 String resourceId = response.id();
51
52 log.info("Created resource: {} under parent: {}", pathPart, parentId);
53 return resourceId;
54
55 } catch (Exception e) {
56 log.error("Failed to create resource: {}", pathPart, e);
57 throw new RuntimeException("Resource creation failed", e);
58 }
59 }
60
61 public void createMethod(String restApiId, String resourceId, String httpMethod,
62 boolean requireAuth, String integrationUri) {
63 try {
64 // Create method
65 PutMethodRequest methodRequest = PutMethodRequest.builder()
66 .restApiId(restApiId)
67 .resourceId(resourceId)
68 .httpMethod(httpMethod)
69 .authorizationType(requireAuth ? AuthorizationType.AWS_IAM : AuthorizationType.NONE)
70 .requestParameters(Map.of(
71 "method.request.header.Content-Type", false,
72 "method.request.header.Authorization", requireAuth
73 ))
74 .build();
75
76 apiGatewayClient.putMethod(methodRequest);
77
78 // Create integration
79 PutIntegrationRequest integrationRequest = PutIntegrationRequest.builder()
80 .restApiId(restApiId)
81 .resourceId(resourceId)
82 .httpMethod(httpMethod)
83 .type(IntegrationType.HTTP_PROXY)
84 .integrationHttpMethod("POST")
85 .uri(integrationUri)
86 .requestParameters(Map.of(
87 "integration.request.header.Content-Type", "'application/json'"
88 ))
89 .build();
90
91 apiGatewayClient.putIntegration(integrationRequest);
92
93 // Create method response
94 PutMethodResponseRequest methodResponseRequest = PutMethodResponseRequest.builder()
95 .restApiId(restApiId)
96 .resourceId(resourceId)
97 .httpMethod(httpMethod)
98 .statusCode("200")
99 .responseModels(Map.of("application/json", "Empty"))
100 .responseParameters(Map.of(
101 "method.response.header.Access-Control-Allow-Origin", false
102 ))
103 .build();
104
105 apiGatewayClient.putMethodResponse(methodResponseRequest);
106
107 // Create integration response
108 PutIntegrationResponseRequest integrationResponseRequest = PutIntegrationResponseRequest.builder()
109 .restApiId(restApiId)
110 .resourceId(resourceId)
111 .httpMethod(httpMethod)
112 .statusCode("200")
113 .responseParameters(Map.of(
114 "method.response.header.Access-Control-Allow-Origin", "'*'"
115 ))
116 .build();
117
118 apiGatewayClient.putIntegrationResponse(integrationResponseRequest);
119
120 log.info("Created method {} for resource {}", httpMethod, resourceId);
121
122 } catch (Exception e) {
123 log.error("Failed to create method: {} for resource: {}", httpMethod, resourceId, e);
124 throw new RuntimeException("Method creation failed", e);
125 }
126 }
127
128 public void deployApi(String restApiId, String stageName, String description) {
129 try {
130 CreateDeploymentRequest request = CreateDeploymentRequest.builder()
131 .restApiId(restApiId)
132 .stageName(stageName)
133 .description(description)
134 .build();
135
136 CreateDeploymentResponse response = apiGatewayClient.createDeployment(request);
137
138 String invokeUrl = String.format("https://%s.execute-api.%s.amazonaws.com/%s",
139 restApiId, region, stageName);
140
141 log.info("Deployed API to stage: {} with URL: {}", stageName, invokeUrl);
142
143 } catch (Exception e) {
144 log.error("Failed to deploy API: {}", restApiId, e);
145 throw new RuntimeException("API deployment failed", e);
146 }
147 }
148
149 public void createUsagePlan(String restApiId, String planName, String stageName,
150 int throttleRate, int throttleBurst, int quotaLimit) {
151 try {
152 CreateUsagePlanRequest request = CreateUsagePlanRequest.builder()
153 .name(planName)
154 .description("Usage plan for " + planName)
155 .apiStages(ApiStage.builder()
156 .apiId(restApiId)
157 .stage(stageName)
158 .build())
159 .throttle(ThrottleSettings.builder()
160 .rateLimit(throttleRate)
161 .burstLimit(throttleBurst)
162 .build())
163 .quota(QuotaSettings.builder()
164 .limit(quotaLimit)
165 .period(QuotaPeriodType.DAY)
166 .build())
167 .build();
168
169 CreateUsagePlanResponse response = apiGatewayClient.createUsagePlan(request);
170
171 log.info("Created usage plan: {} with ID: {}", planName, response.id());
172
173 } catch (Exception e) {
174 log.error("Failed to create usage plan: {}", planName, e);
175 throw new RuntimeException("Usage plan creation failed", e);
176 }
177 }
178
179 private String createApiPolicy() {
180 return "{\n" +
181 " \"Version\": \"2012-10-17\",\n" +
182 " \"Statement\": [\n" +
183 " {\n" +
184 " \"Effect\": \"Allow\",\n" +
185 " \"Principal\": \"*\",\n" +
186 " \"Action\": \"execute-api:Invoke\",\n" +
187 " \"Resource\": \"*\"\n" +
188 " }\n" +
189 " ]\n" +
190 "}";
191 }
192
193 public void cleanup(String restApiId) {
194 try {
195 DeleteRestApiRequest request = DeleteRestApiRequest.builder()
196 .restApiId(restApiId)
197 .build();
198
199 apiGatewayClient.deleteRestApi(request);
200 log.info("Deleted REST API: {}", restApiId);
201
202 } catch (Exception e) {
203 log.error("Failed to delete REST API: {}", restApiId, e);
204 }
205 }
206}
3. Complete Microservices Setup with API Gateway
1@RestController
2@RequestMapping("/api/gateway-demo")
3@Slf4j
4public class ApiGatewayDemoController {
5
6 private final ApiGatewayService apiGatewayService;
7 private final MicroserviceOrchestrator orchestrator;
8
9 public ApiGatewayDemoController(ApiGatewayService apiGatewayService,
10 MicroserviceOrchestrator orchestrator) {
11 this.apiGatewayService = apiGatewayService;
12 this.orchestrator = orchestrator;
13 }
14
15 @PostMapping("/setup-complete-api")
16 public ResponseEntity<ApiSetupResponse> setupCompleteApi() {
17 try {
18 // 1. Create REST API
19 String restApiId = apiGatewayService.createRestApi(
20 "E-commerce-API",
21 "Complete e-commerce microservices API"
22 );
23
24 // 2. Get root resource
25 String rootResourceId = getRootResourceId(restApiId);
26
27 // 3. Setup API resources and methods
28 setupEcommerceApi(restApiId, rootResourceId);
29
30 // 4. Deploy to stage
31 apiGatewayService.deployApi(restApiId, "prod", "Production deployment");
32
33 // 5. Create usage plans
34 setupUsagePlans(restApiId);
35
36 ApiSetupResponse response = new ApiSetupResponse(
37 restApiId,
38 String.format("https://%s.execute-api.%s.amazonaws.com/prod",
39 restApiId, "us-east-1"),
40 "API Gateway setup completed successfully"
41 );
42
43 return ResponseEntity.ok(response);
44
45 } catch (Exception e) {
46 log.error("Failed to setup API Gateway", e);
47 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
48 .body(new ApiSetupResponse(null, null, "Setup failed: " + e.getMessage()));
49 }
50 }
51
52 private void setupEcommerceApi(String restApiId, String rootResourceId) {
53 // Users resource
54 String usersResourceId = apiGatewayService.createResource(restApiId, rootResourceId, "users");
55 apiGatewayService.createMethod(restApiId, usersResourceId, "GET", true,
56 "http://user-service.internal:8080/users");
57 apiGatewayService.createMethod(restApiId, usersResourceId, "POST", true,
58 "http://user-service.internal:8080/users");
59
60 // User by ID resource
61 String userByIdResourceId = apiGatewayService.createResource(restApiId, usersResourceId, "{id}");
62 apiGatewayService.createMethod(restApiId, userByIdResourceId, "GET", true,
63 "http://user-service.internal:8080/users/{id}");
64 apiGatewayService.createMethod(restApiId, userByIdResourceId, "PUT", true,
65 "http://user-service.internal:8080/users/{id}");
66 apiGatewayService.createMethod(restApiId, userByIdResourceId, "DELETE", true,
67 "http://user-service.internal:8080/users/{id}");
68
69 // Orders resource
70 String ordersResourceId = apiGatewayService.createResource(restApiId, rootResourceId, "orders");
71 apiGatewayService.createMethod(restApiId, ordersResourceId, "GET", true,
72 "http://order-service.internal:8080/orders");
73 apiGatewayService.createMethod(restApiId, ordersResourceId, "POST", true,
74 "http://order-service.internal:8080/orders");
75
76 // Products resource
77 String productsResourceId = apiGatewayService.createResource(restApiId, rootResourceId, "products");
78 apiGatewayService.createMethod(restApiId, productsResourceId, "GET", false,
79 "http://product-service.internal:8080/products");
80
81 // Payments resource
82 String paymentsResourceId = apiGatewayService.createResource(restApiId, rootResourceId, "payments");
83 apiGatewayService.createMethod(restApiId, paymentsResourceId, "POST", true,
84 "http://payment-service.internal:8080/payments");
85
86 log.info("E-commerce API resources setup completed");
87 }
88
89 private void setupUsagePlans(String restApiId) {
90 // Basic plan
91 apiGatewayService.createUsagePlan(restApiId, "BasicPlan", "prod",
92 100, 200, 10000); // 100 req/sec, burst 200, 10k daily quota
93
94 // Premium plan
95 apiGatewayService.createUsagePlan(restApiId, "PremiumPlan", "prod",
96 1000, 2000, 100000); // 1000 req/sec, burst 2000, 100k daily quota
97
98 log.info("Usage plans setup completed");
99 }
100
101 private String getRootResourceId(String restApiId) {
102 try {
103 GetResourcesRequest request = GetResourcesRequest.builder()
104 .restApiId(restApiId)
105 .build();
106
107 GetResourcesResponse response = apiGatewayService.apiGatewayClient.getResources(request);
108
109 return response.items().stream()
110 .filter(resource -> "/".equals(resource.path()))
111 .findFirst()
112 .map(Resource::id)
113 .orElseThrow(() -> new RuntimeException("Root resource not found"));
114
115 } catch (Exception e) {
116 throw new RuntimeException("Failed to get root resource", e);
117 }
118 }
119
120 // Supporting classes
121 public static class ApiSetupResponse {
122 private final String restApiId;
123 private final String invokeUrl;
124 private final String message;
125
126 public ApiSetupResponse(String restApiId, String invokeUrl, String message) {
127 this.restApiId = restApiId;
128 this.invokeUrl = invokeUrl;
129 this.message = message;
130 }
131
132 // Getters
133 public String getRestApiId() { return restApiId; }
134 public String getInvokeUrl() { return invokeUrl; }
135 public String getMessage() { return message; }
136 }
137}
4. Custom Authorizer Implementation
1@Component
2public class ApiGatewayCustomAuthorizer implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayCustomAuthorizerResponse> {
3
4 private final JWTVerifier jwtVerifier;
5 private final UserService userService;
6
7 public ApiGatewayCustomAuthorizer() {
8 // Initialize JWT verifier
9 Algorithm algorithm = Algorithm.HMAC256(System.getenv("JWT_SECRET"));
10 this.jwtVerifier = JWT.require(algorithm)
11 .withIssuer("your-app")
12 .build();
13 this.userService = new UserService();
14 }
15
16 @Override
17 public APIGatewayCustomAuthorizerResponse handleRequest(APIGatewayProxyRequestEvent input, Context context) {
18 try {
19 String token = extractToken(input);
20
21 if (token == null) {
22 return createResponse("Deny", null, input.getRequestContext().getRequestId());
23 }
24
25 // Verify JWT token
26 DecodedJWT decodedJWT = jwtVerifier.verify(token);
27 String userId = decodedJWT.getSubject();
28
29 // Additional user validation
30 if (!userService.isUserActive(userId)) {
31 return createResponse("Deny", userId, input.getRequestContext().getRequestId());
32 }
33
34 // Create policy with user context
35 return createResponse("Allow", userId, input.getRequestContext().getRequestId());
36
37 } catch (JWTVerificationException e) {
38 context.getLogger().log("JWT verification failed: " + e.getMessage());
39 return createResponse("Deny", null, input.getRequestContext().getRequestId());
40 } catch (Exception e) {
41 context.getLogger().log("Authorization error: " + e.getMessage());
42 return createResponse("Deny", null, input.getRequestContext().getRequestId());
43 }
44 }
45
46 private String extractToken(APIGatewayProxyRequestEvent input) {
47 String authHeader = input.getHeaders().get("Authorization");
48 if (authHeader != null && authHeader.startsWith("Bearer ")) {
49 return authHeader.substring(7);
50 }
51
52 // Try query parameter
53 if (input.getQueryStringParameters() != null) {
54 return input.getQueryStringParameters().get("token");
55 }
56
57 return null;
58 }
59
60 private APIGatewayCustomAuthorizerResponse createResponse(String effect, String userId, String requestId) {
61 APIGatewayCustomAuthorizerResponse response = new APIGatewayCustomAuthorizerResponse();
62
63 // Set principal ID
64 response.setPrincipalId(userId != null ? userId : "anonymous");
65
66 // Create policy document
67 PolicyDocument policyDocument = new PolicyDocument();
68 policyDocument.setVersion("2012-10-17");
69
70 Statement statement = new Statement();
71 statement.setEffect(effect);
72 statement.setAction("execute-api:Invoke");
73 statement.setResource("*"); // In production, be more specific
74
75 policyDocument.setStatement(Arrays.asList(statement));
76 response.setPolicyDocument(policyDocument);
77
78 // Add context information
79 Map<String, Object> context = new HashMap<>();
80 context.put("userId", userId);
81 context.put("requestId", requestId);
82 context.put("authorizedAt", System.currentTimeMillis());
83 response.setContext(context);
84
85 return response;
86 }
87
88 // Supporting classes for policy document
89 public static class PolicyDocument {
90 private String version;
91 private List<Statement> statement;
92
93 // Getters and setters
94 public String getVersion() { return version; }
95 public void setVersion(String version) { this.version = version; }
96 public List<Statement> getStatement() { return statement; }
97 public void setStatement(List<Statement> statement) { this.statement = statement; }
98 }
99
100 public static class Statement {
101 private String effect;
102 private String action;
103 private String resource;
104
105 // Getters and setters
106 public String getEffect() { return effect; }
107 public void setEffect(String effect) { this.effect = effect; }
108 public String getAction() { return action; }
109 public void setAction(String action) { this.action = action; }
110 public String getResource() { return resource; }
111 public void setResource(String resource) { this.resource = resource; }
112 }
113
114 // Mock user service
115 private static class UserService {
116 public boolean isUserActive(String userId) {
117 // In real implementation, check database
118 return userId != null && !userId.isEmpty();
119 }
120 }
121}
5. Response Aggregation Service
1@Service
2@Slf4j
3public class ResponseAggregationService {
4
5 private final RestTemplate restTemplate;
6 private final RedisTemplate<String, Object> redisTemplate;
7 private final MeterRegistry meterRegistry;
8
9 public ResponseAggregationService(RestTemplate restTemplate,
10 RedisTemplate<String, Object> redisTemplate,
11 MeterRegistry meterRegistry) {
12 this.restTemplate = restTemplate;
13 this.redisTemplate = redisTemplate;
14 this.meterRegistry = meterRegistry;
15 }
16
17 // Aggregate user profile with orders and preferences
18 @Async
19 public CompletableFuture<UserProfileAggregate> getUserProfileAggregate(String userId) {
20 Timer.Sample sample = Timer.start(meterRegistry);
21
22 try {
23 // Check cache first
24 String cacheKey = "user_profile_aggregate:" + userId;
25 UserProfileAggregate cached = (UserProfileAggregate) redisTemplate.opsForValue().get(cacheKey);
26
27 if (cached != null) {
28 meterRegistry.counter("api_gateway.aggregation.cache_hit").increment();
29 return CompletableFuture.completedFuture(cached);
30 }
31
32 // Parallel calls to multiple services
33 CompletableFuture<UserProfile> userProfileFuture = CompletableFuture.supplyAsync(() ->
34 fetchUserProfile(userId));
35
36 CompletableFuture<List<Order>> ordersFuture = CompletableFuture.supplyAsync(() ->
37 fetchUserOrders(userId));
38
39 CompletableFuture<UserPreferences> preferencesFuture = CompletableFuture.supplyAsync(() ->
40 fetchUserPreferences(userId));
41
42 CompletableFuture<List<Recommendation>> recommendationsFuture = CompletableFuture.supplyAsync(() ->
43 fetchRecommendations(userId));
44
45 // Wait for all calls to complete
46 CompletableFuture<Void> allOf = CompletableFuture.allOf(
47 userProfileFuture, ordersFuture, preferencesFuture, recommendationsFuture);
48
49 return allOf.thenApply(v -> {
50 UserProfileAggregate aggregate = new UserProfileAggregate();
51 aggregate.setUserProfile(userProfileFuture.join());
52 aggregate.setRecentOrders(ordersFuture.join());
53 aggregate.setPreferences(preferencesFuture.join());
54 aggregate.setRecommendations(recommendationsFuture.join());
55 aggregate.setAggregatedAt(Instant.now());
56
57 // Cache for 5 minutes
58 redisTemplate.opsForValue().set(cacheKey, aggregate, Duration.ofMinutes(5));
59 meterRegistry.counter("api_gateway.aggregation.cache_miss").increment();
60
61 return aggregate;
62 });
63
64 } finally {
65 sample.stop(Timer.builder("api_gateway.aggregation.duration")
66 .tag("operation", "user_profile")
67 .register(meterRegistry));
68 }
69 }
70
71 // Aggregate product details with reviews and inventory
72 @Async
73 public CompletableFuture<ProductAggregate> getProductAggregate(String productId) {
74 Timer.Sample sample = Timer.start(meterRegistry);
75
76 try {
77 String cacheKey = "product_aggregate:" + productId;
78 ProductAggregate cached = (ProductAggregate) redisTemplate.opsForValue().get(cacheKey);
79
80 if (cached != null) {
81 return CompletableFuture.completedFuture(cached);
82 }
83
84 // Parallel calls
85 CompletableFuture<Product> productFuture = CompletableFuture.supplyAsync(() ->
86 fetchProduct(productId));
87
88 CompletableFuture<List<Review>> reviewsFuture = CompletableFuture.supplyAsync(() ->
89 fetchProductReviews(productId));
90
91 CompletableFuture<InventoryStatus> inventoryFuture = CompletableFuture.supplyAsync(() ->
92 fetchInventoryStatus(productId));
93
94 CompletableFuture<PriceInfo> priceFuture = CompletableFuture.supplyAsync(() ->
95 fetchPriceInfo(productId));
96
97 return CompletableFuture.allOf(productFuture, reviewsFuture, inventoryFuture, priceFuture)
98 .thenApply(v -> {
99 ProductAggregate aggregate = new ProductAggregate();
100 aggregate.setProduct(productFuture.join());
101 aggregate.setReviews(reviewsFuture.join());
102 aggregate.setInventoryStatus(inventoryFuture.join());
103 aggregate.setPriceInfo(priceFuture.join());
104 aggregate.setAggregatedAt(Instant.now());
105
106 // Cache for 10 minutes
107 redisTemplate.opsForValue().set(cacheKey, aggregate, Duration.ofMinutes(10));
108
109 return aggregate;
110 });
111
112 } finally {
113 sample.stop(Timer.builder("api_gateway.aggregation.duration")
114 .tag("operation", "product")
115 .register(meterRegistry));
116 }
117 }
118
119 // Service calls with circuit breaker pattern
120 @CircuitBreaker(name = "user-service", fallbackMethod = "fallbackUserProfile")
121 @TimeLimiter(name = "user-service")
122 @Retry(name = "user-service")
123 private UserProfile fetchUserProfile(String userId) {
124 try {
125 ResponseEntity<UserProfile> response = restTemplate.getForEntity(
126 "http://user-service/users/" + userId, UserProfile.class);
127
128 meterRegistry.counter("api_gateway.service_calls", "service", "user", "status", "success").increment();
129 return response.getBody();
130
131 } catch (Exception e) {
132 meterRegistry.counter("api_gateway.service_calls", "service", "user", "status", "error").increment();
133 throw e;
134 }
135 }
136
137 @CircuitBreaker(name = "order-service", fallbackMethod = "fallbackUserOrders")
138 private List<Order> fetchUserOrders(String userId) {
139 try {
140 ResponseEntity<Order[]> response = restTemplate.getForEntity(
141 "http://order-service/orders?userId=" + userId, Order[].class);
142
143 meterRegistry.counter("api_gateway.service_calls", "service", "order", "status", "success").increment();
144 return Arrays.asList(response.getBody());
145
146 } catch (Exception e) {
147 meterRegistry.counter("api_gateway.service_calls", "service", "order", "status", "error").increment();
148 throw e;
149 }
150 }
151
152 @CircuitBreaker(name = "preference-service", fallbackMethod = "fallbackUserPreferences")
153 private UserPreferences fetchUserPreferences(String userId) {
154 try {
155 ResponseEntity<UserPreferences> response = restTemplate.getForEntity(
156 "http://preference-service/preferences/" + userId, UserPreferences.class);
157
158 meterRegistry.counter("api_gateway.service_calls", "service", "preference", "status", "success").increment();
159 return response.getBody();
160
161 } catch (Exception e) {
162 meterRegistry.counter("api_gateway.service_calls", "service", "preference", "status", "error").increment();
163 throw e;
164 }
165 }
166
167 @CircuitBreaker(name = "recommendation-service", fallbackMethod = "fallbackRecommendations")
168 private List<Recommendation> fetchRecommendations(String userId) {
169 try {
170 ResponseEntity<Recommendation[]> response = restTemplate.getForEntity(
171 "http://recommendation-service/recommendations/" + userId, Recommendation[].class);
172
173 meterRegistry.counter("api_gateway.service_calls", "service", "recommendation", "status", "success").increment();
174 return Arrays.asList(response.getBody());
175
176 } catch (Exception e) {
177 meterRegistry.counter("api_gateway.service_calls", "service", "recommendation", "status", "error").increment();
178 throw e;
179 }
180 }
181
182 @CircuitBreaker(name = "product-service", fallbackMethod = "fallbackProduct")
183 private Product fetchProduct(String productId) {
184 try {
185 ResponseEntity<Product> response = restTemplate.getForEntity(
186 "http://product-service/products/" + productId, Product.class);
187 return response.getBody();
188 } catch (Exception e) {
189 throw e;
190 }
191 }
192
193 private List<Review> fetchProductReviews(String productId) {
194 try {
195 ResponseEntity<Review[]> response = restTemplate.getForEntity(
196 "http://review-service/reviews?productId=" + productId, Review[].class);
197 return Arrays.asList(response.getBody());
198 } catch (Exception e) {
199 return Collections.emptyList(); // Fail gracefully for reviews
200 }
201 }
202
203 private InventoryStatus fetchInventoryStatus(String productId) {
204 try {
205 ResponseEntity<InventoryStatus> response = restTemplate.getForEntity(
206 "http://inventory-service/inventory/" + productId, InventoryStatus.class);
207 return response.getBody();
208 } catch (Exception e) {
209 return new InventoryStatus(productId, 0, false); // Default fallback
210 }
211 }
212
213 private PriceInfo fetchPriceInfo(String productId) {
214 try {
215 ResponseEntity<PriceInfo> response = restTemplate.getForEntity(
216 "http://pricing-service/prices/" + productId, PriceInfo.class);
217 return response.getBody();
218 } catch (Exception e) {
219 return new PriceInfo(productId, BigDecimal.ZERO, null); // Default fallback
220 }
221 }
222
223 // Fallback methods
224 private UserProfile fallbackUserProfile(String userId, Exception e) {
225 log.warn("Fallback for user profile: {}", userId, e);
226 return new UserProfile(userId, "Unknown User", "user@example.com", false);
227 }
228
229 private List<Order> fallbackUserOrders(String userId, Exception e) {
230 log.warn("Fallback for user orders: {}", userId, e);
231 return Collections.emptyList();
232 }
233
234 private UserPreferences fallbackUserPreferences(String userId, Exception e) {
235 log.warn("Fallback for user preferences: {}", userId, e);
236 return new UserPreferences(userId, Collections.emptyMap());
237 }
238
239 private List<Recommendation> fallbackRecommendations(String userId, Exception e) {
240 log.warn("Fallback for recommendations: {}", userId, e);
241 return Collections.emptyList();
242 }
243
244 private Product fallbackProduct(String productId, Exception e) {
245 log.warn("Fallback for product: {}", productId, e);
246 return new Product(productId, "Product Unavailable", "Product temporarily unavailable",
247 BigDecimal.ZERO, false);
248 }
249
250 // Aggregate response classes
251 public static class UserProfileAggregate {
252 private UserProfile userProfile;
253 private List<Order> recentOrders;
254 private UserPreferences preferences;
255 private List<Recommendation> recommendations;
256 private Instant aggregatedAt;
257
258 // Constructors, getters, setters
259 public UserProfileAggregate() {}
260
261 public UserProfile getUserProfile() { return userProfile; }
262 public void setUserProfile(UserProfile userProfile) { this.userProfile = userProfile; }
263 public List<Order> getRecentOrders() { return recentOrders; }
264 public void setRecentOrders(List<Order> recentOrders) { this.recentOrders = recentOrders; }
265 public UserPreferences getPreferences() { return preferences; }
266 public void setPreferences(UserPreferences preferences) { this.preferences = preferences; }
267 public List<Recommendation> getRecommendations() { return recommendations; }
268 public void setRecommendations(List<Recommendation> recommendations) { this.recommendations = recommendations; }
269 public Instant getAggregatedAt() { return aggregatedAt; }
270 public void setAggregatedAt(Instant aggregatedAt) { this.aggregatedAt = aggregatedAt; }
271 }
272
273 public static class ProductAggregate {
274 private Product product;
275 private List<Review> reviews;
276 private InventoryStatus inventoryStatus;
277 private PriceInfo priceInfo;
278 private Instant aggregatedAt;
279
280 // Constructors, getters, setters
281 public ProductAggregate() {}
282
283 public Product getProduct() { return product; }
284 public void setProduct(Product product) { this.product = product; }
285 public List<Review> getReviews() { return reviews; }
286 public void setReviews(List<Review> reviews) { this.reviews = reviews; }
287 public InventoryStatus getInventoryStatus() { return inventoryStatus; }
288 public void setInventoryStatus(InventoryStatus inventoryStatus) { this.inventoryStatus = inventoryStatus; }
289 public PriceInfo getPriceInfo() { return priceInfo; }
290 public void setPriceInfo(PriceInfo priceInfo) { this.priceInfo = priceInfo; }
291 public Instant getAggregatedAt() { return aggregatedAt; }
292 public void setAggregatedAt(Instant aggregatedAt) { this.aggregatedAt = aggregatedAt; }
293 }
294
295 // Supporting data classes
296 public static class UserProfile {
297 private String userId;
298 private String name;
299 private String email;
300 private boolean isPremium;
301
302 public UserProfile() {}
303 public UserProfile(String userId, String name, String email, boolean isPremium) {
304 this.userId = userId;
305 this.name = name;
306 this.email = email;
307 this.isPremium = isPremium;
308 }
309
310 // Getters and setters
311 public String getUserId() { return userId; }
312 public void setUserId(String userId) { this.userId = userId; }
313 public String getName() { return name; }
314 public void setName(String name) { this.name = name; }
315 public String getEmail() { return email; }
316 public void setEmail(String email) { this.email = email; }
317 public boolean isPremium() { return isPremium; }
318 public void setPremium(boolean premium) { isPremium = premium; }
319 }
320
321 public static class Order {
322 private String orderId;
323 private String status;
324 private BigDecimal total;
325 private Instant createdAt;
326
327 // Constructors, getters, setters
328 public Order() {}
329 public String getOrderId() { return orderId; }
330 public void setOrderId(String orderId) { this.orderId = orderId; }
331 public String getStatus() { return status; }
332 public void setStatus(String status) { this.status = status; }
333 public BigDecimal getTotal() { return total; }
334 public void setTotal(BigDecimal total) { this.total = total; }
335 public Instant getCreatedAt() { return createdAt; }
336 public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; }
337 }
338
339 public static class UserPreferences {
340 private String userId;
341 private Map<String, Object> preferences;
342
343 public UserPreferences() { this.preferences = new HashMap<>(); }
344 public UserPreferences(String userId, Map<String, Object> preferences) {
345 this.userId = userId;
346 this.preferences = preferences;
347 }
348
349 // Getters and setters
350 public String getUserId() { return userId; }
351 public void setUserId(String userId) { this.userId = userId; }
352 public Map<String, Object> getPreferences() { return preferences; }
353 public void setPreferences(Map<String, Object> preferences) { this.preferences = preferences; }
354 }
355
356 public static class Recommendation {
357 private String productId;
358 private String title;
359 private double score;
360
361 // Constructors, getters, setters
362 public Recommendation() {}
363 public String getProductId() { return productId; }
364 public void setProductId(String productId) { this.productId = productId; }
365 public String getTitle() { return title; }
366 public void setTitle(String title) { this.title = title; }
367 public double getScore() { return score; }
368 public void setScore(double score) { this.score = score; }
369 }
370
371 public static class Product {
372 private String productId;
373 private String name;
374 private String description;
375 private BigDecimal price;
376 private boolean available;
377
378 public Product() {}
379 public Product(String productId, String name, String description, BigDecimal price, boolean available) {
380 this.productId = productId;
381 this.name = name;
382 this.description = description;
383 this.price = price;
384 this.available = available;
385 }
386
387 // Getters and setters
388 public String getProductId() { return productId; }
389 public void setProductId(String productId) { this.productId = productId; }
390 public String getName() { return name; }
391 public void setName(String name) { this.name = name; }
392 public String getDescription() { return description; }
393 public void setDescription(String description) { this.description = description; }
394 public BigDecimal getPrice() { return price; }
395 public void setPrice(BigDecimal price) { this.price = price; }
396 public boolean isAvailable() { return available; }
397 public void setAvailable(boolean available) { this.available = available; }
398 }
399
400 public static class Review {
401 private String reviewId;
402 private String productId;
403 private int rating;
404 private String comment;
405
406 // Constructors, getters, setters
407 public Review() {}
408 public String getReviewId() { return reviewId; }
409 public void setReviewId(String reviewId) { this.reviewId = reviewId; }
410 public String getProductId() { return productId; }
411 public void setProductId(String productId) { this.productId = productId; }
412 public int getRating() { return rating; }
413 public void setRating(int rating) { this.rating = rating; }
414 public String getComment() { return comment; }
415 public void setComment(String comment) { this.comment = comment; }
416 }
417
418 public static class InventoryStatus {
419 private String productId;
420 private int quantity;
421 private boolean inStock;
422
423 public InventoryStatus() {}
424 public InventoryStatus(String productId, int quantity, boolean inStock) {
425 this.productId = productId;
426 this.quantity = quantity;
427 this.inStock = inStock;
428 }
429
430 // Getters and setters
431 public String getProductId() { return productId; }
432 public void setProductId(String productId) { this.productId = productId; }
433 public int getQuantity() { return quantity; }
434 public void setQuantity(int quantity) { this.quantity = quantity; }
435 public boolean isInStock() { return inStock; }
436 public void setInStock(boolean inStock) { this.inStock = inStock; }
437 }
438
439 public static class PriceInfo {
440 private String productId;
441 private BigDecimal currentPrice;
442 private BigDecimal discountPrice;
443
444 public PriceInfo() {}
445 public PriceInfo(String productId, BigDecimal currentPrice, BigDecimal discountPrice) {
446 this.productId = productId;
447 this.currentPrice = currentPrice;
448 this.discountPrice = discountPrice;
449 }
450
451 // Getters and setters
452 public String getProductId() { return productId; }
453 public void setProductId(String productId) { this.productId = productId; }
454 public BigDecimal getCurrentPrice() { return currentPrice; }
455 public void setCurrentPrice(BigDecimal currentPrice) { this.currentPrice = currentPrice; }
456 public BigDecimal getDiscountPrice() { return discountPrice; }
457 public void setDiscountPrice(BigDecimal discountPrice) { this.discountPrice = discountPrice; }
458 }
459}
📊 Monitoring and Analytics
1. API Gateway Metrics Service
1@Service
2@Slf4j
3public class ApiGatewayMetricsService {
4
5 private final CloudWatchClient cloudWatchClient;
6 private final MeterRegistry meterRegistry;
7
8 public ApiGatewayMetricsService(MeterRegistry meterRegistry) {
9 this.meterRegistry = meterRegistry;
10 this.cloudWatchClient = CloudWatchClient.builder().build();
11 }
12
13 @Scheduled(fixedRate = 60000) // Every minute
14 public void collectApiGatewayMetrics() {
15 try {
16 // Get API Gateway metrics from CloudWatch
17 collectLatencyMetrics();
18 collectErrorMetrics();
19 collectThrottleMetrics();
20 collectCacheMetrics();
21
22 } catch (Exception e) {
23 log.error("Failed to collect API Gateway metrics", e);
24 }
25 }
26
27 private void collectLatencyMetrics() {
28 GetMetricStatisticsRequest request = GetMetricStatisticsRequest.builder()
29 .namespace("AWS/ApiGateway")
30 .metricName("Latency")
31 .startTime(Instant.now().minus(Duration.ofMinutes(5)))
32 .endTime(Instant.now())
33 .period(300) // 5 minutes
34 .statistics(Statistic.AVERAGE, Statistic.MAXIMUM)
35 .build();
36
37 GetMetricStatisticsResponse response = cloudWatchClient.getMetricStatistics(request);
38
39 response.datapoints().forEach(datapoint -> {
40 if (datapoint.average() != null) {
41 Gauge.builder("api_gateway.latency.average")
42 .description("API Gateway average latency")
43 .register(meterRegistry, () -> datapoint.average());
44 }
45
46 if (datapoint.maximum() != null) {
47 Gauge.builder("api_gateway.latency.max")
48 .description("API Gateway maximum latency")
49 .register(meterRegistry, () -> datapoint.maximum());
50 }
51 });
52 }
53
54 private void collectErrorMetrics() {
55 // 4XX Errors
56 GetMetricStatisticsRequest request4xx = GetMetricStatisticsRequest.builder()
57 .namespace("AWS/ApiGateway")
58 .metricName("4XXError")
59 .startTime(Instant.now().minus(Duration.ofMinutes(5)))
60 .endTime(Instant.now())
61 .period(300)
62 .statistics(Statistic.SUM)
63 .build();
64
65 GetMetricStatisticsResponse response4xx = cloudWatchClient.getMetricStatistics(request4xx);
66 response4xx.datapoints().forEach(datapoint -> {
67 if (datapoint.sum() != null) {
68 meterRegistry.counter("api_gateway.errors.4xx").increment(datapoint.sum());
69 }
70 });
71
72 // 5XX Errors
73 GetMetricStatisticsRequest request5xx = GetMetricStatisticsRequest.builder()
74 .namespace("AWS/ApiGateway")
75 .metricName("5XXError")
76 .startTime(Instant.now().minus(Duration.ofMinutes(5)))
77 .endTime(Instant.now())
78 .period(300)
79 .statistics(Statistic.SUM)
80 .build();
81
82 GetMetricStatisticsResponse response5xx = cloudWatchClient.getMetricStatistics(request5xx);
83 response5xx.datapoints().forEach(datapoint -> {
84 if (datapoint.sum() != null) {
85 meterRegistry.counter("api_gateway.errors.5xx").increment(datapoint.sum());
86 }
87 });
88 }
89
90 private void collectThrottleMetrics() {
91 GetMetricStatisticsRequest request = GetMetricStatisticsRequest.builder()
92 .namespace("AWS/ApiGateway")
93 .metricName("Throttles")
94 .startTime(Instant.now().minus(Duration.ofMinutes(5)))
95 .endTime(Instant.now())
96 .period(300)
97 .statistics(Statistic.SUM)
98 .build();
99
100 GetMetricStatisticsResponse response = cloudWatchClient.getMetricStatistics(request);
101 response.datapoints().forEach(datapoint -> {
102 if (datapoint.sum() != null) {
103 meterRegistry.counter("api_gateway.throttles").increment(datapoint.sum());
104 }
105 });
106 }
107
108 private void collectCacheMetrics() {
109 // Cache Hit Count
110 GetMetricStatisticsRequest hitRequest = GetMetricStatisticsRequest.builder()
111 .namespace("AWS/ApiGateway")
112 .metricName("CacheHitCount")
113 .startTime(Instant.now().minus(Duration.ofMinutes(5)))
114 .endTime(Instant.now())
115 .period(300)
116 .statistics(Statistic.SUM)
117 .build();
118
119 GetMetricStatisticsResponse hitResponse = cloudWatchClient.getMetricStatistics(hitRequest);
120 hitResponse.datapoints().forEach(datapoint -> {
121 if (datapoint.sum() != null) {
122 meterRegistry.counter("api_gateway.cache.hits").increment(datapoint.sum());
123 }
124 });
125
126 // Cache Miss Count
127 GetMetricStatisticsRequest missRequest = GetMetricStatisticsRequest.builder()
128 .namespace("AWS/ApiGateway")
129 .metricName("CacheMissCount")
130 .startTime(Instant.now().minus(Duration.ofMinutes(5)))
131 .endTime(Instant.now())
132 .period(300)
133 .statistics(Statistic.SUM)
134 .build();
135
136 GetMetricStatisticsResponse missResponse = cloudWatchClient.getMetricStatistics(missRequest);
137 missResponse.datapoints().forEach(datapoint -> {
138 if (datapoint.sum() != null) {
139 meterRegistry.counter("api_gateway.cache.misses").increment(datapoint.sum());
140 }
141 });
142 }
143
144 public ApiGatewayHealthSummary getHealthSummary() {
145 try {
146 double avgLatency = getAverageLatency();
147 long errorRate4xx = getErrorRate4xx();
148 long errorRate5xx = getErrorRate5xx();
149 double cacheHitRatio = getCacheHitRatio();
150 long throttleCount = getThrottleCount();
151
152 return new ApiGatewayHealthSummary(
153 avgLatency < 1000, // Healthy if under 1 second
154 avgLatency,
155 errorRate4xx,
156 errorRate5xx,
157 cacheHitRatio,
158 throttleCount,
159 Instant.now()
160 );
161
162 } catch (Exception e) {
163 log.error("Failed to get health summary", e);
164 return new ApiGatewayHealthSummary(false, 0, 0, 0, 0, 0, Instant.now());
165 }
166 }
167
168 private double getAverageLatency() {
169 return meterRegistry.find("api_gateway.latency.average")
170 .gauge()
171 .map(Gauge::value)
172 .orElse(0.0);
173 }
174
175 private long getErrorRate4xx() {
176 return (long) meterRegistry.find("api_gateway.errors.4xx")
177 .counter()
178 .map(Counter::count)
179 .orElse(0.0);
180 }
181
182 private long getErrorRate5xx() {
183 return (long) meterRegistry.find("api_gateway.errors.5xx")
184 .counter()
185 .map(Counter::count)
186 .orElse(0.0);
187 }
188
189 private double getCacheHitRatio() {
190 double hits = meterRegistry.find("api_gateway.cache.hits")
191 .counter()
192 .map(Counter::count)
193 .orElse(0.0);
194
195 double misses = meterRegistry.find("api_gateway.cache.misses")
196 .counter()
197 .map(Counter::count)
198 .orElse(0.0);
199
200 return hits + misses > 0 ? hits / (hits + misses) : 0.0;
201 }
202
203 private long getThrottleCount() {
204 return (long) meterRegistry.find("api_gateway.throttles")
205 .counter()
206 .map(Counter::count)
207 .orElse(0.0);
208 }
209
210 public static class ApiGatewayHealthSummary {
211 private final boolean healthy;
212 private final double averageLatency;
213 private final long error4xxCount;
214 private final long error5xxCount;
215 private final double cacheHitRatio;
216 private final long throttleCount;
217 private final Instant timestamp;
218
219 public ApiGatewayHealthSummary(boolean healthy, double averageLatency, long error4xxCount,
220 long error5xxCount, double cacheHitRatio, long throttleCount,
221 Instant timestamp) {
222 this.healthy = healthy;
223 this.averageLatency = averageLatency;
224 this.error4xxCount = error4xxCount;
225 this.error5xxCount = error5xxCount;
226 this.cacheHitRatio = cacheHitRatio;
227 this.throttleCount = throttleCount;
228 this.timestamp = timestamp;
229 }
230
231 // Getters
232 public boolean isHealthy() { return healthy; }
233 public double getAverageLatency() { return averageLatency; }
234 public long getError4xxCount() { return error4xxCount; }
235 public long getError5xxCount() { return error5xxCount; }
236 public double getCacheHitRatio() { return cacheHitRatio; }
237 public long getThrottleCount() { return throttleCount; }
238 public Instant getTimestamp() { return timestamp; }
239
240 @Override
241 public String toString() {
242 return String.format(
243 "ApiGatewayHealth{healthy=%s, latency=%.2fms, 4xx=%d, 5xx=%d, cache=%.2f%%, throttles=%d}",
244 healthy, averageLatency, error4xxCount, error5xxCount, cacheHitRatio * 100, throttleCount
245 );
246 }
247 }
248}
🔐 Security Best Practices
1. API Security Configuration
1# application.yml - API Gateway Security Configuration
2aws:
3 apigateway:
4 security:
5 # API Keys
6 api-keys:
7 enabled: true
8 required-for-anonymous: true
9
10 # Request validation
11 request-validation:
12 enabled: true
13 validate-request-body: true
14 validate-request-parameters: true
15
16 # Rate limiting
17 rate-limiting:
18 requests-per-second: 100
19 burst-capacity: 200
20
21 # CORS
22 cors:
23 enabled: true
24 allowed-origins:
25 - "https://yourdomain.com"
26 - "https://app.yourdomain.com"
27 allowed-methods:
28 - GET
29 - POST
30 - PUT
31 - DELETE
32 allowed-headers:
33 - "Content-Type"
34 - "Authorization"
35 - "X-API-Key"
36 max-age: 3600
37
38 # SSL/TLS
39 ssl:
40 certificate-arn: "arn:aws:acm:region:account:certificate/certificate-id"
41 minimum-tls-version: "TLS_1_2"
42
43 # WAF Integration
44 waf:
45 enabled: true
46 web-acl-arn: "arn:aws:wafv2:region:account:regional/webacl/name/id"
47
48# Monitoring
49management:
50 endpoints:
51 web:
52 exposure:
53 include: health,metrics,info,apigateway
54 endpoint:
55 health:
56 show-details: always
2. Security Interceptor
1@Component
2@Order(1)
3public class ApiGatewaySecurityInterceptor implements HandlerInterceptor {
4
5 private final ApiKeyValidator apiKeyValidator;
6 private final RateLimitService rateLimitService;
7 private final MeterRegistry meterRegistry;
8
9 public ApiGatewaySecurityInterceptor(ApiKeyValidator apiKeyValidator,
10 RateLimitService rateLimitService,
11 MeterRegistry meterRegistry) {
12 this.apiKeyValidator = apiKeyValidator;
13 this.rateLimitService = rateLimitService;
14 this.meterRegistry = meterRegistry;
15 }
16
17 @Override
18 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
19 String requestId = generateRequestId();
20 request.setAttribute("requestId", requestId);
21
22 try {
23 // 1. API Key validation
24 if (!validateApiKey(request)) {
25 meterRegistry.counter("api_gateway.security.api_key_invalid").increment();
26 sendSecurityError(response, HttpStatus.UNAUTHORIZED, "Invalid API Key", requestId);
27 return false;
28 }
29
30 // 2. Rate limiting
31 if (!checkRateLimit(request)) {
32 meterRegistry.counter("api_gateway.security.rate_limited").increment();
33 sendSecurityError(response, HttpStatus.TOO_MANY_REQUESTS, "Rate limit exceeded", requestId);
34 return false;
35 }
36
37 // 3. Request size validation
38 if (!validateRequestSize(request)) {
39 meterRegistry.counter("api_gateway.security.request_too_large").increment();
40 sendSecurityError(response, HttpStatus.PAYLOAD_TOO_LARGE, "Request too large", requestId);
41 return false;
42 }
43
44 // 4. Content type validation
45 if (!validateContentType(request)) {
46 meterRegistry.counter("api_gateway.security.invalid_content_type").increment();
47 sendSecurityError(response, HttpStatus.UNSUPPORTED_MEDIA_TYPE, "Invalid content type", requestId);
48 return false;
49 }
50
51 meterRegistry.counter("api_gateway.security.passed").increment();
52 return true;
53
54 } catch (Exception e) {
55 log.error("Security validation error for request: {}", requestId, e);
56 meterRegistry.counter("api_gateway.security.error").increment();
57 sendSecurityError(response, HttpStatus.INTERNAL_SERVER_ERROR, "Security validation failed", requestId);
58 return false;
59 }
60 }
61
62 private boolean validateApiKey(HttpServletRequest request) {
63 String apiKey = request.getHeader("X-API-Key");
64 if (apiKey == null) {
65 apiKey = request.getParameter("api_key");
66 }
67
68 return apiKeyValidator.isValidApiKey(apiKey);
69 }
70
71 private boolean checkRateLimit(HttpServletRequest request) {
72 String clientId = extractClientId(request);
73 String endpoint = request.getRequestURI();
74
75 return rateLimitService.isAllowed(clientId, endpoint);
76 }
77
78 private boolean validateRequestSize(HttpServletRequest request) {
79 long maxSize = 10 * 1024 * 1024; // 10MB
80 return request.getContentLength() <= maxSize;
81 }
82
83 private boolean validateContentType(HttpServletRequest request) {
84 if ("POST".equals(request.getMethod()) || "PUT".equals(request.getMethod())) {
85 String contentType = request.getContentType();
86 return contentType != null &&
87 (contentType.startsWith("application/json") ||
88 contentType.startsWith("application/x-www-form-urlencoded"));
89 }
90 return true;
91 }
92
93 private String extractClientId(HttpServletRequest request) {
94 // Try API key first
95 String apiKey = request.getHeader("X-API-Key");
96 if (apiKey != null) {
97 return apiKeyValidator.getClientIdForApiKey(apiKey);
98 }
99
100 // Fall back to IP address
101 return getClientIpAddress(request);
102 }
103
104 private String getClientIpAddress(HttpServletRequest request) {
105 String[] headerNames = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP"};
106
107 for (String headerName : headerNames) {
108 String ip = request.getHeader(headerName);
109 if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
110 return ip.split(",")[0].trim();
111 }
112 }
113
114 return request.getRemoteAddr();
115 }
116
117 private void sendSecurityError(HttpServletResponse response, HttpStatus status,
118 String message, String requestId) throws IOException {
119 response.setStatus(status.value());
120 response.setContentType("application/json");
121
122 ObjectMapper mapper = new ObjectMapper();
123 Map<String, Object> error = Map.of(
124 "error", status.getReasonPhrase(),
125 "message", message,
126 "requestId", requestId,
127 "timestamp", Instant.now().toString()
128 );
129
130 response.getWriter().write(mapper.writeValueAsString(error));
131 }
132
133 private String generateRequestId() {
134 return "req_" + System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8);
135 }
136}
137
138@Service
139public class ApiKeyValidator {
140
141 private final Map<String, ApiKeyInfo> validApiKeys = new ConcurrentHashMap<>();
142
143 @PostConstruct
144 public void initializeApiKeys() {
145 // In production, load from database
146 validApiKeys.put("ak_12345", new ApiKeyInfo("ak_12345", "client_1", "Basic Plan", true));
147 validApiKeys.put("ak_67890", new ApiKeyInfo("ak_67890", "client_2", "Premium Plan", true));
148 }
149
150 public boolean isValidApiKey(String apiKey) {
151 if (apiKey == null) return false;
152
153 ApiKeyInfo keyInfo = validApiKeys.get(apiKey);
154 return keyInfo != null && keyInfo.isActive();
155 }
156
157 public String getClientIdForApiKey(String apiKey) {
158 ApiKeyInfo keyInfo = validApiKeys.get(apiKey);
159 return keyInfo != null ? keyInfo.getClientId() : "unknown";
160 }
161
162 private static class ApiKeyInfo {
163 private final String apiKey;
164 private final String clientId;
165 private final String plan;
166 private final boolean active;
167
168 public ApiKeyInfo(String apiKey, String clientId, String plan, boolean active) {
169 this.apiKey = apiKey;
170 this.clientId = clientId;
171 this.plan = plan;
172 this.active = active;
173 }
174
175 public String getApiKey() { return apiKey; }
176 public String getClientId() { return clientId; }
177 public String getPlan() { return plan; }
178 public boolean isActive() { return active; }
179 }
180}
181
182@Service
183public class RateLimitService {
184
185 private final RedisTemplate<String, String> redisTemplate;
186 private final Map<String, RateLimitConfig> rateLimits;
187
188 public RateLimitService(RedisTemplate<String, String> redisTemplate) {
189 this.redisTemplate = redisTemplate;
190 this.rateLimits = initializeRateLimits();
191 }
192
193 private Map<String, RateLimitConfig> initializeRateLimits() {
194 Map<String, RateLimitConfig> limits = new HashMap<>();
195 limits.put("default", new RateLimitConfig(100, Duration.ofMinutes(1)));
196 limits.put("premium", new RateLimitConfig(1000, Duration.ofMinutes(1)));
197 return limits;
198 }
199
200 public boolean isAllowed(String clientId, String endpoint) {
201 RateLimitConfig config = rateLimits.getOrDefault("default", rateLimits.get("default"));
202 String key = "rate_limit:" + clientId + ":" + endpoint;
203
204 try {
205 String currentCount = redisTemplate.opsForValue().get(key);
206 int count = currentCount != null ? Integer.parseInt(currentCount) : 0;
207
208 if (count >= config.getLimit()) {
209 return false;
210 }
211
212 // Increment counter
213 redisTemplate.opsForValue().increment(key);
214 redisTemplate.expire(key, config.getWindow());
215
216 return true;
217
218 } catch (Exception e) {
219 // If Redis fails, allow the request (fail open)
220 log.warn("Rate limiting failed for {}: {}", clientId, e.getMessage());
221 return true;
222 }
223 }
224
225 private static class RateLimitConfig {
226 private final int limit;
227 private final Duration window;
228
229 public RateLimitConfig(int limit, Duration window) {
230 this.limit = limit;
231 this.window = window;
232 }
233
234 public int getLimit() { return limit; }
235 public Duration getWindow() { return window; }
236 }
237}
🎯 Conclusion
AWS API Gateway is a powerful solution for managing microservices architectures, providing essential capabilities that go far beyond simple load balancing. Understanding when to use API Gateway versus Load Balancer is crucial for building scalable, secure, and maintainable systems.
🔑 Key Takeaways:
- API Gateway: Choose for API management, authentication, transformation, and aggregation
- Load Balancer: Choose for simple traffic distribution and high-performance scenarios
- Combined Approach: Use both together for enterprise architectures
- Security First: Implement comprehensive security with API keys, rate limiting, and validation
- Monitor Everything: Track latency, errors, cache performance, and business metrics
📋 Best Practices Summary:
- Design for Scale: Plan your API structure with growth in mind
- Implement Security: Use multiple layers of security validation
- Cache Strategically: Cache responses to reduce backend load
- Monitor Actively: Set up comprehensive monitoring and alerting
- Test Thoroughly: Include load testing and security testing in your pipeline
API Gateway transforms complex microservices communication into organized, secure, and scalable architecture patterns essential for modern distributed systems.