
Thank you for reading, keep learning and keep growing.
Reactions
No comments yet.
Ketika membangun sebuah aplikasi, terutama yang sudah digunakan oleh banyak pengguna atau terintegrasi dengan berbagai sistem, salah satu hal yang perlu diperhatikan adalah bagaimana kita mengelola perubahan pada Application Programming Interface (API).
Bayangkan kita sudah menyediakan API yang digunakan oleh berbagai aplikasi. Kemudian, suatu hari kita ingin menambahkan fitur baru atau mengubah struktur response yang sudah ada. Perubahan tersebut mungkin terlihat sederhana dari sisi backend, tetapi bagaimana dengan aplikasi yang sudah menggunakan API tersebut?
Apakah kita perlu membuat API versi baru, seperti v1, v2, dan seterusnya? Atau sebenarnya kita bisa mengembangkan API tanpa harus melakukan versioning?
Pertanyaan ini berkaitan erat dengan dua konsep penting dalam API development, yaitu API Versioning dan Backward Compatibility.
API Versioning adalah pendekatan untuk mengelola perubahan API dengan menyediakan versi yang berbeda dari suatu API. Biasanya, versioning digunakan ketika terdapat perubahan yang berpotensi membuat aplikasi klien yang menggunakan API sebelumnya tidak lagi berfungsi sebagaimana mestinya.
Sebagai contoh, sebuah aplikasi memiliki beberapa endpoint berikut:
GET /api/v1/users
GET /api/v1/products
POST /api/v1/products
GET /api/v1/products/{id}
PUT /api/v1/products/{id}
DELETE /api/v1/products/{id}GET /api/v1/users
GET /api/v1/products
POST /api/v1/products
GET /api/v1/products/{id}
PUT /api/v1/products/{id}
DELETE /api/v1/products/{id}Seluruh endpoint tersebut menggunakan versi yang sama, yaitu v1.
Kemudian, kita ingin mengembangkan fitur pencarian produk yang lebih kompleks, sehingga struktur response dari endpoint GET /api/v1/products perlu diubah.
Salah satu pendekatan yang bisa dilakukan adalah membuat versi baru:
GET /api/v1/products
GET /api/v2/productsGET /api/v1/products
GET /api/v2/productsDengan begitu, klien lama masih dapat menggunakan v1, sedangkan klien yang membutuhkan struktur response baru dapat menggunakan v2.
Namun, apakah setiap perubahan API memang harus diselesaikan dengan membuat versi baru?
Tidak selalu.
Hal yang perlu dipahami adalah bahwa kompleksitas suatu API tidak secara langsung menentukan apakah API tersebut membutuhkan versioning.
API yang memiliki logic kompleks tidak otomatis membutuhkan banyak versi. Sebaliknya, API yang sederhana sekalipun mungkin memerlukan versioning ketika terdapat perubahan kontrak yang tidak kompatibel dengan klien sebelumnya.
Sebelum memutuskan untuk membuat versi baru, kita perlu memahami konsep Backward Compatibility.
Backward Compatibility adalah kemampuan suatu sistem yang sudah diperbarui untuk tetap mendukung penggunaan atau integrasi yang sebelumnya telah berjalan.
Dalam konteks API, artinya perubahan yang kita lakukan tidak menyebabkan aplikasi klien yang menggunakan kontrak API sebelumnya menjadi rusak.
Misalnya, kita memiliki endpoint berikut:
GET /api/products/123GET /api/products/123Dengan response:
{
"id": 123,
"name": "Laptop",
"price": 8000000
}{
"id": 123,
"name": "Laptop",
"price": 8000000
}Kemudian, kita ingin menambahkan informasi stok produk.
Response baru menjadi:
{
"id": 123,
"name": "Laptop",
"price": 8000000,
"stock": 15
}{
"id": 123,
"name": "Laptop",
"price": 8000000,
"stock": 15
}Perubahan ini umumnya dapat dianggap backward compatible, selama klien lama mampu mengabaikan field tambahan yang tidak digunakannya.
Namun, bagaimana apabila kita mengubah struktur response menjadi seperti berikut?
{
"product": {
"id": 123,
"name": "Laptop"
},
"pricing": {
"amount": 8000000,
"currency": "IDR"
}
}{
"product": {
"id": 123,
"name": "Laptop"
},
"pricing": {
"amount": 8000000,
"currency": "IDR"
}
}Struktur response sekarang berbeda dari sebelumnya.
Klien yang sebelumnya mengambil harga melalui:
const price = response.price;const price = response.price;Tidak lagi dapat memperoleh data dengan cara yang sama karena field price sudah dipindahkan menjadi pricing.amount.
Inilah contoh perubahan yang berpotensi menjadi breaking change, yaitu perubahan yang menyebabkan klien yang bergantung pada kontrak lama tidak lagi berjalan dengan benar.
Dengan mempertahankan backward compatibility, kita dapat terus mengembangkan API tanpa harus meminta seluruh klien melakukan penyesuaian setiap kali ada perubahan.
Salah satu pendekatan yang menarik adalah mengutamakan backward compatibility daripada langsung membuat versi API baru.
Artinya, sejak awal API dirancang agar perubahan di masa depan sebisa mungkin tidak merusak integrasi yang sudah ada.
Misalnya, kita memiliki endpoint berikut:
GET /api/productsGET /api/productsEndpoint tersebut digunakan untuk mengambil daftar produk.
Kemudian, muncul kebutuhan baru untuk melakukan pencarian produk dengan filter yang lebih kompleks.
Daripada langsung membuat endpoint baru dengan versi berbeda:
GET /api/v2/productsGET /api/v2/productsKita bisa mempertimbangkan membuat endpoint yang secara eksplisit menunjukkan fungsionalitas baru:
GET /api/products/searchGET /api/products/searchDengan pendekatan ini, kedua endpoint memiliki tanggung jawab yang berbeda.
| Endpoint | Fungsi |
|---|---|
GET /api/products | Mengambil daftar produk |
GET /api/products/search | Melakukan pencarian produk dengan filter lebih kompleks |
Pendekatan ini membuat kita tidak perlu mengubah kontrak endpoint sebelumnya hanya untuk menambahkan kemampuan baru.
Contoh lain adalah ketika klien membutuhkan ringkasan informasi produk, bukan keseluruhan data produk.
Kita dapat membuat endpoint tambahan:
GET /api/products/summaryGET /api/products/summarySehingga kontrak yang sudah ada tetap dipertahankan, sementara kebutuhan baru dapat dilayani melalui endpoint berbeda.
Namun, perlu diperhatikan bahwa endpoint baru tidak selalu merupakan pilihan terbaik. Jika fitur baru masih memiliki semantik yang sama dan dapat didukung melalui query parameter opsional atau penambahan field yang kompatibel, kita juga dapat mempertahankan endpoint yang sama.
Intinya, jangan menjadikan API versioning sebagai solusi otomatis untuk setiap perubahan kebutuhan.
Salah satu tantangan dalam API versioning adalah konsistensi yang dirasakan oleh aplikasi klien.
Bayangkan sebuah backend menggunakan arsitektur microservices dengan beberapa service:
Kemudian, seiring waktu, masing-masing service melakukan perubahan API secara terpisah.
Akibatnya, klien mungkin harus menggunakan endpoint berikut:
GET /api/v1/users
GET /api/v2/products
GET /api/v1/sellers
GET /api/v3/products/searchGET /api/v1/users
GET /api/v2/products
GET /api/v1/sellers
GET /api/v3/products/searchSecara teknis, pendekatan tersebut memungkinkan untuk dilakukan.
Namun, dari perspektif developer yang menggunakan API, adanya beberapa versi berbeda dapat meningkatkan kompleksitas integrasi.
Developer perlu memahami endpoint mana yang masih menggunakan versi lama, endpoint mana yang sudah menggunakan versi baru, dan bagaimana perbedaan kontrak dari masing-masing versi tersebut.
Hal ini menjadi semakin kompleks apabila API digunakan oleh banyak aplikasi.
Yang perlu diperhatikan adalah bahwa klien seharusnya tidak perlu memahami kompleksitas arsitektur internal backend.
Apakah backend menggunakan microservices atau monolith, ditulis menggunakan Go, Python, Java, atau bahasa pemrograman lainnya, hal tersebut merupakan detail implementasi internal.
Yang dibutuhkan oleh klien adalah kontrak API yang jelas, konsisten, terdokumentasi, dan dapat diandalkan.
Karena itu, strategi versioning sebaiknya dirancang dari perspektif pengguna API, bukan hanya berdasarkan bagaimana service di backend dikembangkan atau di-deploy.
Ketika kita ingin melakukan perubahan terhadap suatu API, salah satu pertanyaan yang sering muncul adalah:
Mengapa tidak langsung mengubah logic atau response API yang sudah ada?
Jawabannya bergantung pada apakah perubahan tersebut memengaruhi kontrak API.
Mengubah implementasi internal, melakukan optimasi query database, meningkatkan performa, atau memperbaiki bug tanpa mengubah kontrak yang digunakan klien umumnya tidak menjadi masalah.
Namun, mengubah struktur response, menghapus field yang digunakan, mengganti tipe data, atau mengubah perilaku endpoint secara tidak kompatibel dapat menyebabkan masalah.
Misalnya, sebuah aplikasi mobile masih menggunakan response:
{
"id": 123,
"price": 8000000
}{
"id": 123,
"price": 8000000
}Kemudian, backend mengubahnya menjadi:
{
"id": 123,
"price": {
"amount": 8000000,
"currency": "IDR"
}
}{
"id": 123,
"price": {
"amount": 8000000,
"currency": "IDR"
}
}Aplikasi mobile yang belum diperbarui mungkin masih mengharapkan price berupa angka, bukan object.
Akibatnya, proses parsing atau pengolahan data dapat mengalami kegagalan.
Perubahan yang terlihat sederhana di backend ternyata dapat berdampak langsung pada aplikasi klien.
Oleh karena itu, sebelum melakukan perubahan, kita harus mempertimbangkan bagaimana kontrak tersebut sudah digunakan oleh sistem lain.
Risiko perubahan API juga bergantung pada siapa yang menggunakan API tersebut.
Pada aplikasi web yang sepenuhnya dikelola oleh tim yang sama, proses migrasi biasanya lebih mudah dikendalikan.
Ketika frontend dan backend diperbarui secara terkoordinasi, developer dapat menyesuaikan keduanya untuk menggunakan kontrak yang baru.
Meskipun begitu, kita tetap perlu memperhatikan pengguna yang masih membuka versi frontend lama, request yang sedang berjalan, dan proses deployment yang mungkin tidak terjadi secara bersamaan.
Tantangan yang lebih besar muncul pada aplikasi mobile.
Aplikasi mobile didistribusikan ke perangkat pengguna dan tidak semua pengguna langsung memperbarui aplikasi mereka ketika versi baru tersedia.
Akibatnya, backend mungkin sudah menggunakan kontrak terbaru, sementara sebagian pengguna masih menjalankan aplikasi mobile versi lama.
Jika endpoint lama langsung dihapus atau diubah secara tidak kompatibel, aplikasi tersebut berpotensi mengalami kegagalan.
Tantangan serupa juga terjadi pada integrasi dengan pihak ketiga.
Jika API digunakan oleh perusahaan lain, kita tidak dapat memaksakan seluruh klien melakukan perubahan pada waktu yang sama.
Setiap perusahaan mungkin memiliki prioritas, jadwal pengembangan, dan roadmap teknologi yang berbeda.
Karena itu, menjaga backward compatibility menjadi sangat penting, terutama untuk API yang sudah digunakan secara luas.
Bagaimana jika kita memang harus mengganti API lama karena perubahan kontrak tidak dapat dihindari?
Dalam kondisi tersebut, kita dapat menggunakan pendekatan API Deprecation dan API Sunset.
Keduanya memiliki tujuan yang berbeda.
API Deprecation adalah proses menyatakan bahwa sebuah API tidak lagi direkomendasikan untuk penggunaan baru, meskipun masih dapat diakses oleh klien lama.
Misalnya, kita memiliki endpoint:
GET /api/productsGET /api/productsKemudian, kita memperkenalkan endpoint baru:
GET /api/products/searchGET /api/products/searchKita dapat mengumumkan bahwa endpoint lama tidak akan lagi menerima pengembangan fitur pencarian baru.
Artinya, endpoint tersebut masih dapat digunakan, tetapi pengembangan fitur selanjutnya diarahkan ke endpoint yang baru.
Pendekatan ini memungkinkan klien lama tetap berjalan tanpa harus langsung melakukan migrasi.
Sementara itu, API Sunset adalah tahap ketika sebuah API benar-benar dihentikan atau tidak lagi tersedia untuk digunakan.
Jika API lama akan dihentikan, sebaiknya terdapat proses migrasi yang jelas dan waktu transisi yang memadai.
Grace period merupakan periode transisi ketika API lama dan API baru masih tersedia secara bersamaan.
Sebagai contoh, kita memiliki:
GET /api/v1/products
GET /api/v2/productsGET /api/v1/products
GET /api/v2/productsVersi v2 diperkenalkan karena terdapat perubahan besar yang tidak dapat dipertahankan kompatibilitasnya dengan v1.
Namun, kita tidak langsung menghapus v1.
Sebaliknya, kita memberikan waktu kepada klien untuk menyesuaikan integrasi mereka.
Sebagai ilustrasi, kita dapat memberikan grace period selama 6–12 bulan, bergantung pada karakteristik pengguna, kontrak layanan, dan kompleksitas migrasi.
Mengapa diperlukan waktu yang cukup panjang?
Karena kita tidak mengetahui secara pasti jadwal pengembangan setiap klien yang menggunakan API tersebut.
Bisa saja sebuah perusahaan sudah memiliki roadmap pengembangan untuk enam bulan ke depan dan tidak dapat langsung memprioritaskan migrasi API.
Dengan menyediakan grace period, kita memberikan kesempatan kepada klien untuk melakukan perubahan secara bertahap.
Alur migrasinya dapat digambarkan seperti berikut:
API v1 Aktif
|
v
API v2 Diperkenalkan
|
v
API v1 Dinyatakan Deprecated
|
v
Grace Period
(v1 dan v2 berjalan bersamaan)
|
v
Monitoring Penggunaan API v1
|
v
Migrasi Klien ke API v2
|
v
Validasi Kesiapan Penghentian
|
v
API v1 SunsetAPI v1 Aktif
|
v
API v2 Diperkenalkan
|
v
API v1 Dinyatakan Deprecated
|
v
Grace Period
(v1 dan v2 berjalan bersamaan)
|
v
Monitoring Penggunaan API v1
|
v
Migrasi Klien ke API v2
|
v
Validasi Kesiapan Penghentian
|
v
API v1 SunsetGrace period tidak memiliki durasi universal. Periode 6–12 bulan hanyalah contoh kebijakan yang dapat dipertimbangkan, bukan standar yang harus diikuti setiap perusahaan.
Yang lebih penting adalah memastikan klien memperoleh informasi yang jelas mengenai jadwal penghentian, perubahan kontrak, dan langkah-langkah migrasinya.
Salah satu hal yang tidak boleh diabaikan selama migrasi adalah monitoring.
Kita perlu mengetahui apakah endpoint lama masih digunakan dan siapa saja klien yang menggunakannya.
Beberapa informasi yang dapat dimonitor antara lain jumlah request pada API lama, tren penggunaan berdasarkan waktu, identitas klien jika tersedia, dan versi aplikasi yang masih melakukan request.
Contohnya, selama proses migrasi dari v1 ke v2, kita dapat mengamati penurunan traffic pada v1.
Jika masih terdapat banyak request ke API lama, berarti masih ada klien yang membutuhkan layanan tersebut.
Menghapus API dalam kondisi ini dapat mengganggu sistem yang masih menggunakannya.
Sebaliknya, apabila penggunaan sudah menurun secara signifikan dan seluruh klien yang relevan telah menyelesaikan migrasi, kita dapat mulai mengevaluasi proses sunset.
Namun, jumlah request yang rendah saja belum cukup untuk memastikan sebuah API aman dihapus. Kita juga perlu mempertimbangkan klien yang jarang mengakses API, integrasi batch, serta kewajiban dukungan yang telah disepakati.
Dengan begitu, proses penghentian API dapat dilakukan berdasarkan data, bukan sekadar asumsi.
Dalam beberapa kasus, perubahan API bukan hanya terkait dengan request dan response, tetapi juga menyangkut arsitektur atau teknologi yang digunakan.
Misalnya, kita memiliki sebuah Product Service lama yang menggunakan teknologi yang sudah sulit dikembangkan.
Kemudian, tim memutuskan membangun Product Service baru menggunakan teknologi yang lebih modern.
Secara internal, sekarang terdapat dua aplikasi:
Client
|
v
API Gateway
/ \
v v
Product Service Product Service
Lama Baru
(v1) (v2)Client
|
v
API Gateway
/ \
v v
Product Service Product Service
Lama Baru
(v1) (v2)Dalam kondisi tersebut, mempertahankan dua service selamanya mungkin tidak efisien karena terdapat biaya operasional dan maintenance tambahan.
Namun, mematikan service lama secara langsung juga dapat merusak integrasi klien yang belum berpindah.
Salah satu pendekatan yang dapat diterapkan adalah menjalankan kedua service secara paralel selama proses migrasi.
Tim kemudian dapat mengarahkan klien secara bertahap ke API baru, memonitor penggunaan API lama, serta memberikan batas waktu dukungan yang telah dikomunikasikan.
Setelah seluruh persyaratan migrasi terpenuhi, service lama dapat dihentikan.
Pendekatan ini memungkinkan perubahan besar pada backend tanpa memaksa seluruh klien bermigrasi pada saat yang sama.
Perlu diingat bahwa versi API dan versi implementasi service tidak selalu harus sama. Sebuah backend dapat diganti sepenuhnya tanpa mengubah versi API publik, selama kontraknya tetap kompatibel.
Selain menggunakan penomoran seperti v1, v2, dan v3, terdapat pendekatan versioning yang menggunakan tanggal.
Contohnya:
/api/2025-01-01/products
/api/2026-01-01/products/api/2025-01-01/products
/api/2026-01-01/productsPendekatan ini dikenal sebagai date-based API versioning.
Pada beberapa sistem, tanggal digunakan untuk mengidentifikasi versi kontrak API tertentu.
Meskipun valid sebagai strategi versioning, pendekatan ini dapat membingungkan ketika dokumentasi dan aturan kompatibilitasnya tidak jelas.
Klien mungkin perlu memahami apa saja perubahan yang terjadi pada setiap versi tanggal dan versi mana yang seharusnya digunakan.
Namun, date-based versioning tidak selalu berarti klien harus mengetahui kapan backend di-deploy. Dalam implementasi yang baik, tanggal tersebut mewakili versi kontrak API yang terdokumentasi, bukan sekadar tanggal deployment aplikasi.
Dengan demikian, baik numeric versioning maupun date-based versioning sebenarnya dapat digunakan dengan baik selama strategi pengelolaannya konsisten dan mudah dipahami.
Setelah memahami berbagai pendekatan tersebut, kita dapat menyimpulkan bahwa API versioning bukan sesuatu yang harus selalu dihindari, tetapi juga bukan sesuatu yang harus diterapkan hanya karena API sudah kompleks.
Hal yang perlu dipertimbangkan adalah dampak perubahan terhadap klien.
| Kondisi | Pendekatan yang dapat dipertimbangkan |
|---|---|
| Menambahkan field response yang aman untuk klien lama | Pertahankan API yang sama |
| Menambahkan parameter opsional | Pertahankan API yang sama |
| Menambahkan fitur dengan fungsi berbeda | Tambahkan endpoint baru bila diperlukan |
| Mengubah implementasi internal tanpa mengubah kontrak | Pertahankan API yang sama |
| Mengubah struktur response secara tidak kompatibel | Pertimbangkan API versioning |
| Menghapus field yang masih digunakan klien | Pertimbangkan API versioning atau strategi migrasi |
| Mengganti backend tetapi kontrak tetap sama | Tidak harus membuat versi baru |
| Menghentikan API lama | Terapkan deprecation, grace period, monitoring, dan sunset |
Pada akhirnya, tidak ada satu pendekatan yang selalu paling tepat untuk semua aplikasi.
Sebagian tim memilih menggunakan versioning sejak awal untuk menyediakan mekanisme perubahan kontrak yang eksplisit. Sebagian lainnya lebih memilih mempertahankan satu kontrak API yang kompatibel selama mungkin dan memperkenalkan endpoint baru hanya ketika diperlukan.
Keduanya dapat menjadi strategi yang valid, bergantung pada kebutuhan bisnis, karakteristik klien, serta kebijakan pengembangan API.
API Versioning dan Backward Compatibility merupakan dua konsep penting ketika kita membangun API yang digunakan oleh banyak aplikasi.
Dari pembahasan ini, salah satu insight yang menarik adalah bahwa mengembangkan API tidak selalu berarti kita harus membuat versi baru.
Kita dapat merancang API dengan mengutamakan backward compatibility, sehingga penambahan fitur dan perubahan implementasi tidak mengganggu klien yang sudah menggunakan kontrak sebelumnya.
Jika terdapat kebutuhan yang berbeda, kita dapat mempertimbangkan penambahan endpoint baru tanpa harus mengubah endpoint yang sudah ada.
Namun, ketika breaking change memang tidak dapat dihindari, API versioning tetap menjadi solusi yang relevan. Dalam kondisi tersebut, proses migrasi perlu direncanakan melalui deprecation, grace period, monitoring, hingga sunset API.
Hal terpenting adalah memahami bahwa API merupakan kontrak antara penyedia layanan dan klien yang menggunakannya.
Perubahan yang terlihat kecil dari sisi backend dapat berdampak besar terhadap sistem lain yang sudah terintegrasi.
Karena itu, ketika mengembangkan API, kita tidak hanya perlu memikirkan bagaimana fitur baru dapat diimplementasikan, tetapi juga bagaimana perubahan tersebut tetap dapat diterima oleh sistem yang sudah berjalan.
Good API design is not just about adding new capabilities, but also about preserving existing integrations.