Capítulo 05: Mapeamento de Modelos e Conexão de Banco de Dados com GORM
Especialização em Backend com Go & Gin • Gin Framework & Go 1.22+ • GORM, Clean Architecture, Concorrência e Alta Performance
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["Struct Go Declarativa + Tags gorm"] --> B["Driver de Banco (postgres, mysql, sqlite)"]
B --> C["gorm.Open(dialector, &gorm.Config{})"]
C --> D["*gorm.DB (Instância Central do ORM)"]
D --> E["db.DB() -> *sql.DB (Pool Nativo Go)"]
E --> F["SetMaxOpenConns(50)"]
E --> G["SetMaxIdleConns(15)"]
E --> H["SetConnMaxLifetime(30m)"]
E --> I["SetConnMaxIdleTime(5m)"]
D --> J["AutoMigrate(&Produto{})"]
subgraph HOOKS["Ciclo de Vida de Persistência (Hooks)"]
K["BeforeCreate(tx *gorm.DB)"]
L["AfterCreate(tx *gorm.DB)"]
M["BeforeUpdate(tx *gorm.DB)"]
K --> L
end
A --> HOOKS
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style D fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style E fill:#fce4ec,stroke:#e91e63,stroke-width:2px
style F fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
style G fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
style H fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
style I fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
style J fill:#fffde7,stroke:#fbc02d,stroke-width:2px
style K fill:#e0f2f1,stroke:#009688,stroke-width:2px
🏛️ 1. O Modelo de Persistência do GORM no Ecossistema Go
O GORM (versão 2) é o ORM mais difundido na comunidade Go corporativa. Ele mapeia structs tipadas para tabelas relacionais por meio de reflexão em tempo de compilação/execução, interpretando as anotações contidas nas tags gorm:"...".
Em Go, a struct base canônica é gorm.Model:
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
Ao embutir gorm.Model em sua entidade, o GORM gerencia automaticamente:
- Chave Primária Auto-incremental: Atribui o ID gerado pelo banco à struct após a inserção.
- Timestamps Automáticos: Preenche
CreatedAte atualizaUpdatedAtcom a data/hora UTC do sistema. - Soft Delete Nativo: A presença de
gorm.DeletedAtfaz com quedb.Delete()não execute umDELETE FROM, mas sim umUPDATE tabela SET deleted_at = NOW() WHERE id = ?. Todas as consultas subsequentes passam a filtrar automaticamente registros ondedeleted_at IS NULL.
Tags GORM Essenciais para Produção
| Tag GORM | Exemplo | Finalidade e Impacto no Banco de Dados |
|---|---|---|
primaryKey |
gorm:"primaryKey" |
Define a coluna como Primary Key. |
column |
gorm:"column:cod_cliente" |
Renomeia o campo no banco sem mudar o identificador Go. |
type |
gorm:"type:varchar(120)" |
Define explicitamente o tipo SQL no DDL. |
size |
gorm:"size:255" |
Define o comprimento de campos de texto ou arrays. |
not null |
gorm:"not null" |
Cria a restrição DDL de obrigatoriedade NOT NULL. |
uniqueIndex |
gorm:"uniqueIndex:idx_email" |
Cria índice com garantia de unicidade relacional. |
default |
gorm:"default:'ATIVO'" |
Atribui valor default no banco de dados. |
check |
gorm:"check:preco >= 0" |
Adiciona constraint SQL CHECK de integridade lógica. |
⚙️ 2. Gerenciamento do Connection Pool do database/sql
O objeto *gorm.DB é uma camada de conveniência em torno do pool de conexões padrão de Go (*sql.DB). Em servidores de alta concorrência com Gin, não configurar o pool de conexões resultará em esgotamento de descritores de arquivo ou sobrecarga de conexões no PostgreSQL/MySQL.
O acesso ao pool físico é feito via sqlDB, err := db.DB(). Os quatro parâmetros vitais são:
package database
import (
"log"
"sync"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var (
dbInstance *gorm.DB
once sync.Once
)
// ObterConexao implementa o padrão Singleton para reutilizar o pool de conexões
func ObterConexao(dsn string) (*gorm.DB, error) {
var initErr error
once.Do(func() {
// Abre o GORM com driver pgx do PostgreSQL
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
PrepareStmt: true, // Habilita cache de Prepared Statements no driver
SkipDefaultTransaction: true, // Reduz ~30% de overhead em operações simples de leitura
Logger: logger.Default.LogMode(logger.Warn),
})
if err != nil {
initErr = err
return
}
// Acessa o pool nativo do Go
sqlDB, err := db.DB()
if err != nil {
initErr = err
return
}
// 1. Limite máximo de conexões abertas simultaneamente (protege o banco)
sqlDB.SetMaxOpenConns(50)
// 2. Conexões ociosas mantidas abertas e prontas na memória (evita TCP handshake)
sqlDB.SetMaxIdleConns(15)
// 3. Tempo de vida máximo de qualquer conexão antes de ser reciclada
sqlDB.SetConnMaxLifetime(30 * time.Minute)
// 4. Tempo máximo que uma conexão pode ficar ociosa antes de ser fechada
sqlDB.SetConnMaxIdleTime(5 * time.Minute)
dbInstance = db
log.Println("[DATABASE] Pool de conexões GORM/PostgreSQL inicializado com sucesso.")
})
return dbInstance, initErr
}
🔍 3. Hooks de Ciclo de Vida: Execução Atômica de Regras de Negócio
O GORM suporta hooks que interceptam operações de escrita e executam lógica de validação ou saneamento dentro da mesma transação SQL:
package models
import (
"errors"
"strings"
"gorm.io/gorm"
)
type Produto struct {
gorm.Model
Nome string `gorm:"size:150;not null" json:"nome"`
SKU string `gorm:"size:50;uniqueIndex;not null" json:"sku"`
Preco float64 `gorm:"type:decimal(10,2);not null;check:preco > 0" json:"preco"`
Categoria string `gorm:"size:60;index;not null" json:"categoria"`
}
// BeforeCreate é executado antes do INSERT físico no banco
func (p *Produto) BeforeCreate(tx *gorm.DB) error {
p.SKU = strings.ToUpper(strings.TrimSpace(p.SKU))
p.Nome = strings.TrimSpace(p.Nome)
if len(p.SKU) < 6 {
return errors.New("o SKU deve conter ao menos 6 caracteres alfanuméricos")
}
return nil
}
// BeforeUpdate é executado antes de qualquer comando UPDATE
func (p *Produto) BeforeUpdate(tx *gorm.DB) error {
p.Nome = strings.TrimSpace(p.Nome)
if p.Preco <= 0 {
return errors.New("o preço do produto deve ser estritamente positivo")
}
return nil
}
⚠️ 4. Boas Práticas e Cuidados com AutoMigrate
AutoMigrateNão Deleta Nem Renomeia Colunas: Para evitar acidentes de perda de dados, oAutoMigrateapenas adiciona novas tabelas, novas colunas e novos índices. Se você remover um campo da struct Go, a coluna correspondente continuará intacta no banco de dados físico.SkipDefaultTransaction: true: Por padrão, o GORM executa todas as operações de escrita dentro de uma transação implícita. Desativar isso para cenários de alta vazão traz ganhos expressivos de throughput, reservando transações explícitas apenas para fluxos de negócio que exigem atomicidade (ver Capítulo 06).