-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
215 additions
and
0 deletions.
There are no files selected for viewing
10 changes: 10 additions & 0 deletions
10
application/backend-credit-card-enrollment/backend-c#/Common/Aggregate/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Aggregate | ||
|
||
In Event Sourcing system, the Aggregate is an in-memory representation of the current state of the system based on | ||
past events. The process of taking events from the Event Store and instantiating an Aggregate from them is called | ||
Aggregate hydration or Aggregate reconstitution. | ||
|
||
An Aggregate is typically hydrated in a command handler, or a reaction handler, when appending new events to the | ||
system. Why? Because we want to check the current state of the system from Aggregates in an immediately consistent | ||
fashion. The Aggregate should be implemented in an immediately consistent fashion through the use of optimistic | ||
or pessimistic locking when reconstituting the Aggregate. |
11 changes: 11 additions & 0 deletions
11
application/backend-credit-card-enrollment/backend-c#/Common/Ambar/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Ambar | ||
|
||
Tracking events in an EventStore for new events, filtering and forwarding them to downstream consumers while maintaining | ||
ordering and delivery guarantees can be complex and error-prone. Event buses such as RabbitMQ and Apache Kafka are often | ||
used to transmit and deliver events but can be complex to configure, manage, and scale. | ||
|
||
Ambar is a data streaming service that empowers you to build mission-critical real-time applications in minutes. Instead | ||
of producing to and consuming from message brokers, Ambar pulls records from databases, such as Event Stores and pushes | ||
records to application endpoints like your projection and reaction endpoints. | ||
|
||
Find out more about how to use Ambar in your production applications by visiting https://ambar.cloud/es |
13 changes: 13 additions & 0 deletions
13
application/backend-credit-card-enrollment/backend-c#/Common/Command/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Command Handler | ||
|
||
A Command Handler in an EventSourcing system is responsible for taking statements of intent (commands) from end users | ||
or other systems (both internal and external), performing validation, and upon valid conditions, adding new Events | ||
to the Event Store. | ||
|
||
To do this, the Command Handler reads past events from the Event store to hydrate / reconstitute an Aggregate. | ||
Once the aggregate is hydrated, the Command Handler checks for any business rules or constraints (e.g., ensuring an | ||
order hasn’t already been completed or that an account has sufficient balance). | ||
|
||
If all validations succeed, the Command Handler generates a new Event reflecting the state change requested by the command. | ||
This Event is then written back to the Event store, allowing the system to evolve while maintaining a full history of | ||
all changes. |
48 changes: 48 additions & 0 deletions
48
application/backend-credit-card-enrollment/backend-c#/Common/Event/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# Event | ||
|
||
## What are Events? | ||
|
||
Events represent state changes that have occurred in the system. | ||
Instead of storing state, our system stores a series of events. | ||
Current state is derived by replaying these events in the order they occurred. | ||
|
||
Events are immutable, meaning once they are created and stored, they cannot be modified. | ||
They are a record of what happened in the system. | ||
|
||
An event typically contains: | ||
|
||
* Event Name: A description of the specific action that occurred (e.g., OrderPlaced, AccountDebited, UserSignedUp). | ||
* Aggregate Identifier: The unique ID of the aggregate the event belongs in. | ||
* Timestamp: The time when the Event occurred. | ||
* Payload: Data describing the state change (the properties of the aggregate that have been changed). | ||
* Metadata (optional): Information such as the user agent or IP of the end user. | ||
|
||
## Why use Events? | ||
|
||
Events are used to: | ||
|
||
* Rebuild the current state of an aggregate by replaying the series of events. | ||
* Trigger side effects (reactions) such as sending notifications. | ||
* Asynchronously update read models (projections). | ||
* Provide an audit trail, capturing the full history of changes in the system for compliance and debugging. | ||
|
||
By relying on events as the source of truth, Event Sourcing allows for greater traceability, flexibility in replaying or | ||
restoring state, and the ability to respond to changes in a distributed, asynchronous manner. | ||
|
||
## Abstractions | ||
|
||
This directory contains our base definition for an Event. That is, `event_id`, `aggregate_id`, `aggregate_version`, | ||
`causation_id`, `correlation_id`, `recorded_on`. The event_name column, which is the name of the event, is not included | ||
because it's based on a mapping of the event class name to the event name. The `payload` column and `metadata` column | ||
are also not included because they are based on the event class properties. We use an abstraction called | ||
Serialized Event (see `src/main/java/cloud/ambar/common/serializedevent/SerializedEvent.java`) to store the `event_name`, | ||
`payload`, and `metadata`. | ||
|
||
**Why are there two extra abstract classes for creation events and transformation events?** | ||
|
||
Creation events are events that are used to create an aggregate. They are used to create the initial state of an aggregate. | ||
Transformation events are events that are used to transform an aggregate. They are used to change the state of an aggregate. | ||
|
||
When reconstituting / hydrating an aggregate, it's better not to have a default state of the aggregate which contains invalid | ||
state. Instead, it's better to codify into our type system which events can create a valid aggregate state on their own | ||
and which events can transform an aggregate. This way, we can ensure that the aggregate is always in a valid state. |
11 changes: 11 additions & 0 deletions
11
application/backend-credit-card-enrollment/backend-c#/Common/EventStore/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Event Store | ||
|
||
The Event Store is responsible for saving new Events and fetching existing Events to hydrate / reconstitute | ||
Aggregates. | ||
|
||
The Event Store saves Events, but it does not save them directly, it first converts them to a SerializedEvent. | ||
The SerializedEvent is a representation of the Event that can be stored in a database. | ||
|
||
Additionally, the Event Store does not simply return aggregates, but it returns an Aggregate plus | ||
Event Ids, that would be necessary to append more events to the Aggregate (event_id and correlation_id in the | ||
last event of that Aggregate). |
49 changes: 49 additions & 0 deletions
49
application/backend-credit-card-enrollment/backend-c#/Common/Projection/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# Projection | ||
|
||
A projection is a read model that is derived from the events in the system. Projections are used to query the current | ||
state of the system. For example, in an ecommerce website users will need to know which items are available, | ||
before they add an item to their cart. This allows the read side of a system to often be decoupled | ||
from the write side. | ||
|
||
When an Event is emitted (e.g., OrderPlaced, ProductUpdated), a projection listens to the stream of events and filters | ||
the relevant events it needs to process. For example, a projection that builds a list of user orders would only listen | ||
for OrderPlaced and OrderCanceled events. It updates the read model by applying the Event data, ensuring the model | ||
reflects the latest state. | ||
|
||
Projections continuously update the projection database as new events arrive, keeping the read model in sync with the | ||
most recent state changes. This enables high-performance queries and ensures that the read side remains highly available | ||
and scalable. | ||
|
||
You can use projections for sharing state with your end users, but also to do validation in command handlers. But | ||
note that projections are built asynchronously, so they are eventually consistent. If you need to enforce business | ||
rules in an immediately consistent manner, you should do so by loading aggregates as opposed to reading projections. | ||
|
||
## How Projections Work | ||
|
||
Projections are built by listening to events and updating the read model accordingly. When an event is received, | ||
we update a projection database (MongoDB), based on the contents of the event and any existing data in the | ||
projection database. This behavior is captured by extending a `ProjectionHandler`. | ||
|
||
### How do events get sent from the Event Store to the Projection Handlers? | ||
|
||
We use Ambar to read events from the Event Store and send them to the Projection Handlers, via an HTTP endpoint. | ||
The HTTP endpoint is defined through extending a `ProjectionController`, which will receive the events and send | ||
them to the corresponding `ProjectionHandler`. | ||
|
||
#### How do we make sure that events are sent at least once, and in order per aggregate, to a Projection Handler? | ||
|
||
Ambar takes care of this out of the box. All you have to take care of is making every `ProjectionController` idempotent. | ||
To make sure projections endpoint only process events once, the `ProjectionController` uses an abstraction called | ||
`ProjectedEvent` which keeps track of every event that has already been processed. | ||
|
||
### Where can I find the Ambar configuration? | ||
|
||
The Ambar configuration is located in the `local-development/ambar-config.yml`. | ||
|
||
### In ambar-config.yml, why are events ordered per correlation id, instead of aggregate id? | ||
|
||
Ordering events per correlation id retains the order of events per aggregate, but also retains the order of events | ||
across related aggregates. E.g., if you have an aggregate for November, and an aggregate for December, and the aggregate | ||
for December directly follows the aggregate for November (using the same correlation id), Ambar will give you the events | ||
in order per aggregate, but will also retain order across aggregates (Ambar will project November first, and December | ||
second). |
16 changes: 16 additions & 0 deletions
16
application/backend-credit-card-enrollment/backend-c#/Common/Query/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Query Handler | ||
|
||
A Query Handler in an EventSourcing system is responsible for taking requests for information (queries) from end users | ||
or other systems (both internal and external), validating the query (e.g., checking if a user has the right | ||
permissions), and returning said information. | ||
|
||
To do this, Query Handlers will read state from read model / projection databases. Those databases are _filled_ up | ||
by Projections (see Projection directory). | ||
|
||
### Advantages: | ||
|
||
* Performance: Since queries access a read-optimized database, response times are faster and more efficient. | ||
* Scalability: The query data storage (projection/read model databases) can be scaled separately from the Event Store | ||
used in Command Handlers. | ||
* Flexibility: Different read models can be tailored for various use cases, offering specialized views for reporting, | ||
analytics, or specific user interfaces. |
39 changes: 39 additions & 0 deletions
39
application/backend-credit-card-enrollment/backend-c#/Common/Reaction/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Reaction | ||
|
||
A reaction performs a side effect in response to an Event. While projections update state, reactions | ||
may trigger actions like sending notifications, updating external systems, or initiating new workflows. Reactions also | ||
filter relevant events, allowing for targeted responses to specific state changes. Reactions are not only responsible | ||
for performing the side effect, but also for ensuring that the side effect is idempotent by writing the result of the | ||
side effect into the Event Store as an Event. | ||
|
||
## How Reactions Work | ||
|
||
Reactions are built by listening to events, triggering side effects, and recording the result of those side effects | ||
to the Event Store. This behavior is captured by extending a `ReactionHandler`. | ||
|
||
### How do events get sent from the Event Store to the Reaction Handlers? | ||
|
||
We use Ambar to read events from the Event Store and send them to the Reaction Handlers, via an HTTP endpoint. | ||
The HTTP endpoint is defined through extending a `ReactionController`, which will receive the events and send | ||
them to the corresponding `ReactionHandler`. | ||
|
||
#### How do we make sure that events are sent at least once, and in order per aggregate, to a Reaction Handler? | ||
|
||
Ambar takes care of this out of the box. All you have to take care of is making every `ReactionController` idempotent. | ||
To make sure reaction endpoint only process Events once, the `ReactionHandler` has to commit the results of its | ||
side effect into the Event Store with a new Event. This way, if the reaction is triggered again, it will be able to find | ||
an existing event in the Event Store. Note that the Reaction Event has to have a deterministic event id, so that | ||
we can check if the event has already been processed with the `ReactionHandler`. | ||
|
||
### Where can I find the Ambar configuration? | ||
|
||
The Ambar configuration is located in the `local-development/ambar-config.yml`. | ||
|
||
### In ambar-config.yml, why are events ordered per correlation id, instead of aggregate id? | ||
|
||
Ordering events per correlation id retains the order of events per aggregate, but also retains the order of events | ||
across related aggregates. E.g., if you have an aggregate for November, and an aggregate for December, and the aggregate | ||
for December directly follows the aggregate for November (using the same correlation id), Ambar will give you the events | ||
in order per aggregate, but will also retain order across aggregates (Ambar will send November first, and December | ||
second, so you can react in order). | ||
|
8 changes: 8 additions & 0 deletions
8
...tion/backend-credit-card-enrollment/backend-c#/Common/SerializedEvent/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# Serialized Event | ||
|
||
This module contains the classes that are used to serialize and deserialize the events that are sent to the database. | ||
A Serialized Event is a representation in which additional properties, not encoded in the abstract Event | ||
are converted into fields that go into the payload or metadata fields. Additionally, the Serialized Event | ||
contains an `event_name` which can be used to figure out which class the SerializedEvent should be deserialized into. | ||
|
||
Serialized Events are used when communicating with the database (Postgres) or event bus (Ambar). |
5 changes: 5 additions & 0 deletions
5
application/backend-credit-card-enrollment/backend-c#/Common/SessionAuth/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Session Authentication | ||
|
||
This directory contains an API to transform a session token into a user ID. This is fetched from a projection database, | ||
but note that the _filling up_ of that projection database is done by another service, so this codebase | ||
only needs to read the session read model / projection database, not update it. |
5 changes: 5 additions & 0 deletions
5
application/backend-credit-card-enrollment/backend-c#/CreditCard/Product/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
The "product" in "credit card product" is not implemented in this language. It's implemented in the php codebase. | ||
But we need to listen to events from the PHP codebase | ||
|
||
So in this directory, we create classes for those events, so we can build projections | ||
and reactions based on those events elsewhere in the java codebase. |