Padrões de Projeto em Go: Nível Avançado para Desenvolvedores Profissionais
\n\nGo (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\n1. Padrão Fan-Out / Fan-In: Processamento Paralelo de Dados
\n\nEste 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\nImplementação do Fan-Out
\nImagine uma tarefa: processar 100 URLs. Em vez de processamento sequencial, iniciamos 10 goroutines, cada uma pegando uma tarefa de um canal comum.
\n\npackage 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\nPontos-chave:
\n- \n
- O canal
jobsserve 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.WaitGroupgarante que fecharemos results somente após a conclusão de todas as goroutines. \n
2. Padrão Pipeline: Processamento em Linha de Montagem de Dados
\n\nPipeline é 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\nExemplo: Pipeline de Processamento de Texto
\n\npackage 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\nVantagens:
\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
3. Padrão Worker Pool: Controle de Concorrência
\n\nWorker 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\nImplementação de um Worker Pool
\n\npackage 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\nQuando 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
4. Padrão Circuit Breaker: Tolerância a Falhas
\n\nCircuit 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\nImplementação Simples
\n\npackage 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\nBenefí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
Conclusão
\n\nOs 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.