Padrões de Projeto em Go em Nível Avançado com Exemplos

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Padrões de Projeto em Go: Nível Avançado para Desenvolvedores Profissionais

\n\n

Go (Golang) — uma linguagem criada para trabalhar eficientemente com concorrência e arquitetura de microsserviços. Padrões de projeto básicos, como Singleton ou Factory, são bem conhecidos, mas para criar sistemas de alta carga e tolerantes a falhas, é necessário um nível avançado de domínio dos padrões. Neste artigo, analisaremos quatro padrões-chave: Fan-Out/Fan-In, Pipeline, Worker Pool e Circuit Breaker. Cada um deles resolve problemas práticos de escalabilidade, processamento paralelo e proteção do sistema contra falhas em cascata.

\n\n

1. Padrão Fan-Out / Fan-In: Processamento Paralelo de Dados

\n\n

Este padrão é a base da computação paralela em Go. Fan-Out (saída em leque) distribui a carga entre várias goroutines, enquanto Fan-In (entrada em leque) coleta os resultados de volta em um único canal.

\n\n

Implementação do Fan-Out

\n

Imagine uma tarefa: processar 100 URLs. Em vez de processamento sequencial, iniciamos 10 goroutines, cada uma pegando uma tarefa de um canal comum.

\n\n
package main\n\nimport (\n    "fmt"\n    "sync"\n)\n\nfunc worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {\n    defer wg.Done()\n    for job := range jobs {\n        // Simulação de processamento\n        results <- job * 2\n        fmt.Printf("Worker %d processed job %d\\n", id, job)\n    }\n}\n\nfunc main() {\n    const numJobs = 20\n    const numWorkers = 5\n\n    jobs := make(chan int, numJobs)\n    results := make(chan int, numJobs)\n    var wg sync.WaitGroup\n\n    // Fan-Out: iniciando workers\n    for w := 1; w <= numWorkers; w++ {\n        wg.Add(1)\n        go worker(w, jobs, results, &wg)\n    }\n\n    // Enviando tarefas\n    for j := 1; j <= numJobs; j++ {\n        jobs <- j\n    }\n    close(jobs)\n\n    // Fan-In: aguardando conclusão e fechando results\n    go func() {\n        wg.Wait()\n        close(results)\n    }()\n\n    // Coletando resultados\n    for result := range results {\n        fmt.Println("Result:", result)\n    }\n}\n
\n\n

Pontos-chave:

\n
    \n
  • O canal jobs serve como ponto de Fan-Out: todos os workers leem dele.
  • \n
  • O canal results é o ponto de Fan-In: todos os workers escrevem nele.
  • \n
  • O uso de sync.WaitGroup garante que fecharemos results somente após a conclusão de todas as goroutines.
  • \n
\n\n

2. Padrão Pipeline: Processamento em Linha de Montagem de Dados

\n\n

Pipeline é uma cadeia de estágios, onde cada estágio processa dados e os passa para o próximo. Em Go, isso é implementado através de uma sequência de canais. O padrão é ideal para processos ETL, processamento de logs ou transformação de dados.

\n\n

Exemplo: Pipeline de Processamento de Texto

\n\n
package main\n\nimport "fmt"\n\n// Estágio 1: geração de números\nfunc generate(nums ...int) <-chan int {\n    out := make(chan int)\n    go func() {\n        for _, n := range nums {\n            out <- n\n        }\n        close(out)\n    }()\n    return out\n}\n\n// Estágio 2: elevação ao quadrado\nfunc square(in <-chan int) <-chan int {\n    out := make(chan int)\n    go func() {\n        for n := range in {\n            out <- n * n\n        }\n        close(out)\n    }()\n    return out\n}\n\n// Estágio 3: multiplicação por 2\nfunc multiply(in <-chan int, factor int) <-chan int {\n    out := make(chan int)\n    go func() {\n        for n := range in {\n            out <- n * factor\n        }\n        close(out)\n    }()\n    return out\n}\n\nfunc main() {\n    // Montagem do pipeline\n    pipeline := multiply(square(generate(1, 2, 3, 4)), 10)\n\n    // Consumo dos resultados\n    for result := range pipeline {\n        fmt.Println(result) // 10, 40, 90, 160\n    }\n}\n
\n\n

Vantagens:

\n
    \n
  • Cada estágio é uma unidade independente e testável.
  • \n
  • Os canais permitem concorrência natural e sincronização.
  • \n
  • Fácil de estender: basta adicionar novos estágios.
  • \n
\n\n

3. Padrão Worker Pool: Controle de Concorrência

\n\n

Worker Pool gerencia um número fixo de goroutines (workers) que processam tarefas de uma fila. Isso evita a criação excessiva de goroutines e protege contra sobrecarga do sistema.

\n\n

Implementação de um Worker Pool

\n\n
package main\n\nimport (\n    "fmt"\n    "time"\n)\n\nfunc worker(id int, jobs <-chan int, results chan<- int) {\n    for job := range jobs {\n        fmt.Printf("Worker %d iniciou tarefa %d\\n", id, job)\n        time.Sleep(time.Second) // Simula trabalho\n        results <- job * 2\n        fmt.Printf("Worker %d finalizou tarefa %d\\n", id, job)\n    }\n}\n\nfunc main() {\n    const numJobs = 10\n    const numWorkers = 3\n\n    jobs := make(chan int, numJobs)\n    results := make(chan int, numJobs)\n\n    // Iniciando workers\n    for w := 1; w <= numWorkers; w++ {\n        go worker(w, jobs, results)\n    }\n\n    // Enviando tarefas\n    for j := 1; j <= numJobs; j++ {\n        jobs <- j\n    }\n    close(jobs)\n\n    // Coletando resultados\n    for r := 1; r <= numJobs; r++ {\n        <-results\n    }\n}\n
\n\n

Quando usar:

\n
    \n
  • Processamento de lotes de requisições HTTP.
  • \n
  • Leitura/escrita concorrente de arquivos.
  • \n
  • Qualquer tarefa onde o número de operações simultâneas precisa ser limitado.
  • \n
\n\n

4. Padrão Circuit Breaker: Tolerância a Falhas

\n\n

Circuit Breaker protege o sistema contra falhas repetidas em serviços externos. Quando o número de erros excede um limite, o "disjuntor" abre e interrompe as chamadas, permitindo que o sistema se recupere.

\n\n

Implementação Simples

\n\n
package main\n\nimport (\n    "errors"\n    "fmt"\n    "sync"\n    "time"\n)\n\ntype CircuitBreaker struct {\n    mu            sync.Mutex\n    failureCount  int\n    threshold     int\n    state         string // "closed", "open", "half-open"\n    lastErrorTime time.Time\n    timeout       time.Duration\n}\n\nfunc NewCircuitBreaker(threshold int, timeout time.Duration) *CircuitBreaker {\n    return &CircuitBreaker{\n        threshold: threshold,\n        state:     "closed",\n        timeout:   timeout,\n    }\n}\n\nfunc (cb *CircuitBreaker) Call(fn func() error) error {\n    cb.mu.Lock()\n    defer cb.mu.Unlock()\n\n    switch cb.state {\n    case "open":\n        if time.Since(cb.lastErrorTime) > cb.timeout {\n            cb.state = "half-open"\n        } else {\n            return errors.New("circuit breaker is open")\n        }\n    case "half-open":\n        // Permite uma chamada de teste\n    }\n\n    err := fn()\n    if err != nil {\n        cb.failureCount++\n        cb.lastErrorTime = time.Now()\n        if cb.failureCount >= cb.threshold {\n            cb.state = "open"\n        }\n        return err\n    }\n\n    // Sucesso: resetando o disjuntor\n    cb.failureCount = 0\n    cb.state = "closed"\n    return nil\n}\n\nfunc main() {\n    cb := NewCircuitBreaker(3, 5*time.Second)\n\n    for i := 0; i < 10; i++ {\n        err := cb.Call(func() error {\n            // Simula uma chamada de API que falha\n            return errors.New("erro de serviço")\n        })\n        fmt.Printf("Chamada %d: %v\\n", i+1, err)\n        time.Sleep(1 * time.Second)\n    }\n}\n
\n\n

Benefícios:

\n
    \n
  • Previne sobrecarga em serviços com falha.
  • \n
  • Melhora a resiliência geral do sistema.
  • \n
  • Fornece degradação graciosa em vez de falha total.
  • \n
\n\n

Conclusão

\n\n

Os padrões Fan-Out/Fan-In, Pipeline, Worker Pool e Circuit Breaker são ferramentas essenciais no arsenal de um desenvolvedor Go avançado. Eles permitem construir sistemas concorrentes, escaláveis e tolerantes a falhas. Pratique a implementação destes padrões em seus projetos e lembre-se: em Go, a concorrência não é apenas um recurso, é um estilo de arquitetura.

Blogs

Book Recommendations