You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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. 
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**. 
21
21
22
-
## Projections and Read Models
22
+
## Projections
23
23
24
24
So we may be in need to build the list of all the tickets and their current status
25
25
26
26
<figure><imgsrc="../../../.gitbook/assets/ticket-list (1).png"alt=""><figcaption><p>List of tickets available in the system with current status</p></figcaption></figure>
27
27
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. \
29
28
For example this list can be stored in the database table with three columns:
30
29
31
30
* id
32
31
* type
33
32
* status
34
33
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**.
36
35
37
36
## Building first Projection
38
37
@@ -70,22 +69,22 @@ We will be subscribing to those in order to build our new **Ticket List Projecti
70
69
#[Projection("ticket_list", Ticket::class)]
71
70
class TicketListProjection {
72
71
73
-
// This is Connection to our Database
72
+
// This is Connection to our Database or wherever we want to store the data
74
73
public function __construct(private Connection $connection) {}
75
74
76
75
(...)
77
76
```
78
77
79
78
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.
81
80
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
83
82
84
83
```php
85
84
#[ProjectionInitialization]
86
85
public function initializeProjection() : void
87
86
{
88
-
if ($this->getConnection()->createSchemaManager()->tablesExist('ticket_list')) {
87
+
if ($this->connection->createSchemaManager()->tablesExist('ticket_list')) {
89
88
return;
90
89
}
91
90
@@ -113,7 +112,7 @@ public function onTicketWasPrepared(TicketWasRegistered $event) : void
113
112
}
114
113
```
115
114
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
117
116
118
117
We also want to change the status, when ticket is closed, so let's add that now:
119
118
@@ -129,37 +128,41 @@ public function onTicketWasCancelled(TicketWasCancelled $event) : void
129
128
}
130
129
```
131
130
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.
133
132
134
-
## How is this triggered
133
+
## How Projections are triggered
135
134
136
135
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. 
137
136
138
137
<figure><imgsrc="../../../.gitbook/assets/aggregate.png"alt=""><figcaption><p>Event Sourced Aggregate stores Event in the Event Stream and then it's published</p></figcaption></figure>
139
138
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
141
140
142
141
<figure><imgsrc="../../../.gitbook/assets/describe (1).png"alt=""><figcaption><p>Projection is executed as a result of published Event</p></figcaption></figure>
143
142
144
143
145
144
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. 
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. 
147
146
148
147
149
148
150
149
<figure><imgsrc="../../../.gitbook/assets/db (1) (1).png"alt=""><figcaption><p>Command Handler and Projection execution is wrapped in same transaction</p></figcaption></figure>
151
150
152
151
{% 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.
156
153
{% endhint %}
157
154
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 %}
159
162
160
163
## Asynchronous Projections
161
164
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). 
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). 
163
166
164
167
So to make the Projection asynchronous, the only thing which we need to do, is to mark it as asynchronous.
165
168
@@ -170,29 +173,33 @@ class TicketListProjection
170
173
```
171
174
172
175
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. 
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. \
175
177
You may read more about execution process in [next section](executing-and-managing/).\
178
+
\
176
179
We need to touch on one more important topic. Where do we actually get the data from for Projections.
177
180
178
181
## The source of data for Projection
179
182
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.
182
185
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:
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:
So this is obviously not way of ensuring consistency in the system. \
192
195
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.\
194
197
195
198
196
199
<figure><imgsrc="../../../.gitbook/assets/projection (2).png"alt=""><figcaption><p>Projection fetches the Event from Event Stream. Incoming Event is just a trigger.</p></figcaption></figure>
197
200
198
201
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. 
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.
0 commit comments