Di bab lalu kita sudah lihat entity Order dan sempat menyinggung: kalau ddl-auto diset validate, siapa yang sebenarnya membuat tabel orders di database? Jawabannya Flyway — alat migration yang jadi topik bab ini.
Apa itu Flyway, dan kenapa bukan ddl-auto: update saja?
Flyway adalah tool migration database: setiap perubahan skema (bikin tabel, tambah kolom, bikin index, dst) ditulis sebagai file SQL bernomor urut, disimpan di dalam project, dan dijalankan otomatis oleh Flyway saat aplikasi start. File-file ini hidup berdampingan dengan kode di git — artinya setiap perubahan skema melewati proses yang sama dengan perubahan kode lainnya: ditulis eksplisit, di-commit, direview lewat pull request, dan bisa dilacak riwayatnya.
Bandingkan dengan membiarkan Hibernate men-generate skema otomatis lewat ddl-auto: update (yang sempat dibahas di bab 13). Cara itu memang lebih cepat di awal, tapi punya tiga masalah nyata begitu project dipakai tim:
- Tidak reviewable — perubahan skema terjadi diam-diam saat aplikasi start, tidak pernah muncul sebagai diff yang bisa dibaca orang lain sebelum di-merge.
- Tidak reproducible — urutan Hibernate memindai entity bisa menghasilkan hasil yang sedikit berbeda antar versi Hibernate atau antar environment.
- Tidak aman untuk tim — dua developer bisa punya skema database lokal yang diam-diam berbeda tanpa sadar, dan baru ketahuan saat sudah terlanjur konflik di production.
Flyway membalik urutannya: skema database mengikuti file migration yang eksplisit, bukan sebaliknya. Karena itulah ddl-auto: validate dan flyway.enabled: true selalu dipasangkan di Order Service — Hibernate cuma memvalidasi bahwa entity Kotlin cocok dengan skema, sementara Flyway yang benar-benar membentuk skema itu.
Membedah V1__create_orders_table.sql
CREATE TABLE orders (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
customer_name VARCHAR(255) NOT NULL,
item_name VARCHAR(255) NOT NULL,
quantity INTEGER NOT NULL,
unit_price NUMERIC(19, 2) NOT NULL,
total_price NUMERIC(19, 2) NOT NULL,
status VARCHAR(20) NOT NULL
);
CREATE INDEX idx_orders_customer_name ON orders (customer_name);
CREATE INDEX idx_orders_status ON orders (status);
Cocokkan setiap kolom dengan entity Order di bab sebelumnya — ini bukan kebetulan, ini yang divalidasi Hibernate lewat ddl-auto: validate:
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY— persis pasangan dari@Id @GeneratedValue(strategy = GenerationType.IDENTITY)di Kotlin.GENERATED ALWAYS AS IDENTITYadalah cara standar SQL modern untuk auto-increment (penggantiSERIALyang lebih lama di PostgreSQL).customer_name,item_name VARCHAR(255) NOT NULL— pasangan daricustomerName: String,itemName: String. Perhatikan penamaan kolom pakaisnake_case(customer_name) sementara di Kotlin pakaicamelCase(customerName) — ini konversi otomatis standar yang dilakukan Hibernate, tidak perlu konfigurasi tambahan.quantity INTEGER NOT NULL— pasanganquantity: Int.status VARCHAR(20) NOT NULL— pasanganstatus: OrderStatusdengan@Enumerated(EnumType.STRING)dari bab lalu. Karena disimpan sebagai string nama enum ("CONFIRMED","NEEDS_REVIEW"), tipe kolomnyaVARCHAR, bukan angka.
Kenapa NUMERIC(19, 2) untuk unitPrice dan totalPrice?
Ini bagian yang paling sering salah diimplementasikan oleh developer yang belum terbiasa dengan data finansial. Di Kotlin, kedua kolom ini bertipe BigDecimal, bukan Double atau Float. Alasannya: tipe floating-point (Double/Float) merepresentasikan angka desimal secara approximate di level binary — angka seperti 0.1 tidak bisa direpresentasikan secara presisi, sehingga operasi seperti 0.1 + 0.2 bisa menghasilkan 0.30000000000000004. Untuk perhitungan biasa itu sepele, tapi untuk uang, error sekecil apa pun yang terakumulasi dari ribuan transaksi adalah bug finansial yang nyata.
BigDecimal di Kotlin/Java merepresentasikan angka desimal secara exact, dan pasangannya di sisi database adalah tipe NUMERIC (alias DECIMAL) — bukan FLOAT atau REAL. NUMERIC(19, 2) artinya: total 19 digit signifikan, dengan 2 digit di belakang koma (cukup untuk representasi mata uang seperti Rupiah dua desimal). Kombinasi BigDecimal di kode + NUMERIC di database inilah pola standar untuk menyimpan nilai uang di sistem backend mana pun, termasuk Order Service ini.
Dua index: idx_orders_customer_name dan idx_orders_status
CREATE INDEX idx_orders_customer_name ON orders (customer_name);
CREATE INDEX idx_orders_status ON orders (status);
Index ini bukan sekadar formalitas — keduanya dipasang karena akan langsung dipakai oleh query custom yang kita bahas di bab berikutnya: pencarian order berdasarkan nama customer (findByCustomerNameContainingIgnoreCase) dan filter berdasarkan status (findAllByStatus). Tanpa index, PostgreSQL harus melakukan full table scan — memindai seluruh baris satu per satu — setiap kali query ini dijalankan. Dengan index, PostgreSQL bisa langsung melompat ke baris yang relevan, jauh lebih cepat begitu tabel orders berisi ribuan atau jutaan baris.
Penamaan file: V{version}__{deskripsi}.sql
Nama file V1__create_orders_table.sql mengikuti konvensi wajib Flyway: V diikuti nomor versi, dua underscore (__), lalu deskripsi bebas. Nomor versi inilah yang menentukan urutan eksekusi — migration berikutnya akan diberi nama V2__..., V3__..., dan seterusnya, selalu naik, tidak pernah diedit ulang setelah pernah dijalankan di suatu environment.
Flyway melacak migration mana saja yang sudah pernah dijalankan lewat sebuah tabel yang ia kelola sendiri, flyway_schema_history — setiap kali aplikasi start, Flyway membandingkan daftar file migration yang ada di project dengan riwayat di tabel ini, lalu hanya menjalankan file yang belum pernah tercatat. Inilah yang membuat migration aman dijalankan berulang kali di berbagai environment (laptop developer, staging, production) tanpa risiko dijalankan dua kali atau tertinggal satu langkah.
Tabel orders sudah siap dan tervalidasi strukturnya. Bab berikutnya kita mulai memanfaatkan struktur ini lewat query yang lebih dari sekadar findAll() — derived query method, JPQL manual, sampai projection langsung ke DTO.
Bagian dari Series: Kotlin Backend Microservice - Belajar Fundamental sampai Microservice Nyata
Belajar Kotlin + Spring Boot dari fundamental bahasa (null safety, OOP, coroutines) sampai membangun sistem microservice sungguhan - REST API, Postgre...
Lihat Series Lengkap