💻 Exemplos: Capítulo 20: Projeto Corporativo: Microsserviço de Alta Vazão com Auditoria JSON
Especialização em Backend com Go & Gin • Demonstrações Práticas Compiláveis
💻 Código de Exemplo 01 — Domínio e Pipeline de Ingestão Assíncrona em Lote
package domain
import "time"
// PedidoAltaVazao é a entidade de domínio: sem tags gin, sem SQL, sem
// dependência de framework — apenas a regra de negócio pura.
type PedidoAltaVazao struct {
ID uint `json:"id"`
ClienteID uint `json:"cliente_id"`
Valor float64 `json:"valor"`
Status string `json:"status"`
CriadoEm time.Time `json:"criado_em"`
}
func NovoPedido(clienteID uint, valor float64) PedidoAltaVazao {
return PedidoAltaVazao{
ClienteID: clienteID,
Valor: valor,
Status: "recebido",
CriadoEm: time.Now().UTC(),
}
}
package usecase
import (
"log"
"sync"
"time"
"meuservico/domain"
)
// PersistidorLote é a única dependência externa do ingestor — implementada
// pela camada repository (GORM), mantendo o usecase agnóstico de banco.
type PersistidorLote interface {
SalvarLote(pedidos []domain.PedidoAltaVazao) error
}
// BatchIngestor absorve picos de tráfego em um canal bufferizado e persiste
// em lote por gatilho duplo: tamanho do lote OU intervalo de tempo, o que
// vier primeiro — evita tanto sobrecarga do banco quanto dados "presos".
type BatchIngestor struct {
canal chan domain.PedidoAltaVazao
persistidor PersistidorLote
tamanhoMaximo int
intervaloFlush time.Duration
wg sync.WaitGroup
}
func NovoIngestor(p PersistidorLote, bufferSize, tamanhoLote int, flush time.Duration) *BatchIngestor {
return &BatchIngestor{
canal: make(chan domain.PedidoAltaVazao, bufferSize),
persistidor: p,
tamanhoMaximo: tamanhoLote,
intervaloFlush: flush,
}
}
// Ingerir enfileira um evento sem bloquear o handler HTTP (desde que o
// buffer não esteja saturado); retorna erro se o canal estiver cheio,
// permitindo que o handler responda 503 em vez de travar indefinidamente.
func (b *BatchIngestor) Ingerir(p domain.PedidoAltaVazao) error {
select {
case b.canal <- p:
return nil
default:
return ErrBufferCheio
}
}
func (b *BatchIngestor) IniciarProcessadorLote() {
b.wg.Add(1)
go func() {
defer b.wg.Done()
lote := make([]domain.PedidoAltaVazao, 0, b.tamanhoMaximo)
ticker := time.NewTicker(b.intervaloFlush)
defer ticker.Stop()
for {
select {
case evento, ok := <-b.canal:
if !ok {
b.flush(&lote)
return
}
lote = append(lote, evento)
if len(lote) >= b.tamanhoMaximo {
b.flush(&lote)
}
case <-ticker.C:
b.flush(&lote)
}
}
}()
}
func (b *BatchIngestor) flush(lote *[]domain.PedidoAltaVazao) {
if len(*lote) == 0 {
return
}
if err := b.persistidor.SalvarLote(*lote); err != nil {
log.Printf("erro ao persistir lote de %d itens: %v", len(*lote), err)
}
*lote = (*lote)[:0]
}
// Fechar drena o canal e aguarda o último flush antes de retornar —
// usado durante o graceful shutdown para não perder eventos em memória.
func (b *BatchIngestor) Fechar() {
close(b.canal)
b.wg.Wait()
}
var ErrBufferCheio = &erroIngestao{"buffer de ingestão cheio, tente novamente"}
type erroIngestao struct{ msg string }
func (e *erroIngestao) Error() string { return e.msg }
💻 Código de Exemplo 02 — Trilha de Auditoria Assinada com HMAC-SHA256
package auditoria
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"time"
)
type EventoAuditoria struct {
Timestamp int64 `json:"timestamp"`
Operacao string `json:"operacao"`
Detalhe string `json:"detalhe"`
Assinatura string `json:"assinatura"`
}
// AssinarEvento gera um evento de auditoria com HMAC-SHA256, garantindo que
// qualquer alteração posterior do registro no banco seja detectável.
func AssinarEvento(operacao, detalhe string, chaveSecreta []byte) EventoAuditoria {
ts := time.Now().UTC().Unix()
mensagem := fmt.Sprintf("%d:%s:%s", ts, operacao, detalhe)
h := hmac.New(sha256.New, chaveSecreta)
h.Write([]byte(mensagem))
assinatura := hex.EncodeToString(h.Sum(nil))
return EventoAuditoria{Timestamp: ts, Operacao: operacao, Detalhe: detalhe, Assinatura: assinatura}
}
// VerificarIntegridade recalcula o HMAC e compara com hmac.Equal — nunca com
// "==" direto, que é vulnerável a timing attacks por comparar byte a byte
// com retorno antecipado na primeira diferença.
func VerificarIntegridade(evento EventoAuditoria, chaveSecreta []byte) bool {
mensagem := fmt.Sprintf("%d:%s:%s", evento.Timestamp, evento.Operacao, evento.Detalhe)
h := hmac.New(sha256.New, chaveSecreta)
h.Write([]byte(mensagem))
assinaturaEsperada := hex.EncodeToString(h.Sum(nil))
return hmac.Equal([]byte(assinaturaEsperada), []byte(evento.Assinatura))
}
💻 Código de Exemplo 03 — main.go: Wiring Completo com Graceful Shutdown
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus/promhttp"
"meuservico/domain"
"meuservico/usecase"
)
type persistidorGorm struct{}
func (persistidorGorm) SalvarLote(pedidos []domain.PedidoAltaVazao) error {
// db.CreateInBatches(&pedidos, 500) em uma implementação real com GORM
log.Printf("lote persistido: %d pedidos", len(pedidos))
return nil
}
func main() {
ingestor := usecase.NovoIngestor(persistidorGorm{}, 10_000, 500, 500*time.Millisecond)
ingestor.IniciarProcessadorLote()
router := gin.New()
router.Use(gin.Recovery())
router.GET("/metrics", gin.WrapH(promhttp.Handler()))
router.POST("/pedidos", func(c *gin.Context) {
var req struct {
ClienteID uint `json:"cliente_id" binding:"required"`
Valor float64 `json:"valor" binding:"required,gt=0"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"erro": err.Error()})
return
}
if err := ingestor.Ingerir(domain.NovoPedido(req.ClienteID, req.Valor)); err != nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"erro": err.Error()})
return
}
c.JSON(http.StatusAccepted, gin.H{"status": "aceito"})
})
srv := &http.Server{Addr: ":8080", Handler: router}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("erro ao iniciar servidor: %v", err)
}
}()
log.Println("servidor de alta vazão escutando em :8080")
// Graceful shutdown: aguarda SIGINT/SIGTERM do orquestrador (ex: Kubernetes)
sinal := make(chan os.Signal, 1)
signal.Notify(sinal, syscall.SIGINT, syscall.SIGTERM)
<-sinal
log.Println("sinal de encerramento recebido, drenando requisições em andamento...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Printf("shutdown forçado após timeout: %v", err)
}
ingestor.Fechar() // drena o buffer de ingestão antes de encerrar o processo
log.Println("encerramento concluído com sucesso.")
}