Aplikasi biasa bisa diperbarui secepat developer mengganti kodenya; API tidak. Begitu ada klien di luar sana — aplikasi mobile yang sudah terinstal, integrasi milik tim lain, kode di mesin pelanggan — setiap perubahan bentuk response berisiko merusak program yang tidak bisa kita update seketika. Chapter penutup Section ini membahas disiplin mengubah API tanpa menghancurkan klien: membedakan perubahan yang aman dari yang merusak, memilih strategi versioning, lalu memensiunkan versi lama secara bertahap lewat siklus deprecation yang sopan.
Perubahan Aman vs Merusak #
Backward compatibility berarti klien lama tetap berfungsi setelah API berubah. Kuncinya ada di tabel berikut:
| Perubahan | Kategori | Alasan |
|---|---|---|
| Menambah field opsional pada response | aman (additive) | klien lama mengabaikan field yang tidak dikenalnya |
| Menambah endpoint baru | aman (additive) | tidak mengubah perilaku yang sudah ada |
| Menghapus field dari response | merusak | klien lama membaca field tersebut |
Mengganti nama field (judul → nama) | merusak | setara menghapus lalu menambah |
| Menambah field wajib pada request | merusak | request lama tiba-tiba ditolak 422 |
Mengubah tipe field (int → str) | merusak | parser klien gagal membaca nilai |
Prinsip praktisnya: kumpulkan perubahan additive sesering apa pun — rilis berkala tanpa versi baru. Perubahan merusak baru boleh masuk lewat versi baru, dan itupun dengan jembatan transisi.
Tiga Gaya Versioning #
Ada tiga tempat umum menaruh nomor versi, masing-masing dengan konsekuensi:
| Gaya | Contoh | Kelebihan | Kekurangan |
|---|---|---|---|
| URL | GET /v1/tugas | eksplisit, mudah di-debug di browser, mudah di-cache | URI berubah antar versi |
| Header | Accept: application/vnd.tugas.v2+json | URL bersih, negosiasi konten | sulit dites manual, tak terlihat di browser |
| Query | GET /tugas?versi=2 | mudah diimplementasikan | bermasalah di caching, jarang dipakai |
Mayoritas API publik memilih URL versioning karena keterlihatannya mengalahkan estetika. Di FastAPI, pola ini dipanggil APIRouter dengan prefix:
from fastapi import APIRouter, FastAPI, Response
from fastapi.testclient import TestClient
app = FastAPI()
router_v1 = APIRouter(prefix="/v1", tags=["tugas v1"])
router_v2 = APIRouter(prefix="/v2", tags=["tugas v2"])
tugas_db = [
{"id": 1, "judul": "Belajar Python", "selesai": False, "prioritas": 2},
{"id": 2, "judul": "Belajar FastAPI", "selesai": True, "prioritas": 1},
]
@router_v1.get("/tugas")
def daftar_tugas_v1(response: Response):
response.headers["Deprecation"] = "true"
response.headers["Sunset"] = "Wed, 01 Jul 2026 00:00:00 GMT"
return [
{"id": t["id"], "judul": t["judul"], "selesai": t["selesai"]} for t in tugas_db
]
@router_v2.get("/tugas")
def daftar_tugas_v2():
return tugas_db
app.include_router(router_v1)
app.include_router(router_v2)
client = TestClient(app)
r = client.get("/v1/tugas")
print("v1:", r.json())
print("Deprecation:", r.headers.get("Deprecation"), "| Sunset:", r.headers.get("Sunset"))
r = client.get("/v2/tugas")
print("v2:", r.json())Output yang Dihasilkan:
v1: [{'id': 1, 'judul': 'Belajar Python', 'selesai': False}, {'id': 2, 'judul': 'Belajar FastAPI', 'selesai': True}]
Deprecation: true | Sunset: Wed, 01 Jul 2026 00:00:00 GMT
v2: [{'id': 1, 'judul': 'Belajar Python', 'selesai': False, 'prioritas': 2}, {'id': 2, 'judul': 'Belajar FastAPI', 'selesai': True, 'prioritas': 1}]Mari kita telaah bagiannya:
- Baris 5-6 (
APIRouter(prefix="/v1", ...)): dua router menampung dua generasi endpoint;prefixmembuat setiap path di dalamnya otomatis berada di bawah versinya, dantagsmenjaga dokumentasi/docsterkelompok per versi. - Baris 16-17 (
response.headers["Deprecation"] = "true"): dua header ini mengumumkan pensiun —Deprecationmenandai versi tua (header standar RFC 8594),Sunsetmemberi tanggal pasti versi berhenti melayani. - Baris 18-20 (
return [...]): v1 memangkas fieldprioritassecara eksplisit — bentuk lama dipertahankan apa adanya meskipun penyimpanan sudah berubah. - Baris 23-25 (
def daftar_tugas_v2()): v2 mengembalikan bentuk penuh termasuk field baruprioritas— perubahan additive dibanding versi sebelumnya ditambah field opsional. - Baris 28-29 (
app.include_router(...)): kedua versi hidup berdampingan di satu aplikasi; migrasi klien boleh berangsur-angsur.
Siklus Deprecation dan Disiplin Changelog #
Header Deprecation dan Sunset hanya berguna bila disertai siklus yang manusiawi. Urutan yang sehat terlihat seperti ini:
- Umumkan lebih awal lewat changelog dan dokumentasi — jangan pernah menghapus endpoint mendadak.
- Tandai versi lama dengan header
Deprecation: trueserta tanggalSunsetyang realistis (enam bulan hingga dua tahun). - Pantau pemakaian versi lama lewat logging atau metrics sebelum tanggal jatuh tempo; bila masih ramai, tunda.
- Sediakan panduan migrasi: tabel padanan endpoint lama ke baru.
- Hapus versi lama setelah tanggal Sunset, dan rilis major version baru bila perubahan merusak banyak hal.
Seluruh siklus itu hanya bisa dipercaya bila ada changelog yang disiplin — satu berkas yang diperbarui di setiap rilis, memisahkan bagian Ditambahkan, Berubah, Tidak digunakan lagi, dan Dihapus. Contoh ringkasnya:
# CHANGELOG
## 2.1.0 — 2026-01-10
### Ditambahkan
- Field `prioritas` pada response GET /v2/tugas (opsional, additive).
## 2.0.0 — 2025-09-01
### Berubah
- Endpoint dipindahkan dari /v1/tugas ke /v2/tugas.
### Tidak digunakan lagi
- GET /v1/tugas dijadwalkan dihapus pada 2026-07-01.Disiplin kecil ini menutup perjalanan Section API Development: desain yang konsisten, validasi ketat, status code yang jujur, autentikasi, dokumentasi otomatis, test yang menjaga kontrak, dan kini evolusi yang terkelola. Seluruhnya dirangkai menjadi aplikasi utuh pada proyek Proyek: REST API Manajemen Tugas.