Saat Mengubah API Merusak Hal yang Tidak Disadari Pengguna Anda

Anda men-deploy versi baru dari backend service. Pipeline hijau. Log terlihat bersih. Lima menit kemudian, chat tim mulai ramai: aplikasi mobile menampilkan layar kosong, frontend web kehilangan data, dan service tim lain melempar error 500 di setiap request.

Apa yang terjadi? Anda mengubah nama field dari nama menjadi full_name di respons API. Kelihatannya tidak berbahaya. Tapi aplikasi mobile mem-parse field tersebut berdasarkan nama, frontend web menampilkannya langsung, dan service tim lain memetakannya ke kolom database mereka. Tidak ada yang tahu karena Anda tidak tahu mereka ada.

Inilah realitas memelihara API. Berbeda dengan background worker atau scheduled job yang hanya disentuh tim Anda, API memiliki konsumen yang mungkin tidak Anda ketahui. Aplikasi mobile yang dirilis tahun lalu. Integrasi partner yang diatur oleh departemen lain. Frontend yang tidak bisa diperbarui sampai review App Store berikutnya. Saat Anda mengubah API, Anda tidak hanya mengubah kode Anda. Anda mengubah kontrak yang diandalkan sistem lain.

Masalah yang Tidak Terlihat

Inti masalahnya adalah visibilitas. Di organisasi besar, satu endpoint API bisa dikonsumsi oleh puluhan tim. Bahkan jika API Anda hanya melayani satu aplikasi, aplikasi itu mungkin sudah ada di tangan pengguna yang tidak bisa langsung memperbarui. Anda tidak bisa mengkoordinasikan rilis serentak ke semua konsumen. Beberapa di antaranya bahkan tidak Anda ketahui keberadaannya.

Di sinilah backward compatibility menjadi kebutuhan praktis, bukan idealisme teoretis. Backward compatibility berarti versi baru API Anda masih bisa melayani request dengan format yang sama seperti versi lama. Jika endpoint /users Anda dulu mengembalikan field bernama nama, versi baru tidak bisa tiba-tiba menggantinya menjadi full_name tanpa masa transisi. Jika parameter page dulu menerima integer, versi baru tidak bisa tiba-tiba membutuhkan string. Perubahan ini memutuskan kontrak, dan kontrak yang rusak berarti konsumen yang rusak.

Apa yang Termasuk Breaking Change

Breaking change datang dalam berbagai bentuk, dan beberapa lebih jelas dari yang lain. Menghapus endpoint jelas merupakan breaking change. Mengganti nama field adalah breaking change. Mengubah tipe data adalah breaking change. Menambahkan field wajib ke request body adalah breaking change. Mengubah format respons error adalah breaking change.

Tapi ada yang lebih halus. Mengubah urutan field di respons JSON bisa merusak kode yang mengandalkan indeks array. Menambahkan header wajib baru bisa merusak klien yang tidak mengirimkannya. Bahkan mengubah HTTP status code untuk kondisi error tertentu bisa merusak logika yang memeriksa nilai status yang tepat.

Pertimbangkan contoh sederhana ini. API Anda dulu mengembalikan objek user seperti ini:

{
  "id": 42,
  "nama": "Ani Wijaya",
  "email": "ani@example.com"
}

Setelah "pembersihan" rename, ia mengembalikan:

{
  "id": 42,
  "full_name": "Ani Wijaya",
  "email": "ani@example.com"
}

Bagi Anda, ini nama yang lebih baik. Bagi aplikasi mobile yang mem-parse response["nama"], ini undefined. Bagi frontend yang menampilkan user.nama, ini kosong. Bagi service partner yang memetakan nama ke kolom database mereka, ini null yang diam. Field itu masih ada secara spiritual, tapi kontraknya rusak.

Bagian yang rumit adalah perubahan ini sering terlihat tidak berbahaya dari sisi server. Anda hanya membersihkan, menambah fitur, atau memperbaiki inkonsistensi penamaan. Tapi dari perspektif konsumen, perilakunya berubah dengan cara yang tidak mereka minta dan tidak bisa mereka adaptasi tanpa perubahan kode di pihak mereka.

Tangkap Breaking Change Sebelum Sampai Produksi

Pendekatan teraman adalah mendeteksi breaking change secara otomatis di pipeline CI Anda. Anda tidak ingin mengandalkan review manual atau berharap seseorang ingat untuk memeriksa. Ada alat yang dirancang khusus untuk ini.

Jika Anda menggunakan format spesifikasi API seperti OpenAPI, alat seperti OpenAPI Diff atau Spectral bisa membandingkan versi lama dan baru dari spesifikasi Anda. Mereka menandai persis apa yang berubah dan apakah itu breaking change. Jika framework Anda menghasilkan spesifikasi secara otomatis, seperti FastAPI, SpringDoc, atau ASP.NET Core dengan Swashbuckle, Anda bisa menjalankan perbandingan ini di setiap pull request.

Alur kerjanya seperti ini: ketika developer membuka pull request, pipeline CI membangun spesifikasi API baru, membandingkannya dengan spesifikasi dari branch utama, dan melaporkan setiap breaking change. Jika tidak ada, PR bisa lanjut normal. Jika ada breaking change, tim bisa mendiskusikan apakah perubahan itu benar-benar diperlukan atau bisa dilakukan secara bertahap.

