Go: RESTful - exemplo com persistência usando gorm
Afluentes: Sistemas Distribuídos e Mobile
Biblioteca GORM
No exemplo anterior trabalhamos com a biblioteca nativa de acesso ao banco de dados postgres (pgx). Ela tem suas vantagens, tais como:
- Desempenho: acesso direto e eficiente ao banco de dados, com menor overhead.
- Controle: maior controle sobre as consultas SQL e as interações com o banco de dados.
- Leveza: menor consumo de memória e processamento.
Mas ela também algumas desvantagens:
- Complexidade: requer mais código para operações comuns.
- Erros: maior propensão a erros manuais, especialmente em consultas SQL complexas.
Existem algumas bibliotecas que tentam abstrair o acesso ao banco de dados, tal como a GORM. O uso do GORM trás algumas vantagens como:
- Facilidade de uso com uma abstração de alto nível para manipulação de dados, tornando a escrita de consultas complexas mais fácil e menos propensa a erros.
- Migrações automáticas das structs do código para os esquemas no banco de dados.
- Relacionamentos entre tabelas facilitado (n:1, n:m).
- Aumenta a produtividade ao reduzir a quantidade de código necessário para operações CRUD.
Mas usar GORM também trás algumas desvantagens, como:
- A abstração e as funcionalidades adicionais podem resultar em maior consumo de memória e CPU.
- Diminuição do desempenho por não ser tão rápido quanto a biblioteca nativa devido à camada de abstração.
Dessa forma, a escolha da biblioteca GORM ou pgx deve ser baseada nas necessidades do projeto. Se o desempenho e controle fino são cruciais para o projeto, a pgx é a melhor escolha, mas se a produtividade e facilidade são mais importantes, GORN pode ser melhor.
Segue abaixo um exemplo de api com uso de GORM
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/rs/cors"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
/*
Definição da tabela users com criação dos atributos com seus nomes e respectivos
domínios de valores.
Nas marcações ao lado dos atributos, além de definir informações para a gorm, também
passando informações para depois criar nosso objeto json para transferência
*/
type User struct {
Id uint `gorm:"primaryKey" json:"id"` // chave primária
Name string `gorm:"type:varchar(50)" json:"name"` // informamos o tipo varchar(50)
Email string `gorm:"type:varchar(50)" json:"email"`
}
func main() {
// String de conexão para PostgreSQL
strconn := "host=localhost user=saulo password=1234 dbname=usersapi sslmode=disable"
// Abrir conexão com o banco de dados PostgreSQL
db, err := gorm.Open(postgres.Open(strconn), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
// Migrar a struct para o esquema (tabela), criando-a caso não exista
if err := db.AutoMigrate(&User{}); err != nil {
log.Fatal(err)
}
// Iniciamos a configuração do nosso serviço web (api)
router := http.NewServeMux()
// Tratamento do POST
router.HandleFunc("POST /users", func(w http.ResponseWriter, r *http.Request) {
var user User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Fazemos o INSERT no banco de dados.
if err := db.Create(&user).Error; err != nil {
log.Println(err) // Registra o erro
http.Error(w, "Error creating user", http.StatusInternalServerError)
return
}
})
// Tratamento do GET sem parâmetros. Retorna todos os registros
router.HandleFunc("GET /users", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
var users []User
// Operação SELECT * FROM users;
if err := db.Find(&users).Error; err != nil {
log.Println(err) // Registra o erro
http.Error(w, "Error creating user", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(users)
})
// Tratamento do GET com parâmetro, retornando um registro pelo Id
router.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
id := r.PathValue("id")
var user User
// Operação SELECT * FROM users WHERE id=?
// Veja que passamos o id como parâmetro.
if err := db.First(&user, id).Error; err != nil { // buscar pelo Id
log.Println(err) // Registra o erro
http.Error(w, "Error creating user", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(user)
})
// Tratamento do PUT. Não passamos parâmetro, mas usamos a chave que vem
// junto no objeto JSON.
router.HandleFunc("PUT /users", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
var user User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Criamos um novo objeto com as informações que serão alteradas
newUser := User{Name: user.Name, Email: user.Email}
// Executamos a operação UPDATE com as novas informações e WHERE id=?
if err := db.Model(&User{}).Where("id = ?", user.Id).Updates(newUser).Error; err != nil {
log.Println(err) // Registra o erro
http.Error(w, "Error creating user", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode("ok")
})
// Tratamento da operação DELETE. Passamos o id como parâmetro
router.HandleFunc("DELETE /users/{id}", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
id := r.PathValue("id")
var user User
// Informamos o id do que queremos excluir.
if err := db.Delete(&user, id).Error; err != nil {
log.Println(err) // Registra o erro
http.Error(w, "Error creating user", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode("ok")
})
// Libera tudo do cors
c := cors.AllowAll()
// Coloca o servidor no modo listen, aguardando conexões
err = http.ListenAndServe("localhost:8080", c.Handler(router))
if err != nil {
fmt.Println(err.Error())
}
}
Usando SQLite
Para usar SQLite em vez de PostgreSQL no seu programa Go com GORM, você precisa fazer algumas modificações simples no código. Substitua o driver postgres pelo driver sqlite e ajuste a string de conexão.
Aqui está como modificar o seu código para usar SQLite:
- Importe o driver SQLite do GORM (gorm.io/driver/sqlite).
- Substitua o postgres.Open por sqlite.Open, fornecendo o nome do arquivo de banco de dados SQLite.
import (
//...
"gorm.io/driver/sqlite"
//...
)
//...
func main() {
// Conexão com o banco de dados SQLite (cria o arquivo se não existir)
db, err := gorm.Open(sqlite.Open("usersapi.db"), &gorm.Config{})
//...