← Back to list

CommandBus vs EventBus: cuándo usar cada uno y por qué importa

Introducción

Andrey Oliveros · 2026-03-21 19:53 · 0 claps · 4.8 min read
#command-bus #eventbus
Open on Medium ↗
Wiki topics: 🥊 · Combat Sports

CommandBus vs EventBus: cuándo usar cada uno y por qué importa

Diseñando microservicios desacoplados con CQRS, Domain Events y programación reactiva

Diseñando microservicios desacoplados con CQRS, Domain Events y programación reactiva

Introducción

A medida que los sistemas evolucionan hacia arquitecturas basadas en microservicios y Domain-Driven Design, uno de los desafíos más importantes es evitar el acoplamiento excesivo entre componentes del dominio y la capa de aplicación.

En sistemas complejos, los casos de uso suelen interactuar con múltiples servicios: persistencia, notificaciones, auditoría, métricas e integración con otros microservicios. Cuando toda esta lógica se concentra en un único caso de uso, la aplicación rápidamente se vuelve difícil de mantener y extender.

Para resolver este problema, muchas arquitecturas modernas adoptan patrones de mensajería interna como el CommandBus y el EventBus. Aunque ambos pueden parecer similares, cumplen roles completamente distintos dentro del sistema.

📌Comprender cuándo usar cada uno es clave para diseñar sistemas desacoplados, extensibles y mantenibles.

El problema del acoplamiento en casos de uso

Supongamos un caso de uso dentro de un sistema de pagos. A primera vista parece razonable:

El caso de uso conoce todos los procesos secundarios directamente. Esto genera varios problemas:

  • Alto acoplamiento: el caso de uso depende de cada colaborador concreto
  • Violación del principio Open/Closed: agregar un nuevo proceso requiere modificar el caso de uso
  • Lógica difícil de probar: hay que mockear todos los colaboradores en cada test
  • Fragilidad: un cambio en cualquier colaborador puede romper el caso de uso

Si mañana se necesita agregar métricas para machine learning, hay que tocar el caso de uso. Si después se necesita un nuevo canal de notificación, otra modificación. El sistema crece en fragilidad con cada nuevo requerimiento.

⚠️ El objetivo no es eliminar las dependencias, sino invertirlas. El caso de uso no debería conocer quién reacciona a sus acciones.

Introduciendo mensajería interna

Una solución elegante es separar las responsabilidades utilizando mensajería interna dentro de la aplicación.

Existen dos tipos principales de mensajes:

Commands

La solución es separar las responsabilidades usando mensajería interna. Existen dos tipos de mensajes con propósitos distintos:

Qué es un CommandBus

El CommandBus es un mecanismo que permite enviar comandos dentro de la aplicación para que sean procesados por su handler correspondiente. Cada comando tiene exactamente un handler: no hay ambigüedad sobre quién lo ejecuta.

Definición del comando

public record CreateOrderCommand(
        String customerId,
        List<OrderItem> items
) {}

Command Handler

@Component
public class CreateOrderCommandHandler {
    private final OrderRepository repository;

    public Mono<Void> handle(CreateOrderCommand command) {
        Order order = Order.create(
                command.customerId(),
                command.items()
        );
        return repository.save(order).then();
    }
}

Implementación del CommandBusController

@Component
public class CommandBus {
    private final Map<Class<?>, CommandHandler<?>> handlers;

    public <T> Mono<Void> dispatch(T command) {
        CommandHandler handler = handlers.get(command.getClass());
        if (handler == null) {
            return Mono.error(new IllegalArgumentException(
                "No handler found for " + command.getClass().getSimpleName()));
        }        
return handler.handle(command);
    }
}

Uso desde un controller reactivo

@PostMapping("/orders")
public Mono<Void> createOrder(@RequestBody OrderRequest request) {
    CreateOrderCommand command = new CreateOrderCommand(
            request.customerId(),
            request.items()
    );
    return commandBus.dispatch(command);
}

Qué es un EventBus

El EventBus permite publicar eventos dentro de la aplicación para que múltiples componentes puedan reaccionar a ellos de forma independiente. El publicador no sabe quién escucha — ni le importa.

                            Flujo del EventBus

                UseCase
                   │
                EventBus  ← publica el evento
                   │
                ┌──┴──────────────┬──────────────────┐
                │                 │                  │
       NotificationHandler  AuditHandler  MetricsHandler

