Especialização em Backend com Go & Gin • Demonstrações Práticas Compiláveis


💻 Código de Exemplo 01 — AutoMigrate de Múltiplos Modelos

package main

import (
	"log"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

type Produto struct {
	gorm.Model
	Nome  string
	SKU   string `gorm:"uniqueIndex"`
	Preco float64
}

type Cliente struct {
	gorm.Model
	Nome string
}

type Pedido struct {
	gorm.Model
	ClienteID uint
	Valor     float64
}

func main() {
	db, err := gorm.Open(sqlite.Open("app.db"), &gorm.Config{})
	if err != nil {
		log.Fatal(err)
	}
	// AutoMigrate é aditivo: cria tabelas/colunas/índices que faltam,
	// mas nunca altera tipo nem remove coluna existente.
	if err := db.AutoMigrate(&Produto{}, &Cliente{}, &Pedido{}); err != nil {
		log.Fatal(err)
	}
	log.Println("AutoMigrate sincronizou 3 tabelas.")
}

💻 Código de Exemplo 02 — Migração Versionada com golang-migrate

package main

import (
	"database/sql"
	"log"

	"github.com/golang-migrate/migrate/v4"
	"github.com/golang-migrate/migrate/v4/database/sqlite3"
	_ "github.com/golang-migrate/migrate/v4/source/file"
)

// ExecutarMigracoesSQL aplica, em ordem, apenas os arquivos .up.sql
// ainda não registrados na tabela de controle schema_migrations.
func ExecutarMigracoesSQL(dbSQL *sql.DB) error {
	driver, err := sqlite3.WithInstance(dbSQL, &sqlite3.Config{})
	if err != nil {
		return err
	}
	m, err := migrate.NewWithDatabaseInstance("file://migrations", "sqlite3", driver)
	if err != nil {
		return err
	}
	if err := m.Up(); err != nil && err != migrate.ErrNoChange {
		return err
	}
	log.Println("migrações aplicadas com sucesso")
	return nil
}

// migrations/000001_criar_tabela_usuarios.up.sql
// CREATE TABLE usuarios (id SERIAL PRIMARY KEY, email TEXT UNIQUE NOT NULL);
//
// migrations/000001_criar_tabela_usuarios.down.sql
// DROP TABLE usuarios;

💻 Código de Exemplo 03 — Seed Idempotente e Verificação de Drift

package main

import (
	"log"

	"gorm.io/gorm"
)

type Usuario struct {
	gorm.Model
	Email string `gorm:"uniqueIndex"`
	Papel string
}

// SeedDadosIniciais só insere o admin padrão se a tabela estiver vazia,
// podendo rodar em todo boot sem risco de duplicar registros.
func SeedDadosIniciais(db *gorm.DB) error {
	var count int64
	db.Model(&Usuario{}).Count(&count)
	if count > 0 {
		return nil
	}
	admin := Usuario{Email: "admin@app.com", Papel: "ADMIN"}
	return db.Create(&admin).Error
}

// VerificarSchemaVersion compara a versão esperada (fixada no código
// versionado) com a aplicada no banco, usada como gate em pipelines CI/CD.
func VerificarSchemaVersion(versaoEsperada, versaoAtual uint) bool {
	if versaoAtual != versaoEsperada {
		log.Printf("ERRO: drift de schema! esperado=%d atual=%d", versaoEsperada, versaoAtual)
		return false
	}
	return true
}