ORMs e query builders (visão geral)
Esta aula apresenta uma visão geral sobre ORMs e query builders em Go, comparando abordagens como database/sql puro, GORM e sqlc. Discute trade-offs, critérios de escolha e boas práticas para decidir quando usar cada ferramenta em projetos reais.
Bem-vindo à aula 65 do nosso curso de Go! Hoje vamos mergulhar em um dos temas mais debatidos no desenvolvimento backend: como interagir com bancos de dados relacionais. Em Go, temos uma variedade de ferramentas, desde o pacote padrão database/sql até ORMs completos como GORM e geradores de código como sqlc. O objetivo desta aula é fornecer uma visão geral clara dessas abordagens, seus prós e contras, e quando cada uma é mais adequada.
Vamos explorar três caminhos principais: o uso de database/sql puro, que é a base de tudo; o GORM, um ORM completo; e o sqlc, que gera código seguro e tipado a partir de consultas SQL. Ao final, você terá um entendimento sólido para tomar decisões informadas em seus próprios projetos.
sqlc, GORM (visão geral)
GORM é um ORM (Object-Relational Mapping) muito popular em Go. Ele permite que você interaja com o banco de dados usando estruturas (structs) Go, abstraindo o SQL. Com GORM, você pode realizar operações CRUD, migrações, associações, etc., sem escrever SQL manualmente. Ele é excelente para desenvolvimento rápido e para quem prefere trabalhar com modelos Go.
sqlc, por outro lado, não é um ORM tradicional. Ele é um gerador de código que lê suas consultas SQL e gera funções Go tipadas que executam essas consultas. Você escreve SQL puro, e o sqlc gera código com tipos seguros, evitando erros de runtime e melhorando a manutenibilidade. É uma abordagem que combina a flexibilidade do SQL com a segurança de tipos do Go.
Vejamos um exemplo simples de como seria usar GORM para criar e consultar um usuário:
import (
"gorm.io/gorm"
"gorm.io/driver/sqlite"
)
type User struct {
ID uint
Name string
Age int
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil { panic(err) }
// Migração automática
db.AutoMigrate(&User{})
// Criar
db.Create(&User{Name: "Alice", Age: 30})
// Consultar
var user User
db.First(&user, 1)
fmt.Printf("%+v\n", user)
}
Com sqlc, você escreveria um arquivo SQL como:
-- name: GetUser :one
SELECT * FROM users WHERE id = ?;
E depois geraria o código Go com sqlc generate, obtendo uma função GetUser(ctx, id) (User, error).
Trade-offs
Cada abordagem tem seus trade-offs em termos de desempenho, segurança, produtividade e curva de aprendizado.
GORM: oferece alta produtividade, mas pode ter overhead de performance devido à reflexão e à geração dinâmica de SQL. Além disso, pode esconder detalhes do SQL, tornando consultas complexas mais difíceis de otimizar. No entanto, para CRUD simples, é muito eficiente.
sqlc: gera código sem reflexão, o que é mais rápido que GORM. A segurança de tipos é excelente, pois consultas são validadas em tempo de compilação. Porém, você precisa escrever SQL manualmente, o que pode ser um obstáculo para quem prefere abstração. Além disso, sqlc não gera código para consultas dinâmicas, exigindo que você escreva SQL explícito.
database/sql puro: oferece o máximo controle e desempenho, mas você precisa gerenciar manualmente a conexão, o scan de resultados e a construção de consultas, o que é mais trabalhoso e propenso a erros.
Em termos de segurança, todos podem prevenir SQL injection se usados corretamente, mas sqlc e database/sql com placeholders são mais explícitos.
Quando usar
A escolha depende do contexto do projeto.
- GORM é ideal para protótipos, projetos pequenos a médios, ou quando a equipe prefere trabalhar com modelos Go e não quer se preocupar com SQL.
- sqlc é excelente para projetos que exigem alta performance, consultas complexas e segurança de tipos. É muito usado em microsserviços e sistemas críticos.
- database/sql puro é adequado para projetos que precisam de controle total sobre o SQL e o acesso ao banco, ou quando a equipe é experiente e deseja evitar dependências externas.
Considere também a complexidade das consultas: se você precisa de joins complexos ou subqueries, sqlc e database/sql puro são mais flexíveis. Para CRUD simples, GORM pode acelerar o desenvolvimento.
database/sql puro
O pacote database/sql é a base para qualquer interação com bancos relacionais em Go. Ele fornece uma interface genérica para SQL, mas você precisa usar drivers específicos (ex.: github.com/go-sql-driver/mysql, github.com/lib/pq para PostgreSQL).
Um exemplo típico de uso:
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
"log"
)
func main() {
db, err := sql.Open("sqlite3", "test.db")
if err != nil { log.Fatal(err) }
defer db.Close()
// Criar tabela
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)`)
if err != nil { log.Fatal(err) }
// Inserir
_, err = db.Exec(`INSERT INTO users (name, age) VALUES (?, ?)`, "Alice", 30)
if err != nil { log.Fatal(err) }
// Consultar
rows, err := db.Query(`SELECT id, name, age FROM users`)
if err != nil { log.Fatal(err) }
defer rows.Close()
for rows.Next() {
var u struct { ID int; Name string; Age int }
err = rows.Scan(&u.ID, &u.Name, &u.Age)
if err != nil { log.Fatal(err) }
fmt.Printf("%+v\n", u)
}
}
Note que você precisa gerenciar o ciclo de vida das linhas, fechar o rows, e tratar erros manualmente. Isso é mais verboso, mas dá controle total.
Boas práticas e observações finais
Ao escolher entre essas ferramentas, lembre-se de que não há uma solução única. Avalie os requisitos do projeto, a experiência da equipe e a manutenibilidade a longo prazo. Para projetos grandes, sqlc pode ser mais sustentável, mas GORM pode acelerar o início. Sempre prefira a simplicidade e a segurança, e não hesite em combinar abordagens (ex.: usar GORM para CRUD simples e sqlc para consultas complexas).
Referências
- GORM Documentation
- sqlc Documentation
- database/sql - Go Packages
- Go Database Documentation
- sqlx - Extensions to database/sql
- Go Wiki: SQL Drivers
Exercícios
- Explique as principais diferenças entre GORM e sqlc em termos de abordagem e segurança de tipos.
- Cite dois cenários em que GORM é mais adequado que sqlc, e dois em que sqlc é melhor.
- Escreva um código usando
database/sqlpara conectar a um banco SQLite, criar uma tabela 'produtos' (id, nome, preço) e inserir um produto. - Qual é a principal vantagem de usar sqlc em relação a GORM em termos de desempenho? Explique.
- Dê um exemplo de situação em que você escolheria
database/sqlpuro em vez de ORM ou query builder.
db, err := sql.Open("sqlite3", "loja.db")
if err != nil { log.Fatal(err) }
defer db.Close()
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS produtos (id INTEGER PRIMARY KEY, nome TEXT, preco REAL)`)
if err != nil { log.Fatal(err) }
_, err = db.Exec(`INSERT INTO produtos (nome, preco) VALUES (?, ?)`, "Caneta", 1.99)
if err != nil { log.Fatal(err) }
database/sql puro para evitar a abstração e o overhead.