Un evento puede tener muchos handlers.

Ejemplo de Domain Event

public class OrderCreatedEvent {
private final String orderId;
    private final LocalDateTime createdAt;
    public OrderCreatedEvent(String orderId, LocalDateTime createdAt) {
        this.orderId = orderId;
        this.createdAt = createdAt;
    }
}

Publicando el evento desde el caso de uso

public Mono<Void> execute(CreateOrderCommand command) {
    Order order = Order.create(
            command.customerId(),
            command.items()
    );
    return repository.save(order)
            .then(eventBus.publish(
                    new OrderCreatedEvent(
                            order.getId(),
                            command.customerId(),
                            LocalDateTime.now()
                    )
            ));
}

Implementación de EventBus

@Component
public class InMemoryEventBus {
    private final Map<Class<?>, List<EventHandler>> handlers = new HashMap<>();

    public Mono<Void> publish(Object event) {
        List<EventHandler> eventHandlers =
                handlers.getOrDefault(event.getClass(), List.of());
        return Flux.fromIterable(eventHandlers)
                .flatMap(handler -> handler.handle(event))
                .then();
    }
}

Handler de eventos

Ejemplo:

@Component
public class InMemoryEventBus {
    private final Map<Class<?>, List<EventHandler>> handlers = new HashMap<>();

    public Mono<Void> publish(Object event) {
        List<EventHandler> eventHandlers =
                handlers.getOrDefault(event.getClass(), List.of());
        return Flux.fromIterable(eventHandlers)
                .flatMap(handler -> handler.handle(event))
                .then();
    }
}

Diferencias entre CommandBus y EventBus

Aunque ambos patrones usan el concepto de mensaje y handler, sus roles son opuestos:

💡Una forma simple de distinguirlos: si el mensaje dice ‘haz esto’, es un Command. Si dice ‘esto ocurrió’, es un Event.

Integración con CQRS y programación reactiva

En sistemas CQRS, ambos patrones se complementan. Los comandos modifican el estado del sistema a través del write model. Los eventos sincronizan el read model como efecto secundario.

En arquitecturas reactivas, ambos buses pueden devolver Mono para integrarse naturalmente en el pipeline:

public Mono<Void> execute(CreateOrderCommand command) {
    return commandBus.dispatch(command)
            .then(eventBus.publish(
                    new OrderCreatedEvent(command.customerId())
            ))
            .then();
}

Este diseño permite que el caso de uso principal permanezca enfocado en su responsabilidad central, mientras que los efectos secundarios se resuelven de forma desacoplada y extensible.

Errores comunes al usar estos patrones

Adoptar CommandBus y EventBus no garantiza un buen diseño por sí solo. Estos son los errores más frecuentes y cómo evitarlos:

Cuándo usar cada uno

Conclusión

CommandBus y EventBus son patrones fundamentales para diseñar arquitecturas limpias basadas en DDD y CQRS. No son intercambiables: cada uno resuelve un problema diferente y su uso correcto define la calidad del diseño.

El CommandBus permite ejecutar acciones de forma controlada y determinística. El EventBus permite que el sistema reaccione a cambios del dominio de forma desacoplada, sin que el origen del cambio sepa quién escucha.

Juntos, eliminan el acoplamiento que hace que los sistemas sean frágiles: el caso de uso principal se enfoca en la lógica esencial, y los efectos secundarios se resuelven por reacción, no por instrucción directa.

🧭El objetivo no es tener más clases ni más patrones. Es que cada componente del sistema sepa exactamente cuál es su responsabilidad, y que los cambios puedan hacerse en un lugar sin romper el resto.

Cuando esto se logra, agregar un nuevo proceso al sistema es tan simple como registrar un nuevo handler — sin tocar nada más.


메타데이터
post_id
44dcff4d7212
slug
commandbus-vs-eventbus-cuándo-usar-cada-uno-y-por-qué-importa-44dcff4d7212
url
https://medium.com/@andrey.oliveros/commandbus-vs-eventbus-cu%C3%A1ndo-usar-cada-uno-y-por-qu%C3%A9-importa-44dcff4d7212
canonical_url
https://medium.com/@andrey.oliveros/commandbus-vs-eventbus-cu%C3%A1ndo-usar-cada-uno-y-por-qu%C3%A9-importa-44dcff4d7212
author_url
https://medium.com/@andrey.oliveros
status
ok
fetched_at
2026-06-20 20:29:01