RESTForge

Idempotency

Proteksi duplikasi request menggunakan header Idempotency-Key

Idempotency melindungi endpoint mutasi kritis dari duplikasi data akibat retry. Ketika response gagal diterima client karena network timeout, client melakukan retry yang menyebabkan operasi dieksekusi ulang. Fitur ini menyimpan response sukses di Redis sehingga retry dengan key yang sama mengembalikan response tersimpan tanpa mengeksekusi operasi ulang.

Referensi Cepat (Quick Reference)

PropertiNilai
DependencyRedis Server 6.0+, CACHE_ENABLED=true
AktivasiIDEMPOTENCY_ENABLED=true di file .env
Default TTL300 detik (5 menit)
Header wajibIdempotency-Key pada endpoint kritis
Endpoint dilindungi/create, /create-composite, /adjust, /update-composite
Response header (replay)Idempotent-Replayed: true

Masalah yang Diselesaikan (Problem Statement)

Tanpa Idempotency:
  1. Client kirim POST /create { supplier_code: "SUP-001" }
  2. Server INSERT berhasil, UUID "aaa-111" tersimpan
  3. Response GAGAL sampai ke client (network timeout)
  4. Client retry → Server INSERT lagi → UUID "bbb-222" (duplikat)

Dengan Idempotency:
  1. Client kirim POST /create + Header Idempotency-Key: "req-001"
  2. Server INSERT berhasil, response disimpan ke Redis
  3. Response GAGAL sampai ke client
  4. Client retry dengan key sama → Redis HIT → return response tersimpan
  5. Client menerima UUID "aaa-111" (sama, tanpa duplikasi)

Endpoint yang Dilindungi (Protected Endpoints)

EndpointMasalah tanpa IdempotencyTingkat Kritis
/createRecord duplikat (UUID baru per retry)Tinggi
/create-compositeHeader + seluruh detail items terduplikatTinggi
/adjustPenyesuaian terakumulasi (stock + N dijalankan 2x)Kritis
/update-compositeDetail insert menghasilkan item duplikatTinggi

Endpoint yang tidak memerlukan idempotency: /read, /first, /datatables, /lookup (read-only), /update (idempotent secara natural karena SET field = value), dan /delete (retry return 404).

Konfigurasi (Configuration)

config/production.env
IDEMPOTENCY_ENABLED=true
IDEMPOTENCY_TTL=300
CACHE_ENABLED=true
REDIS_HOST=redis.production.internal
REDIS_PORT=6380
VariableDefaultKeterangan
IDEMPOTENCY_ENABLEDfalseAktifkan/nonaktifkan idempotency secara global
IDEMPOTENCY_TTL300Durasi penyimpanan response di Redis (detik)

CACHE_ENABLED=true wajib aktif karena idempotency menggunakan Redis via cache layer. Server menolak start jika IDEMPOTENCY_ENABLED=true tanpa CACHE_ENABLED=true.

Cara Penggunaan (How to Use)

Sertakan header Idempotency-Key pada setiap request ke endpoint kritis:

curl -X POST http://localhost:3080/api/mini-inventory/supplier/create \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: sup-create-001" \
  -d '{
    "supplier_code": "SUP-TEST",
    "supplier_name": "Test Supplier"
  }'

Response pertama (HTTP 201):

{
    "success": true,
    "message": "supplier data successfully added",
    "data": {
        "supplier_id": "aaa-111-bbb-222",
        "supplier_code": "SUP-TEST",
        "supplier_name": "Test Supplier"
    },
    "timestamp": "2026-04-16T10:30:00.000Z"
}

Response retry (dari cache, header Idempotent-Replayed: true):

Response body dan status code identik dengan request pertama. Header Idempotent-Replayed: true menandakan response ini adalah replay.

Format Idempotency-Key

FormatContohKeterangan
UUID550e8400-e29b-41d4-a716-446655440000Format standar, direkomendasikan
CustomINV-2026-001-createBebas, sesuai konvensi project

Panjang key maksimum 255 karakter.

Penanganan Error (Error Handling)

HTTP StatusKondisiPenyebab
400Missing Idempotency-KeyRequest ke endpoint kritis tanpa header
400Invalid Idempotency-KeyKey melebihi 255 karakter
422Idempotency-Key conflictKey sama digunakan dengan payload berbeda

Error 422: Key Conflict

Terjadi ketika key yang sama digunakan untuk mengirim request dengan payload yang berbeda:

{
    "success": false,
    "error": "Idempotency-Key conflict",
    "message": "This Idempotency-Key has been used with a different request body",
    "timestamp": "2026-04-16T10:30:00.000Z"
}

Setiap operasi bisnis yang berbeda harus menggunakan key yang unik. Key hanya boleh di-reuse untuk retry operasi yang sama (payload identik).

Pola Retry yang Benar (Correct Retry Pattern)

// Key di-generate SEBELUM loop retry
const idempotencyKey = crypto.randomUUID();

async function createWithRetry(data, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            const response = await fetch('/api/mini-inventory/supplier/create', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Idempotency-Key': idempotencyKey  // key SAMA di setiap retry
                },
                body: JSON.stringify(data)
            });
            return await response.json();
        } catch (error) {
            if (attempt === maxRetries) throw error;
            await sleep(attempt * 1000);
        }
    }
}

Key harus di-generate sebelum loop retry, bukan di dalamnya. Key baru di setiap retry akan melewati proteksi idempotency.

Graceful Degradation

SkenarioPerilaku
Redis tidak tersedia saat startupServer menolak start
Redis terputus saat runtimeRequest diteruskan tanpa proteksi idempotency
Redis GET errorRequest dieksekusi sebagai baru
Redis SET errorDiabaikan, response tetap dikirim

Penggunaan di Processor (Processor Usage)

Idempotency manager tersedia di processor melalui services.idempotency:

const { idempotency } = services;

// Generate key random untuk panggilan ke service lain
const key = idempotency.generateKey('payment');
// Hasil: "payment_550e8400-..."

// Generate key deterministik (selalu sama untuk input sama)
const key = idempotency.buildDeterministicKey('inventory', 'receive', input.inbound_id);
// Hasil: "inventory:receive:INB-2026-001"

// Cek status
if (idempotency && idempotency.isEnabled()) { ... }

Langkah Selanjutnya (Next Steps)

On this page