Go: Projeto Integrado: API de comércio eletrônico (Parte 3)

Última atualização: 2026-08-26

Temos a arquitetura, temos a segurança — agora, vamos colocá-la em operação.

Da primeira linha de código até a fase de produção: documentação Swagger, desligamento gradual, implantação no Docker, ajuste do pprof e monitoramento com o Prometheus. A API de comércio eletrônico do Bob está pronta para receber usuários reais.

1. Você aprenderá


2. História: A noite antes do lançamento

(1) Pontos críticos: falta de documentação, falta de monitoramento e implantação manual

A API de comércio eletrônico do Bob está totalmente funcional, mas a equipe de operações se recusou a aceitá-la:

“A equipe de operações disse: ‘Sem documentação da API, o front-end não consegue se integrar a ela. Sem verificações de integridade, não sabemos se o serviço está funcionando. Sem monitoramento, nem saberíamos se ele tivesse parado de funcionar. E ainda precisamos transferir manualmente os binários via SCP para a implantação — isso é muito primitivo.’”

Lista de verificação para o lançamento:

TEXT 📖 Somente leitura
❌ API docs → Frontend has to ask "what fields does this endpoint return?" every time
❌ Graceful shutdown → Kill process causes in-flight orders to be lost
❌ Docker deploy → SCP binary to server, start manually
❌ Performance monitoring → Don't know which part of the API is slow

(2) Objetivo da aula: Pronto para produção

TEXT 📖 Somente leitura
✅ Swagger docs → Frontend self-service browsing
✅ Graceful Shutdown → Signal handling + wait for connections to close
✅ Docker Compose → One command to start the full stack
✅ pprof + Prometheus → Performance visualization

3. Implementação completa

(1) ▶ Exemplo: Desligamento controlado

⚙️ Pré-requisito: Execute go get github.com/mattn/go-sqlite3 (requer CGO; como alternativa, use modernc.org/sqlite para um driver exclusivamente de Go)

GO 📖 Somente leitura
// cmd/server/main.go
package main

import (
    "context"
    "database/sql"
    "encoding/json"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "ecommerce/internal/handler"
    "ecommerce/internal/repository"
    "ecommerce/internal/service"
    _ "github.com/mattn/go-sqlite3"
)

func main() {
    db, err := sql.Open("sqlite3", "./ecommerce.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(10)
    db.SetConnMaxLifetime(5 * time.Minute)

    // Dependency injection
    userRepo := repository.NewUserRepository(db)
    productRepo := repository.NewProductRepository(db)
    orderRepo := repository.NewOrderRepository(db)

    userSvc := service.NewUserService(userRepo)
    productSvc := service.NewProductService(productRepo)
    orderSvc := service.NewOrderService(orderRepo, productRepo, userRepo)

    userHandler := handler.NewUserHandler(userSvc)
    productHandler := handler.NewProductHandler(productSvc)
    orderHandler := handler.NewOrderHandler(orderSvc)

    mux := http.NewServeMux()
    userHandler.Register(mux)
    productHandler.Register(mux)
    orderHandler.Register(mux)

    // Health check endpoint
    mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
    })

    mux.HandleFunc("GET /ready", func(w http.ResponseWriter, r *http.Request) {
        if err := db.Ping(); err != nil {
            w.WriteHeader(http.StatusServiceUnavailable)
            json.NewEncoder(w).Encode(map[string]string{"status": "not ready"})
            return
        }
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{"status": "ready"})
    })

    server := &http.Server{
        Addr:    ":8080",
        Handler: mux,
        // Timeout settings (prevent slowloris attacks)
        ReadTimeout:       10 * time.Second,
        WriteTimeout:      10 * time.Second,
        IdleTimeout:       60 * time.Second,
        ReadHeaderTimeout: 5 * time.Second,
    }

    // Graceful Shutdown
    go func() {
        sigCh := make(chan os.Signal, 1)
        signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
        sig := <-sigCh
        log.Printf("Received signal %v, shutting down...", sig)

        // Give in-flight requests up to 30 seconds to complete
        ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
        defer cancel()

        if err := server.Shutdown(ctx); err != nil {
            log.Printf("Forced shutdown: %v", err)
        }
    }()

    log.Println("E-commerce API listening on :8080")
    if err := server.ListenAndServe(); err != http.ErrServerClosed {
        log.Fatal(err)
    }
    log.Println("Server shut down safely")
}
76 linhas de lógica (limite de 40, somente leitura)

(2) ▶ Exemplo: Integração do pprof com o Prometheus

⚙️ Pré-requisito: Executar go get github.com/prometheus/client_golang/prometheus e go get github.com/prometheus/client_golang/prometheus/promhttp

GO 📖 Somente leitura
// monitoring.go
package main

import (
    "net/http"
    "net/http/pprof"
    "runtime"
    "strconv"
    "time"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

// Prometheus metrics
var (
    httpRequestsTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total HTTP requests",
        },
        []string{"method", "path", "status"},
    )

    httpRequestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request duration in seconds",
            Buckets: []float64{.001, .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5},
        },
        []string{"method", "path"},
    )

    activeGoroutines = prometheus.NewGaugeFunc(
        prometheus.GaugeOpts{
            Name: "go_goroutines_active",
            Help: "Current number of goroutines",
        },
        func() float64 {
            return float64(runtime.NumGoroutine())
        },
    )
)

