Go: RESTful - exemplo com persistência usando gorm

De Aulas

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())
	}
}