Strategi Versioning API: URL, Header, atau Tanpa Versi Sama Sekali

Foto oleh Beyzaa Yurtkuran

Foto oleh Beyzaa Yurtkuran
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.
Diskusi versioning berputar-putar sampai semua orang sepakat definisi breaking change. Daftar praktisnya pendek. Hal-hal ini merusak integrasi:
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.
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+jsonModel 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.
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.
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:
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.
Kalau Anda menjalankan backend yang dikonsumsi segelintir client yang dikenal, ini playbook yang terbukti awet di proyek-proyek saya:
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.