To ensure that all domain events are reliably published to the message bus, we commit new state of the aggregate and domain events as one atomic transaction. After that some message relay reads new unpublished events from the DB, sends them to the message bus, and marks the events as published or deletes them.

New domain events can be fetched in either:

  • Pull: polling publisher.
  • Push: tailing transactions log, stream of events (AWS DynamoDB).

Guaranties at-least once delivery.

Why

This pattern addresses possible issues with other implementations:

  • When we publish events from the aggregate itself we can publish event and not yet commit new state of the aggregate to the DB.
  • When we publish events from the application layer after we commit changes to the DB, some errors in the code between these two steps may lead to event not being published, even though state of the aggregate changed.

Applications

  • Relational databases support atomic commits to different tables as part of transactions.
  • AWS DynamoDB even though is an NoSQL DB, supports transactions.
  • For NoSQL databases, that don’t support transactions, events can be embedded in the aggregate body.
{
    "campaign-id": "364b33c3-2171-446d-b652-8e5a7b2be1af",
    "state": {
        "name": "Autumn 2017",
        "publishing-state": "DEACTIVATED",
        "ad-locations": [
            ...
        ]
        ...
    },
    "outbox": [  // this
        {
            "campaign-id": "364b33c3-2171-446d-b652-8e5a7b2be1af",
            "type": "campaign-deactivated",
            "reason": "Goals met",
            "published": false
        }
    ]
}