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

Exercícios

  1. Explique as principais diferenças entre GORM e sqlc em termos de abordagem e segurança de tipos.
  2. ✓ Resposta: GORM é um ORM que mapeia structs Go para tabelas, gerando SQL automaticamente, mas usa reflexão e pode esconder detalhes. sqlc gera código a partir de consultas SQL escritas manualmente, garantindo tipagem segura e melhor desempenho, sem reflexão.
  3. Cite dois cenários em que GORM é mais adequado que sqlc, e dois em que sqlc é melhor.
  4. ✓ Resposta: GORM é mais adequado para prototipagem rápida, CRUD simples, e quando a equipe prefere não escrever SQL. sqlc é melhor para consultas complexas, alto desempenho, e quando se deseja segurança de tipos em tempo de compilação.
  5. Escreva um código usando database/sql para conectar a um banco SQLite, criar uma tabela 'produtos' (id, nome, preço) e inserir um produto.
  6. ✓ Resposta:
    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) }
    
  7. Qual é a principal vantagem de usar sqlc em relação a GORM em termos de desempenho? Explique.
  8. ✓ Resposta: A principal vantagem é que sqlc gera código sem reflexão, evitando o overhead de reflexão do GORM, o que torna as operações mais rápidas. Além disso, as consultas são pré-compiladas e tipadas, reduzindo o trabalho em runtime.
  9. Dê um exemplo de situação em que você escolheria database/sql puro em vez de ORM ou query builder.
  10. ✓ Resposta: Quando tenho consultas altamente otimizadas e específicas, ou quando preciso de controle fino sobre o SQL gerado, como em relatórios complexos, prefiro database/sql puro para evitar a abstração e o overhead.