AWS API Gateway: Complete Guide with Load Balancer Comparison, Microservices Architecture, and Java Implementation

🎯 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:

  1. Inconsistent Interfaces: Each service may have different authentication methods, response formats, and error handling
  2. Increased Latency: Multiple round trips between client and services
  3. Security Concerns: Each service needs its own security implementation
  4. Traffic Spikes: Individual services can be overwhelmed without proper traffic management
  5. 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

  1. Request Handling: Clients send all API requests to the gateway
  2. Routing: Gateway inspects requests and routes to appropriate backend services
  3. Cross-Cutting Features: Handles authentication, rate limiting, caching, logging
  4. Response Aggregation: Combines data from multiple services into single responses

⚖️ API Gateway vs Load Balancer: Detailed Comparison

📊 Comprehensive Comparison Table

FeatureLoad BalancerAPI Gateway
Primary RoleDistribute traffic across multiple backend serversManage and route API requests from clients to services
OSI LayerNetwork/Transport Layer (L4) or Application Layer (L7)Application Layer (L7 only)
Core FunctionBalances traffic for availability and fault toleranceRoutes, authenticates, throttles, transforms API requests
HTTP API UnderstandingOnly L7 load balancers understand HTTPBuilt specifically for HTTP APIs
Smart RoutingBasic (round-robin, IP hash, least connections)Advanced (path-based /users, method-based POST /auth)
Security FeaturesSSL termination, basic IP filteringBuilt-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 & AnalyticsBasic health checksDetailed API metrics, usage analytics
Example ToolsAWS ELB, NGINX, HAProxy, EnvoyAWS 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:

  1. API Gateway: Choose for API management, authentication, transformation, and aggregation
  2. Load Balancer: Choose for simple traffic distribution and high-performance scenarios
  3. Combined Approach: Use both together for enterprise architectures
  4. Security First: Implement comprehensive security with API keys, rate limiting, and validation
  5. Monitor Everything: Track latency, errors, cache performance, and business metrics

📋 Best Practices Summary:

  1. Design for Scale: Plan your API structure with growth in mind
  2. Implement Security: Use multiple layers of security validation
  3. Cache Strategically: Cache responses to reduce backend load
  4. Monitor Actively: Set up comprehensive monitoring and alerting
  5. 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.