API Design / Reliability

    Idempotencja API

    Bezpieczne retry bez podwójnych płatności — Idempotency Keys w Redis, Inbox Pattern dla Kafka consumers, ochrona transakcji finansowych.

    Idempotency Key
    Klucz per żądanie
    Redis TTL 24h
    Cache odpowiedzi
    UNIQUE constraint
    DB protection
    Inbox Pattern
    Kafka consumers

    6 wzorców idempotencji

    Każda warstwa architektury wymaga innego podejścia — od HTTP po bazę danych i message brokera.

    Wzorzec Warstwa Gwarancja Storage TTL Kto używa
    Idempotency Key (header) API / HTTP Exactly-once per key Redis / PostgreSQL 24-48h Stripe, PayPal, AWS
    Natural HTTP idempotency API / HTTP Per HTTP method spec Brak N/A REST API z PUT/DELETE
    Database UNIQUE constraint Database Exactly-once insert Baza danych Dożywotni Systemy płatności
    Inbox Pattern Message Consumer Exactly-once consume DB (processed_messages) Konfigurowalne Kafka consumers
    Optimistic Locking Database Exactly-once update DB (version column) N/A Concurrent updates
    Kafka EOS (exactly-once) Message Broker Exactly-once end-to-end Kafka internal N/A Kafka Streams pipelines

    Często zadawane pytania

    Co to jest idempotencja w API i dlaczego jest kluczowa?

    Idempotencja: operacja jest idempotentna gdy wielokrotne wykonanie daje ten sam wynik co jednokrotne. f(f(x)) = f(x). HTTP idempotencja: GET — idempotentny (nie modyfikuje stanu). PUT — idempotentny (zastąpienie zasobu). DELETE — idempotentny (usunięcie zasobu, 404 przy kolejnym = ten sam stan). POST — niekoniecznie idempotentny (tworzenie zasobu). PATCH — niekoniecznie idempotentny (zależy od implementacji). Problem bez idempotencji: sieć zrywa się po przetworzeniu żądania ale przed odesłaniem odpowiedzi. Klient nie wie czy operacja się udała. Retry -> duplikat (podwójna płatność, podwójne zamówienie). Rozwiązanie: Idempotency Keys. Klient generuje unikalny klucz per żądanie. Wysyła w nagłówku: Idempotency-Key: UUID. Serwer: sprawdź czy klucz istnieje w cache/DB. Jeśli tak — zwróć poprzednią odpowiedź (bez ponownego przetwarzania). Jeśli nie — przetwórz i zapisz odpowiedź z kluczem. Stripe używa Idempotency-Key. PayPal PayPal-Request-Id. AWS API X-Amzn-Idempotency-Token. Idempotency Key storage: Redis (TTL 24h). PostgreSQL (idempotency_keys tabela). DynamoDB (conditional writes).

    Jak zaimplementować Idempotency Keys na serwerze?

    Implementacja serwera — kluczowe kroki: 1. Odczytaj Idempotency-Key z nagłówka. Jeśli brak — zwróć 400 Bad Request (dla operacji nie-idempotentnych). 2. Sprawdź w Redis/DB czy klucz istnieje. 3. Jeśli istnieje i status = COMPLETED — zwróć cached response. 4. Jeśli istnieje i status = IN_PROGRESS — zwróć 409 Conflict (duplicate concurrent request). 5. Zapisz klucz ze statusem IN_PROGRESS (atomic check-and-set). 6. Wykonaj operację (transakcja biznesowa). 7. Zapisz response z statusem COMPLETED. 8. Zwróć response. Race condition: dwa równoległe requesty z tym samym kluczem. Rozwiązanie: Redis SET key NX EX 86400 — atomic lock. Tylko jeden request wygrywa, drugi dostaje 409. PostgreSQL: INSERT INTO idempotency_keys (key, status) VALUES ($1, 'IN_PROGRESS') ON CONFLICT DO NOTHING — sprawdź affected rows. Redis Lua script dla atomowego check-and-set. Idempotent zapis w DB: przy retry mogą pojawić się duplikaty w DB. Rozwiązanie: unikalne constraint na kolumnie (np. external_reference_id). INSERT ... ON CONFLICT DO NOTHING — bezpieczny upsert. Zakres klucza: per user (klucz ważny tylko dla danego użytkownika). per endpoint (ten sam klucz w innym endpoint = inny zasób). TTL: 24-48h standardowo. Stripe: 24h. AWS: per-service, często 7 dni.

    Idempotent consumers w message brokerach — jak zapewnić exactly-once?

    Problem: message broker gwarantuje at-least-once delivery. Consumer może dostać tę samą wiadomość wielokrotnie (retry po ack timeout, rebalance). Aplikacja musi obsłużyć duplikaty. Strategie idempotent consumer: Deduplikacja przez ID: każda wiadomość ma unikalny message_id. Consumer sprawdza w DB/Redis czy message_id już przetworzony. Jeśli tak — skip (ack wiadomość bez przetwarzania). Idempotent operacja: operacja sama z siebie jest idempotentna. UPDATE SET balance = 100 WHERE id = 1 — zawsze ten sam wynik. Problem: UPDATE SET balance = balance - 10 — nie jest idempotentna. Natural idempotency: użyj SET/REPLACE zamiast ADD. Versioned updates: optimistic locking — WHERE version = X AND SET version = X+1. Reject jeśli version nie pasuje. Inbox Pattern (Transactional Outbox): w DB tabela 'processed_messages' z message_id. INSERT ... ON CONFLICT DO NOTHING przed przetworzeniem. Atomowe z przetwarzaniem (ta sama transakcja). Kafka Consumer Groups: Kafka offset commit po przetworzeniu. Enable.auto.commit = false. Manual commit po sukcesie. Problem: crash po przetworzeniu ale przed commit -> replay. Rozwiązanie: Inbox Pattern lub idempotent operations. Kafka Streams: exactly-once.v2 (EOS) — kombinacja Kafka transactions i idempotent producers. Najlepsza gwarancja ale kosztem latencji i throughpucie.

    Idempotencja przy płatnościach — jak chronić przed podwójną opłatą?

    Podwójna płatność: najkosztowniejszy bug w e-commerce. Klient kliknie 'Pay' dwa razy. Timeout i retry przez klienta. Network error po przetworzeniu. Warstwy ochrony: Frontend: disable przycisk po kliknięciu. Toast/spinner podczas przetwarzania. Nie pokazuj formularza płatności dwa razy. Backend Idempotency Key: klient generuje UUID per próbę płatności. Wysyła w nagłówku Idempotency-Key. Backend sprawdza w Redis/DB. Duplicate -> zwróć poprzednią odpowiedź (202/201 z tym samym payment_id). Database Unique Constraint: external_reference_id (klucz z klienta lub order_id + timestamp) jako UNIQUE. INSERT ON CONFLICT DO NOTHING. Payment Gateway idempotency: Stripe Idempotency-Key header. PayPal PayPal-Request-Id. Braintree własny mechanizm. Optimistic Locking: Order status: PENDING -> PROCESSING -> COMPLETED. UPDATE orders SET status = 'PROCESSING' WHERE id = X AND status = 'PENDING'. Jeśli 0 rows affected -> już przetwarzane. Auditlog: zapisuj każdą próbę z wynikiem. Łatwa diagnoza incydentów. Monitorowanie: metryki - duplicate_request_rate. Alert gdy wzrasta. Testy: scenariusze retry i concurrent requests w testach integracyjnych.

    PUT vs PATCH — które HTTP metody są idempotentne?

    PUT — zawsze idempotentny: zastąp cały zasób. PUT /users/123 {name: 'Jan', age: 30} — wynik zawsze ten sam, niezależnie ile razy wykonasz. Pełny zasób musi być w body. PATCH — może być nieide mpotentny: częściowa aktualizacja. PATCH /account {action: 'increment', amount: 10} — wielokrotne wykonanie sumuje. Idempotentny PATCH: PATCH /account {balance: 100} — ustawianie wartości, nie dodawanie. Wymaga przemyślanego API design. DELETE — idempotentny (zazwyczaj): pierwsza odpowiedź 200/204. Kolejne: 404 lub 204 (zależy od implementacji). Ważne: stan jest taki sam — zasób usunięty. POST — zwykle nie-idempotentny: POST /orders — tworzy nowe zamówienie per request. POST /login — może być idempotentny (ten sam token). POST /payments — nie-idempotentny bez Idempotency-Key. Safe vs Idempotent: Safe = nie modyfikuje stanu (GET, HEAD, OPTIONS). Idempotent = wielokrotne = jednokrotne (GET, PUT, DELETE, HEAD, OPTIONS). POST i PATCH mogą być nie-idempotentne. Projektowanie API: preferuj PUT dla aktualizacji gdy chcesz idempotencji. Używaj Idempotency-Key dla POST operacji które muszą być bezpieczne przy retry. Unikaj PATCH z increment/decrement semantics. REST Maturity Model (Richardson): Level 2+ wymaga prawidłowego użycia HTTP metod.

    Czytaj dalej

    Powiązane artykuły

    Kontakt

    Skontaktuj się z nami

    Porozmawiajmy o Twoim projekcie. Bezpłatna wycena w ciągu 24 godzin.

    Wyślij zapytanie

    Bezpłatna wycena w 24h
    Bez zobowiązań
    Indywidualne podejście
    Ekspresowa realizacja

    Telefon

    +48 790 814 814

    Pon-Pt: 9:00 - 18:00

    Email

    adam@fotz.pl

    Odpowiadamy w ciągu 24h

    Adres

    Plac Wolności 16

    61-739 Poznań

    Godziny pracy

    Pon - Pt9:00 - 18:00
    Sob - NdzZamknięte

    Wolisz porozmawiać?

    Zadzwoń teraz i porozmawiaj z naszym specjalistą o Twoim projekcie.

    Zadzwoń teraz