Kodeloka
  1. Kodeloka
  2. Kelas Golang
  3. Materi

Domain 16 · 7 bab

Golang Database Migration

174. Pengenalan Database Migration & Golang Migrate Tool

Memahami konsep versi skema database (DDL) dan pentingnya automation migration di tim software.

Mengapa Membutuhkan Database Migration?

Dalam pengembangan aplikasi tim, perubahan struktur database (seperti membuat tabel baru, menambah kolom, atau membuat index) harus tercatat dan terversi dengan rapi layaknya source code (Git).

💡 Masalah Tanpa Database Migration:
  • Skema database di local developer, staging, dan production sering tidak sinkron.
  • Perubahan dilakukan secara manual melalui GUI database (DBeaver/phpMyAdmin) yang rentan human error.
  • Kesulitan melakukan rollback (membatalkan perubahan DDL) saat timbul bug.

Golang Migrate (`golang-migrate/migrate`):

Salah satu CLI tool & Go library terpopuler untuk mengelola database migration secara otomatis yang mendukung MySQL, PostgreSQL, SQLite, MongoDB, dll.

Contoh program

package main

import (
	"fmt"
)

func main() {
	fmt.Println("=== DEMO DATABASE MIGRATION ===")
	fmt.Println("Database Migration mengelola versi DDL (Data Definition Language) skema DB.")
	fmt.Println("Tool popular: golang-migrate/migrate")
}

Praktik dan kuis bab ini di kelas interaktif →

175. Anatomi File Migration (.up.sql dan .down.sql)

Memahami konvensi penamaan file migration, nomor versi, serta pasangan script Up dan Down.

Konvensi File Migration

Tool golang-migrate mewajibkan file migration dibuat berpasangan dalam format SQL standar dengan aturan nama file berikut:

{version}_{title}.up.sql
{version}_{title}.down.sql
📌 Penjelasan File Up & Down:
  • .up.sql: Berisi instruksi DDL untuk menerapkan perubahan (misal: CREATE TABLE users (...)).
  • .down.sql: Berisi instruksi DDL untuk membatalkan/rollback perubahan (misal: DROP TABLE IF EXISTS users;).

Contoh penamaan versi dengan timestamp: 20260727000001_create_users_table.up.sql

Contoh program

package main

import (
	"fmt"
)

func main() {
	upSql := "CREATE TABLE categories (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL);"
	downSql := "DROP TABLE IF EXISTS categories;"

	fmt.Println("=== MIGRATION SCRIPTS ===")
	fmt.Println("[UP.SQL]  :", upSql)
	fmt.Println("[DOWN.SQL]:", downSql)
}

Praktik dan kuis bab ini di kelas interaktif →

176. Perintah CLI golang-migrate (up, down, version, force)

Menguasai perintah CLI `migrate` untuk eksekusi migration skema ke database.

Perintah Utama CLI `migrate`

Setelah menginstall golang-migrate CLI, kita dapat menjalankan perintah-perintah berikut dari terminal:

Command CLIFungsi Utama
migrate create -ext sql -dir db/migrations -seq create_users_tableMembuat pasangan file migration baru dengan nomor urut.
migrate -path db/migrations -database "mysql://root:root@tcp(localhost:3306)/golang_db" upMenjalankan seluruh file migration `up` yang belum dieksekusi.
migrate -path db/migrations -database "..." down 1Rollback migration `down` sebanyak 1 langkah ke belakang.
migrate -path db/migrations -database "..." versionMengecek nomor versi migration yang aktif saat ini di DB.

Contoh program

package main

import (
	"fmt"
)

func main() {
	fmt.Println("=== PERINTAH CLI MIGRATE ===")
	fmt.Println("1. migrate create -ext sql -dir migrations -seq init_schema")
	fmt.Println("2. migrate -path migrations -database 'mysql://...' up")
	fmt.Println("3. migrate -path migrations -database 'mysql://...' down 1")
}

Praktik dan kuis bab ini di kelas interaktif →

177. Managing Dirty State Migration & Handling Failed Migration

Memahami kondisi Dirty State saat SQL migration gagal dan memperbaikinya dengan `migrate force`.

Apa itu Dirty State Migration?

Jika terjadi kesalahan sintaks SQL di tengah-tengah eksekusi file migration `up` atau `down`, `golang-migrate` akan menandai kolom dirty = true pada tabel pelacak schema_migrations di database.

⚠️ Efek Dirty State:

Selama status database masih dirty, `golang-migrate` akan menolak menjalankan migration selanjutnya untuk mencegah ketidakselarasan DDL.

Cara Memperbaiki (Fixing Dirty State):

  1. Perbaiki kesalahan sintaks pada file SQL terkait.
  2. Jalankan command force untuk mereset versi yang gagal:
    migrate -path db/migrations -database "..." force {version_number}
  3. Jalankan kembali migrate up.

