Jika sebuah produk dijual per kemampuan, maka API juga perlu dirancang per batas kemampuan itu. Bukan sekadar kumpulan endpoint teknis, melainkan kontrak yang memetakan dengan jelas: apa unit bisnisnya, siapa yang boleh mengakses, bagaimana operasi tulis dijaga agar tidak ganda, dan bagaimana klien boleh melakukan retry tanpa merusak state.
Gagasan ini selaras dengan ide minimum viable unit of saleable software: pelanggan membeli nilai yang terpisah dan terdefinisi, bukan blob fitur yang kabur. Dalam konteks integrasi, dampaknya langsung terasa. Tiap unit yang dijual sebaiknya punya batas kontrak API yang eksplisit, model auth yang tepat, error model yang konsisten, strategi idempotency untuk operasi tulis, dan mekanisme sinkronisasi yang aman antara billing, provisioning, dan access control.
Gunakan ide dari minimum viable unit sebagai lensa desain: jika sebuah kemampuan bisa dijual, diaktifkan, dinonaktifkan, ditagih, atau diaudit secara terpisah, maka kemungkinan besar kemampuan itu juga perlu batas API yang terpisah.
Mengapa API perlu mengikuti batas unit jual
Banyak integrasi rapuh bukan karena HTTP atau JSON, melainkan karena batas bisnisnya kabur. Contoh yang umum:
- Satu token API memberi akses ke semua fitur, padahal langganan pelanggan hanya mencakup sebagian.
- Endpoint generik seperti
/actionsatau/executemenyatukan beberapa kemampuan yang sebenarnya ditagih terpisah. - Status billing mengatakan fitur aktif, tetapi ACL atau permission cache belum ikut berubah.
- Klien me-retry request create setelah timeout, lalu resource tercipta dua kali.
Dengan merancang API per batas unit jual, Anda memperoleh beberapa keuntungan praktis:
- Kontrak lebih stabil: perubahan pada satu unit tidak merusak unit lain.
- Auth lebih presisi: scope atau permission mengikuti produk yang benar-benar dibeli.
- Billing dan akses lebih mudah diselaraskan: entitlement menjadi objek yang nyata, bukan hasil inferensi dari banyak tabel.
- Retry lebih aman: operasi tulis punya aturan idempotency yang jelas per unit.
- Observability lebih baik: error, quota, dan SLA bisa dilacak per kemampuan.
Memetakan unit bisnis ke resource dan endpoint
Langkah pertama bukan memilih format URL, tetapi mengidentifikasi unit bisnis yang dijual, diaktifkan, atau dibatasi. Tanyakan:
- Apa yang benar-benar dibeli pelanggan?
- Apa yang bisa di-upgrade atau di-downgrade secara terpisah?
- Apa yang perlu audit trail terpisah?
- Apa yang punya quota atau limit pemakaian sendiri?
Pola pemetaan yang sehat
Biasanya ada tiga lapisan objek yang perlu dibedakan:
- Product/plan: definisi komersial.
- Entitlement/access grant: hasil aktivasi hak akses untuk tenant atau account.
- Operational resource: objek yang dipakai aplikasi sehari-hari.
Misalnya Anda menjual kemampuan invoice sync dan payout automation secara terpisah. Hindari menyembunyikan keduanya di balik satu endpoint konfigurasi umum. Lebih baik pisahkan resource agar batas kontraknya terlihat:
GET /accounts/{account_id}/entitlements
GET /accounts/{account_id}/entitlements/invoice-sync
PUT /accounts/{account_id}/entitlements/invoice-sync
POST /accounts/{account_id}/invoice-sync-jobs
GET /accounts/{account_id}/invoice-sync-jobs/{job_id}
POST /accounts/{account_id}/payout-runs
GET /accounts/{account_id}/payout-runs/{run_id}Di sini:
entitlementsmewakili hak akses hasil billing/provisioning.invoice-sync-jobsdanpayout-runsadalah resource operasional yang hanya bisa dipakai jika entitlement terkait aktif.
Kapan perlu endpoint terpisah, bukan parameter fitur
Memakai parameter seperti feature=invoice_sync bisa terlihat fleksibel, tetapi sering mengaburkan kontrak. Endpoint terpisah lebih tepat jika:
- fitur punya lifecycle sendiri,
- fitur punya model auth atau quota berbeda,
- fitur punya error domain sendiri,
- fitur bisa dijual atau dimatikan tanpa memengaruhi kemampuan lain.
Parameter tetap berguna untuk variasi kecil dalam satu domain yang sama, tetapi bukan untuk menyatukan produk yang berbeda secara bisnis.
Contract API: jelas, sempit, dan bisa dievolusi
Kontrak API untuk unit jual harus menjawab empat hal: identitas resource, state yang valid, operasi yang diizinkan, dan kegagalan yang mungkin terjadi. Kontrak yang terlalu umum membuat klien harus menebak logika server; kontrak yang terlalu longgar membuat retry dan debugging menjadi sulit.
Contoh kontrak entitlement
GET /accounts/acc_123/entitlements/invoice-sync
200 OK
Content-Type: application/json
{
"feature": "invoice-sync",
"status": "active",
"plan": "pro",
"effective_at": "2026-06-30T10:00:00Z",
"expires_at": null,
"limits": {
"monthly_jobs": 10000
},
"capabilities": {
"create_job": true,
"read_job": true,
"cancel_job": false
}
}Beberapa hal penting dari contoh ini:
- Status entitlement eksplisit, misalnya
active,suspended,expired, ataupending. - Batas kemampuan diuraikan sebagai capability yang bisa dipakai server maupun klien.
- Limit terlihat sehingga klien tidak perlu menebak alasan penolakan.
Versi kontrak
Versi kontrak diperlukan ketika Anda perlu mengubah bentuk payload atau semantics secara tidak kompatibel. Prinsip umumnya:
- Gunakan versi untuk kontrak eksternal, bukan untuk setiap refactor internal.
- Hindari perubahan makna field tanpa menaikkan versi.
- Tambahan field baru umumnya aman jika klien diharapkan toleran terhadap field yang tidak dikenal.
Pola yang umum dipakai antara lain versi di path, header, atau media type. Apa pun yang dipilih, pastikan konsisten dan terdokumentasi. Yang lebih penting dari lokasi versi adalah disiplin perubahan kontrak.
Error model yang konsisten
Error model perlu bisa membedakan:
- Authentication failure: identitas pemanggil tidak valid.
- Authorization failure: identitas valid, tetapi tidak punya entitlement/scope.
- Business state conflict: misalnya fitur belum aktif atau quota habis.
- Validation error: payload salah.
- Retryable failure: timeout downstream, lock timeout, atau kegagalan sementara.
Contoh respons error:
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": {
"code": "feature_not_entitled",
"message": "Account tidak memiliki akses ke invoice-sync.",
"request_id": "req_9f2b",
"details": {
"feature": "invoice-sync"
}
}
}Jangan menyembunyikan semua masalah di balik 400 atau 500. Klien perlu tahu apakah harus memperbaiki request, menunggu, refresh token, atau melakukan retry.
Auth yang mengikuti produk, bukan sekadar identitas
Model auth yang baik bukan hanya memverifikasi siapa pemanggilnya, tetapi juga kemampuan apa yang boleh dipakai oleh account atau tenant tersebut. Ada dua lapisan yang sering tercampur:
- Authentication: siapa pemanggilnya? API key, OAuth access token, mTLS, atau mekanisme lain.
- Authorization: boleh melakukan apa? Scope, role, permission, entitlement, atau policy.
Pisahkan scope teknis dan entitlement bisnis
Kesalahan umum adalah menganggap scope token sama dengan hak produk. Padahal keduanya berbeda:
- Scope token biasanya menyatakan apa yang diizinkan untuk klien aplikasi.
- Entitlement bisnis menyatakan apa yang dibeli atau diaktifkan untuk account.
Request baru boleh lolos jika keduanya valid. Contoh:
- Token punya scope
invoice_sync:write. - Account target punya entitlement
invoice-syncberstatusactive.
Jika salah satunya tidak ada, tolak request dengan jelas. Ini mencegah drift antara konfigurasi integrasi dan status langganan.
Menangani drift antara billing dan access control
Drift sering terjadi ketika billing mengubah status paket, tetapi cache permission, job provisioning, atau replica database belum sinkron. Untuk mengurangi masalah ini:
- Jadikan entitlement sebagai sumber kebenaran yang bisa di-query API.
- Simpan waktu efektif seperti
effective_atdanexpires_at. - Bedakan status
pending,active, dansuspendedagar klien tidak salah interpretasi. - Jika propagasi asinkron, nyatakan secara eksplisit bahwa perubahan akses mungkin belum instan.
- Sediakan event atau webhook perubahan entitlement agar klien bisa menyesuaikan state lokalnya.
Jangan langsung mengikat izin endpoint pada tabel invoice atau status subscription mentah. Buat lapisan entitlement yang lebih stabil dan dekat dengan kontrak API.
Idempotency untuk operasi tulis
Jika API memiliki operasi tulis, maka idempotency bukan tambahan opsional. Pada jaringan nyata, timeout dan retry pasti terjadi. Tanpa idempotency, klien yang sebenarnya benar bisa menyebabkan duplikasi order, invoice, job, atau charge.
Kapan wajib memakai idempotency key
Gunakan idempotency key pada operasi yang:
- menciptakan resource baru,
- memicu side effect eksternal,
- berpotensi dipanggil ulang setelah timeout atau koneksi putus.
Contoh request:
POST /accounts/acc_123/invoice-sync-jobs
Idempotency-Key: 01JY7JQ5M2YF3F7M8A1B2C3D4E
Content-Type: application/json
{
"range": {
"from": "2026-06-01",
"to": "2026-06-30"
},
"source": "erp"
}Contoh respons pertama:
HTTP/1.1 201 Created
Content-Type: application/json
{
"job_id": "job_789",
"status": "queued",
"created_at": "2026-06-30T10:01:00Z"
}Jika klien mengirim ulang request yang sama dengan key yang sama karena timeout, server sebaiknya mengembalikan hasil yang sama secara logis, misalnya resource yang sama atau replay dari respons sebelumnya.
Aturan implementasi idempotency key
- Scope-kan key minimal per account + endpoint + metode.
- Simpan fingerprint request agar key yang sama tidak dipakai untuk payload berbeda.
- Tentukan masa simpan key yang cukup untuk jendela retry realistis.
- Simpan hasil final atau referensi ke resource yang dibuat.
- Dokumentasikan status apa yang bisa direplay.
Jika request yang sama datang dengan Idempotency-Key sama tetapi payload berbeda, balas error konflik. Jangan diam-diam memproses request kedua.
Idempotency bukan pengganti transaksi
Idempotency mencegah duplikasi akibat retry, tetapi tidak otomatis menyelesaikan partial failure. Misalnya server berhasil menulis database, tetapi gagal mengirim event downstream. Untuk kasus seperti ini, Anda tetap membutuhkan pola seperti:
- transaksi database yang jelas,
- outbox pattern untuk event,
- status resource yang merepresentasikan proses asinkron,
- rekonsiliasi berkala untuk side effect eksternal.
Strategi retry yang aman
Retry aman terjadi ketika server dan klien memiliki kontrak yang konsisten tentang kapan retry boleh dilakukan, kapan tidak, dan bagaimana hasil duplikasi diperlakukan.
Klasifikasi error untuk retry
Secara praktis, bagi kegagalan menjadi tiga kelompok:
- Jangan retry tanpa perubahan request: validation error, unauthorized, forbidden karena entitlement tidak aktif.
- Boleh retry dengan backoff: timeout, temporary upstream failure, rate limit, transient lock contention.
- Retry perlu pemeriksaan state dulu: koneksi putus setelah request dikirim, respons tidak diterima, atau status akhir belum pasti.
Untuk kelompok ketiga, idempotency key dan endpoint GET status sangat membantu. Klien tidak perlu menebak apakah operasi sebelumnya berhasil.
Pola backoff yang disarankan
Gunakan exponential backoff dengan jitter agar klien tidak menyerbu server secara bersamaan setelah gangguan singkat. Retry tanpa jeda atau dengan interval tetap sering memperparah insiden.
Yang tak kalah penting: batasi jumlah percobaan dan expose request_id agar investigasi lebih mudah jika klien perlu menghubungi penyedia API.
Timeout dan operasi asinkron
Untuk operasi yang berpotensi lama, lebih aman memakai pola POST untuk membuat job lalu GET untuk memeriksa status, daripada menahan koneksi terlalu lama. Contoh:
POST /accounts/acc_123/invoice-sync-jobs
201 Created
Location: /accounts/acc_123/invoice-sync-jobs/job_789
{
"job_id": "job_789",
"status": "queued"
}Lalu:
GET /accounts/acc_123/invoice-sync-jobs/job_789
200 OK
{
"job_id": "job_789",
"status": "running",
"progress": 42
}Pola ini membuat timeout lebih mudah ditangani dan retry menjadi lebih aman.
Webhook vs polling untuk perubahan state
Ketika unit jual memiliki perubahan state penting, seperti entitlement aktif, job selesai, atau kuota habis, Anda perlu memilih bagaimana klien diberi tahu.
Kapan webhook lebih tepat
Pilih webhook jika:
- klien perlu notifikasi cepat,
- event relatif jarang tetapi penting,
- biaya polling akan tinggi atau tidak efisien.
Tetapi webhook membawa tanggung jawab tambahan:
- signature verification,
- retry delivery,
- deduplication di sisi penerima,
- urutan event yang tidak selalu terjamin.
Kapan polling lebih sederhana
Polling cocok jika:
- state berubah secara bertahap dan bisa dibaca kapan saja,
- klien tidak butuh latensi rendah,
- operasional webhook terlalu mahal atau terlalu kompleks.
Sering kali kombinasi terbaik adalah:
- webhook sebagai sinyal perubahan,
- GET resource sebagai sumber kebenaran final.
Artinya, webhook tidak perlu memuat seluruh state; cukup identitas resource dan jenis event. Klien lalu melakukan fetch untuk mendapatkan state terbaru.
Menangani duplicate delivery dan out-of-order event
Webhook harus dianggap at least once delivery. Artinya event bisa terkirim lebih dari sekali. Kadang event juga datang tidak berurutan. Karena itu:
- Sertakan event_id unik untuk deduplication.
- Sertakan occurred_at atau version/resource revision jika relevan.
- Jangan bergantung pada urutan pengiriman sebagai satu-satunya mekanisme konsistensi.
- Gunakan GET resource sebagai rekonsiliasi bila state lokal meragukan.
Contoh payload webhook ringkas:
{
"event_id": "evt_123",
"type": "entitlement.updated",
"occurred_at": "2026-06-30T10:03:00Z",
"account_id": "acc_123",
"resource": {
"type": "entitlement",
"id": "invoice-sync"
}
}Edge case yang paling sering merusak integrasi
1. Duplicate delivery
Terjadi pada webhook, queue, maupun retry klien. Solusi:
- idempotency key untuk write API,
- event_id untuk webhook,
- constraint unik di database untuk identitas bisnis yang memang harus tunggal.
2. Timeout dengan hasil tak pasti
Klien mengirim request, koneksi putus, tidak tahu apakah server sempat memproses. Solusi:
- wajibkan idempotency key pada operasi kritis,
- sediakan endpoint status resource atau job,
- hindari side effect besar dalam request sinkron panjang.
3. Partial failure
Database berhasil commit, tetapi publish event gagal. Atau provisioning eksternal sukses sebagian. Solusi:
- jadikan state perantara eksplisit, seperti
pendingatauprocessing, - pakai outbox/retry internal untuk side effect,
- sediakan workflow rekonsiliasi dan observability.
4. Drift antara billing dan access control
Pelanggan sudah bayar, tetapi akses belum aktif; atau akses masih aktif setelah downgrade. Solusi:
- pisahkan objek entitlement dari invoice/subscription mentah,
- catat effective time,
- sinkronkan lewat event yang dapat diaudit,
- buat endpoint read untuk memverifikasi entitlement aktual.
5. Resource operasional tanpa kaitan ke entitlement
Misalnya job lama masih bisa dijalankan setelah fitur dicabut. Anda perlu memutuskan policy secara eksplisit:
- apakah job yang sudah dibuat tetap boleh selesai,
- apakah create baru ditolak tetapi read historis tetap boleh,
- apakah cancel masih diizinkan setelah entitlement nonaktif.
Jangan biarkan jawaban ini tersembunyi di implementasi.
Checklist desain API per batas unit jual
- Identifikasi unit jual: fitur apa yang benar-benar dibeli atau diaktifkan terpisah?
- Buat resource entitlement: jangan langsung bergantung pada tabel billing mentah.
- Definisikan kontrak resource operasional: endpoint, state, transisi, dan error.
- Tentukan model auth: authentication dan authorization harus terpisah jelas.
- Selaraskan scope dan entitlement: keduanya harus lolos untuk operasi sensitif.
- Wajibkan idempotency key pada create/action yang punya side effect.
- Definisikan error model: mana yang retryable, mana yang tidak.
- Sediakan endpoint status untuk operasi asinkron atau hasil yang mungkin tak pasti.
- Pilih webhook, polling, atau kombinasi sesuai kebutuhan latensi dan kompleksitas.
- Rancang deduplication untuk webhook dan write request.
- Antisipasi partial failure dengan status transisi dan mekanisme rekonsiliasi.
- Versikan kontrak bila ada perubahan tidak kompatibel.
- Tambahkan request_id dan audit trail untuk debugging lintas sistem.
Anti-pattern yang sering membuat integrasi rapuh
- Satu endpoint generik untuk semua fitur, sehingga kontrak, auth, dan quota bercampur.
- Menyamakan role user dengan entitlement produk, padahal tenant bisa punya kombinasi produk berbeda.
- Tidak memakai idempotency key karena merasa klien “tidak akan retry”. Di produksi, retry tetap terjadi.
- Mengembalikan 200 sebelum side effect aman tanpa status job atau rekonsiliasi.
- Webhook dianggap exactly-once, lalu sistem rusak saat event terkirim dua kali.
- Semua error jadi 400 atau 500, sehingga klien tidak tahu tindakan yang benar.
- Billing sebagai sumber izin langsung tanpa lapisan entitlement, membuat propagasi status sulit dipahami.
- Perubahan kontrak diam-diam, misalnya mengganti makna field tanpa versi baru atau changelog jelas.
Contoh alur end-to-end yang lebih tahan gangguan
Aktivasi fitur
- Sistem billing menandai pembelian berhasil.
- Service provisioning membuat atau mengubah
entitlementmenjadipending. - Setelah propagasi selesai, status entitlement menjadi
active. - Webhook
entitlement.updateddikirim ke klien. - Klien melakukan
GET /entitlements/invoice-syncuntuk verifikasi state akhir.
Menjalankan operasi tulis
- Klien memanggil
POST /invoice-sync-jobsdenganIdempotency-Key. - Server memeriksa token scope dan entitlement account.
- Server membuat job dan menyimpan relasi idempotency key ke
job_id. - Jika respons timeout, klien retry dengan key yang sama.
- Server mengembalikan hasil yang sama tanpa membuat job kedua.
Menyelesaikan job
- Worker memproses job secara asinkron.
- Jika selesai, server mengubah status job menjadi
succeededataufailed. - Webhook
job.updateddikirim. - Jika webhook ganda atau terlambat, klien tetap bisa membaca state final lewat
GET job.
Penutup
Merancang API per batas unit jual berarti memaksa batas bisnis menjadi nyata di level teknis: resource yang jelas, kontrak yang stabil, auth yang mencerminkan entitlement, idempotency untuk operasi tulis, dan retry yang aman. Hasilnya bukan hanya API yang lebih rapi, tetapi integrasi yang lebih tahan terhadap kenyataan produksi: timeout, event ganda, partial failure, dan ketidaksinkronan antar sistem.
Jika sebuah kemampuan bisa dijual, dibatasi, diaudit, atau dimatikan secara terpisah, perlakukan itu sebagai kandidat kuat untuk batas kontrak API yang terpisah. Dari sana, keputusan tentang endpoint, auth, idempotency key, webhook, dan error model menjadi jauh lebih masuk akal.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!