docs: update event sourcing

This commit is contained in:
Ilkka Seppälä
2024-05-25 22:46:05 +03:00
parent 865d98cbec
commit 74f039f1cd
2 changed files with 91 additions and 23 deletions
+91 -19
View File
@@ -23,27 +23,23 @@ Event Sourcing is a design pattern that advocates for the storage of state chang
## Explanation
Real world example
> The modern emailing system is an example of the fundamental process behind the event-queue design pattern. When an email is sent, the sender continues their daily tasks without the necessity of an immediate response from the receiver. Additionally, the receiver has the freedom to access and process the email at their leisure. Therefore, this process decouples the sender and receiver so that they are not required to engage with the queue at the same time.
Real-world example
> Consider a banking application that tracks all transactions for user accounts. In this system, every deposit, withdrawal, and transfer is recorded as an individual event in an event log. Instead of simply updating the current account balance, each transaction is stored as a discrete event. This approach allows the bank to maintain a complete and immutable history of all account activities. If a discrepancy occurs, the bank can replay the sequence of events to reconstruct the account state at any point in time. This provides a robust audit trail, facilitates debugging, and supports features like transaction rollback and historical data analysis.
In plain words
> The buffer between sender and receiver improves maintainability and scalability of a system. Event queues are typically used to organise and carry out interprocess communication (IPC).
> Event Sourcing records all state changes as a sequence of immutable events to ensure reliable state reconstruction and auditability.
Wikipedia says
[Microsoft's documentation](https://learn.microsoft.com/en-us/azure/architecture/patterns/event-sourcing) says
> Message queues (also known as event queues) implement an asynchronous communication pattern between two or more processes/threads whereby the sending and receiving party do not need to interact with the queue at the same time.
Key drawback
> As the event queue model decouples the sender-receiver relationship - this means that the event-queue design pattern is unsuitable for scenarios in which the sender requires a response. For example, this is a prominent feature within online multiplayer games, therefore, this approach require thorough consideration.
> The Event Sourcing pattern defines an approach to handling operations on data that's driven by a sequence of events, each of which is recorded in an append-only store. Application code sends a series of events that imperatively describe each action that has occurred on the data to the event store, where they're persisted. Each event represents a set of changes to the data (such as AddedItemToOrder).
**Programmatic Example**
In the given code, we can see an example of the Event Sourcing pattern in the Event class. This class manages a queue of events and controls thread operations for asynchronous processing. Each event can be seen as a state change that affects the state of the system.
In the programmatic example we transfer some money between bank accounts.
The `Event` class manages a queue of events and controls thread operations for asynchronous processing. Each event can be seen as a state change that affects the state of the system.
```java
public class Event {
@@ -67,7 +63,7 @@ public class Event {
}
```
The triggerEvent method is where the events are created. Each time an event is triggered, it is created and added to the queue. This event contains the details of the state change.
The `triggerEvent` method is where the events are created. Each time an event is triggered, it is created and added to the queue. This event contains the details of the state change.
```java
public void triggerEvent(EventMessage eventMessage) {
@@ -83,7 +79,7 @@ public void triggerEvent(EventMessage eventMessage) {
}
```
The init and startThread methods ensure the thread is properly initialized and running. The stopService method is used to stop the thread when it's no longer needed. These methods manage the lifecycle of the thread used to process the events.
The `init` and `startThread` methods ensure the thread is properly initialized and running. The `stopService` method is used to stop the thread when it's no longer needed. These methods manage the lifecycle of the thread used to process the events.
```java
public synchronized void stopService() throws InterruptedException {
@@ -118,6 +114,84 @@ private synchronized void startThread() {
}
```
The example is driven by the `App` class and its `main` method.
```java
@Slf4j
public class App {
public static final int ACCOUNT_OF_DAENERYS = 1;
public static final int ACCOUNT_OF_JON = 2;
public static void main(String[] args) {
var eventProcessor = new DomainEventProcessor(new JsonFileJournal());
LOGGER.info("Running the system first time............");
eventProcessor.reset();
LOGGER.info("Creating the accounts............");
eventProcessor.process(new AccountCreateEvent(
0, new Date().getTime(), ACCOUNT_OF_DAENERYS, "Daenerys Targaryen"));
eventProcessor.process(new AccountCreateEvent(
1, new Date().getTime(), ACCOUNT_OF_JON, "Jon Snow"));
LOGGER.info("Do some money operations............");
eventProcessor.process(new MoneyDepositEvent(
2, new Date().getTime(), ACCOUNT_OF_DAENERYS, new BigDecimal("100000")));
eventProcessor.process(new MoneyDepositEvent(
3, new Date().getTime(), ACCOUNT_OF_JON, new BigDecimal("100")));
eventProcessor.process(new MoneyTransferEvent(
4, new Date().getTime(), new BigDecimal("10000"), ACCOUNT_OF_DAENERYS,
ACCOUNT_OF_JON));
LOGGER.info("...............State:............");
LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS).toString());
LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_JON).toString());
LOGGER.info("At that point system had a shut down, state in memory is cleared............");
AccountAggregate.resetState();
LOGGER.info("Recover the system by the events in journal file............");
eventProcessor = new DomainEventProcessor(new JsonFileJournal());
eventProcessor.recover();
LOGGER.info("...............Recovered State:............");
LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS).toString());
LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_JON).toString());
}
}
```
Running the example produces the following console output.
```
22:40:47.982 [main] INFO com.iluwatar.event.sourcing.app.App -- Running the system first time............
22:40:47.984 [main] INFO com.iluwatar.event.sourcing.app.App -- Creating the accounts............
22:40:47.985 [main] INFO com.iluwatar.event.sourcing.domain.Account -- Some external api for only realtime execution could be called here.
22:40:48.089 [main] INFO com.iluwatar.event.sourcing.domain.Account -- Some external api for only realtime execution could be called here.
22:40:48.090 [main] INFO com.iluwatar.event.sourcing.app.App -- Do some money operations............
22:40:48.090 [main] INFO com.iluwatar.event.sourcing.domain.Account -- Some external api for only realtime execution could be called here.
22:40:48.095 [main] INFO com.iluwatar.event.sourcing.domain.Account -- Some external api for only realtime execution could be called here.
22:40:48.099 [main] INFO com.iluwatar.event.sourcing.domain.Account -- Some external api for only realtime execution could be called here.
22:40:48.099 [main] INFO com.iluwatar.event.sourcing.domain.Account -- Some external api for only realtime execution could be called here.
22:40:48.101 [main] INFO com.iluwatar.event.sourcing.app.App -- ...............State:............
22:40:48.104 [main] INFO com.iluwatar.event.sourcing.app.App -- Account{accountNo=1, owner='Daenerys Targaryen', money=90000}
22:40:48.104 [main] INFO com.iluwatar.event.sourcing.app.App -- Account{accountNo=2, owner='Jon Snow', money=10100}
22:40:48.104 [main] INFO com.iluwatar.event.sourcing.app.App -- At that point system had a shut down, state in memory is cleared............
22:40:48.104 [main] INFO com.iluwatar.event.sourcing.app.App -- Recover the system by the events in journal file............
22:40:48.124 [main] INFO com.iluwatar.event.sourcing.app.App -- ...............Recovered State:............
22:40:48.124 [main] INFO com.iluwatar.event.sourcing.app.App -- Account{accountNo=1, owner='Daenerys Targaryen', money=90000}
22:40:48.124 [main] INFO com.iluwatar.event.sourcing.app.App -- Account{accountNo=2, owner='Jon Snow', money=10100}
```
In this example, the state of the system can be recreated at any point by replaying the events in the queue. This is a key feature of the Event Sourcing pattern.
## Class diagram
@@ -158,10 +232,8 @@ Benefits:
## Credits
* [Building Microservices: Designing Fine-Grained Systems](https://amzn.to/443WfiS)
* [Implementing Domain-Driven Design](https://amzn.to/3JgvA8V)
* [Patterns, Principles, and Practices of Domain-Driven Design](https://amzn.to/3VVhfWX)
* [Building Microservices: Designing Fine-Grained Systems](https://amzn.to/443WfiS)
* [Martin Fowler - Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html)
* [Event Sourcing in Microsoft's documentation](https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing)
* [Reference 3: Introducing Event Sourcing](https://msdn.microsoft.com/en-us/library/jj591559.aspx)
* [Event Sourcing pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing)
* [Event Sourcing (Martin Fowler)](https://martinfowler.com/eaaDev/EventSourcing.html)
* [Event Sourcing pattern (Microsoft)](https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing)
@@ -49,8 +49,6 @@ import lombok.extern.slf4j.Slf4j;
* After the shut-down, system state is recovered by re-creating the past events from event
* journals. Then state is printed so a user can view the last state is same with the state before a
* system shut-down.
*
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
*/
@Slf4j
public class App {
@@ -112,6 +110,4 @@ public class App {
LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS).toString());
LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_JON).toString());
}
}