Contoh program

package main

import (
	"fmt"
)

type SchemaMigration struct {
	Version int64
	Dirty   bool
}

func main() {
	status := SchemaMigration{Version: 2026072701, Dirty: true}

	fmt.Println("=== STATUS SCHEMA MIGRATIONS ===")
	fmt.Printf("Version: %d | Dirty: %t\n", status.Version, status.Dirty)
	if status.Dirty {
		fmt.Println("⚠️ WARN: Database dalam kondisi DIRTY! Jalankan perintah 'migrate force'!")
	}
}

Praktik dan kuis bab ini di kelas interaktif →

178. Programmatic Database Migration (Running Migration via Go Code)

Mengeksekusi database migration secara otomatis dari dalam kode Go saat aplikasi dinyalakan.

Programmatic Migration di Kode Go

Selain menggunakan CLI terminal terpisah, kita dapat mengintegrasikan library golang-migrate/migrate/v4 langsung ke dalam fungsi main() aplikasi Go. Hal ini memastikan database selalu ter-update secara otomatis begitu aplikasi di-deploy.

💡 Contoh Kode Go Programmatic Migration:
import (
    "github.com/golang-migrate/migrate/v4"
    _ "github.com/golang-migrate/migrate/v4/database/mysql"
    _ "github.com/golang-migrate/migrate/v4/source/file"
)

m, err := migrate.New("file://db/migrations", "mysql://root:root@tcp(localhost:3306)/golang_db")
if err != nil {
    log.Fatal(err)
}
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
    log.Fatal(err)
}

Contoh program

package main

import (
	"fmt"
)

func AutoMigrate(dbUrl string) string {
	// Simulasi pemanggilan m.Up()
	return "Migration UP sukses dijalankan secara otomatis saat startup aplikasi!"
}

func main() {
	fmt.Println("=== PROGRAMMATIC MIGRATION DEMO ===")
	result := AutoMigrate("mysql://user:pass@tcp(127.0.0.1:3306)/appdb")
	fmt.Println(result)
}

Praktik dan kuis bab ini di kelas interaktif →

179. Strategi Rollback & Schema Versioning Best Practices

Panduan praktik terbaik mengelola versi skema database di lingkungan produksi dan pipeline CI/CD.

Best Practices Database Migration

  • Backward Compatibility: Hindari menghapus kolom yang masih dibaca oleh versi aplikasi lama. Gunakan strategi 2-step deployment (Deprecate -> Drop).
  • Gunakan Embed untuk File Migration: Kemas folder migration ke dalam binary Go menggunakan //go:embed agar aplikasi tidak membutuhkan file SQL fisik terpisah di server.
  • Gunakan Transaction DDL jika Didukung: Database seperti PostgreSQL mendukung transaksi untuk DDL (sehingga jika migration gagal, otomatis rollback penuh).
  • Otomatisasi di CI/CD: Jalankan verifikasi lint SQL migration pada Pull Request sebelum di-merge ke branch main.

Contoh program

package main

import (
	"fmt"
)

func main() {
	fmt.Println("=== BEST PRACTICES MIGRATION ===")
	fmt.Println("1. Embed file migration SQL menggunakan //go:embed")
	fmt.Println("2. Buat perubahan DDL yang compatible ke belakang (Backward Compatible)")
	fmt.Println("3. Uji coba script .down.sql secara berkala")
}

Praktik dan kuis bab ini di kelas interaktif →

180. Integration Database Migration dengan Driver MySQL/PostgreSQL

Menggunakan driver instance `database/sql` Go langsung pada objek migration.

Migration dengan Instance `*sql.DB`

Selain memberikan string URL koneksi, `golang-migrate` mengizinkan kita menggunakan instance koneksi *sql.DB yang sudah ada menggunakan driver bawaan seperti mysql.WithInstance(db, &mysql.Config{}).

📌 Contoh Penggunaan Driver Instance:
import (
    "database/sql"
    "github.com/golang-migrate/migrate/v4"
    "github.com/golang-migrate/migrate/v4/database/mysql"
)

db, _ := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/golang_db")
driver, _ := mysql.WithInstance(db, &mysql.Config{})
m, _ := migrate.NewWithDatabaseInstance("file://db/migrations", "mysql", driver)

Contoh program

package main

import (
	"fmt"
)

func main() {
	fmt.Println("=== INTEGRASI DRIVER DB INSTANCE ===")
	fmt.Println("Membuka koneksi sql.Open() -> Membuat driver instance -> Memanggil NewWithDatabaseInstance()")
	fmt.Println("Keuntungan: Dapat menghemat pool koneksi database aplikasi.")
}

Praktik dan kuis bab ini di kelas interaktif →