Event Bus e padrão Observer em Go (Golang): da teoria à prática
O padrão Observer (observador) e sua implementação como Event Bus (barramento de eventos) são ferramentas fundamentais para construir sistemas fracamente acoplados e escaláveis. Em Go, com sua filosofia de "comunique-se através de canais, não através de memória compartilhada", esses padrões adquirem uma elegância especial. Neste artigo, vamos analisar como criar seu próprio Event Bus em Go, usando goroutines e canais, e examinar cenários de uso típicos.
Padrão Observer: essência e problema
Imagine que você tem um sistema onde um objeto (editor) deve notificar muitos outros (assinantes) sobre uma mudança em seu estado. Sem o Observer, você teria que acoplar rigidamente o editor a cada assinante. O Observer resolve isso introduzindo uma interface de inscrição: o editor mantém uma lista de assinantes e chama o método Update() deles quando um evento ocorre.
Event Bus é um barramento centralizado que assume o papel de intermediário. Qualquer componente pode publicar um evento, e qualquer outro pode se inscrever nele, sem saber da existência um do outro.
Por que Go é ideal para Event Bus?
- Goroutines e canais: processamento assíncrono de eventos sem bloqueios.
- Interfaces: permitem definir facilmente contratos para manipuladores.
- Ausência de herança: composição através de canais e funções é mais natural do que a implementação clássica de OOP.
Implementação simples de Event Bus em Go
Vamos começar com uma versão básica, onde os eventos são strings e os assinantes são funções manipuladoras.
package main
import ( "fmt" "sync" "time")
// EventBus — nosso barramento de eventostype EventBus struct { subscribers map[string][]func(string) mu sync.RWMutex}
// NewEventBus cria um novo barramentofunc NewEventBus() *EventBus { return &EventBus{ subscribers: make(map[string][]func(string)), }}
// Subscribe inscreve um manipulador em um eventofunc (eb *EventBus) Subscribe(event string, handler func(string)) { eb.mu.Lock() defer eb.mu.Unlock() eb.subscribers[event] = append(eb.subscribers[event], handler)}
// Publish publica um evento e chama todos os assinantes de forma síncronafunc (eb *EventBus) Publish(event string, data string) { eb.mu.RLock() defer eb.mu.RUnlock() if handlers, ok := eb.subscribers[event]; ok { for _, handler := range handlers { handler(data) } }}
func main() { bus := NewEventBus() bus.Subscribe("user.login", func(data string) { fmt.Printf("Login: %s", data) }) bus.Subscribe("user.login", func(data string) { fmt.Printf("Auditoria: %s entrou no sistema", data) }) bus.Publish("user.login", "alice@example.com") // Saída: // Login: alice@example.com // Auditoria: alice@example.com entrou no sistema}Este código funciona, mas há uma nuance: os manipuladores são chamados de forma síncrona. Se um manipulador travar, ele bloqueará todo o barramento.
Versão assíncrona com canais
Vamos adicionar assincronicidade através de canais com buffer. Cada assinante receberá seu próprio canal.
type AsyncEventBus struct { subscribers map[string][]chan string mu sync.RWMutex}
func NewAsyncEventBus() *AsyncEventBus { return &AsyncEventBus{ subscribers: make(map[string][]chan string), }}
func (eb *AsyncEventBus) Subscribe(event string, buffer int) <-chan string { eb.mu.Lock() defer eb.mu.Unlock() ch := make(chan string, buffer) eb.subscribers[event] = append(eb.subscribers[event], ch) return ch}
func (eb *AsyncEventBus) Publish(event string, data string) { eb.mu.RLock() defer eb.mu.RUnlock() if channe Também na biblioteca
Programação Reativa em JavaScript (Node.js): RxJS & Observables
Doctrine ORM para PHP: guia completo com exemplos práticos
Trabalhando com Kafka em JavaScript (Node.js): biblioteca node-rdkafka
Métricas Prometheus em Kotlin: integração com Micrometer e Ktor
Serialização de objetos em Java: guia completo para iniciantes