Mutasi GraphQL harus siap menghadapi retry atau webhook yang mengulang permintaan tanpa menimbulkan efek ganda. Kontrak GraphQL Idempoten memaksa setiap mutasi mempertahankan logika deterministik, termasuk validasi input, header autentikasi, dan deduplikasi berdasarkan identifier unik. Dalam dua paragraf ke depan, akan diperlihatkan pola praktis untuk mencegah efek retry sekaligus memfasilitasi observabilitas yang cukup.

Solusi ini langsung menjawab kebutuhan tim backend yang merancang API GraphQL: pastikan mutasi tidak berubah status meski diterima beberapa kali, kendalikan otentikasi, dan tetap bisa melaporkan partial failure kepada tim integrasi.

Masalah Retry Tanpa Kontrak GraphQL Idempoten

Tanpa kontrak yang ketat, retry otomatis (misalnya dari API Gateway atau webhook target) bisa mengeksekusi mutasi yang sama berkali-kali, menciptakan double charge, duplikasi data, ataupun status yang tidak konsisten. Masalah utama sering kali adalah:

  • Mutasi tidak memisahkan data input dari identitas permintaan, sehingga sulit mendeteksi retry.
  • Autentikasi stateless tidak mempertimbangkan token kadaluarsa saat penanganan ulang.
  • Kurangnya observabilitas membuat partial failure atau deduplikasi gagal tidak terdeteksi.

Dengan kontrak GraphQL idempoten, setiap mutasi mendefinisikan aturan input, header, dan output yang bisa diinsepksi alat monitoring maupun tim integrasi sebelum retry dijalankan.

Merancang Kontrak Mutasi Idempoten

Mulailah dengan mendefinisikan schema mutasi yang mengharuskan klien mengirim identifier unik.

input PaymentRequest {
  clientMutationId: ID!
  referenceId: String!  # ID unik dari sistem pemanggil
  amount: Int!
  metadata: JSONObject
}

type MutatePaymentPayload {
  success: Boolean!
  appliedAt: DateTime
  warnings: [String!]
}

mutation applyPayment($input: PaymentRequest!) {
  applyPayment(input: $input) {
    success
    appliedAt
  }
}

Aturan kontrak yang harus ditegakkan:

  • Input validation: Pastikan referenceId diterima dalam format yang konsisten, tidak boleh kosong, dan cocok dengan pola yang bisa digunakan untuk deduplikasi.
  • State persistence: Simpan status request berbasis referenceId dan clientMutationId, sertakan timestamp, status akhir, dan hash payload.
  • Response deterministik: Output hanya mengandung data yang bisa diprediksi dan tidak menggantungkan waktu server secara langsung.

Header Autentikasi dan Token Expiry

Periksa header Authorization untuk memastikan token tidak kadaluarsa. Kontrak menetapkan bahwa token harus memuat claim seperti exp dan sub, tetapi server juga wajib mengecek timestamp terakhir penggunaan agar retry yang tertunda tidak menerima token usang.

Contoh alur: jika token kadaluarsa terjadi, server mengembalikan error 401 Unauthorized yang menyarankan integrasi untuk refresh token sebelum retry. Jangan coba memproses permintaan yang sama setelah menolak autentikasi, karena ini menyulitkan deduplikasi yang efektif.

Strategi Idempotensi dan Deduplikasi

Gunakan pendekatan berikut untuk memaksa idempoten:

  • UUID/Reference ID: Klien harus mengirimkan referenceId yang unik per transaksi. Server menyimpan entry dengan status final, sehingga permintaan kedua dengan reference sama menghasilkan keluaran identik tanpa memodifikasi state.
  • Last-known-state: Setelah mutasi sukses, simpan response summary. Jika retry masuk dengan reference sama, cukup kirim kembali summary itu tanpa mengulang efek bisnis.
  • Dedup layer: Gunakan database dengan constraint unik (misalnya UNIQUE(reference_id)) atau cache seperti Redis untuk menandai request yang sudah diproses selama window tertentu.

Jika terjadi partial failure (misalnya berhasil menulis ke satu service tapi gagal ke service lain), kontrak harus mengizinkan status "pending" atau "reconciliation needed", dan response GraphQL wajib menyampaikan informasi ini agar tim integrasi bisa mengambil tindakan.

Observabilitas dan Partial Failure

Kontrak GraphQL idempoten tidak lengkap tanpa observabilitas. Catat log yang mencakup requestId, referenceId, status deduplikasi, dan status autentikasi. Tambahkan metric seperti:

  • Retry frequency per mutasi
  • Dedup hits vs misses
  • Partial failure rate (misalnya response warnings berisi "reconciliation needed")

Tambahkan alert jika deduplikasi gagal atau jika ada token yang sering kadaluarsa. Dokumentasikan pula kontrak ini pada tim integrasi, sertakan contoh request/respon, dan jelaskan bagaimana menandai retry dan apa yang harus dilakukan jika menerima webhook duplikat.

Praktik Komunikasi dengan Tim Integrasi

Pastikan dokumentasi meliputi:

  1. Header mandatory (Authorization, Content-Type, atau custom header idempotensi).
  2. Respon error standar beserta kode khusus (misalnya 409 untuk deduplikasi, 202 untuk pending, 401 untuk token exp).
  3. Panduan observasi: bagaimana membaca logs, metric deduplikasi, serta cara mengonfirmasi permintaan sudah diterima atau harus dicoba ulang.

Koordinasikan pula dengan tim integrasi untuk menyepakati window retry, grace period token, dan bagaimana menandai webhook yang harus disinkronkan ulang.

Kesimpulan

Dengan kontrak GraphQL idempoten yang jelas, Anda bisa mengendalikan retry dan webhook tanpa mengganggu integritas data. Fokus pada validasi input, autentikasi dengan token expiry, deduplikasi berbasis reference ID, serta observabilitas dan komunikasi tim integrasi agar partial failure cepat terdeteksi. Pendekatan ini memungkinkan mutasi tetap aman meski dipanggil ulang berkali-kali.