Desde La Capa De Dominio
← Volver al blog

¿PostgreSQL como cola de mensajería? Depende.

Reflexiones sobre cuándo usar PostgreSQL como event bus, cómo implementarlo con dos tablas y cómo se relaciona con las transacciones en sistemas con arquitectura orientada a eventos.

Event-Driven Architecture PostgreSQL DDD Clean Architecture SOLID Infraestructura

Este último fin de semana aproveché para hacer un par de cursos de Codely: "Event bus en base de datos [Diseño de Infraestructura]" y "Transacciones [Diseño de infraestructura]", con el fin de profundizar en la teoría de cómo implementar estas soluciones.


🐇 El contexto: RabbitMQ no siempre está en la mesa


En los últimos años he experimentado los beneficios de sistemas con event-driven architecture usando RabbitMQ principalmente. Aun así, hay proyectos en los que participo que o bien no tienen implementados los eventos, o todavía no requieren de esa potencia que nos proporcionan piezas de infraestructura como RabbitMQ o Kafka.

Sin embargo, he notado que no utilizar este tipo de sistemas reduce significativamente la capacidad de construir código sin romper principios como OCP o SRP, sobre todo en sistemas con varios casos de uso derivados. Además, la dificultad en cuanto a testeabilidad y mantenibilidad del código incrementa exponencialmente.


🤔 Entonces, ¿PostgreSQL como cola?


Volviendo a la pregunta del principio: una vez más, depende. Un bus en memoria podría resolver todos los problemas de mantenibilidad y testeabilidad y, en el caso de un sistema sencillo en el que podamos permitirnos sincronía, es una buena solución.

En el caso particular que estoy resolviendo, la respuesta definitiva es que sí: PostgreSQL es mi nueva cola de mensajería. Me garantiza:


🗃️ La implementación: dos tablas


En mi caso necesito dos tablas:

Cuando se levanta la app, se asigna un worker que va haciendo consultas a la tabla de eventos y consumiendo lo que se va creando. Desde ahí, es el funcionamiento típico de un bus: el publisher publica los eventos de dominio y los consumers los van ejecutando mediante los subscribers para los casos de uso derivados.

flowchart TB
    UC[Use Case]
    EB[Event Bus]
    EV[(domain_events)]
    SR[(event_subscribers)]
    W[Worker]
    S1[Subscriber A]
    S2[Subscriber B]
    DL[(dead_letter)]

    UC -->|publica| EB
    EB -->|inserta| EV
    EV -.->|referencia| SR
    EV -->|poll + SKIP LOCKED| W
    W --> S1
    W --> S2
    S1 -->|marca procesado| EV
    S2 -->|marca procesado| EV
    EV -->|max retries| DL

    style EV fill:#1e1b4b,stroke:#818cf8,color:#e0e7ff
    style SR fill:#1e1b4b,stroke:#818cf8,color:#e0e7ff
    style DL fill:#3b0764,stroke:#a855f7,color:#f3e8ff
    style W fill:#0f172a,stroke:#818cf8,color:#e0e7ff

🔗 El rol de las transacciones


Enlazo esto con las transacciones, ya que me parece una posible fase intermedia previa a los eventos de dominio. Aunque abogaría por su uso en las implementaciones de los repositorios, en caso de tener que orquestar varios casos de uso las utilizaría como decorador en la capa de infraestructura.

Mi conclusión final es reducir su uso a situaciones estrictamente necesarias. Teniendo un sistema de eventos con retry, dead letter, etc., me inclino por tratar todos los casos de uso derivados sin bloquear la opción de continuar generando recursos mediante la funcionalidad principal.


Publicado originalmente en LinkedIn.