Desain API async yang aman diperlukan ketika server menerima pekerjaan yang durasinya tidak dapat dipastikan selesai dalam satu siklus request-response, misalnya import data besar, pembuatan laporan, atau sinkronisasi ke sistem pihak ketiga. Dalam kasus seperti ini, pola yang umum dan benar adalah: client mengirim request submit, server merespons 202 Accepted, lalu client memantau status operasi melalui endpoint terpisah sampai pekerjaan selesai, gagal, atau dibatalkan.
Intinya, 202 Accepted bukan berarti pekerjaan berhasil. Kode ini hanya menyatakan bahwa permintaan diterima untuk diproses. Karena itu, kontrak API harus jelas: bagaimana client mendapatkan operation ID, bagaimana status berubah dari waktu ke waktu, kapan boleh retry, kapan error dianggap final, dan bagaimana mencegah duplikasi jika request submit terkirim ulang.
Kapan memakai API async dan 202 Accepted
Gunakan pola async jika satu atau lebih kondisi berikut terjadi:
- Waktu proses tidak dapat diprediksi dan bisa melebihi timeout HTTP normal.
- Pekerjaan bergantung pada layanan eksternal yang lambat atau tidak stabil.
- Proses perlu dijalankan di antrean agar throughput sistem tetap terjaga.
- Hasil akhir tidak langsung diperlukan pada saat request pertama.
- Pekerjaan bersifat berat, misalnya komputasi besar, ekspor file, atau import jutaan baris.
Contoh yang cocok:
- Import CSV atau Excel ke database.
- Pembuatan report PDF/Excel.
- Sinkronisasi katalog atau order ke pihak ketiga.
- Rebuild indeks pencarian.
- Provisioning resource yang melibatkan beberapa langkah backend.
Sebaliknya, jangan memakai 202 hanya untuk terlihat modern. Jika operasi selalu cepat, deterministik, dan hasilnya langsung tersedia, respons sinkron dengan 200 OK, 201 Created, atau 204 No Content biasanya lebih sederhana dan lebih baik untuk client.
Kontrak dasar: endpoint submit dan endpoint cek status
1. Endpoint submit
Endpoint submit menerima permintaan pekerjaan baru. Jika request valid dan pekerjaan berhasil diterima untuk diproses, balas dengan 202 Accepted.
Respons awal minimal sebaiknya berisi:
- operation_id: identitas unik operasi.
- status: status awal, biasanya
queuedataupending. - status_url: URL untuk mengecek status operasi.
- submitted_at: waktu submit dalam format yang konsisten.
- idempotency_key atau penanda ekivalen jika fitur ini dipakai.
- retry_after opsional: saran jeda polling dalam detik.
Contoh request submit:
POST /imports
Content-Type: application/json
Idempotency-Key: imp-20240411-001
{
"source_url": "https://example.com/data.csv",
"mapping": {
"email": "email",
"name": "full_name"
}
}Contoh response:
HTTP/1.1 202 Accepted
Location: /operations/op_9f3a1c
Retry-After: 5
Content-Type: application/json
{
"operation_id": "op_9f3a1c",
"status": "queued",
"status_url": "/operations/op_9f3a1c",
"submitted_at": "2026-04-11T10:15:30Z",
"idempotency_key": "imp-20240411-001",
"retry_after": 5
}Header Location berguna untuk menunjuk resource status operasi. Ini tidak wajib di semua desain, tetapi sangat membantu karena memberi lokasi kanonis yang bisa diikuti client.
2. Endpoint cek status
Endpoint status mengembalikan keadaan terbaru operasi. Ia harus stabil, mudah dipolling, dan menjelaskan apakah client perlu menunggu lagi, boleh retry submit, atau harus menganggap pekerjaan selesai/gagal final.
Contoh:
GET /operations/op_9f3a1cContoh response saat masih berjalan:
HTTP/1.1 200 OK
Content-Type: application/json
{
"operation_id": "op_9f3a1c",
"type": "import",
"status": "running",
"submitted_at": "2026-04-11T10:15:30Z",
"started_at": "2026-04-11T10:15:35Z",
"updated_at": "2026-04-11T10:16:10Z",
"progress": {
"current": 420,
"total": 1000,
"unit": "rows"
},
"retry_after": 5,
"links": {
"self": "/operations/op_9f3a1c",
"cancel": "/operations/op_9f3a1c/cancel"
}
}Contoh response saat sukses:
HTTP/1.1 200 OK
Content-Type: application/json
{
"operation_id": "op_9f3a1c",
"type": "import",
"status": "succeeded",
"submitted_at": "2026-04-11T10:15:30Z",
"started_at": "2026-04-11T10:15:35Z",
"finished_at": "2026-04-11T10:16:48Z",
"result": {
"imported_rows": 998,
"skipped_rows": 2,
"resource_url": "/imports/imp_7821/result"
}
}Contoh response saat gagal final:
HTTP/1.1 200 OK
Content-Type: application/json
{
"operation_id": "op_9f3a1c",
"type": "import",
"status": "failed",
"submitted_at": "2026-04-11T10:15:30Z",
"finished_at": "2026-04-11T10:16:02Z",
"error": {
"code": "INVALID_SOURCE_FORMAT",
"message": "File tidak sesuai format yang didukung",
"retriable": false
}
}Perhatikan bahwa endpoint status tetap dapat mengembalikan 200 OK walaupun operasinya gagal. Itu karena request membaca status berhasil. Kegagalan berada pada state operasi, bukan pada request GET itu sendiri.
Model status operasi yang sederhana dan aman
Jangan membuat status terlalu banyak jika tidak memberi nilai. Untuk banyak kasus, model berikut cukup:
- queued: request diterima, menunggu worker.
- running: pekerjaan sedang diproses.
- succeeded: selesai sukses, status final.
- failed: selesai gagal, status final.
- cancelled: dibatalkan, status final.
Bila perlu, tambahkan cancelling jika pembatalan tidak instan. Hindari status ambigu seperti done, complete-ish, atau processing tanpa definisi jelas.
Transisi status yang disarankan
queued -> runningrunning -> succeededrunning -> failedqueued -> cancelledrunning -> cancelling -> cancelledbila pembatalan butuh waktu
Status final harus benar-benar final. Setelah operasi masuk ke succeeded, failed, atau cancelled, client tidak boleh melihatnya kembali ke running. Perubahan mundur seperti ini mempersulit cache, retry, monitoring, dan debugging.
Catatan: Jika Anda butuh menyimpan detail upaya internal seperti retry worker ke-3 atau perpindahan antar queue, simpan itu sebagai metadata diagnostik, bukan sebagai status publik yang membingungkan client.
Error terminal vs retriable
Salah satu bagian terpenting dalam desain API async adalah membedakan error yang masih layak dicoba ulang dengan error yang final.
Error terminal
Error terminal berarti submit ulang dengan data yang sama kemungkinan besar akan tetap gagal. Contoh:
- Format file tidak valid.
- Field wajib hilang.
- Autorisasi tidak memadai.
- Resource input tidak ditemukan dan memang salah.
Untuk kasus ini, status akhir operasi bisa failed dengan retriable: false.
Error retriable
Error retriable biasanya disebabkan kondisi sementara:
- Layanan pihak ketiga timeout.
- Koneksi jaringan terganggu.
- Rate limit sementara.
- Worker crash dan sistem akan mencoba lagi.
Ada dua pendekatan yang umum:
- Retry dilakukan sepenuhnya oleh backend sampai batas tertentu, dan client cukup menunggu status final.
- Backend menyatakan gagal sementara dengan
failedplusretriable: true, lalu client memutuskan apakah akan submit ulang.
Untuk kebanyakan sistem, opsi pertama lebih mudah untuk client dan mengurangi duplikasi pekerjaan. Namun, jika retry berarti biaya tinggi atau risiko efek samping besar, Anda mungkin ingin memberi kontrol lebih ke client.
Yang penting: jangan pakai pesan error bebas tanpa kode mesin. Sertakan kode error stabil seperti THIRD_PARTY_TIMEOUT atau INVALID_SOURCE_FORMAT agar client dapat membuat keputusan otomatis.
Retry client, timeout, dan polling yang tidak merusak sistem
Polling dengan backoff
Polling sederhana memang paling mudah diterapkan, tetapi harus dibatasi agar tidak membebani server. Jangan biarkan client memanggil endpoint status setiap 200 ms tanpa jeda. Itu anti-pattern.
Praktik yang lebih aman:
- Mulai dari interval polling moderat, misalnya beberapa detik.
- Gunakan backoff jika operasi belum selesai.
- Tambahkan jitter agar ribuan client tidak menembak server bersamaan.
- Hormati header Retry-After jika server mengirimkannya.
Pseudocode client:
delay = 2
maxDelay = 30
while true:
resp = GET /operations/{id}
if resp.status in ["succeeded", "failed", "cancelled"]:
break
sleep(delay + random_jitter())
delay = min(delay * 2, maxDelay)Timeout
Timeout perlu didefinisikan di beberapa level:
- HTTP timeout client untuk request submit dan GET status.
- Queue/job timeout agar worker tidak berjalan tanpa batas.
- Operation expiry untuk menentukan kapan status lama boleh dibersihkan atau diarsipkan.
Jangan mencampur semuanya. Timeout request HTTP yang pendek tidak berarti operasinya gagal; itu hanya berarti client gagal menerima respons saat itu. Jika submit sebelumnya mungkin sudah diterima server, idempotency menjadi penting agar retry tidak membuat duplikasi.
Kapan client boleh submit ulang
Client boleh mempertimbangkan submit ulang jika:
- Request submit gagal sebelum ada kepastian respons diterima, dan client memakai idempotency key.
- Endpoint status menyatakan operasi gagal dengan
retriable: true. - Dokumentasi API menyebut operasi dapat dibuat ulang secara aman.
Tanpa idempotency, retry submit setelah network timeout sangat berisiko karena server mungkin sudah membuat pekerjaan pertama.
Idempotency saat request submit terkirim ulang
Inilah area yang sering diabaikan. Pada jaringan nyata, client bisa timeout, koneksi putus setelah request terkirim, atau library HTTP melakukan retry otomatis. Jika endpoint submit membuat operasi baru setiap kali menerima request yang identik, Anda akan mendapat duplikasi import, report ganda, atau sinkronisasi ganda.
Cara kerja yang aman
Gunakan idempotency key yang dikirim client per niat operasi, bukan per percobaan HTTP. Server menyimpan relasi antara key tersebut dan hasil submit pertama.
Perilaku yang diharapkan:
- Request pertama dengan key baru membuat operasi baru dan mengembalikan 202.
- Request ulang dengan key yang sama dan payload identik mengembalikan operasi yang sama, bukan membuat operasi baru.
- Jika key sama tetapi payload berbeda, server sebaiknya menolak karena itu indikasi bug client atau replay yang salah.
Contoh respons saat submit ulang yang identik:
HTTP/1.1 202 Accepted
Location: /operations/op_9f3a1c
Content-Type: application/json
{
"operation_id": "op_9f3a1c",
"status": "queued",
"status_url": "/operations/op_9f3a1c",
"idempotency_key": "imp-20240411-001"
}Implementasi umum di server:
- Simpan
idempotency_key, fingerprint request, danoperation_id. - Buat constraint unik pada kombinasi yang relevan sesuai kebutuhan tenancy.
- Tentukan masa berlaku key, misalnya beberapa jam atau hari, sesuai risiko duplikasi.
- Pastikan proses penyimpanan key dan pembuatan operasi cukup atomik agar dua request paralel tidak membuat dua operasi.
Jika Anda memakai database relasional, ini biasanya berarti transaksi + unique index. Jika memakai cache cepat, tetap pikirkan ketahanan terhadap restart dan race condition.
Cancellation: kapan didukung dan bagaimana kontraknya
Tidak semua operasi perlu bisa dibatalkan. Namun, untuk proses panjang dan mahal, cancellation sering berguna.
Kontrak sederhana:
POST /operations/op_9f3a1c/cancelKemungkinan respons:
- 202 Accepted: permintaan pembatalan diterima, proses penghentian sedang dijalankan.
- 200 OK atau 204 No Content: operasi berhasil dibatalkan secara langsung, jika sistem Anda memang bisa memastikan itu.
- 409 Conflict: operasi sudah final dan tidak bisa dibatalkan lagi.
- 404 Not Found: operation ID tidak ada atau tidak terlihat oleh caller.
Hal penting yang perlu jelas:
- Pembatalan sering bersifat best effort, bukan jaminan instan.
- Worker perlu memeriksa sinyal cancel di titik aman, terutama untuk loop panjang atau panggilan eksternal.
- Jika operasi memodifikasi data, definisikan apakah cancel berarti rollback penuh atau berhenti pada titik aman berikutnya.
Jangan mengiklankan cancellation jika implementasinya hanya mengubah status tetapi pekerjaan sebenarnya tetap berjalan di belakang layar. Itu akan menghasilkan state yang menyesatkan.
Kode status HTTP yang tepat
Berikut aturan praktis yang paling berguna:
- 202 Accepted: submit valid dan pekerjaan diterima untuk diproses async.
- 200 OK: GET status berhasil; isi body menjelaskan status operasi saat ini.
- 201 Created: bila request sinkron benar-benar langsung membuat resource final, bukan operasi async. Untuk pola ini biasanya tidak dipakai pada submit async kecuali Anda memang membuat resource operasi secara eksplisit.
- 204 No Content: aksi berhasil tanpa body, misalnya cancellation tertentu atau delete status resource.
- 400 Bad Request: payload submit tidak valid secara sintaks atau kontrak dasar.
- 401 Unauthorized / 403 Forbidden: masalah autentikasi atau otorisasi.
- 404 Not Found: operation ID tidak ditemukan.
- 409 Conflict: konflik state, misalnya cancellation pada operasi yang sudah final, atau idempotency key sama dengan payload berbeda.
- 429 Too Many Requests: polling atau submit melebihi batas yang diizinkan.
- 500/502/503: error server saat menangani request HTTP itu sendiri, bukan status bisnis operasi async.
Jebakan umum adalah memakai 200 OK pada submit padahal pekerjaan baru dimasukkan ke queue. Secara teknis bisa saja, tetapi 202 lebih jelas menyampaikan bahwa hasil akhir belum tersedia.
Anti-pattern yang sering muncul
1. 200 palsu untuk pekerjaan yang belum selesai
Respons seperti ini menyesatkan:
{
"success": true,
"message": "Report berhasil dibuat"
}padahal report baru dimasukkan ke antrean. Client lalu menganggap proses selesai dan tidak pernah mengecek status.
2. Polling tanpa backoff
Jika setiap client memanggil status tiap detik atau lebih cepat, endpoint status akan menjadi sumber beban terbesar. Masalah ini makin parah saat terjadi lonjakan operasi panjang.
3. Status tidak final
Operasi yang sudah failed lalu kembali running, atau succeeded lalu berubah failed, akan merusak asumsi client dan membuat debugging sulit.
4. Error message tanpa kode stabil
Client tidak bisa membedakan apakah perlu retry, meminta input baru, atau menampilkan pesan ke user.
5. Tidak ada idempotency pada submit
Hasilnya adalah duplikasi pekerjaan saat terjadi retry jaringan atau pengguna menekan tombol berkali-kali.
6. Resource status dibersihkan terlalu cepat
Jika operasi selesai lalu status langsung hilang, client yang terlambat polling akan mendapat 404 dan tidak tahu hasil akhirnya. Simpan status final setidaknya selama window yang masuk akal.
7. Menyimpan progress palsu
Progress 90% terus-menerus selama 20 menit lebih buruk daripada tidak ada progress. Jika progress tidak dapat dihitung dengan jujur, lebih baik tampilkan status dan timestamp tanpa persentase menyesatkan.
Checklist implementasi
- Definisikan endpoint submit dan endpoint status yang terpisah jelas.
- Gunakan 202 Accepted untuk submit async yang berhasil diterima.
- Kembalikan
operation_id,status, danstatus_urlpada respons awal. - Gunakan model status yang kecil dan final:
queued,running,succeeded,failed,cancelled. - Pastikan status final tidak berubah lagi.
- Sediakan kode error mesin yang stabil dan flag
retriablebila relevan. - Dukung idempotency key untuk submit.
- Pastikan pembuatan operasi dan pencatatan idempotency aman terhadap race condition.
- Sediakan Retry-After atau panduan polling dengan backoff.
- Tentukan timeout job dan kebijakan retry worker internal.
- Jika cancellation didukung, dokumentasikan apakah bersifat best effort atau kuat.
- Simpan status final cukup lama agar client dapat mengambil hasilnya.
- Dokumentasikan hak akses: siapa boleh submit, lihat status, dan membatalkan.
Observability dasar yang wajib ada
API async tanpa observability akan sulit dioperasikan saat volume naik atau saat ada job macet. Minimal, siapkan hal berikut:
Logging terstruktur
operation_ididempotency_keyrequest_idatau trace ID- jenis operasi
- status lama dan status baru
- durasi
- kode error final bila gagal
Metrics
- Jumlah submit per jenis operasi.
- Jumlah operasi per status final.
- Durasi dari submit sampai selesai.
- Waktu tunggu di queue.
- Jumlah retry internal worker.
- Tingkat kegagalan per kode error.
- Laju polling endpoint status.
Tracing
Jika sistem Anda terdiri dari API, queue, worker, database, dan layanan eksternal, tracing sangat membantu untuk melihat alur satu operasi end-to-end. Korelasikan span HTTP submit, enqueue job, eksekusi worker, panggilan pihak ketiga, dan update status.
Alerting dasar
- Lonjakan operasi
failed. - Waktu queue terlalu lama.
- Banyak operasi terjebak di
running. - Tingkat polling terlalu tinggi.
- Retry worker melonjak tajam.
Contoh kontrak yang ringkas dan masuk akal
Jika disederhanakan, kontrak API async yang sehat biasanya seperti ini:
- Client mengirim
POST /importsdenganIdempotency-Key. - Server memvalidasi request. Jika valid dan diterima, server membuat operation record lalu membalas
202 Accepted. - Client membaca
operation_iddanstatus_url. - Worker memproses job dan memperbarui status ke
running, lalu ke status final. - Client melakukan polling ke
GET /operations/{id}dengan backoff. - Jika sukses, respons status menyertakan hasil atau link ke resource hasil.
- Jika gagal, respons status menyertakan kode error stabil dan apakah kegagalan retriable.
- Jika user membatalkan, client memanggil endpoint cancel dan tetap memantau status sampai final.
Penutup
Desain API async yang aman bukan sekadar menaruh job ke queue lalu mengembalikan 202. Yang menentukan kualitasnya adalah kontrak yang jelas: kapan request dianggap diterima, bagaimana status dipublikasikan, bagaimana retry dilakukan tanpa duplikasi, kapan error final, bagaimana cancel bekerja, dan bagaimana sistem dapat diamati saat ada masalah.
Jika Anda mulai dari satu prinsip utama, gunakan ini: pisahkan penerimaan pekerjaan dari hasil akhirnya. Dari sana, bangun resource status operasi yang stabil, final, mudah dipolling, dan aman terhadap retry. Dengan itu, API akan lebih jujur terhadap client, lebih tahan terhadap kegagalan jaringan, dan lebih mudah dioperasikan di produksi.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!