Especialização em Backend com Go & Gin • Gin Framework & Go 1.22+ • GORM, Clean Architecture, Concorrência e Alta Performance


🗺️ Mapa Conceitual do Tópico

flowchart TD
    A["Cliente HTTP / Frontend"] --> B["Framework Gin (HTTP Delivery)"]
    
    subgraph DELIVERY["Camada 1: Handlers / Controllers"]
        B --> C["UsuarioHandler.Criar(c *gin.Context)"]
        C --> D["Validação de DTO de Entrada (ShouldBindJSON)"]
    end

    subgraph USE_CASES["Camada 2: Use Cases / Services"]
        D --> E["UsuarioService.CadastrarUsuario(ctx, dto)"]
        E --> F["Regras de Negócio: Hashing de Senha, Validação de Domínio"]
    end

    subgraph DOMAIN["Camada 3: Domínio Puro (Entities)"]
        G["models.Usuario (Entidade de Negócio)"]
    end

    subgraph REPOSITORIES["Camada 4: Repositories (Persistência)"]
        E --> H["Interface: UsuarioRepository (Ports)"]
        H --> I["Implementação Concreta: GormUsuarioRepository (Adapters)"]
        I --> J[("Banco de Dados PostgreSQL")]
    end

    E -.-> G
    I -.-> G

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style D fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style E fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style F fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style G fill:#fce4ec,stroke:#e91e63,stroke-width:2px
    style H fill:#fffde7,stroke:#fbc02d,stroke-width:2px
    style I fill:#e0f2f1,stroke:#009688,stroke-width:2px
    style J fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px

🏛️ 1. Princípios de Clean Architecture Idiomática em Go

Em sistemas de grande porte, acoplar a lógica de negócio diretamente aos handlers do Gin ou aos métodos do GORM cria uma base de código rígida, difícil de testar e dependente de bibliotecas externas. A Clean Architecture (Arquitetura Limpa / Portas e Adaptadores) preconiza que a regra de negócio central da empresa deve ser totalmente agnóstica a bancos de dados, frameworks web e bibliotecas de terceiros.

Em Go, a Clean Architecture estrutura-se em quatro camadas com dependências unidirecionais voltadas para dentro:

Camada Responsabilidade Central O que PODE Conhecer O que NÃO PODE Conhecer
Domain (Entities) Modelos de dados e regras de negócio essenciais Apenas a biblioteca padrão Go (time, errors) Nem Gin, nem GORM, nem SQL.
Repositories Interface de persistência (Port) e implementação GORM (Adapter) Entidades do Domínio e Drivers de Banco Não conhece Gin nem HTTP.
Services (Use Cases) Orquestração de casos de uso e regras de aplicação Repositórios via interfaces e Entidades Não conhece *gin.Context nem SQL.
Delivery (Handlers) Parse de requisições HTTP, headers, status codes e JSON DTOs e Interfaces dos Services Não acessa o banco de dados diretamente.

⚙️ 2. O Poder das Interfaces Implícitas de Go

Uma das características mais elegantes da linguagem Go é que interfaces são satisfeitas implicitamente: uma struct não precisa declarar implements UsuarioRepository. Basta que ela possua os métodos com a mesma assinatura para ser aceita como implementação válida. Isso viabiliza o princípio da Inversão de Dependência (IoC) sem acoplamento.

1. Interface do Repositório (Camada de Domínio/Portas)

package domain

import (
	"context"
	"time"
)

type Usuario struct {
	ID        uint
	Nome      string
	Email     string
	SenhaHash string
	CriadoEm  time.Time
}

type UsuarioRepository interface {
	Salvar(ctx context.Context, u *Usuario) error
	BuscarPorEmail(ctx context.Context, email string) (*Usuario, error)
}

2. Camada de Serviço (Regras de Aplicação)

package service

import (
	"context"
	"errors"
	"golang.org/x/crypto/bcrypt"
	"meu-modulo/domain"
)

type UsuarioService interface {
	Registrar(ctx context.Context, nome, email, senhaPlana string) (*domain.Usuario, error)
}

type usuarioService struct {
	repo domain.UsuarioRepository // Injeção de dependência via interface
}

func NovoUsuarioService(repo domain.UsuarioRepository) UsuarioService {
	return &usuarioService{repo: repo}
}

func (s *usuarioService) Registrar(ctx context.Context, nome, email, senhaPlana string) (*domain.Usuario, error) {
	// 1. Validação de Regra de Negócio
	existente, _ := s.repo.BuscarPorEmail(ctx, email)
	if existente != nil {
		return nil, errors.New("o e-mail informado já está cadastrado no sistema")
	}

	// 2. Criptografia da Senha
	hash, err := bcrypt.GenerateFromPassword([]byte(senhaPlana), bcrypt.DefaultCost)
	if err != nil {
		return nil, err
	}

	// 3. Criação da Entidade
	novo := &domain.Usuario{
		Nome:      nome,
		Email:     email,
		SenhaHash: string(hash),
	}

	if err := s.repo.Salvar(ctx, novo); err != nil {
		return nil, err
	}

	return novo, nil
}

🚀 3. Camada de Entrega: Handler Gin Desacoplado

O handler HTTP limita-se a receber o payload, validar o formato com Pydantic/Validator, invocar o serviço e serializar a resposta:

package handler

import (
	"net/http"
	"github.com/gin-gonic/gin"
	"meu-modulo/service"
)

type CriarUsuarioRequest struct {
	Nome  string `json:"nome" binding:"required,min=3"`
	Email string `json:"email" binding:"required,email"`
	Senha string `json:"senha" binding:"required,min=8"`
}

type UsuarioHandler struct {
	servico service.UsuarioService
}

func NovoUsuarioHandler(servico service.UsuarioService) *UsuarioHandler {
	return &UsuarioHandler{servico: servico}
}

func (h *UsuarioHandler) Registrar(c *gin.Context) {
	var req CriarUsuarioRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"erro": "payload inválido", "detalhes": err.Error()})
		return
	}

	usuario, err := h.servico.Registrar(c.Request.Context(), req.Nome, req.Email, req.Senha)
	if err != nil {
		c.JSON(http.StatusUnprocessableEntity, gin.H{"erro": err.Error()})
		return
	}

	c.JSON(http.StatusCreated, gin.H{
		"id":    usuario.ID,
		"nome":  usuario.Nome,
		"email": usuario.Email,
	})
}

🧪 4. Testabilidade Absoluta via Mocks de Interfaces

Com essa arquitetura, a camada de serviço pode ser testada em 100% dos seus caminhos de código em microssegundos, sem depender de banco de dados real nem de contêineres Docker:

// Exemplo de Mock de Repositório para Testes Unitários
type mockUsuarioRepo struct {
	usuarios map[string]*domain.Usuario
}

func (m *mockUsuarioRepo) Salvar(ctx context.Context, u *domain.Usuario) error {
	u.ID = 1
	m.usuarios[u.Email] = u
	return nil
}

func (m *mockUsuarioRepo) BuscarPorEmail(ctx context.Context, email string) (*domain.Usuario, error) {
	if u, ok := m.usuarios[email]; ok {
		return u, nil
	}
	return nil, nil
}