Ini menggeser percakapan dari "saya harap ini tidak merusak apa pun" menjadi "ini persis apa yang berubah dan apakah itu memutuskan kontrak." Ini mengubah risiko yang tidak terlihat menjadi titik keputusan yang terlihat.

Saat Anda Tidak Bisa Menghindari Breaking Change

Tidak semua breaking change bisa dihindari. Terkadang kebutuhan bisnis memaksa desain ulang. Terkadang arsitektur perlu berevolusi. Terkadang desain lama memang salah dan perlu diganti.

Saat Anda harus membuat breaking change, API versioning adalah pendekatan standar. Idenya sederhana: Anda memelihara beberapa versi API secara bersamaan. Konsumen lama tetap menggunakan versi lama sementara konsumen baru mengadopsi versi baru. Anda memberi semua orang waktu untuk migrasi.

Ada beberapa cara untuk menerapkan versioning, masing-masing dengan trade-off.

URL versioning adalah pendekatan paling umum. Anda menempatkan versi di path: /v1/users dan /v2/users. Mudah dipahami, mudah di-routing, dan mudah diuji. Kekurangannya adalah URL menjadi lebih panjang, dan Anda memelihara jalur kode terpisah untuk setiap versi.

Header versioning menjaga URL tetap bersih tetapi mengharuskan konsumen mengatur header kustom, seperti Accept: application/vnd.myapi.v1+json. Ini lebih RESTful secara teori, tetapi menambah kompleksitas bagi konsumen yang perlu mengonfigurasi header dengan benar.

Query parameter versioning menggunakan sesuatu seperti ?version=1. Ini sederhana tetapi kurang umum karena bisa mengotori query string dan lebih sulit di-cache dengan benar.

Metode mana pun yang Anda pilih, versioning tidak gratis. Setiap versi yang Anda pelihara adalah kode yang perlu dijalankan, diuji, di-debug, dan akhirnya didepresiasi. Anda perlu kebijakan berapa lama versi lama akan didukung. Enam bulan adalah umum. Satu tahun adalah murah hati. Apa pun yang Anda pilih, komunikasikan dengan jelas dan jauh-jauh hari. Beri konsumen jendela migrasi, bukan penutupan mendadak.

Jalur yang Lebih Baik: Desain untuk Evolusi

Versioning adalah fallback, bukan strategi. Pendekatan yang lebih baik adalah mendesain API Anda agar bisa berevolusi tanpa perlu versi baru. Ini berarti bersikap sengaja tentang apa yang Anda ekspos dan bagaimana Anda mengeksposnya.

Jangan mengembalikan field yang tidak dibutuhkan konsumen. Setiap field yang Anda tambahkan ke respons adalah field yang mungkin diandalkan seseorang. Jika Anda tidak yakin apakah suatu field berguna, tinggalkan saja. Anda selalu bisa menambahkannya nanti, tetapi menghapusnya adalah breaking change.

Gunakan tipe data yang fleksibel jika memungkinkan. Terima baik string maupun angka untuk parameter yang bisa masuk akal sebagai keduanya. Kembalikan field tambahan dalam respons tanpa mengharuskan konsumen menggunakannya. Prinsipnya sederhana: bersikap liberal dalam apa yang Anda terima dan konservatif dalam apa yang Anda janjikan.

Saat Anda perlu menambah fungsionalitas baru, tambahkan field atau endpoint baru. Jangan memodifikasi yang sudah ada. Field baru dalam respons tidak merusak konsumen lama karena mereka mengabaikannya. Endpoint baru tidak merusak konsumen lama karena mereka tidak pernah memanggilnya. Selama Anda tidak menghapus atau mengganti nama yang sudah ada, Anda bisa terus berevolusi tanpa menaikkan versi.

Checklist Praktis untuk Perubahan API

Sebelum Anda me-merge pull request itu, jalankan pemeriksaan ini:

  • Apakah Anda menghapus atau mengganti nama field, parameter, atau endpoint yang sudah ada?
  • Apakah Anda mengubah tipe data dari field atau parameter yang sudah ada?
  • Apakah Anda menambahkan field wajib ke request body?
  • Apakah Anda mengubah format respons error?
  • Apakah Anda mengubah HTTP status code untuk skenario yang sudah ada?
  • Apakah Anda menjalankan diff otomatis terhadap spesifikasi API sebelumnya?

Jika Anda menjawab ya untuk salah satu dari lima pertanyaan pertama, Anda memiliki breaking change. Putuskan apakah akan menundanya, membuat versi, atau mengomunikasikannya dengan jelas ke semua konsumen yang diketahui.

Kesimpulan

API Anda adalah kontrak. Setiap konsumen yang bergantung padanya telah membuat kesepakatan implisit berdasarkan perilakunya hari ini. Mengubah kontrak itu tanpa peringatan bukanlah kesalahan teknis. Itu adalah kegagalan koordinasi yang mengikis kepercayaan antar tim.

Tujuannya bukan untuk tidak pernah membuat breaking change. Tujuannya adalah untuk tahu kapan Anda membuatnya, memutuskan secara sadar apakah itu sepadan, dan memberi konsumen Anda jalan ke depan. Otomatiskan deteksi, komunikasikan perubahan, dan desain untuk evolusi dari awal. Konsumen Anda tidak akan pernah berterima kasih karena Anda menjaga backward compatibility, tetapi mereka pasti akan menyadari saat Anda tidak melakukannya.