Pergunta

I'm considering two types of notifications:

  • Domain events used internally within a bounded context
  • Application events used to exchange notification messages between bounded contexts

Each aggregate in my bounded context defines its own transactional boundary, so that only one aggregate is modified per transaction. When a transaction is successfully committed, a domain event is published asynchronously and internally in order to inform the other aggregates of the bounded context that something happened.

A few application services in the bounded context react to these events. They each update an aggregate in a separate transaction. At the end, all the aggregates of the bounded context are made eventually consistent.

I need to publish some of these domain events to other bounded contexts. But I would feel more comfortable if I could keep a loose coupling between the domain events of each bounded context and the notification messages that are exchanged between the bounded contexts. Furthermore, I can foresee situations where there is not always a 1-1 match between domain events and application events, rhetorically speaking.

Then I have some questions:

  • Is this a correct usage of domain events within a bounded context?
  • If the container / VM that hosts the bounded context (implemented in a micro-service) crashes before all the aggregates are made eventually consistent, I have an inconsistent state when the micro-service is restarted. In his red book, Vaughn Vernon suggests the implementation of an event store. There are several advantages to having an event store, but among all these advantages, will it help solving the inconsistency issue?
  • Shall I really separate domain events from application events?
  • If I do implement an event store for the domain events, what's the best strategy to turn these domains events into application events and forward these application events to other bounded contexts while making sure that if the delivery of a notification message fails it can be redelivered when needed?
Foi útil?

Solução

I'm considering two types of notifications

For a number of reasons, the literature is pretty weak here; but I believe that you will find that the taxonomy of "domain events" and "integration events" is more popular than the spelling you are using.

Is this a correct usage of domain events within a bounded context

Pretty much. Many of the earlier uses of domain events had handlers that ran in the same transaction as the triggering "aggregate".

That distinction is important for your other questions -- because the handlers were running in the same transaction, all of the updates would fail or succeed together. That eliminated consistency concerns.

There are several advantages to having an event store, but among all these advantages, will it help solving the inconsistency issue?

No; having an event store won't solve this problem. The main consistency issue that you face is a consequence of the fact that you aren't saving the domain events in the transaction with the aggregate.

If you have the domain events in place, and appropriate plumbing to retry delivery of those events after a restart, then your system can eventually recover itself into a consistent state.

See, for instance, Udi Dahan: Reliable Messaging without Distributed Transactions.

Outras dicas

After watching the Udi Dahan's video, I'm trying the following approach:

  • I have an event store that contains the domain events,
  • And I have an achievement store that contains what has been accomplished.

An application service processes a request. It opens a transaction, updates an aggregate, and publishes a domain event. The domain event is stored in the event store with a unique ID and a timestamp. Then, when the transaction is successfully committed, the event is dispatched asynchronously.

Another application service in the same bounding context receives the published domain event. It opens a transaction, and checks if an achievement related to the domain event exists in the achievement store. If no such achievement could be found, the application service updates another aggregate to achieve an eventual consistency, and publishes an achievement. This achievement is stored in the achievement store, with a reference to the domain event that has been processed, and with an achievement type, which can simply be the name of the aggregate.

AchievementID | EventID | AchievementType | CompletedOn
--------------|---------|-----------------|------------
     5        |    3    |        A        |  timestamp  <-- first achievement
     6        |    3    |        B        |  timestamp  <-- second achievement

Of course, this application service can also publish a domain event itself, but for the sake of simplicity, I won't consider this scenario.

If the micro-service crashes, the domain events that have been stored in the last xx seconds before the crash are replayed. Each application service interested in these domain events behaves as if these events were just published. They check if a corresponding achievement exists in the achievement store, and process the domain events one by one if no achievement could be found.

I can reuse this structure to also process messages from a message queue by storing in the event store a special event named MessageReceived. The payload of this event contains the ID of the received but not yet processed message. When the message is processed, an achievement is stored in the achievement store. This record is used to identify already processed messages in case the micro-service crashed.

Finally, a notification service listens to all the published domain events. It opens a transaction, translates a domain event into an application (integration) event, sends that event to the other bounded contexts, and publishes an achievement. The ID of the message is the ID of the domain event, so if the micro-service crashes and the domain events are replayed, the messages that are sent again can be identified as duplicates.

AchievementID | EventID | AchievementType | CompletedOn
--------------|---------|-----------------|------------
     5        |    3    |        A        |  timestamp
     6        |    3    |        B        |  timestamp
     7        |    3    |  Notification   |  timestamp  <-- won't be published again

The application (integration) events are not stored. There is no need to make them persistent since they are computed from domain events that have already been stored.

This looks a bit complex, but all the building blocks of this solution can be implemented in a micro-service chassis and therefore reused.

Licenciado em: CC-BY-SA com atribuição
scroll top