💻 Exemplos: Capítulo 09: Clean Architecture em Go: Handlers, Services e Repositories
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)
}