Pendahuluan

Pada arsitektur backend modern, memastikan schema database sinkron dengan definisi model menjadi bagian penting agar fitur baru dapat berjalan mulus. Go Fiber dan GORM sering dipadukan untuk membangun API yang cepat, tetapi migrasi schema memerlukan pendekatan strategis agar dapat dipercaya di lingkungan produksi. Artikel ini membahas cara memanfaatkan AutoMigrate, interface Migrator, dan proses manual untuk mengecek kolom, indeks, serta foreign key, termasuk alur deployment schema versi dan rollback ketika diperlukan. Kita juga akan melihat contoh integrasi migrasi schema ke CLI Fiber saat release.

Strategi Dasar Migrasi di GORM

GORM menyediakan dua pendekatan utama: auto-migrasi dengan AutoMigrate dan migrasi manual melalui interface Migrator. AutoMigrate cocok untuk cepat menyelaraskan tabel dengan struct Go, sementara Migrator menawarkan kontrol granular, misalnya saat perlu mengganti tipe kolom atau membuat constraint yang tidak otomatis.

AutoMigrate: Kelebihan dan Batasannya

Fungsi AutoMigrate(&User{}, &Product{}) akan membuat tabel yang belum ada dan menambahkan kolom baru. Ini cocok untuk fase development dan tim kecil karena mengurangi boilerplate. Namun, AutoMigrate tidak menghapus kolom, tidak mengubah tipe kolom secara otomatis, dan tidak membuat constraint kompleks seperti enum atau partial index.

Contoh implementasi sederhana:

db.AutoMigrate(&User{}, &Product{})

Ini cukup untuk memastikan field-field seperti Name string atau Price float64 terefleksi ke tabel. Tapi jika Anda perlu menambahkan constraint unik atau foreign key, AutoMigrate tidak menjamin pembuatan otomatis kecuali dialog struct benar-benar mencerminkan tag GORM.

Migrator: Kontrol Manual untuk Situasi Produksi

Interface Migrator memberi Anda akses seperti HasColumn, AddColumn, CreateIndex, dan CreateConstraint. Hal ini memungkinkan migrasi fase deploy yang lebih terprediksi.

if !db.Migrator().HasColumn(&Product{}, "category_id") {
    db.Migrator().AddColumn(&Product{}, "CategoryID")
}
db.Migrator().CreateConstraint(&Product{}, "fk_products_category")

Dengan pendekatan ini, Anda bisa menambahkan kolom hanya jika belum ada, serta memastikan constraint atau index tidak duplikat.

Pengecekan Kolom & Constraint Saat Migrasi

Permasalahan umum di produksi adalah perubahan schema yang tidak tersinkron, entah karena deployment order tidak konsisten atau karena script migrasi gagal di tengah jalan. Teknik terbaik adalah memeriksa keberadaan kolom dan index sebelum mencoba menambahkannya.

  • HasColumn: Gunakan untuk menghindari error ketika kolom sudah ada, terutama saat deploy ulang.
  • HasIndex: Pastikan index dibuat sekali. Jika index unik diperlukan, pastikan nama konsisten.
  • HasConstraint & CreateConstraint: Cek dan buat foreign key secara manual untuk menghindari duplikasi constraint.

Contoh pemeriksaan dan pembuatan index:

if !db.Migrator().HasIndex(&Order{}, "idx_orders_status") {
    db.Migrator().CreateIndex(&Order{}, "Status")
}

Untuk constraint foreign key:

if !db.Migrator().HasConstraint(&Order{}, "fk_orders_user") {
    db.Migrator().CreateConstraint(&Order{}, "User")
}

Pastikan struktur tag pada struct Go komprehensif, misal:

type Order struct {
    ID     uint
    UserID uint
    User   User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:RESTRICT;foreignKey:UserID"`
}

Menetapkan Versi Schema dan Alur Deployment

