Observer: o padrão por trás de todo evento
De addEventListener ao broker de mensagens: o Observer do zero, a genealogia que organiza a cabeça (RxJS, Signals, filas) e as 3 armadilhas que se repetem em toda escala.
Se você já escreveu addEventListener, .on("data"), subscribe ou publicou um evento numa fila, você já usou o Observer: o padrão GoF mais onipresente do software moderno. A ideia cabe numa frase: quem tem a informação notifica; quem se interessa se inscreve, sem que os dois se conheçam.
O mecanismo, do zero
type Listener<T> = (dado: T) => void;
class Emissor<T> {
private listeners = new Set<Listener<T>>();
inscrever(fn: Listener<T>): () => void {
this.listeners.add(fn);
return () => this.listeners.delete(fn); // devolve o cancelamento!
}
emitir(dado: T) {
for (const fn of this.listeners) fn(dado);
}
}
// O domínio emite fatos; não conhece os interessados
class CarrinhoDeCompras {
readonly aoAdicionar = new Emissor<Item>();
adicionar(item: Item) {
this.itens.push(item);
this.aoAdicionar.emitir(item);
}
}
// Os interessados se inscrevem, cada um por si
carrinho.aoAdicionar.inscrever((item) => analytics.registrar("add", item));
carrinho.aoAdicionar.inscrever((item) => badge.atualizar());
O ganho estrutural é o mesmo da mensageria em escala, aqui dentro do processo: o carrinho não conhece analytics nem badge; os interessados variam sem tocar em quem emite. Amanhã, o estoque quer reservar ao adicionar? Uma inscrição, zero mudanças no carrinho.
Onde ele vive (a genealogia que organiza a cabeça)
- DOM e Node:
addEventListenereEventEmittersão o padrão, embutidos na plataforma. - RxJS: Observer com esteroides: o fluxo de eventos vira valor de primeira classe, com operadores.
- Signals e reatividade: a inscrição fica automática (ler é inscrever-se), mas o coração é o mesmo: dependentes notificados na mudança.
- Filas e brokers: o Observer atravessando a rede, com durabilidade e as garantias que processo nenhum dá sozinho.
Entender essa linhagem evita redescobrir os mesmos problemas em cada camada, porque os problemas são os mesmos:
As três armadilhas clássicas (em qualquer escala)
1. O vazamento de inscrição. Quem se inscreve e não cancela vira listener zumbi: callbacks rodando sobre componentes mortos, memória crescendo. Por isso o inscrever do exemplo devolve a função de cancelamento, o mesmo motivo do async pipe e do takeUntilDestroyed. Regra: toda inscrição tem dono e tem fim.
2. O erro que derruba os irmãos. No loop de emitir, o listener 2 lançou exceção: os listeners 3, 4 e 5 nunca rodam? Emissores sérios isolam falhas (try/catch por listener, a DLQ é a versão distribuída disso). Um interessado quebrado não pode censurar os demais.
3. A cascata invisível. Evento que dispara evento que dispara evento: com dezenas de listeners mutando estado, ninguém mais sabe por que algo aconteceu, e a depuração vira espiritismo. Antídotos: eventos como fatos nomeados do domínio ("ItemAdicionado", não "mudouAlgo"), fluxo documentado, e IDs de correlação quando atravessa fronteiras.

Quando usar (e quando é indireção)
Use quando vários interessados reagem ao mesmo fato, os interessados variam com o tempo, ou você quer desacoplar módulos (o domínio emite; a infraestrutura escuta). Evite quando há um interessado fixo e a sequência importa: uma chamada direta é mais clara, mais rastreável e aparece no stack trace. Evento não é jeito chique de chamar função; é declaração de que você não quer saber quem ouve.
Nos próximos capítulos da série: Adapter (fazer contratos incompatíveis conversarem) e Factory Method (criar sem acoplar). O catálogo aplicado com essa régua é parte da revisão de desenho do nosso serviço de Arquitetura de Software.
Tem uma cascata de eventos que ninguém mais entende? Descreva-a para a IA da Rabelo Digital, aqui no canto da tela, e ela ajuda a redesenhar com fatos nomeados. Sem formulário, sem espera.


Deixe um Comentário