Dlaczego AWS Lambda zmienia sposób myślenia o aplikacjach
Wyobraź sobie, że mógłbyś uruchomić swój kod Java bez kupowania serwerów, instalowania systemów operacyjnych czy martwienia się o skalowanie. AWS Lambda to przyszłość – serverless computing, które rewolucjonizuje sposób tworzenia aplikacji w 2018 roku.
Co się nauczysz:
- Czym jest serverless computing i dlaczego to game-changer
- Jak napisać pierwszą funkcję Lambda w Javie
- Kiedy używać Lambda zamiast tradycyjnych serwerów
- Najlepsze praktyki dla Java developerów
- Jak integrować Lambda z innymi usługami AWS
- Podstawowa znajomość Java (6-18 miesięcy doświadczenia)
- Rozumienie REST API
- Konto AWS (można założyć za darmo)
- IDE z obsługą Maven
Czym jest AWS Lambda?
AWS Lambda działa na prostej zasadzie – uploadsz swój kod, AWS zajmuje się resztą. Nie musisz się martwić o:
– Kupowanie i konfigurowanie serwerów
– Instalowanie systemów operacyjnych
– Skalowanie w górę czy w dół
– Zarządzanie bezpieczeństwem infrastruktury
– Monitorowanie dostępności serwerów
Model płatności – pay per execution
W tradycyjnym modelu płacisz za serwer 24/7, nawet gdy nic się nie dzieje. W Lambda płacisz tylko za:
– Liczbę wywołań funkcji
– Czas wykonania (w milisekundach)
– Ilość używanej pamięci
Twoja pierwsza funkcja Lambda w Javie
Zacznijmy od prostego przykładu – funkcja, która przetwarza zamówienia w e-commerce.
Krok 1: Struktura projektu Maven
<project xmlns="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>lambda-order-processor</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-core</artifactId> <version>1.2.0</version> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-events</artifactId> <version>2.0.2</version> </dependency> </dependencies> </project>
Krok 2: Handler – serce funkcji Lambda
package com.example.lambda; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; // POJO dla incoming data class OrderRequest { private String customerId; private double amount; private String productId; // Konstruktory, gettery, settery public OrderRequest() {} public String getCustomerId() { return customerId; } public void setCustomerId(String customerId) { this.customerId = customerId; } public double getAmount() { return amount; } public void setAmount(double amount) { this.amount = amount; } public String getProductId() { return productId; } public void setProductId(String productId) { this.productId = productId; } } // Response object class OrderResponse { private String orderId; private String status; private String message; public OrderResponse(String orderId, String status, String message) { this.orderId = orderId; this.status = status; this.message = message; } // Gettery public String getOrderId() { return orderId; } public String getStatus() { return status; } public String getMessage() { return message; } } // Główna klasa Handler public class OrderProcessor implements RequestHandler<OrderRequest, OrderResponse> { @Override public OrderResponse handleRequest(OrderRequest request, Context context) { // Logowanie - ważne dla debugowania context.getLogger().log("Processing order for customer: " + request.getCustomerId()); try { // Business logic - walidacja if (request.getAmount() <= 0) { return new OrderResponse(null, "ERROR", "Invalid amount"); } if (request.getCustomerId() == null || request.getCustomerId().isEmpty()) { return new OrderResponse(null, "ERROR", "Customer ID required"); } // Symulacja przetwarzania zamówienia String orderId = generateOrderId(); // W prawdziwej aplikacji tutaj byłaby integracja z bazą danych // saveOrderToDatabase(request, orderId); context.getLogger().log("Order processed successfully: " + orderId); return new OrderResponse(orderId, "SUCCESS", "Order processed successfully"); } catch (Exception e) { context.getLogger().log("Error processing order: " + e.getMessage()); return new OrderResponse(null, "ERROR", "Internal processing error"); } } private String generateOrderId() { return "ORD-" + System.currentTimeMillis(); } }
Krok 3: Deployment
# Build JAR mvn clean package # Upload do AWS Lambda (przez AWS CLI lub console) aws lambda create-function \ --function-name OrderProcessor \ --runtime java8 \ --role arn:aws:iam::YOUR-ACCOUNT:role/lambda-execution-role \ --handler com.example.lambda.OrderProcessor::handleRequest \ --zip-file fileb://target/lambda-order-processor-1.0.0.jar
Kiedy używać AWS Lambda?
Lambda nie jest rozwiązaniem na wszystko. Oto kiedy sprawdza się idealnie:
Doskonale sprawdza się | Nie najlepszy wybór |
---|---|
Event-driven processing (upload pliku → przetwarzanie) | Long-running processes (>15 minut) |
API endpoints z nieregularnym traffic | High-frequency, low-latency aplikacje |
Batch processing małych/średnich danych | Aplikacje wymagające state między requestami |
Microservices z jasno określoną odpowiedzialnością | Monolityczne aplikacje |
Integracje między systemami | Real-time streaming z dużym volume |
Integracja z ekosystemem AWS
Prawdziwa siła Lambda objawia się w integracji z innymi usługami AWS:
Popularne triggery
// Handler dla API Gateway public class ApiGatewayHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { @Override public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent request, Context context) { APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); try { // Parse HTTP method String httpMethod = request.getHttpMethod(); String path = request.getPath(); context.getLogger().log("Received " + httpMethod + " request for " + path); if ("GET".equals(httpMethod) && "/orders".equals(path)) { // GET /orders logic response.setStatusCode(200); response.setBody("{\"message\": \"Orders retrieved\"}"); } else if ("POST".equals(httpMethod) && "/orders".equals(path)) { // POST /orders logic String requestBody = request.getBody(); context.getLogger().log("Request body: " + requestBody); response.setStatusCode(201); response.setBody("{\"message\": \"Order created\"}"); } else { response.setStatusCode(404); response.setBody("{\"error\": \"Not found\"}"); } // CORS headers Map<String, String> headers = new HashMap<>(); headers.put("Content-Type", "application/json"); headers.put("Access-Control-Allow-Origin", "*"); response.setHeaders(headers); return response; } catch (Exception e) { context.getLogger().log("Error: " + e.getMessage()); response.setStatusCode(500); response.setBody("{\"error\": \"Internal server error\"}"); return response; } } }
Best practices dla Java developerów
1. Minimalizuj cold start
public class OptimizedHandler implements RequestHandler<String, String> { // Inicjalizacja POZA metodą handleRequest - wykonuje się raz private static final DatabaseClient dbClient = createDatabaseClient(); private static final ObjectMapper jsonMapper = new ObjectMapper(); @Override public String handleRequest(String input, Context context) { // Ta metoda wykonuje się przy każdym wywołaniu // Używaj już zainicjalizowanych objectów return processRequest(input); } private static DatabaseClient createDatabaseClient() { // Heavy initialization tylko raz return new DatabaseClient(); } }
2. Zarządzanie pamięcią
3. Error handling i retry logic
public class ResilientHandler implements RequestHandler<String, String> { private static final int MAX_RETRIES = 3; @Override public String handleRequest(String input, Context context) { for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) { try { return processWithExternalService(input); } catch (TransientException e) { context.getLogger().log("Attempt " + attempt + " failed: " + e.getMessage()); if (attempt == MAX_RETRIES) { throw new RuntimeException("Max retries exceeded", e); } // Exponential backoff try { Thread.sleep(1000 * attempt); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new RuntimeException("Interrupted", ie); } } } return null; // Never reached } }
Monitoring i debugging
AWS Lambda automatycznie integruje się z CloudWatch, ale warto wiedzieć co monitorować:
Kluczowe metryki:
– **Invocations:** ile razy funkcja została wywołana
– **Duration:** średni czas wykonania
– **Errors:** liczba błędów
– **Throttles:** czy funkcja była ograniczana
Koszty – jak kontrolować wydatki
Lambda może być bardzo tanim rozwiązaniem, ale może też kosztować fortunę przy niewłaściwym użyciu:
Optymalizacja kosztów:
1. **Zmniejsz memory allocation** jeśli nie potrzebujesz
2. **Optymalizuj czas wykonania** – każda milisekunda ma znaczenie
3. **Używaj provisioned concurrency** tylko gdy naprawdę potrzebujesz
4. **Monitoruj unused functions** – usuń nieużywane
Nie całkowicie. Lambda to świetne rozwiązanie dla event-driven aplikacji i mikrousług, ale tradycyjne serwery nadal są lepsze dla long-running processes i aplikacji wymagających state.
Lambda automatycznie skaluje się do 1000 concurrent executions (domyślnie). Można zwiększyć ten limit. Dla bardzo high traffic lepiej kombinować z SQS do queue’owania requestów.
Tak, ale pamiętaj o limicie 50MB dla deployment package. Dla większych zależności użyj Lambda Layers (dostępne w późniejszych wersjach AWS Lambda).
Lambda świetnie współpracuje z DynamoDB. Dla RDS pamiętaj o connection pooling – każda instancja Lambda tworzy nowe połączenie.
Użyj SAM CLI (Serverless Application Model) – pozwała uruchamiać Lambda lokalnie. Możesz też pisać zwykłe unit testy dla business logic.
AWS zarządza security na poziomie infrastruktury. Ty odpowiadasz za IAM roles, encryption in transit/at rest i validację input data.
To prawdziwe wyzwanie. Kod business logic można przenieść, ale integracje z AWS services wymagają przepisania. Rozważ abstrakcje na poziomie architektury.
Przydatne zasoby:
- AWS Lambda Java Programming Model
- AWS Lambda Java Libraries
- AWS Lambda Pricing Calculator
- SAM CLI dla lokalnego developmentu
🚀 Zadanie dla Ciebie
Stwórz funkcję Lambda, która:
- Przyjmuje JSON z danymi użytkownika (imię, email, wiek)
- Waliduje dane (email format, wiek 18-120)
- Zwraca success/error response
- Loguje każde wywołanie do CloudWatch
Bonus: Zintegruj z API Gateway i przetestuj przez Postman.
AWS Lambda to przyszłość aplikacji event-driven. Zaczynasz już dziś budować mikrousługi, które skalują się automatycznie i kosztują tylko tyle, ile faktycznie używasz?