Em que Observer é diferente de Pub/Sub?
1. Visão geral
Observer e Pub/Sub resolvem o problema “um evento acontece, muitos lugares precisam saber”. Muitas pessoas confundem esses dois termos porque são semelhantes na ideia, mas diferem na implementação e no nível de acoplamento. Este artigo ajuda você a diferenciar claramente os dois conceitos, com código Java ilustrando cada um, e saber quando escolher Observer puro, quando usar Pub/Sub através de um message broker.
2. O que é o padrão Observer
Observer é um dos padrões de comportamento (behavioral pattern) clássicos do GoF. A ideia: um Subject (ou Publisher) mantém uma lista de Observers, quando o estado muda, o Subject chama diretamente o método em cada Observer registrado.
public interface OrderObserver {
void onOrderPaid(Order order);
}
public class Order {
private final List<OrderObserver> observers = new ArrayList<>();
private String status = "NEW";
public void addObserver(OrderObserver observer) {
observers.add(observer);
}
public void markAsPaid() {
this.status = "PAID";
for (OrderObserver observer : observers) {
observer.onOrderPaid(this);
}
}
}
Explicação:
observersé a lista de referências diretas aos objetos interessados.markAsPaid()chamaonOrderPaid()em cada observer em ordem, na mesma thread, de forma síncrona.Ordersabe exatamente quem está escutando — este é o ponto-chave para diferenciar de Pub/Sub.
public class EmailNotifier implements OrderObserver {
@Override
public void onOrderPaid(Order order) {
System.out.println("Enviar email de confirmação para pedido pago");
}
}
Order order = new Order();
order.addObserver(new EmailNotifier());
order.markAsPaid();
Enviar email de confirmação para pedido pago
2.1. Características principais do Observer
- Acoplamento direto:
Subjectmantém referência aoObserver, precisam importar a interface um do outro. - Síncrono: a chamada
notifybloqueia até que todos os observers terminem o processamento. - Mesmo processo: geralmente roda em uma JVM, não passa por rede.
- Subject conhece observer: se esquecer de chamar
removeObserver(), você facilmente terá vazamento de memória porqueSubjectmantém referência “viva” ao observer já descartado.
Nota: O padrão Observer clássico não tem mecanismo de unsubscribe seguro de forma nativa. Se você usar em aplicações de longa duração (por exemplo, componente UI, listener registrado e depois destruído), lembre-se de chamar
removeObserver()quando o objeto for destruído, evitando vazamento de memória.
3. O que é Pub/Sub
Pub/Sub (Publish/Subscribe) separa completamente Publisher e Subscriber através de uma camada intermediária — chamada de broker, event bus ou topic. O Publisher não sabe quem está subscrito, apenas “dispara” a mensagem em um topic/canal.
public record OrderPaidEvent(String orderId, double amount) {}
public class OrderService {
private final EventBus eventBus;
public OrderService(EventBus eventBus) {
this.eventBus = eventBus;
}
public void payOrder(String orderId, double amount) {
// processa o pagamento...
eventBus.publish("order.paid", new OrderPaidEvent(orderId, amount));
}
}
Explicação:
OrderServicenão sabe seEmailNotifierouInventoryServiceexistem ou não.- Apenas publica no topic
"order.paid", o resto é responsabilidade do broker. - O subscriber se registra com o broker, não diretamente com
OrderService.
3.1. Pub/Sub no processo com Spring Events
Spring Boot tem um event bus simples usando ApplicationEventPublisher. Este é o Pub/Sub em nível de aplicação, ainda sem broker externo como Kafka/RabbitMQ.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
public class OrderPaidEvent extends ApplicationEvent {
private final String orderId;
public OrderPaidEvent(String orderId) {
super(orderId);
this.orderId = orderId;
}
public String getOrderId() {
return orderId;
}
}
@Service
public class OrderService {
private final ApplicationEventPublisher publisher;
public OrderService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void payOrder(String orderId) {
publisher.publishEvent(new OrderPaidEvent(orderId));
}
}
@Component
public class EmailEventListener {
@EventListener
public void handle(OrderPaidEvent event) {
System.out.println("Enviar email para pedido " + event.getOrderId());
}
}
Explicação:
OrderServicepublica o evento através deApplicationEventPublisher— um bean gerenciado por Spring, que faz o papel de broker interno.EmailEventListenernão é conhecido porOrderService; Spring escaneia automaticamente métodos com@EventListenere roteia o evento ao handler correto baseado no tipo.- Por padrão,
@EventListenerroda síncrono na mesma thread do publisher — este é o ponto que confunde muita gente, pois pensam que Pub/Sub é sempre assíncrono.
Para rodar de forma assíncrona de verdade, adicione @Async:
@Component
public class EmailEventListener {
@Async
@EventListener
public void handle(OrderPaidEvent event) {
System.out.println("Enviar email para pedido " + event.getOrderId());
}
}
@Configuration
@EnableAsync
public class AsyncConfig {
}
@EnableAsyncativa o proxy para Spring rodar o método@Asyncem uma thread pool separada.- Neste caso
payOrder()retorna imediatamente, sem esperar queEmailEventListenertermine o processamento.
3.2. Pub/Sub por message broker (Kafka)
Em escala maior, o “broker” não é mais um bean dentro da JVM, mas um sistema separado (Kafka, RabbitMQ, SQS…). Publisher e subscriber podem estar em dois serviços, dois processos, duas máquinas diferentes.
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
@Service
public class OrderService {
private final KafkaTemplate<String, OrderPaidEvent> kafkaTemplate;
public OrderService(KafkaTemplate<String, OrderPaidEvent> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
public void payOrder(String orderId, double amount) {
kafkaTemplate.send("order-paid-topic", new OrderPaidEvent(orderId, amount));
}
}
@Component
public class EmailConsumer {
@KafkaListener(topics = "order-paid-topic", groupId = "email-service")
public void consume(OrderPaidEvent event) {
System.out.println("Serviço de email processando pedido " + event.orderId());
}
}
OrderServiceeEmailConsumerpodem estar em dois microserviços completamente diferentes, deployados independentemente.- Kafka mantém a mensagem no topic, o consumer lê na sua própria velocidade — este é o verdadeiro Pub/Sub, com buffer, replay, múltiplos subscribers independentes por grupo.
4. Comparação direta
| Critério | Observer | Pub/Sub |
|---|---|---|
| Acoplamento | Subject conhece Observer diretamente (pela interface) | Publisher não sabe que Subscriber existe |
| Intermediário | Nenhum, chama método direto | Broker/event bus fica no meio |
| Síncrono/Assíncrono | Por padrão síncrono, mesma thread | Pode ser síncrono (Spring Event) ou assíncrono (Kafka, RabbitMQ) |
| Escopo | Dentro de um processo/módulo | Dentro do processo (event bus) ou entre múltiplos serviços/processos |
| Durabilidade de mensagem | Nenhuma, se Subject morrer perde | Broker externo (Kafka) armazena, permite replay |
| Número de publisher-subscriber | Geralmente 1 Subject - múltiplos Observers | Múltiplos Publishers - múltiplos Subscribers por múltiplos topics |
A diferença mais importante: Observer é uma relação direta entre dois objetos, Pub/Sub é uma relação através de uma camada intermediária anônima. Essencialmente, Pub/Sub pode ser visto como o padrão Observer “afrouxado” inserindo um broker para eliminar a dependência direta.
5. Quando usar Observer, quando usar Pub/Sub
Use Observer quando:
- A lógica fica no mesmo módulo/classe, número de observers é fixo e muda pouco.
- Você precisa de resposta imediata, síncrona (por exemplo, validar dados antes de salvar).
- Não precisa desacoplar demais — por exemplo, componente UI escutando mudanças de um modelo.
class ValidationObserver implements OrderObserver {
@Override
public void onOrderPaid(Order order) {
if (order.getStatus() == null) {
throw new IllegalStateException("Pedido ainda não tem status válido");
}
}
}
Use Pub/Sub quando:
- Múltiplos serviços independentes precisam saber de um evento mas não devem depender um do outro.
- Precisa da capacidade de adicionar/remover subscribers sem mudar o código do publisher.
- Precisa de durabilidade de mensagem (se o subscriber estiver down, a mensagem espera quando ele ficar online novamente).
- O sistema é microserviços, com comunicação pela rede.
Nota: Não use Kafka/RabbitMQ apenas para substituir uma
List<Observer>dentro da mesma JVM — o custo de operar um broker não vale para um problema pequeno. Inversamente, não tente forçar lógica cross-service no padrão Observer puro, pois você terá que reinventar todo o mecanismo de rede, retry, serialização que o broker já oferece.
6. Testar comportamento do Observer
class OrderObserverTest {
@Test
void shouldNotifyAllObserversWhenOrderIsPaid() {
Order order = new Order();
List<String> notified = new ArrayList<>();
order.addObserver(o -> notified.add("email"));
order.addObserver(o -> notified.add("inventory"));
order.markAsPaid();
assertThat(notified).containsExactly("email", "inventory");
}
}
Explicação:
- Use lambda em vez de classe implementando
OrderObserverporque a interface tem apenas um método — mais conciso ao escrever testes. assertThat(...).containsExactly(...)confirma que a ordem de chamada dos observers é a mesma da ordem de registro — característica importante do Observer síncrono que Pub/Sub por broker não garante (Kafka pode processar em paralelo em múltiplas instâncias de consumer).
7. Erros comuns
- Confundir Spring
@EventListenercom assíncrono: por padrão é síncrono, erro no listener lançará exceção de volta ao publisher se não for capturado. - Observer mantendo referência e esquecendo de remover: causa vazamento de memória, especialmente em aplicações com ciclo de vida curto de objetos (request-scope) mas registrados em singleton.
- Abusar de Pub/Sub para lógica que precisa de transação consistente: se publisher e subscriber devem ambos ter sucesso ou fracasso (atomic), Pub/Sub assíncrono não é adequado — reconsidere padrão Saga ou transactional outbox.
- Não nomear topics claramente: em sistemas grandes, topics com nomes vagos como
"event1"fazem ninguém saber quem subscribe o quê, perdendo todo o benefício do desacoplamento.
8. Conclusão
Observer e Pub/Sub compartilham o mesmo objetivo — notificar múltiplas partes interessadas sobre mudanças — mas diferem no nível de acoplamento e escopo de operação. Observer é apropriado para relações diretas, síncronas, dentro do mesmo processo; Pub/Sub é apropriado quando você precisa desacoplar completamente o publisher do subscriber, especialmente em sistemas de microserviços que precisam de durabilidade de mensagem e capacidade de expansão com cada consumer independente. Entender bem esta diferença ajuda você a escolher a ferramenta certa: nem sempre você precisa de Kafka, e nem sempre uma List<Observer> é suficiente para seu problema.