¿En qué se diferencia Observer de Pub/Sub?
1. Descripción general
Observer y Pub/Sub ambos resuelven el problema “un evento ocurre, muchos lugares necesitan saberlo”. Muchas personas usan estos dos términos indistintamente porque son similares en idea, pero difieren en implementación y grado de acoplamiento. Este artículo te ayuda a distinguir claramente entre los dos conceptos, con código Java que ilustra cada tipo, y saber cuándo elegir Observer puro, y cuándo usar Pub/Sub a través de un message broker.
2. ¿Qué es Observer Pattern?
Observer es uno de los patrones de comportamiento (behavioral pattern) clásicos en GoF. La idea: un Subject (o Publisher) mantiene una lista de Observers, cuando el estado cambia, Subject llama directamente el método en 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);
}
}
}
Explicación:
observerses la lista de referencias directas a objetos interesados.markAsPaid()llamaonOrderPaid()en cada observer en orden, en el mismo thread, de forma síncrona.Ordersabe exactamente quién lo está escuchando — este es el punto clave para distinguirlo de Pub/Sub.
public class EmailNotifier implements OrderObserver {
@Override
public void onOrderPaid(Order order) {
System.out.println("Enviar correo de confirmación para la orden pagada");
}
}
Order order = new Order();
order.addObserver(new EmailNotifier());
order.markAsPaid();
Enviar correo de confirmación para la orden pagada
2.1. Características fundamentales de Observer
- Acoplamiento directo:
Subjectmantiene referencia aObserver, deben importarse mutuamente. - Síncrono: la llamada a
notifyse bloquea hasta que todos los observers terminen de procesar. - Mismo proceso: normalmente se ejecuta en una JVM, sin pasar por la red.
- Subject conoce al Observer: si olvidas llamar a
removeObserver(), fácilmente tendrás fuga de memoria porqueSubjectmantiene una referencia “viva” a un observer que ya no se usa.
Nota: El patrón Observer clásico no tiene un mecanismo integrado de desuscripción segura. Si lo usas en una aplicación de larga duración (por ejemplo, componentes UI, listeners registrados luego destruidos), recuerda llamar a
removeObserver()cuando el objeto sea destruido, para evitar fugas de memoria.
3. ¿Qué es Pub/Sub?
Pub/Sub (Publish/Subscribe) separa completamente Publisher y Subscriber a través de una capa intermedia — llamada broker, event bus, o topic. El Publisher no sabe quién está suscrito, solo “dispara” un mensaje a un 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) {
// procesar pago...
eventBus.publish("order.paid", new OrderPaidEvent(orderId, amount));
}
}
Explicación:
OrderServiceno sabe siEmailNotifieroInventoryServiceexisten o no.- Solo publica al topic
"order.paid", el resto es responsabilidad del broker. - El Subscriber se registra con el broker, no directamente con
OrderService.
3.1. Pub/Sub en-proceso con Spring Events
Spring Boot tiene un event bus simple usando ApplicationEventPublisher. Este es Pub/Sub a nivel de aplicación, sin necesidad de un 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 correo para la orden " + event.getOrderId());
}
}
Explicación:
OrderServicepublica el evento a través deApplicationEventPublisher— un bean administrado por Spring, jugando el rol de broker interno.EmailEventListenerno es conocido porOrderService; Spring automáticamente escanea los métodos con@EventListenery enruta el evento al handler correcto basado en el tipo.- Por defecto,
@EventListenerse ejecuta síncronamente en el mismo thread que el publisher — este es un punto fácil de confundir, muchas personas piensan que Pub/Sub siempre es asíncrono.
Para ejecutar de forma asíncrona, agrega @Async:
@Component
public class EmailEventListener {
@Async
@EventListener
public void handle(OrderPaidEvent event) {
System.out.println("Enviar correo para la orden " + event.getOrderId());
}
}
@Configuration
@EnableAsync
public class AsyncConfig {
}
@EnableAsyncactiva un proxy para que Spring ejecute el método@Asyncen un thread pool separado.- Ahora
payOrder()retorna inmediatamente, sin esperar a queEmailEventListenertermine de procesar.
3.2. Pub/Sub mediante message broker (Kafka)
A mayor escala, el “broker” no es un bean dentro de la JVM sino un sistema separado (Kafka, RabbitMQ, SQS…). El Publisher y el Subscriber pueden estar en dos servicios, dos procesos, dos 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("Servicio de correo procesa la orden " + event.orderId());
}
}
OrderServiceyEmailConsumerpueden estar en dos microservicios completamente diferentes, desplegados independientemente.- Kafka mantiene el mensaje en el topic, el consumer lo lee a su propia velocidad — este es Pub/Sub real, con buffer, replay, múltiples subscribers independientes por grupo.
4. Comparación directa
| Criterio | Observer | Pub/Sub |
|---|---|---|
| Acoplamiento | Subject conoce directamente a Observer (a través de interfaz) | Publisher no sabe si Subscriber existe |
| Intermediario | Ninguno, llama método directamente | Hay broker/event bus entre ellos |
| Síncrono/Asíncrono | Por defecto síncrono, mismo thread | Puede ser síncrono (Spring Event) o asíncrono (Kafka, RabbitMQ) |
| Alcance | Dentro de un proceso/módulo | Dentro de proceso (event bus) o entre múltiples servicios/procesos |
| Durabilidad de mensajes | Ninguna, si Subject muere se pierden | Broker externo (Kafka) almacena, permite replay |
| Cantidad de publisher-subscriber | Generalmente 1 Subject - múltiples Observer | Múltiples Publisher - múltiples Subscriber sobre múltiples topics |
La diferencia más importante: Observer es una relación directa entre dos objetos, Pub/Sub es una relación a través de una capa intermedia anónima. En esencia, Pub/Sub puede verse como Observer pattern “relajado” al insertar un broker para eliminar la dependencia directa.
5. Cuándo usar Observer, cuándo usar Pub/Sub
Usa Observer cuando:
- La lógica está en el mismo módulo/clase, el número de observers es fijo y cambia poco.
- Necesitas respuesta inmediata y síncrona (por ejemplo, validar datos antes de guardar).
- No necesitas desacoplar demasiado — por ejemplo, componentes UI escuchando cambios del modelo.
class ValidationObserver implements OrderObserver {
@Override
public void onOrderPaid(Order order) {
if (order.getStatus() == null) {
throw new IllegalStateException("La orden no tiene un estado válido");
}
}
}
Usa Pub/Sub cuando:
- Múltiples servicios independientes necesitan saber sobre un evento pero no deben depender uno del otro.
- Necesitas la capacidad de agregar/eliminar subscribers sin cambiar el código del publisher.
- Necesitas durabilidad de mensajes (si un subscriber está caído, el mensaje aún espera cuando se conecte de nuevo).
- El sistema es microservicios, comunicándose por red.
Nota: No uses Kafka/RabbitMQ solo para reemplazar una
List<Observer>dentro de la misma JVM — el costo de operar un broker no vale para problemas pequeños. Por el contrario, no intentes embutir lógica entre servicios en Observer pattern puro, porque tendrías que escribir todo el mecanismo de red, reintentos, serialización que el broker ya hace.
6. Testear el comportamiento de 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");
}
}
Explicación:
- Usa lambda en lugar de clase implementando
OrderObserverporque la interfaz tiene solo un método — más limpio al escribir tests. assertThat(...).containsExactly(...)confirma que el orden de llamadas a observers es correcto según el orden de registro — una característica importante de Observer síncrono que Pub/Sub sobre broker no garantiza (Kafka puede procesar en paralelo en múltiples instancias de consumer).
7. Errores comunes
- Confundir Spring
@EventListenercon asíncrono: por defecto se ejecuta síncronamente, una excepción en el listener se lanzará de vuelta al publisher si no la capturas. - Observer mantiene referencia pero se olvida de removerla: causa fuga de memoria, especialmente en aplicaciones con ciclo de vida corto de objetos (request-scope) pero registrados en un singleton.
- Abusar de Pub/Sub para lógica que necesita transacción consistente: si el publisher y el subscriber deben ambos tener éxito o ambos fallar (atómico), Pub/Sub asíncrono no es apropiado — revisa patrones como Saga u outbox transaccional.
- No nombrar topics claramente: en sistemas grandes, topics con nombres vagos como
"event1"hacen que nadie sepa quién se suscribe a qué, perdiéndose los beneficios del desacoplamiento.
8. Conclusión
Observer y Pub/Sub comparten un objetivo — notificar cambios a múltiples partes interesadas — pero difieren en grado de acoplamiento y alcance de operación. Observer es apropiado para relaciones directas, síncronas, dentro del mismo proceso; Pub/Sub es apropiado cuando necesitas desacoplar completamente el publisher del subscriber, especialmente en sistemas de microservicios que requieren durabilidad de mensajes y escalabilidad en cada consumer independiente. Entender claramente esta diferencia te ayuda a elegir la herramienta correcta: no siempre necesitas Kafka, y no siempre una List<Observer> es suficiente para tu problema.