Event Bus e padrão Observer em Go (Golang): implementação pub/sub

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

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

Recommendations