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:

PerubahanKategoriAlasan
Menambah field opsional pada responseaman (additive)klien lama mengabaikan field yang tidak dikenalnya
Menambah endpoint baruaman (additive)tidak mengubah perilaku yang sudah ada
Menghapus field dari responsemerusakklien lama membaca field tersebut
Mengganti nama field (judul → nama)merusaksetara menghapus lalu menambah
Menambah field wajib pada requestmerusakrequest lama tiba-tiba ditolak 422
Mengubah tipe field (int → str)merusakparser 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:

GayaContohKelebihanKekurangan
URLGET /v1/tugaseksplisit, mudah di-debug di browser, mudah di-cacheURI berubah antar versi
HeaderAccept: application/vnd.tugas.v2+jsonURL bersih, negosiasi kontensulit dites manual, tak terlihat di browser
QueryGET /tugas?versi=2mudah diimplementasikanbermasalah di caching, jarang dipakai

Mayoritas API publik memilih URL versioning karena keterlihatannya mengalahkan estetika. Di FastAPI, pola ini dipanggil APIRouter dengan prefix:

python
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:

text
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:

  1. Baris 5-6 (APIRouter(prefix="/v1", ...)): dua router menampung dua generasi endpoint; prefix membuat setiap path di dalamnya otomatis berada di bawah versinya, dan tags menjaga dokumentasi /docs terkelompok per versi.
  2. Baris 16-17 (response.headers["Deprecation"] = "true"): dua header ini mengumumkan pensiun — Deprecation menandai versi tua (header standar RFC 8594), Sunset memberi tanggal pasti versi berhenti melayani.
  3. Baris 18-20 (return [...]): v1 memangkas field prioritas secara eksplisit — bentuk lama dipertahankan apa adanya meskipun penyimpanan sudah berubah.
  4. Baris 23-25 (def daftar_tugas_v2()): v2 mengembalikan bentuk penuh termasuk field baru prioritas — perubahan additive dibanding versi sebelumnya ditambah field opsional.
  5. 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:

  1. Umumkan lebih awal lewat changelog dan dokumentasi — jangan pernah menghapus endpoint mendadak.
  2. Tandai versi lama dengan header Deprecation: true serta tanggal Sunset yang realistis (enam bulan hingga dua tahun).
  3. Pantau pemakaian versi lama lewat logging atau metrics sebelum tanggal jatuh tempo; bila masih ramai, tunda.
  4. Sediakan panduan migrasi: tabel padanan endpoint lama ke baru.
  5. 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:

text
# 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.