func init() {
    prometheus.MustRegister(httpRequestsTotal)
    prometheus.MustRegister(httpRequestDuration)
    prometheus.MustRegister(activeGoroutines)
}

// Prometheus middleware
func prometheusMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        // Wrap ResponseWriter to get status code
        wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
        next.ServeHTTP(wrapped, r)

        duration := time.Since(start)
        httpRequestsTotal.WithLabelValues(
            r.Method, r.URL.Path, strconv.Itoa(wrapped.statusCode),
        ).Inc()
        httpRequestDuration.WithLabelValues(
            r.Method, r.URL.Path,
        ).Observe(duration.Seconds())
    })
}

type responseWriter struct {
    http.ResponseWriter
    statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.statusCode = code
    rw.ResponseWriter.WriteHeader(code)
}

// Register pprof and Prometheus endpoints in main
func registerMonitoring(mux *http.ServeMux) {
    // pprof endpoints
    mux.HandleFunc("/debug/pprof/", pprof.Index)
    mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
    mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
    mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
    mux.HandleFunc("/debug/pprof/trace", pprof.Trace)

    // Prometheus metrics
    mux.Handle("/metrics", promhttp.Handler())
}

func main() {
    mux := http.NewServeMux()
    registerMonitoring(mux)

    // Example business endpoint
    mux.HandleFunc("GET /api/products", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"products":[]}`))
    })

    // Health check
    mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"status":"ok"}`))
    })

    log.Println("Monitoring + API server listening on :8080")
    http.ListenAndServe(":8080", prometheusMiddleware(mux))
}
85 linhas de lógica (limite de 40, somente leitura)

(3) ▶ Exemplo: Implantação do Docker

DOCKERFILE
# Dockerfile (Multi-stage Build)
FROM golang:1.22-alpine AS builder
WORKDIR /app

# Dependency cache
COPY go.mod go.sum ./
RUN go mod download

# Source + compile
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server

# === Run stage ===
FROM alpine:3.19
RUN apk --no-cache add ca-certificates tzdata

COPY --from=builder /app/server /server

EXPOSE 8080
EXPOSE 6060  # pprof

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget -qO- http://localhost:8080/health || exit 1

CMD ["/server"]
YAML
# docker-compose.yml
version: '3.8'

services:
  api:
    build: .
    ports:
      - "8080:8080"
      - "6060:6060"   # pprof
    environment:
      - DB_PATH=/data/ecommerce.db
      - GIN_MODE=release
    volumes:
      - data:/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 5s

  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana

volumes:
  data:
  prometheus_data:
  grafana_data:
YAML
# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'ecommerce-api'
    static_configs:
      - targets: ['api:8080']
    metrics_path: '/metrics'

(4) ▶ Exemplo: Documentação do Swagger

GO
// Generate OpenAPI docs from comments (use with swag tool)

// Package handler handles HTTP requests.
//
// E-commerce API
//
//     Schemes: http
//     Host: localhost:8080
//     BasePath: /api/v1
//     Version: 1.0.0
//
//     Consumes:
//     - application/json
//
//     Produces:
//     - application/json
//
// swagger:meta
package handler

import "ecommerce/pkg/model"

// User information
// swagger:model
type User struct {
    ID    int    `json:"id"`
    Email string `json:"email"`
    Name  string `json:"name"`
}

// RegisterUser registers a new user
// swagger:route POST /api/v1/register users registerUser
//
// Register a new user
//
//     Consumes:
//     - application/json
//
//     Produces:
//     - application/json
//
//     Responses:
//       201: User
//       400: ErrorResponse
//       409: ErrorResponse
func (h *UserHandler) RegisterUser(w http.ResponseWriter, r *http.Request) {
    // handler logic
}

// swagger:parameters registerUser
type RegisterUserParams struct {
    // in: body
    Body model.RegisterRequest
}

// ErrorResponse error response
// swagger:response
type ErrorResponse struct {
    // in: body
    Body struct {
        Error string `json:"error"`
    }
}
▶ Experimente
BASH
# Install swag CLI
$ go install github.com/swaggo/swag/cmd/swag@latest

# Generate docs
$ swag init -g cmd/server/main.go

# Access docs
# http://localhost:8080/swagger/index.html

(5) Lista de verificação para o lançamento

