Begitu tim kedua, aplikasi mobile, atau pelanggan berbayar mulai memanggil REST API Anda, Anda kehilangan hak untuk mengubahnya seenaknya. Setiap field yang Anda ganti nama adalah deploy production seseorang yang rusak. Versioning API adalah cara Anda tetap berevolusi, dan industri sudah mengerucut ke segelintir strategi: versi di URL, versi di header, versi yang dipatok tanggal per akun, atau opsi yang diam-diam radikal yaitu tidak pernah versioning dan hanya membuat perubahan aditif.
Saya pernah memakai semuanya di integrasi ERP dan backend publik, dan perbedaannya tidak akademis; mereka menentukan berapa banyak rasa sakit yang diserap client Anda dan codebase Anda sendiri selama bertahun-tahun. Jawaban di awal untuk kebanyakan tim kecil: desain supaya Anda jarang perlu merusak apa pun, pakai perubahan aditif secara agresif, dan simpan satu versi URL kasar sebagai pintu darurat. Ini keputusan lengkapnya, dengan Stripe dan GitHub sebagai implementasi rujukan yang layak ditiru.
Pertama, Sepakati Apa yang Benar-Benar Merusak Client
Diskusi versioning berputar-putar sampai semua orang sepakat definisi breaking change. Daftar praktisnya pendek. Hal-hal ini merusak integrasi:
- Menghapus atau mengganti nama field, endpoint, atau nilai enum yang sudah diterima atau dikirim client.
- Mengubah tipe atau format field, seperti ID numerik menjadi string, atau timestamp pindah dari epoch ke ISO 8601.
- Memperketat validasi sehingga request yang tadinya diterima mulai gagal, termasuk membuat parameter opsional menjadi wajib.
- Mengubah bentuk error atau status code yang dijadikan percabangan oleh client, yang sering dilupakan tim karena error terasa seperti detail implementasi. Padahal itu bagian dari kontrak.
Hampir semua yang lain bersifat aditif dan aman: endpoint baru, field opsional baru di respons, parameter request opsional baru, nilai enum baru bila client sudah diberi tahu untuk menangani nilai tak dikenal. Kalimat paling berharga di dokumentasi API Anda adalah yang memberi tahu konsumen bahwa field baru bisa muncul kapan saja dan harus diabaikan bila tidak dikenali. Satu aturan itu mengubah kelas besar calon breaking change menjadi rilis rutin.
Empat Strategi, Dibandingkan dengan Jujur
Setiap strategi menjawab satu pertanyaan: di mana client mendeklarasikan kontrak mana yang ia harapkan? Tabel ini merangkum dampak pilihan itu, dan snippet di bawah menunjukkan request yang sama di tiap skema:
| Strategi | Bentuknya | Kekuatan | Biaya |
|---|---|---|---|
| URL path | Prefix path v1, v2 di setiap route | Mustahil terlewat, gampang di-cache dan di-route, mudah menjalankan v1 dan v2 berdampingan sebagai deployment terpisah. | Kasar: menaikkan versi untuk satu endpoint menyeret seluruh API. Mendorong migrasi big-bang yang ditunda client bertahun-tahun. |
| Header kustom | Header versi di tiap request, gaya GitHub | URL tetap stabil dan berorientasi resource; versi ikut request, jadi granularitasnya fleksibel; default menjaga client lama tetap jalan. | Tak terlihat saat pengujian santai dan mudah terlupa di salah satu dari lima HTTP client Anda; perantara dan cache harus diajari vary terhadapnya. |
| Patokan tanggal per akun | Akun dipatok ke tanggal rilis, gaya Stripe | Client tidak pernah rusak tanpa bertindak: tiap akun bertahan di perilaku versi era pendaftarannya sampai memilih upgrade. | Paling mahal dioperasikan sejauh ini: setiap breaking change menjadi modul transformasi yang Anda rawat berpotensi bertahun-tahun. |
| Tanpa versioning (aditif saja) | Satu kontrak hidup, expand dan contract | Nol pipa versi; memaksa desain yang sengaja kompatibel; ideal untuk API internal di mana Anda mengendalikan semua konsumen. | Menuntut disiplin nyata dan tooling seperti telemetri deprecation; breaking change yang benar-benar tak terhindarkan tetap butuh rencana darurat. |
# The same request under each strategy:
# 1. URL path version
GET /v2/invoices/inv_123
# 2. Header version (GitHub style)
GET /invoices/inv_123
X-GitHub-Api-Version: 2022-11-28
# 3. Date-pinned account version (Stripe style)
GET /v1/invoices/inv_123
Stripe-Version: 2024-06-20 # override the account's pinned date
# 4. Media-type version (rare in practice)
GET /invoices/inv_123
Accept: application/vnd.myapp.v2+jsonCara Stripe Membuat Breaking Change Tanpa Merusak Siapa Pun
Model Stripe, yang dijelaskan di blog engineering mereka, adalah standar emas untuk API publik dengan integrasi ekor panjang. Versi bersifat rolling dan dinamai berdasarkan tanggal rilis. Tiap akun dipatok ke versi yang berlaku saat request pertamanya, dan bertahan di sana sampai ia upgrade secara eksplisit. Secara internal, setiap perubahan yang tidak kompatibel ke belakang dibungkus dalam modul version change yang tahu cara mengubah respons terkini menjadi bentuk yang lebih lama.
Request dari akun yang dipatok tiga tahun ke belakang melewati stack sekali di versi terkini, lalu berjalan mundur melalui tiap modul transformasi sampai responsnya cocok dengan harapan akun itu. Kejeniusannya ada di batasannya: logika versi hidup di modul deklaratif yang mendokumentasikan dirinya sendiri, bukan di percabangan yang tercecer di kode bisnis. Harganya adalah investasi engineering sungguhan, dan itu persis alasan Anda boleh mengagumi model ini tapi sebaiknya tidak mengadopsinya sebelum punya kewajiban kompatibilitas sekelas Stripe.
Versioning Kalender Berbasis Header ala GitHub
REST API GitHub mengambil sepupu yang lebih ringan dari ide yang sama. Client mengirim header versi dengan nilai tanggal, seperti versi 2022-11-28 yang disebut di dokumentasi mereka, dan request tanpa header itu default ke versi baseline tersebut sehingga integrasi lama tetap berjalan. Breaking change hanya dirilis di versi bertanggal baru, perubahan aditif mengalir ke semua versi yang didukung, dan GitHub berkomitmen mendukung tiap versi setidaknya 24 bulan dengan pemberitahuan deprecation sebelum dimatikan. Untuk pembuat API pelajarannya adalah kebijakannya, bukan header-nya: beri nama kontrak Anda, beri default yang masuk akal, dan terbitkan jendela dukungan supaya client bisa merencanakan alih-alih takut.
Mode kegagalan semua skema versioning itu sama: versi mudah dibuat dan secara politis menyakitkan untuk dipensiunkan. Tiap versi yang hidup melipatgandakan matriks tes Anda dan menggandakan permukaan tempat perbaikan keamanan harus mendarat. Sebelum memperkenalkan v2, tuliskan kebijakan sunset v1, tanggalnya, rencana komunikasinya, dan siapa yang bertugas mengejar sepuluh client terakhir. Kalau tidak ada yang mau memilikinya, Anda bukan sedang versioning, Anda sedang menimbun.
Opsi yang Diremehkan: Berevolusi Tanpa Versi
Untuk API internal, dan untuk kebanyakan sistem B2B di mana Anda mengenal semua konsumen, strategi terkuat adalah mendesain supaya versi jarang dibutuhkan. Perangkatnya adalah migrasi expand-and-contract, diterapkan ke kontrak alih-alih skema database:
- Expand: tambahkan field atau endpoint baru berdampingan dengan yang lama. Rilis client yang membaca bentuk baru sementara bentuk lama masih mengalir.
- Migrate: pindahkan konsumen satu per satu, dengan logging atau metrik atas pemakaian bentuk lama supaya Anda tahu persis siapa yang tersisa, diukur bukan ditebak.
- Contract: hapus field lama hanya saat telemetri menunjukkan nol pemakaian selama periode yang ditetapkan. Di pekerjaan ERP kami periode itu satu siklus tutup buku akhir bulan penuh, karena alur finance bulanan adalah tempat client basi bersembunyi.
- Dokumentasikan aturan kompatibilitas: client wajib mengabaikan field tak dikenal, tidak boleh bergantung pada urutan key, dan memperlakukan enum sebagai himpunan terbuka. Tegakkan di SDK Anda sendiri supaya aturannya teruji, bukan sekadar harapan.
Apa pun strategi yang Anda pilih, pasang lapisan observability deprecation lebih dulu: log peringatan terstruktur setiap kali field atau endpoint yang deprecated dipakai, ditandai per API key. Saat kami melakukannya untuk API integrasi ERP, dashboard menunjukkan tepat tiga konsumen masih di bentuk invoice legacy. Dua di antaranya tool internal kami sendiri. Menghapus kontrak lama berubah dari ketidakpastian menakutkan menjadi tugas 30 menit dengan dua pesan Slack.
Rekomendasi Saya untuk Tim Kecil
Kalau Anda menjalankan backend yang dikonsumsi segelintir client yang dikenal, ini playbook yang terbukti awet di proyek-proyek saya:
- Pasang satu versi kasar di URL sejak hari pertama, prefix v1, meski tidak pernah Anda naikkan. Biayanya nol sekarang dan memberi pintu darurat bersih, jauh lebih murah daripada memasang versioning di bawah tekanan nanti.
- Jadikan evolusi aditif-saja sebagai default. Perlakukan setiap breaking change sebagai kegagalan desain yang harus diperjuangkan, bukan alat rutin. Hampir setiap minggu, opsi aditifnya ada dan hanya sedikit kurang elegan.
- Terbitkan aturan field-tak-dikenal dan kebijakan deprecation eksplisit, sekalipun sederhana seperti pemberitahuan 90 hari. Prediktabilitas lebih penting bagi integrator daripada kemurahan hati.
- Baru raih skema header atau berbasis tanggal saat Anda punya pelanggan eksternal sungguhan yang siklus deploy-nya tidak bisa Anda lihat. Itulah titik di mana versioning kalender gaya GitHub dengan default menjadi sepadan dengan biaya operasionalnya.
Versioning Adalah Masalah Manajemen Janji
Kupas mekanikanya dan versioning API hanyalah manajemen janji: memutuskan perilaku mana yang Anda jamin, kepada siapa, dan untuk berapa lama. Versi URL membuat janjinya lantang dan kasar, header membuatnya presisi dan senyap, patokan tanggal Stripe membuatnya abadi dan mahal, dan evolusi aditif-saja menghindari membuat janji ulang sama sekali. Intinya: belanjakan disiplin Anda untuk tidak merusak kontrak, instrumenkan pemakaian yang deprecated supaya pensiun versi berbasis data, dan simpan satu tuas versi murah untuk hari Anda benar-benar membutuhkannya. Versi terbaik API Anda adalah yang tidak pernah perlu dipikirkan client.

