EDA Patterns

Explore the event patterns: event notifications, event-carried state transfer, and event sourcing.

Three different uses or patterns exist that can be called EDA individually or altogether, as follows:

  • Event notifications

  • Event-carried state transfer

  • Event sourcing

Event notifications

Events can be used to notify something has occurred within our application. A notification event typically carries the absolute minimum state, perhaps even just the identifier (ID) of an entity or the exact time of the occurrence of their payload. Components that are notified of these events may take any action they deem necessary. Events might be recorded locally for auditing purposes, or the component may make calls back to the originating component to fetch additional relevant information about the event.

Example

Let’s see an example of PaymentReceived as an event notification in Go, as follows:

type PaymentReceived struct {
PaymentID string
}
Go code: Event notification of payment received

Here is how that notification might be used:

Press + to interact
Receiving of PaymentReceived notification by two different services
Receiving of PaymentReceived notification by two different services

The above image shows the PaymentReceived notification being received by two different services. While ServiceA only needed to be notified of the event, ServiceB requires additional information and must make a call back to the Payments service to fetch it.

Event-carried state transfer

Event-carried state transfer is an asynchronous cousin to representational state transfer (REST). In contrast with REST’s on-demand pull model, event-carried state transfer is a push model where data changes are sent out to be consumed by any components that might be interested. The components may create their own local cached copies, negating any need to query the originating component to fetch any information to complete their work.

Example

Let’s see an example of PaymentReceived as an event-carried state transfer, as follows:

type PaymentReceived struct {
PaymentID string
CustomerID string
OrderID string
Amount int
}
Go code: Event-carried state transfer of payment received

In this example for event-carried state transfer, we’ve included some additional IDs and an amount collected, but more detail could be added to provide as much detail as possible, as illustrated in the following diagram:

Press + to interact
Receiving of PaymentReceived with additional information
Receiving of PaymentReceived with additional information

When the PaymentReceived event is sent with additional information, it changes how downstream services might react to it. We can see in the above image that ServiceB no longer needs to call the Payments service because the event it has received already contains everything it requires.

Event sourcing

Instead of capturing changes as irreversible modifications to a single record, those changes are stored as events. These changes or streams of events can be read and processed to recreate the final state of an entity when it is needed again.

How it works

When we use event sourcing, we store the events in an event store rather than communicating them with other services, as illustrated in the following diagram:

Press + to interact
Payment data recorded using event sourcing
Payment data recorded using event sourcing

In the above figure, we see that the entire history of our data is kept as individual entries in the event store. When we need to work with a payment in the application, we would read all the entries associated with that record and then perform a left fold of the entries to recreate the final state.