TEXT 📖 Somente leitura
✅ Graceful Shutdown — wait for requests to complete after kill
✅ Health Check — /health and /ready endpoints
✅ pprof — /debug/pprof/ performance analysis
✅ Prometheus — /metrics endpoint exposed
✅ Swagger — Auto-generated API documentation
✅ Docker — Multi-stage build, image < 20 MB
✅ docker-compose — One command to start the full stack
✅ ReadTimeout / WriteTimeout — prevent slowloris attacks
✅ DB connection pool — MaxOpenConns=25, MaxIdleConns=10
✅ server.Shutdown — SIGINT/SIGTERM handling
💡 Dica: Os ambientes de produção também exigem: (1) um proxy reverso (Nginx / Caddy) para lidar com TLS e nomes de domínio; (2) um pipeline de CI/CD para compilações e implantações automatizadas; (3) gerenciamento centralizado de logs (como Loki ou Elasticsearch); (4) regras de alerta (por exemplo, acionar um alerta quando a taxa de erros 5xx ultrapassar 1%). Esses tópicos estão fora do escopo desta lição, mas é recomendável que você os configure antes da implantação.


❓ Perguntas Frequentes

P: Por que o desligamento gradual é importante? R: Quando um sinal SIGTERM é recebido (como quando o Kubernetes encerra um Pod), o server.Shutdown aguarda a conclusão de todas as solicitações HTTP ativas (até o tempo limite especificado) antes de fechar o listener. Se o processo for encerrado imediatamente, as solicitações que estão sendo processadas no momento serão interrompidas — resultando em dados de pedidos com ordem inconsistente.

P: Quais são as configurações de tempo limite do servidor? R: (1) ReadTimeout: tempo limite para a leitura de toda a solicitação (incluindo o corpo); (2) WriteTimeout: tempo limite para o envio de uma resposta; (3) IdleTimeout: tempo limite de inatividade para conexões Keep-Alive; (4) ReadHeaderTimeout: tempo limite para a leitura dos cabeçalhos da solicitação. Recomenda-se configurar todas essas opções para evitar ataques de conexão lenta.

P: O pprof é seguro em um ambiente de produção? R: O endpoint do pprof não deve ser exposto ao mundo externo. Solução: (1) Hospede-o em uma porta interna (por exemplo, :6060) que não seja compartilhada com as portas de negócios; (2) Proteja o caminho /debug/pprof/ com middleware de autenticação; (3) Habilite-o apenas no ambiente de teste. No ambiente de produção, recomenda-se habilitar a criação de perfis temporariamente, somente quando necessário.

P: Como faço para expor métricas do Prometheus? R: Use promhttp.Handler() para expor métricas no endpoint /metrics. O servidor do Prometheus faz o scraping desse endpoint periodicamente. O Grafana se conecta à fonte de dados do Prometheus e cria visualizações. Métricas padrão: número de solicitações, distribuição de latência, taxa de erros, número de goroutines e número de eventos de GC.

P: Como faço para gerar automaticamente a documentação OpenAPI? R: Use a ferramenta swaggo/swag — escreva comentários em um formato específico dentro do código do seu handler e, em seguida, execute swag init para gerar o diretório docs/. Use swaggo/http-swagger para montar a documentação no endpoint /swagger/. Formato dos comentários: // swagger:route, // swagger:model, // swagger:parameters.

P: Como orquestrar vários serviços com o Docker Compose? R: Defina vários serviços (API, Prometheus, Grafana), use depends_on para controlar a ordem de inicialização e use volumes para persistir os dados. Execute docker-compose up -d para iniciar todos os serviços com um único comando. Em um ambiente de produção, use o Docker Stack ou o Kubernetes.

P: Existem três níveis de granularidade para as verificações de integridade? R: (1) /health (ativo) — A verificação mais simples; uma resposta 200 indica que o processo está em execução; (2) /ready (pronto) — Verifica se as dependências (banco de dados, cache) estão acessíveis; (3) /status (detalhado) — retorna o status e a latência de todas as dependências.


📖 Resumo


📝 Exercícios

  1. Básico (Dificuldade ⭐): Adicione configurações de desligamento gradual e de tempo limite à API de comércio eletrônico abordada nesta lição. Use curl para testar os endpoints /health e /ready. Use kill -SIGTERM <pid> para testar o desligamento gradual.

  2. Avançado (Dificuldade ⭐⭐): Implemente uma implantação completa baseada em Docker. Requisitos: (1) Dockerfile com compilação em múltiplas etapas; (2) docker-compose.yml (API + Prometheus + Grafana); (3) HEALTHCHECK; (4) endpoint pprof (somente porta interna); (5) Verifique se todos os serviços iniciam normalmente após a execução de docker-compose up.

  3. Desafio (Dificuldade ⭐⭐⭐): Implementar o monitoramento do Prometheus + painéis do Grafana. Requisitos: (1) Adicionar o middleware do Prometheus à API (contagem de solicitações, latência, taxa de erros); (2) Adicionar métricas de negócios (por exemplo, número de pedidos criados, número de usuários registrados); (3) Incluir o Prometheus e o Grafana no arquivo do Docker Compose; (4) Importar ou criar um painel do Grafana para exibir QPS, latência P99, taxa de erros e número de goroutines; (5) Verificar se as métricas estão corretas usando o Grafana após o teste de carga.

🎉 Parabéns! Você concluiu todas as 30 lições do tutorial de Go! Desde o “Hello World” até uma API de comércio eletrônico pronta para produção, você dominou todo o conjunto de habilidades necessárias para o desenvolvimento de back-end em Go.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%