In order to support the development of independently deploy-able services, we need a mechanism that allows us to perform business transactions that span multiple services. Event-Driven Architecture supported by a Pub/Sub system can help us achieve this.
Whenever a significant change occurs in the domain data of a service, an event is published to the Pub/Sub system. Any interested service is able to subscribe to these events and perform any manner of actions in response, each of which could trigger additional events.
The Anti-Fraud Service
In this example we have a virtual wallet service. An anti-fraud service could subscribe to “Transaction Added” events published by the virtual wallet service in order to detect fraudulent activity. If the anti-fraud service determines that a specific transaction appears suspicious, it can in turn publish a “Fraud Detected” event. This allows the Virtual Wallet to mark the account in question as flagged and disallow any further transactions.
An event is the result of a significant change in the domain data that a service manages. In the virtual wallet example above, adding a transaction would warrant an event to be published, since other services are likely to be interested in this type of event. This means that the operation to add a transaction in the virtual wallet involves the following two operations:
- Updating the domain data (Persist the transaction and update the account balance)
- Publishing an Event
In order to ensure reliable communication between services, it is essential that no events are lost in the event of catastrophic failure which could occur at any point in the process. If the domain data is updated, but the publishing of the event fails, the data becomes inconsistent.
Similarly, if the event is published first, but the operation to update the domain data fails, the data is inconsistent.
Adding Domain Data and Events Atomically
Where a relational database is employed as the primary data store for a service, local database transactions can be leveraged to ensure that the events are added atomically. Domain data and events can be added in the same database transaction, ensuring that the data remains consistent:
If there is a failure at any point of the operation, the entire transaction fails – the domain data remains in the state is was in before the transaction started and the event is not added to the event table.
However, if there is no failure during the transaction, the domain data is updated and the event is added to the event table atomically.
An Event Producer, which operates in a separate thread to the main service application, reads events from the event table which are then published to the Pub/Sub system.
On the other hand, any service that wishes to be notified of events can implement an Event Consumer. The Event Consumer subscribes to any events it is interested in and updates the domain data of the Consuming Service. This could in turn trigger more events.
The Consumer is responsible for keeping track of Events which have been processed, since duplication could occur. It does so by storing the GUID / UUID of the published event in its data store. For each event it consumes, it first checks the data store if the event exists before applying domain data changes and adding the event in a single transaction.
Message Delivery Guarantees
Exactly-once message delivery guarantees come at a cost of performance and complexity. However, it is possible to implement an Event Consumer that is able to remove duplicate events. This means that the Event Publisher as well as the Pub/Sub system can implement At-least-once delivery guarantees and remain performant.