Especialização em Backend com Go & Gin • Demonstrações Práticas Compiláveis


💻 Código de Exemplo 01 — Domínio e Repository Pattern

package domain

// Produto é a entidade de domínio: puro Go, sem tags de ORM obrigatórias
// e sem qualquer dependência de framework web ou banco de dados.
type Produto struct {
	ID    uint
	Nome  string
	Preco float64
}

// ProdutoRepository é o contrato que a camada de service depende — não a
// implementação concreta. Isso viabiliza mocks em testes unitários.
type ProdutoRepository interface {
	BuscarPorID(id uint) (*Produto, error)
	Salvar(p *Produto) error
	Listar() ([]Produto, error)
}

💻 Código de Exemplo 02 — Service com Regra de Negócio + Repository GORM

package usecase

import (
	"errors"

	"gorm.io/gorm"
)

var ErrDescontoInvalido = errors.New("desconto deve estar entre 1% e 50%")

type ProdutoService struct {
	repo ProdutoRepository // interface, não struct concreta
}

func NewProdutoService(repo ProdutoRepository) *ProdutoService {
	return &ProdutoService{repo: repo}
}

func (s *ProdutoService) AplicarDesconto(id uint, pct float64) (*Produto, error) {
	if pct <= 0 || pct > 50 {
		return nil, ErrDescontoInvalido
	}
	p, err := s.repo.BuscarPorID(id)
	if err != nil {
		return nil, err
	}
	p.Preco -= p.Preco * (pct / 100)
	if err := s.repo.Salvar(p); err != nil {
		return nil, err
	}
	return p, nil
}

// gormProdutoRepository é a única camada que conhece o GORM.
type gormProdutoRepository struct{ db *gorm.DB }

func NewGormProdutoRepository(db *gorm.DB) *gormProdutoRepository {
	return &gormProdutoRepository{db: db}
}

func (r *gormProdutoRepository) BuscarPorID(id uint) (*Produto, error) {
	var p Produto
	if err := r.db.First(&p, id).Error; err != nil {
		return nil, err
	}
	return &p, nil
}

func (r *gormProdutoRepository) Salvar(p *Produto) error {
	return r.db.Save(p).Error
}

func (r *gormProdutoRepository) Listar() ([]Produto, error) {
	var lista []Produto
	err := r.db.Find(&lista).Error
	return lista, err
}

💻 Código de Exemplo 03 — Handler Gin + Composição Raiz

package main

import (
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
)

type ProdutoHandler struct {
	service *ProdutoService
}

func NewProdutoHandler(s *ProdutoService) *ProdutoHandler {
	return &ProdutoHandler{service: s}
}

// AplicarDesconto traduz HTTP <-> domínio: parseia o parâmetro, delega a
// regra de negócio ao service e formata o resultado como JSON.
func (h *ProdutoHandler) AplicarDesconto(c *gin.Context) {
	id, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erro": "id inválido"})
		return
	}
	prod, err := h.service.AplicarDesconto(uint(id), 10.0)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erro": err.Error()})
		return
	}
	c.JSON(http.StatusOK, prod)
}

// SetupApp é a composição raiz: monta Repository -> Service -> Handler
// sem que nenhuma camada instancie sua própria dependência.
func SetupApp(db *gorm.DB) *ProdutoHandler {
	repo := NewGormProdutoRepository(db)
	service := NewProdutoService(repo)
	return NewProdutoHandler(service)
}