AWS Lambda – serverless computing

TL;DR: AWS Lambda to serverless computing platform, która pozwala uruchamiać kod bez zarządzania serwerami. Płacisz tylko za faktyczne wykonanie – perfect dla Java developerów chcących skupić się na logice biznesowej zamiast na infrastrukturze.

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
Wymagania wstępne:

  • 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 to usługa serverless computing, która uruchamia twój kod w odpowiedzi na zdarzenia bez konieczności zarządzania serwerami.

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

Lambda to jak Uber dla kodu – nie musisz kupować samochodu (serwera), żeby dojechać do celu. Płacisz tylko za przejazd (execution time).

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

Przykład: Jeśli twoja funkcja wykonuje się 100 razy dziennie po 200ms każda, koszt miesięczny to około $0.20. Tradycyjny serwer to minimum $5-20/miesiąc niezależnie od użycia.

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();
    }
}
Pro tip: W Lambda zawsze używaj Context.getLogger() do logowania. Logi trafiają automatycznie do CloudWatch, gdzie możesz je analizować.

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 trafficHigh-frequency, low-latency aplikacje
Batch processing małych/średnich danychAplikacje wymagające state między requestami
Microservices z jasno określoną odpowiedzialnościąMonolityczne aplikacje
Integracje między systemamiReal-time streaming z dużym volume
Uwaga: Lambda ma limit 15 minut na execution. Dla długotrwałych procesów lepiej użyć EC2 lub ECS.

Integracja z ekosystemem AWS

Prawdziwa siła Lambda objawia się w integracji z innymi usługami AWS:

Popularne triggery

API Gateway: Lambda jako backend dla REST API – idealny dla mikrousług
// 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;
        }
    }
}
S3 Events: Automatyczne przetwarzanie uploadowanych plików
DynamoDB Streams: Reagowanie na zmiany w bazie danych
CloudWatch Events: Scheduled tasks (cron-like functionality)

Best practices dla Java developerów

1. Minimalizuj cold start

Pułapka: Cold start to czas potrzebny na uruchomienie nowej instancji Lambda. Dla Javy może to być 1-3 sekundy.
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ą

Pro tip: Więcej pamięci = więcej CPU power. Czasem zwiększenie pamięci z 128MB do 256MB przyspiesza execution i zmniejsza koszty.

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

Typowy błąd: Ignorowanie throttling. Jeśli widzisz throttles, zwiększ concurrent execution limit lub zoptymalizuj kod.

Koszty – jak kontrolować wydatki

Lambda może być bardzo tanim rozwiązaniem, ale może też kosztować fortunę przy niewłaściwym użyciu:

Uwaga: Funkcja wykonująca się 1000 razy dziennie po 5 sekund kosztuje ~$18/miesiąc. Ta sama logika na micro EC2 to ~$8/miesiąc, ale bez automatycznego skalowania.

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

Czy Lambda zastąpi tradycyjne serwery?

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.

Jak radzi sobie Lambda z high traffic?

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.

Czy mogę używać zewnętrznych bibliotek?

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).

Co z bazami danych?

Lambda świetnie współpracuje z DynamoDB. Dla RDS pamiętaj o connection pooling – każda instancja Lambda tworzy nowe połączenie.

Jak testować funkcje Lambda lokalnie?

Użyj SAM CLI (Serverless Application Model) – pozwała uruchamiać Lambda lokalnie. Możesz też pisać zwykłe unit testy dla business logic.

Czy Lambda jest bezpieczny?

AWS zarządza security na poziomie infrastruktury. Ty odpowiadasz za IAM roles, encryption in transit/at rest i validację input data.

Co z vendor lock-in?

To prawdziwe wyzwanie. Kod business logic można przenieść, ale integracje z AWS services wymagają przepisania. Rozważ abstrakcje na poziomie architektury.

Przydatne zasoby:

🚀 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?

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry