Versioning kontrak API adalah cara paling praktis untuk mencegah integrasi klien rusak saat skema berubah. Masalah utamanya bukan sekadar menambah field baru, tetapi memastikan perubahan format, enum, nullability, dan struktur payload tetap bisa diproses oleh klien lama selama masa transisi.
Untuk API REST dan webhook, pendekatan yang aman biasanya menggabungkan beberapa hal: definisi kontrak yang eksplisit, klasifikasi perubahan menjadi additive atau breaking, jendela kompatibilitas yang jelas, kebijakan deprecasi, validasi skema di CI, serta rollout bertahap. Konteks seperti rilis jj v0.43.0 mengingatkan bahwa perubahan yang dikelola dengan baik lebih penting daripada perubahan yang cepat: saat antarmuka berubah, konsumen butuh jalur migrasi yang stabil, bukan kejutan.
Referensi konteks: jj v0.43.0 release notes. Artikel ini tidak membahas rilis tersebut sebagai berita, melainkan menggunakan semangat perubahan terkelola sebagai inspirasi desain kontrak API.
Mengapa kontrak API mudah merusak integrasi
Integrasi rusak sering terjadi bukan karena endpoint hilang, tetapi karena asumsi klien terhadap payload berubah tanpa koordinasi. Beberapa contoh umum:
- Field diganti nama dari
full_namemenjadiname. - Field yang dulu selalu ada sekarang kadang
null. - Nilai enum baru ditambahkan, tetapi parser klien hanya mengenal daftar lama.
- Tipe data berubah, misalnya ID numerik menjadi string.
- Struktur nested berubah, misalnya
customer.emaildipindah kecontact.email.
Semua perubahan di atas tampak kecil dari sisi server, tetapi bisa memicu kegagalan deserialisasi, bug logika, atau data hilang di sisi konsumen. Karena itu, kontrak API harus diperlakukan seperti antarmuka publik yang memiliki siklus hidup.
Klasifikasi perubahan: additive vs breaking change
Langkah pertama dalam versioning kontrak API adalah membedakan perubahan yang aman dari yang berisiko merusak klien.
Perubahan additive
Perubahan additive menambah informasi tanpa mengubah arti data lama. Contohnya:
- Menambah field baru opsional.
- Menambah endpoint baru tanpa mengubah endpoint lama.
- Menambah header respons yang tidak wajib dipakai klien.
Contoh payload sebelum dan sesudah perubahan additive:
{
"id": "ord_123",
"status": "paid",
"amount": 150000
}{
"id": "ord_123",
"status": "paid",
"amount": 150000,
"currency": "IDR"
}Jika klien mengabaikan field yang tidak dikenal, perubahan ini biasanya aman.
Perubahan breaking
Perubahan breaking mengubah interpretasi, struktur, atau kewajiban pemrosesan payload. Contohnya:
- Menghapus field.
- Mengganti nama field.
- Mengubah tipe data.
- Mengubah nilai enum yang mungkin muncul.
- Mengubah field opsional menjadi wajib, atau sebaliknya dengan dampak semantik.
Contoh breaking change:
{
"id": "ord_123",
"status": "paid",
"amount": 150000
}{
"id": "ord_123",
"payment_status": "settled",
"amount": "150000"
}Di sini ada tiga masalah sekaligus: status diganti nama, nilai enum berubah dari paid menjadi settled, dan amount berubah dari number ke string. Bagi klien yang mengandalkan kontrak lama, ini jelas merusak.
Prinsip desain kontrak yang tahan perubahan
1. Perlakukan rename sebagai breaking change
Rename field sering dianggap remeh karena datanya “sama”. Padahal parser, mapper, dan query klien biasanya bergantung pada nama field secara langsung. Jika ingin migrasi aman, kirim kedua field untuk sementara, dokumentasikan prioritasnya, lalu hapus field lama setelah masa deprecasi.
Contoh transisi yang lebih aman:
{
"id": "ord_123",
"status": "paid",
"payment_status": "paid"
}Setelah semua klien bermigrasi, baru versi baru dapat menghapus status.
2. Hati-hati dengan enum
Menambah nilai enum baru sering dianggap additive, tetapi di banyak klien hal ini bisa menjadi breaking secara praktis. Misalnya klien memiliki switch yang hanya mengenal pending, paid, dan failed. Ketika server mulai mengirim refunded, logika klien bisa gagal.
Karena itu:
- Dokumentasikan bahwa klien harus menangani nilai enum tak dikenal.
- Sediakan nilai fallback seperti
unknowndi domain model klien. - Hindari mengganti arti enum lama tanpa versi baru.
3. Nullability adalah bagian dari kontrak
Perubahan dari non-null menjadi nullable bisa merusak kode yang tidak menyiapkan pengecekan null. Sebaliknya, mengubah field nullable menjadi wajib juga berisiko bagi klien yang belum mengirimkannya.
Anggap aturan berikut sebagai aman:
- Response: jangan ubah field yang tadinya selalu ada menjadi kadang null tanpa versi atau masa transisi.
- Request: jangan ubah field opsional menjadi wajib di versi yang sama.
4. Gunakan semantik yang stabil, bukan hanya bentuk JSON yang stabil
Dua payload bisa tampak mirip tetapi bermakna berbeda. Misalnya field amount dulu berarti total kotor, lalu diam-diam berubah menjadi total bersih. Ini breaking meskipun nama dan tipe field tidak berubah. Versioning harus mempertimbangkan makna bisnis, bukan hanya struktur.
Memilih strategi versioning: path, header, atau media type
Tidak ada satu strategi yang selalu paling benar. Yang penting adalah konsistensi, kemudahan observabilitas, dan kemampuan menjalankan beberapa versi secara paralel.
Versioning di path
GET /api/v1/orders/ord_123
GET /api/v2/orders/ord_123Kelebihan:
- Mudah dipahami manusia.
- Mudah dirouting di gateway, reverse proxy, dan observability.
- Mudah menjalankan dua implementasi paralel.
Kekurangan:
- URL berubah meski resource secara konsep sama.
- Bisa mendorong duplikasi endpoint jika semua perubahan kecil langsung jadi versi baru.
Versioning lewat header
GET /api/orders/ord_123
API-Version: 2024-10-01Kelebihan:
- URL tetap bersih dan stabil.
- Cocok bila versi dianggap bagian dari negosiasi kontrak, bukan identitas resource.
Kekurangan:
- Lebih sulit diuji manual jika tooling tim belum terbiasa.
- Sering kurang terlihat di log atau dashboard jika header tidak dicatat dengan baik.
Media type versioning
Accept: application/vnd.example.orders+json;version=2Kelebihan:
- Secara konsep rapi untuk content negotiation.
- Berguna jika representasi resource sangat bervariasi.
Kekurangan:
- Lebih kompleks untuk banyak tim.
- Sering berlebihan untuk API internal atau integrasi sederhana.
Kapan memilih yang mana?
Untuk banyak tim backend, path versioning paling praktis untuk perubahan breaking besar pada REST API. Untuk webhook, lebih umum memakai version per endpoint subscription atau header metadata versi event, karena pengirim yang mengontrol payload perlu tahu format apa yang diharapkan konsumen.
Yang lebih penting dari pilihan mekanisme adalah aturan ini:
- Perubahan additive tidak harus selalu membuat versi baru.
- Perubahan breaking harus punya versi baru atau compatibility layer yang jelas.
- Satu klien harus bisa tetap di versi lama selama jendela kompatibilitas.
Webhook perlu disiplin lebih ketat daripada REST
Pada REST, klien aktif meminta data. Pada webhook, server mendorong data ke konsumen. Artinya, jika payload webhook berubah mendadak, konsumen tidak punya kontrol penuh atas timing perubahan. Karena itu, webhook sebaiknya memiliki kontrak yang lebih konservatif.
Gunakan versi pada subscription atau endpoint webhook
Contoh pendekatan:
- Konsumen mendaftarkan endpoint webhook dengan versi tertentu, misalnya
2024-10-01. - Server mengirim payload sesuai versi itu sampai konsumen memperbarui subscription.
Contoh metadata event:
{
"event_id": "evt_9f3a",
"event_type": "order.paid",
"api_version": "2024-10-01",
"occurred_at": "2024-10-12T09:15:00Z",
"data": {
"id": "ord_123",
"status": "paid",
"amount": 150000
}
}Dengan begitu, tim integrasi dapat memproses event berdasarkan versi kontrak yang disepakati, bukan menebak-nebak bentuk payload saat runtime.
Idempotency penting untuk retry aman
Webhook hampir pasti akan di-retry jika endpoint penerima lambat, timeout, atau mengembalikan status non-2xx. Karena itu, payload perlu memiliki identifier unik yang stabil, dan penerima perlu memprosesnya secara idempoten.
Praktik yang umum:
- Sertakan
event_idunik pada setiap event. - Penerima menyimpan
event_idyang sudah diproses. - Jika event yang sama datang lagi, balas sukses tanpa memproses ulang efek samping.
Contoh pseudocode penerima webhook:
if event_id sudah ada di storage:
return 200
validasi signature
validasi schema sesuai api_version
jalankan proses bisnis
simpan event_id sebagai processed
return 200Ini mencegah order diproses dua kali, email dikirim ulang, atau mutasi data ganda saat retry terjadi.
Compatibility window dan deprecation policy
Versioning yang baik bukan hanya soal menambahkan /v2, tetapi juga memberi waktu migrasi yang realistis. Tanpa compatibility window, versi baru hanya memindahkan masalah dari desain ke operasional.
Tentukan jendela kompatibilitas
Beberapa prinsip praktis:
- Tetapkan berapa lama versi lama tetap didukung setelah versi baru tersedia.
- Pastikan klien bisa menguji versi baru sebelum cut-off.
- Untuk webhook, izinkan konsumen memilih kapan pindah versi selama masa dukungan.
Durasi pastinya tergantung jenis pelanggan, SLA, dan jumlah integrasi. Tidak perlu menetapkan angka sembarangan; yang penting adalah jelas, terdokumentasi, dan konsisten.
Buat kebijakan deprecasi yang operasional
Deprecation policy sebaiknya menjawab empat hal:
- Apa yang berubah.
- Siapa yang terdampak.
- Kapan perilaku lama berhenti didukung.
- Langkah migrasi yang diperlukan.
Contoh informasi yang perlu diumumkan:
- Field
statusakan digantikan olehpayment_status. - Mulai tanggal tertentu, field lama ditandai deprecated tetapi masih dikirim.
- Pada versi baru, hanya field baru yang tersedia.
- Contoh payload, daftar perubahan enum, dan aturan nullability baru.
Kesalahan umum adalah mengumumkan deprecasi di changelog, tetapi tidak menambahkan sinyal di dokumentasi, dashboard integrasi, log, atau komunikasi ke pemilik integrasi.
Schema validation dan contract testing
Dokumentasi saja tidak cukup. Untuk mencegah perubahan breaking lolos ke produksi, kontrak harus diuji otomatis.
Validasi skema untuk request dan response
Gunakan skema yang eksplisit, misalnya OpenAPI atau JSON Schema, lalu validasi:
- Request yang masuk ke server.
- Response yang keluar dari server.
- Payload webhook yang diproduksi.
Tujuannya bukan membuat sistem kaku, melainkan memastikan perubahan yang tidak disengaja segera terdeteksi. Validasi sangat berguna untuk kasus seperti:
- Field wajib tiba-tiba hilang.
- Tipe data berubah tanpa sadar.
- Enum baru muncul tanpa pembaruan kontrak.
Gunakan contract diff di CI
Saat ada pull request yang mengubah skema, jalankan pemeriksaan yang membandingkan kontrak lama dan baru. Tinjau apakah perubahan bersifat additive atau breaking. Jika breaking, CI sebaiknya memaksa pengembang untuk:
- Menandai versi baru, atau
- Menambahkan compatibility layer, atau
- Mendapatkan persetujuan eksplisit dengan rencana rollout.
Meski tool yang dipakai bisa berbeda-beda, prinsipnya sama: jangan mengandalkan review manual semata.
Consumer-driven contract bila integrasi banyak
Jika ada banyak konsumen dengan kebutuhan berbeda, pertimbangkan pendekatan consumer-driven contract. Konsumen mendefinisikan ekspektasi minimumnya, lalu provider memverifikasi bahwa perubahan server tidak melanggar ekspektasi itu. Pendekatan ini berguna terutama untuk API internal antarlayanan.
Strategi rollout bertahap yang aman
Perubahan kontrak yang benar secara desain tetap bisa gagal jika rollout-nya kasar. Jalur yang lebih aman biasanya bertahap.
Urutan rollout yang disarankan
- Tambahkan field atau versi baru tanpa menghapus yang lama.
- Perbarui dokumentasi dan contoh payload.
- Aktifkan validasi dan logging untuk mendeteksi pemakaian versi lama.
- Uji dengan klien internal atau sandbox.
- Izinkan opt-in ke versi baru.
- Pantau error rate, parsing failure, dan retry webhook.
- Setelah semua konsumen utama bermigrasi, baru nonaktifkan kontrak lama.
Observabilitas yang perlu disiapkan
- Log versi kontrak yang dipakai per request atau event.
- Hitung distribusi trafik per versi.
- Lacak validation error per field.
- Pisahkan metrik retry webhook dan duplicate event handling.
Tanpa observabilitas, tim biasanya tidak tahu klien mana yang masih bergantung pada kontrak lama sampai insiden terjadi.
Contoh evolusi payload yang aman
Skenario: mengganti nama field dan menambah detail baru
Payload lama:
{
"id": "ord_123",
"status": "paid",
"customer": {
"full_name": "Budi Santoso"
}
}Target desain baru:
{
"id": "ord_123",
"payment_status": "paid",
"customer": {
"name": "Budi Santoso",
"email": "[email protected]"
}
}Jika langsung diganti, ini breaking. Jalur migrasi yang lebih aman pada masa transisi:
{
"id": "ord_123",
"status": "paid",
"payment_status": "paid",
"customer": {
"full_name": "Budi Santoso",
"name": "Budi Santoso",
"email": "[email protected]"
}
}Lalu dokumentasikan:
statusdeprecated, gunakanpayment_status.customer.full_namedeprecated, gunakancustomer.name.- Versi berikutnya akan menghapus field deprecated.
Strategi ini bekerja karena klien lama tetap berjalan, sementara klien baru bisa segera pindah tanpa menunggu cut-over besar.
Checklist review perubahan kontrak API
Sebelum merilis perubahan pada REST API atau webhook, gunakan checklist ini:
- Apakah ada field yang dihapus, diganti nama, dipindah, atau diubah tipenya?
- Apakah ada perubahan enum, termasuk penambahan nilai baru?
- Apakah nullability berubah?
- Apakah arti bisnis suatu field berubah meski nama dan tipe tetap?
- Apakah request lama masih valid?
- Apakah response lama masih bisa diparse klien lama?
- Apakah webhook retry tetap aman karena idempotency sudah diterapkan?
- Apakah kontrak tervalidasi otomatis di CI?
- Apakah ada compatibility window dan tanggal deprecasi yang jelas?
- Apakah dokumentasi, contoh payload, dan log versi sudah diperbarui?
Kesalahan umum yang sering menimbulkan insiden
"Hanya rename field"
Ini hampir selalu breaking. Jangan anggap aman hanya karena datanya sama.
Menambah enum tanpa fallback
Jika klien tidak siap menerima nilai tak dikenal, penambahan enum baru bisa mematahkan alur bisnis.
Mengubah nullability diam-diam
Field yang tiba-tiba null sering lolos tes unit tetapi gagal di produksi saat data nyata bervariasi.
Mengandalkan dokumentasi tanpa validasi otomatis
Kontrak yang tidak diuji akan menyimpang seiring waktu. Dokumentasi mudah ketinggalan dibanding implementasi.
Menghapus versi lama terlalu cepat
Tim server sering merasa migrasi sudah selesai, padahal ada klien batch, integrasi partner, atau worker lama yang masih aktif.
Penutup
Versioning kontrak API yang baik bukan berarti membuat versi baru untuk setiap perubahan, melainkan membedakan perubahan additive dan breaking dengan disiplin, lalu menyediakan jalur migrasi yang bisa dijalankan klien tanpa insiden. Untuk REST dan webhook, kombinasi yang paling berguna biasanya adalah kontrak eksplisit, compatibility window, deprecation policy, validasi skema, idempotency untuk retry aman, dan rollout bertahap.
Jika harus memilih satu prinsip inti, pilih ini: jangan pernah memaksa semua klien berubah pada saat yang sama. Desain kontrak yang baik memberi ruang transisi, observabilitas, dan kepastian perilaku saat skema berkembang.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!