Skip to content

Commit ce82d1d

Browse files
dgafkagitbook-bot
authored andcommitted
GITBOOK-874: No subject
1 parent 789ab05 commit ce82d1d

File tree

1 file changed

+33
-26
lines changed
  • modelling/event-sourcing/setting-up-projections

1 file changed

+33
-26
lines changed

modelling/event-sourcing/setting-up-projections/README.md

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,28 @@ Before diving into this topic, read the [Event Sourcing Introduction](../event-s
1010

1111
The power of Event Sourcing is not only the full history of what happened. \
1212
As we do have a full history, it's easy to imagine that we may want to use it for different purposes.\
13-
One most of our purposes will be related with the need to view this history in specific way. \
13+
And one of our purposes will be related to view this data in specific way. \
1414

1515

1616
Let's take as an example our Ticket's Event Stream:
1717

1818
<figure><img src="../../../.gitbook/assets/ticket_event_stream_2.png" alt=""><figcaption></figcaption></figure>
1919

20-
Having the whole history of our Ticket is great, yet in most of the situations we would also want to know how given Ticket looks like at this very moment, and for this we can use Projections.&#x20;
20+
In most of the situations besides knowing the history, we would also want to know how all does Tickets looks at present moment. Therefore it means we need to build an view from those events which will represent current state, and for this we use **Projections**.&#x20;
2121

22-
## Projections and Read Models
22+
## Projections
2323

2424
So we may be in need to build the list of all the tickets and their current status
2525

2626
<figure><img src="../../../.gitbook/assets/ticket-list (1).png" alt=""><figcaption><p>List of tickets available in the system with current status</p></figcaption></figure>
2727

28-
This is an list of our tickets with their current status. This is the approach that we would most likely implement when there would be no Event Sourcing involved at all. \
2928
For example this list can be stored in the database table with three columns:
3029

3130
* id
3231
* type
3332
* status
3433

35-
The difference between the traditional approach and ES approach is that we will be delivering this view from the Event Stream. Therefore this will be only the representation build up from the past events, and will be used only for reading. This kind of data delivered from Events, we call **Read Models**.
34+
The difference between the traditional approach and ES approach is that we will be delivering this view from the Event Stream. Therefore this will be only the representation build up from the past events, and will be used only for reading. Data delivered from Events to shape specific view, we call **Read Models**.
3635

3736
## Building first Projection
3837

@@ -70,22 +69,22 @@ We will be subscribing to those in order to build our new **Ticket List Projecti
7069
#[Projection("ticket_list", Ticket::class)]
7170
class TicketListProjection {
7271

73-
// This is Connection to our Database
72+
// This is Connection to our Database or wherever we want to store the data
7473
public function __construct(private Connection $connection) {}
7574

7675
(...)
7776
```
7877

7978
We do start by creating new class, which we mark with **Projection** attribute.\
80-
The first argument is the name of our new projection "ticket\_list", the second is the related ES Aggregate from which we will be subscribing to Events. The second argument is important, and we will see why a bit further in the road.
79+
The first argument is the name of our new projection **"ticket\_list",** the second is the related ES Aggregate **"Ticket"** from which we will be subscribing Events. We will touch on the second argument more in next sections.
8180

82-
* Ecotone **will take care of creating the Projection for us**, therefore we can tell him how to do it
81+
* Ecotone **will take care of creating the Projection for us**, therefore we can tell it how to do it
8382

8483
```php
8584
#[ProjectionInitialization]
8685
public function initializeProjection() : void
8786
{
88-
if ($this->getConnection()->createSchemaManager()->tablesExist('ticket_list')) {
87+
if ($this->connection->createSchemaManager()->tablesExist('ticket_list')) {
8988
return;
9089
}
9190

@@ -113,7 +112,7 @@ public function onTicketWasPrepared(TicketWasRegistered $event) : void
113112
}
114113
```
115114

116-
This is enough for Ecotone to know that this should be triggered whenever TicketWasRegistered happens. In here we basically store new ticket in the our table
115+
This is enough for Ecotone to know that this should be triggered whenever **TicketWasRegistered** happens. As a result of triggering this Event Handler we store new ticket in the our database table
117116

118117
We also want to change the status, when ticket is closed, so let's add that now:
119118

@@ -129,37 +128,41 @@ public function onTicketWasCancelled(TicketWasCancelled $event) : void
129128
}
130129
```
131130

132-
This is all to make our Projection work. There is no any additional configuration needed as we are working from higher level abstraction. We tell what events we want to have delivered, and Ecotone will take care of delivering and triggering our Projections.
131+
This is all to make our Projection work. There is no any additional configuration needed as we are working from higher level abstraction. We tell what events we want to have delivered, and Ecotone will take care of delivering and initializing and triggering our Projections.
133132

134-
## How is this triggered
133+
## How Projections are triggered
135134

136135
By default this Projection will be triggered synchronously. This means that after Event Sourced Aggregate is called, Events will first be stored in the Event Stream, and then Projection will be called.&#x20;
137136

138137
<figure><img src="../../../.gitbook/assets/aggregate.png" alt=""><figcaption><p>Event Sourced Aggregate stores Event in the Event Stream and then it's published</p></figcaption></figure>
139138

140-
So when Command Handler returns Events, they are first stored in the Event Stream and then published. Our Projection subscribe to those Events, therefore it will be triggered as a result
139+
Our Projection subscribe to those Events, therefore it will be triggered as a result
141140

142141
<figure><img src="../../../.gitbook/assets/describe (1).png" alt=""><figcaption><p>Projection is executed as a result of published Event</p></figcaption></figure>
143142

144143

145144

146-
By default projections work synchronously as part of the same process of execution of Command Handler. This ensures that our Projection is always consistent with changes in the Event Stream.&#x20;
145+
By default projections work synchronously as part of the same process of Command Handler execution. This ensures that our Projection is always consistent with changes in the Event Stream, because it's wrapped in database transaction.&#x20;
147146

148147

149148

150149
<figure><img src="../../../.gitbook/assets/db (1) (1).png" alt=""><figcaption><p>Command Handler and Projection execution is wrapped in same transaction</p></figcaption></figure>
151150

152151
{% hint style="success" %}
153-
Synchronous projects are done within same transaction as the Command execution. This way Ecotone ensures consistency of the data by default.\
154-
\
155-
Synchronous projections are simpler in development, as we can immediately fetch the data from Read Model and be sure that is consistent with the changes.
152+
Synchronous projects are done within same transaction as the Command execution. This way Ecotone ensures consistency of the data by default. This behaviour is configurable.
156153
{% endhint %}
157154

158-
This works well for scenarios when there are no much changes to happening to given instance of Event Sourced Aggregate. How Ecotone handles Concurrency was described in more details in [previous section](../event-sourcing-introduction/working-with-event-streams.md). However was is important here is that for low writes this solution will work perfectly for high volume of writes we will want to push Projections into background process.
155+
This works well for scenarios when there are no much changes to happening to given instance of Event Sourced Aggregate. How Ecotone handles Concurrency was described in more details in [previous section](../event-sourcing-introduction/working-with-event-streams.md). \
156+
What is important is that for low writes this solution will work perfectly, for high volume of writes on other hand we may want to trigger Projections asynchronously.
157+
158+
{% hint style="success" %}
159+
Synchronous projections are simpler in development, as we can immediately fetch the data from Read Model and be sure that is consistent with the changes.\
160+
With Asynchronous data may be refreshed after some time, therefore if fetched immediately, we may get stale results.
161+
{% endhint %}
159162

160163
## Asynchronous Projections
161164

162-
Ecotone provides great abstraction for making the code asynchronous. From end user perspective the code stays the same like asynchronous, yet under the hood thanks to Messaging abstraction it can be easily switched to work [asynchronously via Message Channels](../../asynchronous-handling/asynchronous-message-handlers.md).&#x20;
165+
Ecotone provides great abstraction for making the code asynchronous. From development perspective the code stays the same like synchronous, yet under the hood thanks to Messaging abstraction it can be easily switched to work [asynchronously via Message Channels](../../asynchronous-handling/asynchronous-message-handlers.md).&#x20;
163166

164167
So to make the Projection asynchronous, the only thing which we need to do, is to mark it as asynchronous.
165168

@@ -170,29 +173,33 @@ class TicketListProjection
170173
```
171174

172175
Ecotone will take care of delivering the triggering Event via given **async** channel to the Projection. \
173-
This way we can start with synchronous projections, and when we will the need simply switch them to Asynchronous without any line of code being changed.&#x20;
174-
176+
This way we can start with synchronous projections, and when we will feel the need, simply switch them to Asynchronous without any single line of code being changed. \
175177
You may read more about execution process in [next section](executing-and-managing/).\
178+
\
176179
We need to touch on one more important topic. Where do we actually get the data from for Projections.
177180

178181
## The source of data for Projection
179182

180-
What is important here is that triggering Events are not actually a source for the data for Projections.\
181-
The one reason for that is. if we would lose Event Message due some failure (We can avoid that by using [Outbox](../../recovering-tracing-and-monitoring/resiliency/outbox-pattern.md)) or it would land in [Dead Letter](../../recovering-tracing-and-monitoring/resiliency/error-channel-and-dead-letter.md), then we would basically skip over an Event.
183+
Events that trigger Projections are not actually a source of the data for them.\
184+
This is because if we would lose Event Message along the way due some failure (For example we don't use [Outbox](../../recovering-tracing-and-monitoring/resiliency/outbox-pattern.md)) or it would and in [Dead Letter](../../recovering-tracing-and-monitoring/resiliency/error-channel-and-dead-letter.md) then we would basically skip over an Event.
182185

183-
Let's take as an example Asynchronous Projection, where we want to store Ticket with new alert-warning type. However let's suppose we've created column with limited size for type - which is up to 10 characters. Therefore our Projection will fail on storing that:
186+
Let's take as an example Asynchronous Projection, where we want to store Ticket with new "**alert-warning**" type. However let's suppose we've created column with limited size for type - which is up to 10 characters. Therefore our Projection will fail on storing that, because the type is 13 characters long:
184187

185188
<figure><img src="../../../.gitbook/assets/alert-warning.png" alt=""><figcaption></figcaption></figure>
186189

187-
Now after that Ticket was closed comes in, yet there is no ticket in our Read Model that it can reference to, therefore this event will be lost:
190+
Now if after that we will receive **Ticket was closed** event, then related Event Handler would update nothing, as there is no this Ticket stored in our Read Model:
188191

189192
<figure><img src="../../../.gitbook/assets/closed-2.png" alt=""><figcaption></figcaption></figure>
190193

191194
So this is obviously not way of ensuring consistency in the system. \
192195
Ecotone does it differently and treats the incoming Events just as "triggers". \
193-
This is information for the Projection to fetch the Events from the Event Stream and start Projecting.\
196+
It works like information for the Projection to fetch the Events from the Event Stream and start Projecting.\
194197

195198

196199
<figure><img src="../../../.gitbook/assets/projection (2).png" alt=""><figcaption><p>Projection fetches the Event from Event Stream. Incoming Event is just a trigger.</p></figcaption></figure>
197200

198201
This way if even so Ticket Was Registered failed, when Ticket was Closed would come after it would still get the original event first. So if we would fix the problem with column size, it would basically self-heal automatically.&#x20;
202+
203+
{% hint style="success" %}
204+
Each Projection keep track of it's position. Therefore whenever new Event comes in, it knows from which point in Event Stream it should fetch the Events from.
205+
{% endhint %}

0 commit comments

Comments
 (0)