Build Hijau, Runtime Runtuh: Kontrak API yang Hanya di Kode
Nilai status baru di handler backend lolos build frontend karena spec OpenAPI tidak ikut diubah. Catatan kenapa kontrak API harus eksplisit di spec.
Ringkasan
Gue nambah status tiket di backend kirain aman soalnya nggak ada error pas kompilasi, eh pas dibuka malah kacau. Usut punya usut ternyata tipe frontend cuma ngikutin openapi yaml jadi nilai baru itu nggak kebaca. Pelajarannya spec harus jadi patokan utama, tulis enum lengkap di sana baru regenerate biar compiler bisa negur.
Pagi Itu Build Hijau, Layar Merah
Sore sebelumnya saya nambah satu nilai status tiket di handler backend KotaPortal. Build frontend paginya hijau, nggak ada satu pun error merah di terminal. Begitu halaman daftar tiket dibuka, komponennya langsung kacau: baris status tampil kosong, dan satu panel melempar error karena nemu nilai yang nggak dikenalnya.
Tebakan pertama saya salah total. Saya curiga cache build. Hapus node_modules, bersihkan cache, jalanin ulang. Sama saja. Kompilasi sukses, runtime gagal. Dua jam hilang untuk investigasi yang ujungnya satu kalimat: openapi-typescript cuma menerjemahkan apa yang tertulis di openapi.yaml. Nilai baru itu ada di handler, tapi nggak ada di spec. Buat compiler, nilai itu nggak pernah ada.
Di situ saya sadar kebiasaan yang selama ini saya anggap wajar: kontrak API hidup di kode backend, dan spec cuma jadi dokumentasi tambahan yang di-update kalau sempat. Padahal frontend KotaPortal mengambil tipe API-nya dari spec lewat generator, bukan dari handler [4]. Selama spec dan handler boleh beda, dua tim itu bekerja di atas kontrak masing-masing, dan biayanya baru kerasa pas aplikasi jalan.
Spec Itu Bukan Pelengkap
OpenAPI Specification memang dirancang sebagai deskripsi antarmuka yang language-agnostic: manusia dan mesin bisa memahami kemampuan sebuah API tanpa membaca source code, dokumentasi tambahan, atau mengintip trafik jaringan [1]. Kalau deskripsi itu yang jadi sumber tipe frontend, maka spec yang kurang satu nilai enum berarti tipe frontend yang kurang satu nilai enum. Bukan sekadar dokumentasi agak basi, tapi bug yang lolos compiler.
Detail pentingnya ada di enum. Kata kunci enum menyatakan nilai-nilai yang mungkin untuk sebuah parameter atau properti, semua nilainya harus patuh pada tipe yang dideklarasikan, dan penjelasan per nilai ditaruh di field description [2]. Enum yang dipakai lintas endpoint tinggal didefinisikan sekali di global components, lalu dirujuk lewat $ref [2]. Hampir semua objek di spec juga menerima field description untuk menjelaskan hal yang nggak terwakili di field mesin [3]. Konteks kayak "status ini artinya apa dan kapan dipakai" selama ini saya tempel di komentar handler yang nggak pernah dibaca frontend; tempat yang tepat justru di situ.
Pass Kontrak: Bidang, Enam Status, Sisanya
Perbaikannya satu pass: dimensi scoping bidang dan enam status tiket saya nyatakan eksplisit di spec sebagai enum, lengkap dengan deskripsi per nilai. Field history, tanggal, dan link tiket ikut dinaikkan ke kontrak yang sama, sekalian menutup ganti kosakata status yang saya tulis minggu lalu di migrasi ENUM-nya. Strukturnya kira-kira begini:
components:
schemas:
TicketStatus:
type: string
enum: [draft, reviewed, approved, rejected, published, archived]
description: |
Six-stage document lifecycle. A value that exists in handlers
but not here is invisible to every generated frontend type.
TicketScope:
type: string
enum: [finance, hr, asset, ALL]
description: |
Unit scope of a record. ALL is the explicit cross-unit value,
not the absence of a choice.
Contoh di atas sengaja generik; polanya yang penting. Sesudah regenerate, compiler yang nge-sweep: setiap pemakaian status lama yang belum menyesuaikan langsung merah. Aturan mainnya pun berubah: spec dulu, baru handler. Membaliknya berarti berbohong pada compiler, dan tagihan itu selalu jatuh di runtime, saat yang paling mahal untuk menyadarinya.
Sikap saya soal ini keras: spec adalah satu-satunya artefak yang dibaca dua sisi. Generated types, dokumentasi, validasi, bahkan reviewer eksternal, semuanya turunan dari dia. Kalau sumbernya asimetris, semua turunannya ikut asimetris.
Yang Akan Saya Awasi
Dua kebiasaan lama yang bisa bikin ini luruh lagi. Pertama, endpoint eksperimental: kepancing cepat, langsung tulis handler tanpa nyentuh spec dengan alasan "nanti dibenerin". Kedua, nilai yang sengaja nggak didokumentasikan karena masih ragu masuk kontrak atau nggak. Dua-duanya jebakan yang sama: compiler nggak bisa memperingatkan soal sesuatu yang nggak ada di kontrak. Ujian keseluruhan ini juga sederhana dan bisa dilakukan siapa pun: hapus satu nilai enum dari spec, regenerate, lalu lihat compiler menunjuk setiap baris yang masih memakainya. Itu bukti nyata kontraknya hidup di spec, bukan di kepala.
## Sources [1] OpenAPI Specification v3.2.1 — What is the OpenAPI Specification [2] Swagger, OpenAPI 3.0 guide: Enums [3] OpenAPI Learn: Providing Documentation and Examples [4] openapi-typescript, README (repo openapi-ts/openapi-typescript)