<h2>Introdução ao GORM e por que ele é indispensável</h2>
<p>GORM é a biblioteca ORM (Object-Relational Mapping) mais popular e robusta do ecossistema Go. Ela abstrai a complexidade de interagir diretamente com bancos de dados SQL, permitindo que você trabalhe com dados através de structs Go em vez de escrever queries SQL manualmente. Isso não significa que você abandona SQL — muito pelo contrário. GORM é uma ferramenta que simplifica operações comuns enquanto oferece escape hatches para casos complexos.</p>
<p>A razão pela qual GORM é tão valorizada em projetos profissionais é que ela reduz boilerplate, melhora a segurança contra SQL injection através de prepared statements, e mantém seu código Go idiomático. Se você trabalha com qualquer banco de dados relacional em Go (PostgreSQL, MySQL, SQLite), GORM será inevitavelmente parte de seu toolbox.</p>
<h2>Configuração Inicial e Conexão com Banco de Dados</h2>
<h3>Instalação e Setup Básico</h3>
<p>Comece instalando GORM e o driver do banco de dados desejado. Aqui usaremos PostgreSQL como exemplo, mas o conceito aplica-se a qualquer banco suportado.</p>
<pre><code class="language-bash">go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres</code></pre>
<p>A conexão com o banco de dados é o primeiro passo. Você estabelece uma conexão e a mantém aberta para toda a aplicação:</p>
<pre><code class="language-go">package main
import (
"fmt"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func main() {
dsn := "host=localhost user=postgres password=password dbname=myapp port=5432 sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic("Failed to connect to database")
}
fmt.Println("Connected successfully")
}</code></pre>
<p>O <code>gorm.DB</code> retornado é a instância que você usará em toda sua aplicação. É recomendado injetar essa instância via dependency injection ou armazená-la em um contexto. Nunca crie novas conexões em cada requisição — reutilize a mesma instância.</p>
<h3>Definindo Modelos</h3>
<p>Modelos em GORM são simples structs Go com tags que mapeiam para colunas do banco de dados. GORM usa convenções (como pluralizar nomes de tabelas automaticamente) mas permite customização completa:</p>
<pre><code class="language-go">package models
import "time"
type User struct {
ID uint gorm:"primaryKey"
Name string gorm:"column:name;size:100;not null"
Email string gorm:"column:email;size:255;uniqueIndex:,type:btree"
Age int gorm:"column:age"
CreatedAt time.Time gorm:"autoCreateTime"
UpdatedAt time.Time gorm:"autoUpdateTime"
DeletedAt gorm.DeletedAt gorm:"index"
}
// TableName sobrescreve o nome da tabela (plural automático seria "users")
func (User) TableName() string {
return "users"
}</code></pre>
<p>As tags definem o comportamento no banco de dados: <code>primaryKey</code> marca a chave primária, <code>not null</code> enforça NOT NULL, <code>uniqueIndex</code> cria um índice único. <code>DeletedAt</code> implementa soft delete — registros não são deletados fisicamente, apenas marcados como deletados. GORM automaticamente os exclui de queries normais.</p>
<h2>Migrations: Versionando seu Esquema de Banco de Dados</h2>
<h3>O que são Migrations e por que importam</h3>
<p>Migrations são código versionado que define e altera o esquema do banco de dados. Em vez de executar comandos SQL manualmente (o que é propenso a erros e não é rastreável), você escreve migrations que podem ser executadas, revertidas e auditadas. GORM oferece suporte nativo a migrations através de <code>AutoMigrate</code> para casos simples e migrations customizadas para casos complexos.</p>
<h3>AutoMigrate: O Caminho Rápido</h3>
<p><code>AutoMigrate</code> é perfeito para desenvolvimento inicial e prototipagem. Ele examina seus modelos e cria tabelas, colunas e índices automaticamente. Se você adicionar novos campos, ele adiciona as colunas (sem perder dados existentes):</p>
<pre><code class="language-go">package main
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
"myapp/models"
)
func main() {
db, _ := gorm.Open(postgres.Open(dsn), &gorm.Config{})
// Cria tabelas para todos os modelos
db.AutoMigrate(
&models.User{},
&models.Post{},
&models.Comment{},
)
}</code></pre>
<p>O <code>AutoMigrate</code> é idempotente — você pode rodá-lo várias vezes sem causar erro. Se a tabela já existe, ele apenas verifica se há colunas faltando e as adiciona.</p>
<h3>Migrations Customizadas para Controle Total</h3>
<p>Em ambientes de produção, é comum usar migrations explícitas para ter controle granular sobre o que muda. GORM integra-se com bibliotecas de migration como <code>golang-migrate</code>, mas você também pode escrever migrations customizadas:</p>
<pre><code class="language-go">package migrations
import (
"gorm.io/gorm"
"myapp/models"
)
func CreateUserTable(db *gorm.DB) error {
// Migração encapsulada em função
return db.Migrator().CreateTable(&models.User{})
}
func AddEmailUniqueConstraint(db *gorm.DB) error {
return db.Migrator().CreateIndex(&models.User{}, "email")
}
func DropUserTable(db *gorm.DB) error {
return db.Migrator().DropTable(&models.User{})
}</code></pre>
<p>Em um projeto real, você organizaria migrations em arquivos com timestamp (ex: <code>001_create_users.go</code>, <code>002_add_posts.go</code>) e teria um sistema que as executa em ordem. Isso garante rastreabilidade e permite reverter mudanças se necessário.</p>
<h2>CRUD Operations: Criando, Lendo, Atualizando e Deletando Dados</h2>
<h3>Create: Inserindo Dados</h3>
<p>Criar registros em GORM é direto — você instancia a struct, popula com dados e chama <code>Create</code>:</p>
<pre><code class="language-go">user := models.User{
Name: "Alice",
Email: "alice@example.com",
Age: 28,
}
result := db.Create(&user)
if result.Error != nil {
panic(result.Error)
}
fmt.Println(user.ID) // ID auto-gerado pelo banco está aqui</code></pre>
<p><code>Create</code> retorna um <code>*gorm.DB</code> com informações sobre a operação. <code>result.Error</code> contém qualquer erro ocorrido. <code>result.RowsAffected</code> diz quantas linhas foram inseridas. O campo <code>ID</code> é automaticamente populado após inserção se a coluna for <code>autoincrement</code>.</p>
<p>Para inserir múltiplos registros, use <code>CreateInBatches</code> para melhor performance:</p>
<pre><code class="language-go">users := []models.User{
{Name: "Bob", Email: "bob@example.com"},
{Name: "Charlie", Email: "charlie@example.com"},
{Name: "Diana", Email: "diana@example.com"},
}
db.CreateInBatches(users, 100) // Insere em lotes de 100</code></pre>
<h3>Read: Recuperando Dados</h3>
<p>Leitura é onde GORM brilha. Ele oferece uma API fluent para consultas:</p>
<pre><code class="language-go">var user models.User
// Buscar por chave primária
db.First(&user, 1) // SELECT * FROM users WHERE id = 1
// Buscar com condição
db.Where("email = ?", "alice@example.com").First(&user)
// Buscar múltiplos registros
var users []models.User
db.Where("age > ?", 25).Find(&users)
// Com ordenação e limite
db.Order("created_at DESC").Limit(10).Find(&users)
// Selecionar colunas específicas
db.Select("id", "name").Find(&users)</code></pre>
<p>O padrão é sempre: <code>db.Where(...).Order(...).Limit(...).Find(&variable)</code>. A variável destino recebe os resultados — use um valor único para <code>First</code> ou uma slice para <code>Find</code>.</p>
<p>Caso nenhum resultado seja encontrado, <code>result.Error</code> conterá <code>gorm.ErrRecordNotFound</code>. Sempre verifique:</p>
<pre><code class="language-go">result := db.First(&user, 1)
if result.Error == gorm.ErrRecordNotFound {
fmt.Println("Usuário não encontrado")
} else if result.Error != nil {
panic(result.Error)
}</code></pre>
<h3>Update: Alterando Dados</h3>
<p>Atualizar dados requer cuidado para não fazer mudanças não intencionais. GORM oferece várias formas:</p>
<pre><code class="language-go">// Atualizar um registro específico
db.Model(&models.User{}).Where("id = ?", 1).Update("name", "Alice Updated")
// Atualizar múltiplos campos
db.Model(&models.User{}).Where("id = ?", 1).Updates(models.User{
Name: "Alice",
Age: 29,
})
// Atualizar usando um map (para campos dinâmicos)
db.Model(&models.User{}).Where("id = ?", 1).Updates(map[string]interface{}{
"name": "Alice",
"age": 30,
})</code></pre>
<p>Note que <code>Update</code> (singular) usa um valor e chave, enquanto <code>Updates</code> (plural) usa uma struct ou map. <code>Model</code> especifica qual tipo de dados você está atualizando e é geralmente necessário.</p>
<h3>Delete: Removendo Dados</h3>
<p>Deletar também requer cautela. Se seu modelo tem <code>DeletedAt</code>, GORM faz soft delete por padrão:</p>
<pre><code class="language-go">// Soft delete (marca como deletado, não remove fisicamente)
db.Delete(&models.User{}, 1)
// Hard delete (remove fisicamente)
db.Unscoped().Delete(&models.User{}, 1)
// Queries automaticamente excluem soft-deleted
var users []models.User
db.Find(&users) // Não inclui deletados
// Para incluir deletados
db.Unscoped().Find(&users)</code></pre>
<h2>Relacionamentos: Conectando Dados entre Tabelas</h2>
<h3>One-to-Many: Um para Muitos</h3>
<p>O relacionamento mais comum é one-to-many. Um usuário tem muitos posts. Defina assim:</p>
<pre><code class="language-go">type User struct {
ID uint
Name string
Posts []Post // Slice de posts
}
type Post struct {
ID uint
Title string
UserID uint // Foreign key
User User // Relacionamento inverso
}</code></pre>
<p>Quando você chama <code>AutoMigrate</code> com esses modelos, GORM automaticamente adiciona a coluna <code>UserID</code> em <code>Post</code> e cria a constraint de chave estrangeira. <code>UserID</code> mapeia para <code>User.ID</code> por convenção.</p>
<p>Ao carregar dados, use <code>Preload</code> para popular relacionamentos:</p>
<pre><code class="language-go">var user models.User
// Carrega user E seus posts em uma query
db.Preload("Posts").First(&user, 1)
// Agora user.Posts está populado
for _, post := range user.Posts {
fmt.Println(post.Title)
}
// Pode fazer preload condicional
db.Preload("Posts", "published = ?", true).First(&user, 1)</code></pre>
<p><code>Preload</code> carrega dados em queries separadas (mais eficiente para aplicações Go). <code>Joins</code> faria um SQL JOIN se você preferir.</p>
<h3>Many-to-Many: Muitos para Muitos</h3>
<p>Quando duas entidades têm relação muitos-para-muitos (ex: usuários e papéis), você precisa de uma tabela de junção:</p>
<pre><code class="language-go">type User struct {
ID uint
Name string
Roles []Role gorm:"many2many:user_roles"
}
type Role struct {
ID uint
Name string
}</code></pre>
<p>GORM cria a tabela <code>user_roles</code> automaticamente com colunas <code>user_id</code> e <code>role_id</code>. Para associar dados:</p>
<pre><code class="language-go">var user models.User
db.First(&user, 1)
var role models.Role
db.First(&role, 1)
// Associar role a user
db.Model(&user).Association("Roles").Append(&role)
// Carregar roles
db.Preload("Roles").First(&user, 1)
// Desassociar
db.Model(&user).Association("Roles").Delete(&role)</code></pre>
<h3>Belongs-to: Pertence a</h3>
<p><code>BelongsTo</code> define o inverso de um relacionamento. Um post pertence a um usuário:</p>
<pre><code class="language-go">type Post struct {
ID uint
Title string
UserID uint // Foreign key
User User // Carregado via Preload
}
type User struct {
ID uint
Name string
}</code></pre>
<p>Quando você faz <code>db.Preload("User").First(&post, 1)</code>, GORM carrega o usuário associado:</p>
<pre><code class="language-go">var post models.Post
db.Preload("User").First(&post, 1)
fmt.Println(post.User.Name) // Nome do usuário</code></pre>
<h3>Exemplo Prático Completo: Blog com Posts e Comentários</h3>
<p>Vamos criar um exemplo real que combina conceitos:</p>
<pre><code class="language-go">package models
import "time"
type User struct {
ID uint
Name string
Email string gorm:"uniqueIndex"
Posts []Post
CreatedAt time.Time
UpdatedAt time.Time
}
type Post struct {
ID uint
Title string
Content string
UserID uint // Foreign key
User User // Belongs to
Comments []Comment // One-to-many
CreatedAt time.Time
}
type Comment struct {
ID uint
Text string
PostID uint
Post Post
UserID uint
User User
CreatedAt time.Time
}</code></pre>
<p>Operações:</p>
<pre><code class="language-go">// Criar usuário com posts
user := models.User{Name: "John", Email: "john@example.com"}
db.Create(&user)
post := models.Post{Title: "Hello World", Content: "...", UserID: user.ID}
db.Create(&post)
// Carregar tudo com relacionamentos
var fullUser models.User
db.Preload("Posts", func(db gorm.DB) gorm.DB {
return db.Preload("Comments").Order("created_at DESC")
}).First(&fullUser, user.ID)
// Agora temos user com todos posts, e cada post com seus comentários
for _, post := range fullUser.Posts {
fmt.Printf("Post: %s\n", post.Title)
for _, comment := range post.Comments {
fmt.Printf(" Comment: %s\n", comment.Text)
}
}</code></pre>
<h2>Hooks e Callbacks: Automatizando Comportamentos</h2>
<p>GORM oferece hooks que permitem executar código em momentos específicos do ciclo de vida de um modelo. Use isso para validações, auditorias e transformações de dados:</p>
<pre><code class="language-go">func (u User) BeforeSave(tx gorm.DB) error {
// Validação antes de salvar
if len(u.Name) == 0 {
return fmt.Errorf("name cannot be empty")
}
return nil
}
func (u User) AfterCreate(tx gorm.DB) error {
// Executado após criar
fmt.Printf("Usuário %s criado com ID %d\n", u.Name, u.ID)
return nil
}
func (p Post) BeforeDelete(tx gorm.DB) error {
// Limpar antes de deletar
fmt.Printf("Deletando post %s\n", p.Title)
return nil
}</code></pre>
<p>Hooks são poderosos mas use com moderação — lógica complexa em hooks torna o código difícil de debugar. Mantenha hooks simples e coloque lógica complexa em services/repositories.</p>
<h2>Tratamento de Erros e Debugging</h2>
<p>Toda operação GORM retorna <code>*gorm.DB</code> com um campo <code>Error</code>. Sempre verifique:</p>
<pre><code class="language-go">result := db.Create(&user)
if result.Error != nil {
// Pode ser violação de constraint, erro de conexão, etc
log.Printf("Erro ao criar usuário: %v", result.Error)
// Trate apropriadamente
}</code></pre>
<p>Para debugging, ative o logger do GORM:</p>
<pre><code class="language-go">import "gorm.io/logger"
db, _ := gorm.Open(
postgres.Open(dsn),
&gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
},
)</code></pre>
<p>Isso imprime todas as queries executadas, útil para otimizar e entender o que está acontecendo. Em produção, use um logger mais sofisticado com níveis configuráveis.</p>
<h2>Boas Práticas e Performance</h2>
<h3>N+1 Queries: O Vilão Comum</h3>
<p>Um erro clássico é carregar dados sem preload:</p>
<pre><code class="language-go">// RUIM: N+1 queries
var users []models.User
db.Find(&users)
for _, user := range users {
db.Find(&user.Posts) // Query adicional para cada usuário!
}
// BOM: Uma query com preload
var users []models.User
db.Preload("Posts").Find(&users)</code></pre>
<p>Sempre use <code>Preload</code> quando souber que precisará de relacionamentos.</p>
<h3>Índices e Constraints</h3>
<p>Defina índices nas colunas que filtra frequentemente:</p>
<pre><code class="language-go">type Post struct {
ID uint
Title string
UserID uint gorm:"index" // Índice simples
Status string gorm:"index:idx_status_user,type:btree" // Índice composto
CreatedAt time.Time gorm:"index:,type:btree,sort:desc"
}</code></pre>
<p>Índices melhoram performance de leitura mas custam espaço e desaceleram escritas. Use sabiamente.</p>
<h3>Connection Pooling</h3>
<p>GORM reutiliza a mesma conexão, mas você pode configurar o pool:</p>
<pre><code class="language-go">sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)</code></pre>
<p>Essas configurações dependem da sua aplicação e banco de dados. Monitore métricas de conexão em produção.</p>
<h2>Conclusão</h2>
<p>Você agora domina os conceitos fundamentais de GORM: (1) <strong>Migrations</strong> são seu instrumento para versionamento e auditoria de esquemas — use <code>AutoMigrate</code> para desenvolvimento e migrations customizadas para produção com precisão. (2) <strong>Relacionamentos</strong> (one-to-many, many-to-many, belongs-to) permitem modelar dados complexos de forma idiomática em Go — sempre use <code>Preload</code> para evitar N+1 queries. (3) <strong>CRUD operations</strong> em GORM são simples e seguras contra SQL injection, mas exigem compreensão de quando usar <code>Create</code>, <code>First</code>, <code>Find</code>, <code>Update</code> e <code>Delete</code> e sempre verificar <code>result.Error</code>.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://gorm.io" target="_blank" rel="noopener noreferrer">GORM Official Documentation</a></li>
<li><a href="https://gorm.io/docs/associations.html" target="_blank" rel="noopener noreferrer">GORM Guides - Associations</a></li>
<li><a href="https://gorm.io/docs/migration.html" target="_blank" rel="noopener noreferrer">GORM Guides - Migration</a></li>
<li><a href="https://github.com/lib/pq" target="_blank" rel="noopener noreferrer">Go PostgreSQL driver</a></li>
<li><a href="https://golang.org/doc/effective_go#interfaces_and_types" target="_blank" rel="noopener noreferrer">Effective Go - Database</a></li>
</ul>
<p><!-- FIM --></p>