Setiap perubahan schema harus dicatat sebagai versi; ini membantu rollback dan verifikasi migrasi. Pendekatan yang umum adalah menyimpan tabel schema_migrations berisi version dan applied_at. Setiap release migrasi dijalankan berdasar urutan versi.

Alur general:

  1. Tambahkan file migrasi Go (misalnya 20241001_add_order_status.go) dengan fungsi Up dan Down.
  2. CLI release membaca tabel schema_migrations untuk menentukan migrasi belum dijalankan.
  3. Menjalankan migrasi satu per satu (dengan AutoMigrate atau Migrator custom).
  4. Setelah berhasil, catat versi baru ke tabel.

Contoh struktur file migrasi:

func Up(db *gorm.DB) error {
    if !db.Migrator().HasColumn(&Order{}, "status") {
        return db.Migrator().AddColumn(&Order{}, "Status")
    }
    return nil
}

func Down(db *gorm.DB) error {
    if db.Migrator().HasColumn(&Order{}, "status") {
        return db.Migrator().DropColumn(&Order{}, "Status")
    }
    return nil
}

Catatan: meskipun ada fungsi Down, melakukan rollback di produksi membutuhkan perhatian khusus karena data mungkin hilang jika Anda drop column.

Rollback Manual dan Perlindungan Data

Rollback sering kali tidak otomatis. Jika terjadi kegagalan setelah migrasi, Anda perlu:

  • Menjalankan Down secara manual untuk membatalkan perubahan schema.
  • Mengembalikan data dari backup (misalnya menggunakan logical backup atau point-in-time recovery).
  • Memastikan tim QA sudah menguji rollback di environment staging sebelum dipakai di production.

Tips debugging:

  • Periksa log GORM (gunakan db.Debug()) untuk melihat SQL yang dieksekusi.
  • Bandingkan schema antara staging dan production dengan tool seperti pg_dump --schema-only.
  • Gunakan environment variable untuk mengunci migrasi hanya di staging/production sesuai kebutuhan.

Integrasi Migrasi dengan CLI Fiber Saat Release

CLI Fiber adalah cara praktis menjalankan migrasi saat release. Anda dapat membuat perintah baru misalnya fiber migrate yang memanggil fungsi migrasi.

Contoh implementasi di file Fiber CLI:

app.Command("migrate", "jalankan migrasi GORM", func(cmd *fiber.CLI) {
    cmd.Action(func(c *fiber.CLI) error {
        db := config.NewDB()
        if err := migrations.Run(db); err != nil {
            return err
        }
        fmt.Println("Migrasi selesai")
        return nil
    })
})

Fungsi migrations.Run dapat membaca tabel schema_migrations dan mengeksekusi file migrasi terkait. Pastikan CLI hanya dipanggil saat release pipeline sudah siap, misalnya setelah build dan test lulus.

Dalam pipeline CI/CD, Anda bisa menambahkan step seperti:

  1. Build artifact Go.
  2. Deploy ke staging dan jalankan fiber migrate.
  3. Verifikasi schema lewat integration test dan manual check.
  4. Deploy ke production, lalu jalankan fiber migrate.

Jika migrasi gagal setelah menambahkan versi baru, pastikan perintah migrasi dapat dihentikan tanpa mengubah tabel schema_migrations agar rollback dapat dilakukan secara manual.

Kesimpulan

Memilih antara AutoMigrate dan Migrator tergantung pada tingkat kontrol yang diperlukan. AutoMigrate sangat cepat di fase awal, sementara Migrator memberikan keamanan untuk production releases. Jaga alur strict untuk versi schema, gunakan pengecekan kolom/index sebelum modifikasi, dan selalu siapkan rencana rollback. Integrasi migrasi ke CLI Fiber membantu memastikan migrasi dijalankan konsisten selama release. Dengan pendekatan ini, schema dan deployment Fiber bisa berjalan lebih dapat diprediksi dan aman.