Skip to content

Commit cdf095b

Browse files
dgafkagitbook-bot
authored andcommitted
GITBOOK-893: No subject
1 parent 288ae18 commit cdf095b

File tree

9 files changed

+176
-32
lines changed

9 files changed

+176
-32
lines changed

SUMMARY.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,12 @@
5959
* [Working with Aggregates](modelling/event-sourcing/event-sourcing-introduction/event-sourcing-aggregates/working-with-aggregates.md)
6060
* [Applying Events](modelling/event-sourcing/event-sourcing-introduction/event-sourcing-aggregates/applying-events.md)
6161
* [Different ways to Record Events](modelling/event-sourcing/event-sourcing-introduction/event-sourcing-aggregates/different-ways-to-record-events.md)
62-
* [Making Stream immune to changes](modelling/event-sourcing/event-sourcing-introduction/event-sourcing-aggregates/making-stream-immune-to-changes.md)
63-
* [Snapshoting](modelling/event-sourcing/event-sourcing-introduction/event-sourcing-aggregates/snapshoting.md)
6462
* [Working with Metadata](modelling/event-sourcing/event-sourcing-introduction/working-with-metadata.md)
6563
* [Event versioning](modelling/event-sourcing/event-sourcing-introduction/event-versioning.md)
6664
* [Event Stream Persistence](modelling/event-sourcing/event-sourcing-introduction/persistence-strategy.md)
65+
* [Event Sourcing Repository](modelling/event-sourcing/event-sourcing-introduction/persistence-strategy/event-sourcing-repository.md)
66+
* [Making Stream immune to changes](modelling/event-sourcing/event-sourcing-introduction/persistence-strategy/making-stream-immune-to-changes.md)
67+
* [Snapshoting](modelling/event-sourcing/event-sourcing-introduction/persistence-strategy/snapshoting.md)
6768
* [Persistence Strategies](modelling/event-sourcing/event-sourcing-introduction/persistence-strategy/persistence-strategies.md)
6869
* [Event Serialization and PII Data (GDPR)](modelling/event-sourcing/event-sourcing-introduction/persistence-strategy/event-serialization-and-pii-data-gdpr.md)
6970
* [Projection Introduction](modelling/event-sourcing/setting-up-projections/README.md)

enterprise.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ Ecotone comes with two plans:
99
* **Ecotone Enterprise** is based Enterprise licence. It does provide more advanced features that help building larger scale systems, optimize resource costs and speed up daily development even more than the basic functionality.
1010

1111
{% hint style="success" %}
12-
Each Enterprise feature is marked with hint on the documentation page. Enterprise features can't be run without licence key.\
12+
Each Enterprise feature is marked with hint on the documentation page. Enterprise features can only be run with licence key.\
1313
Enterprise Licence Keys will be sold at **https://ecotone.tech** in early **2025**.\
14-
Stay tuned by [subscribe to mailing list](https://blog.ecotone.tech/#/portal).
14+
Stay tuned by [subscribing to mailing list](https://blog.ecotone.tech/#/portal).
1515
{% endhint %}
1616

1717
## Available Enterprise Features

modelling/command-handling/repository.md

+5-19
Original file line numberDiff line numberDiff line change
@@ -131,25 +131,7 @@ final class EcotoneTicketRepository implements StandardRepository
131131
}
132132
```
133133

134-
## Repository for Event Sourced Aggregate
135-
136-
```php
137-
interface EventSourcedRepository
138-
{
139-
public function canHandle(string $aggregateClassName): bool;
140-
141-
1 public function findBy(string $aggregateClassName, array $identifiers) : EventStream;
142-
143-
2 public function save(array $identifiers, string $aggregateClassName, array $events, array $metadata, int $versionBeforeHandling): void;
144-
}
145-
```
146-
147-
Event Sourced Repository instead of working with aggregate instance, works with events. 
148-
149-
1. `findBy method` returns previously created events for given aggregate. 
150-
2. `save method` gets array of events to save returned by `CommandHandler` after performing an action
151-
152-
### Set up your own Implementation
134+
## Set up your own Implementation
153135

154136
When your implementation is ready simply mark it with `#[Repository]` attribute:
155137

@@ -191,3 +173,7 @@ However, if we register multiple Repositories, then we need to take over the pro
191173

192174
* In case of [Custom Repositories](repository.md#set-up-your-own-implementation) we do it using **canHandle** method.
193175
* In case of inbuilt Repositories, we should follow configuration section for given type
176+
177+
## Repository for Event Sourced Aggregate
178+
179+
Custom repository for Event Sourced Aggregates is described in more details under [Event Sourcing Repository section](../event-sourcing/event-sourcing-introduction/persistence-strategy/event-sourcing-repository.md).

modelling/event-sourcing/event-sourcing-introduction/event-sourcing-aggregates/working-with-aggregates.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ We will explore how applying Events works more in [next section](applying-events
8484
### Aggregate Type
8585

8686
Aggregate Type will be the same as Aggregate Class. \
87-
We can decouple the class from the Aggregate Type, more about this can be found in "[Making Stream immune to changes](making-stream-immune-to-changes.md)" section.
87+
We can decouple the class from the Aggregate Type, more about this can be found in "[Making Stream immune to changes](../persistence-strategy/making-stream-immune-to-changes.md)" section.
8888

8989
### Recording Events in the Event Stream
9090

@@ -117,4 +117,4 @@ $eventStore->appendTo(
117117
```
118118

119119
As storing in Event Store is abstracted away, the code stays clean and contains only of the business part. \
120-
We can [customize](making-stream-immune-to-changes.md) the Stream Name, Aggregate Type and even Event Names when needed.
120+
We can [customize](../persistence-strategy/making-stream-immune-to-changes.md) the Stream Name, Aggregate Type and even Event Names when needed.

modelling/event-sourcing/event-sourcing-introduction/event-versioning.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,5 @@ class Product
7979

8080
{% hint style="success" %}
8181
We may inject any type of Header that was stored together with the Event. \
82-
This means inbuilt headers like **timestamp**, **id**, **correlationId are avaiable** out of the box**.**
82+
This means inbuilt not only headers like **timestamp**, **id**, **correlationId are avaiable** out of the box, but also custom headers provided by the application (e.g. userId).
8383
{% endhint %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Event Sourcing Repository
2+
3+
Ecotone comes with inbuilt Event Sourcing repository after [Event Sourcing package is installed](../../installation.md). However you want to roll out your own storage for Events, or maybe you already use some event-sourcing framework and would like to integrate with it. \
4+
For this you can take over the control by introducing your own Event Sourcing Repository.
5+
6+
{% hint style="warning" %}
7+
Using Custom Event Sourcing Repository will not allow you to make use of [inbuilt projection system](../../setting-up-projections/). Therefore consider configuring your own Event Sourcing Repository only if you want to build your own projecting system.
8+
{% endhint %}
9+
10+
## Custom Event Sourcing Repository
11+
12+
We do start by implementing **EventSourcingRepository** interface:
13+
14+
```php
15+
interface EventSourcedRepository
16+
{
17+
1. public function canHandle(string $aggregateClassName): bool;
18+
19+
2. public function findBy(string $aggregateClassName, array $identifiers, int $fromAggregateVersion = 1) : EventStream;
20+
21+
3. public function save(array $identifiers, string $aggregateClassName, array $events, array $metadata, int $versionBeforeHandling): void;
22+
}
23+
```
24+
25+
1. **canHandle** - Tells whatever given Aggregate is handled by this Repository
26+
2. **findBy** - Method returns previously created events for given aggregate. Which Ecotone will use to reconstruct the Aggregate. 
27+
3. **save** - Stores events recorded by Event Sourced Aggregate
28+
29+
and then we need to mark class which implements this interface as **Repository**
30+
31+
```php
32+
#[Repository]
33+
class CustomEventSourcingRepository
34+
```
35+
36+
## Storing Events
37+
38+
Ecotone provides enough information to decide how to store provided events. 
39+
40+
```php
41+
public function save(
42+
array $identifiers,
43+
string $aggregateClassName,
44+
array $events,
45+
array $metadata,
46+
int $versionBeforeHandling
47+
): void;
48+
```
49+
50+
Identifiers will hold array of **identifiers** related to given aggregate (e.g. _\["orderId" ⇒ 123]_). \
51+
**Events** will be list of Ecotone's Event classes, which contains of **payload** and **metadata,** where payload is your Event class instance and metadata is specific to this event. \
52+
**Metadata** as parameter is generic metadata available at the moment of Aggregate execution.\
53+
**Version** before handling on other hand is the version of the Aggregate before any action was triggered on it. This can be used to protect from concurrency issues.
54+
55+
The structure of Events is as follows:
56+
57+
```php
58+
class Event
59+
{
60+
private function __construct(
61+
private string $eventName, // either class name or name of the event
62+
private object $payload, // event object instance
63+
private array $metadata // related metadata
64+
)
65+
}
66+
```
67+
68+
### Core metadata
69+
70+
It's worth to mention about Ecotone's Events and especially about metadata part of the Event.\
71+
Each metadata for given Event contains of three core Event attributes:
72+
73+
**"\_aggregate\_id" -** This provides aggregate identifier of related Aggregate
74+
75+
**"\_aggregate\_version" -** This provides version of the related Event (e.g. 1/2/3/4)
76+
77+
**"\_aggregate\_type" -** This provides type of the Aggregate being stored, which can be customized
78+
79+
### Aggregate Type
80+
81+
If our repository stores multiple Aggregates is useful to have the information about the type of Aggregate we are storing. However keeping the class name is not best idea, as simply refactor would break our Event Stream. Therefore Ecotone provides a way to mark our Aggregate type using Attribute
82+
83+
```php
84+
#[EventSourcingAggregate]
85+
#[AggregateType("basket")]
86+
class Basket
87+
```
88+
89+
This now will be passed together with Events under **\_aggregate\_type** metadata.
90+
91+
### Named Events
92+
93+
In Ecotone we can name the events to avoid storing class names in the Event Stream, to do so we use NamedEvent.
94+
95+
```php
96+
#[NamedEvent("order_was_placed")]
97+
class OrderWasPlaced
98+
```
99+
100+
then when events will be passed to save method, they will automatically provide this name under **eventName** property.
101+
102+
## Snapshoting
103+
104+
With custom repository we still can use inbuilt [Snapshoting mechanism](snapshoting.md). To use it for customized repository we will use **BaseEventSourcingConfiguration**.
105+
106+
```php
107+
#[ServiceContext]
108+
public function configuration()
109+
{
110+
return BaseEventSourcingConfiguration::withDefaults()
111+
->withSnapshotsFor(Basket::class, thresholdTrigger: 100);
112+
}
113+
```
114+
115+
Ecotone then after fetching snapshot, will load events only from this given moment using **\`fromAggregateVersion\`.**
116+
117+
```php
118+
public function findBy(
119+
string $aggregateClassName,
120+
array $identifiers,
121+
int $fromAggregateVersion = 1
122+
)
123+
```
124+
125+
## Testing
126+
127+
If you want to test out your flow and storing with your custom Event Sourced Repository, you should disable default in memory repository
128+
129+
```php
130+
$repository = new CustomEventSourcingRepository;
131+
$ecotoneLite = EcotoneLite::bootstrapFlowTesting(
132+
[OrderAggregate::class, CustomEventSourcingRepository::class],
133+
[CustomEventSourcingRepository::class => $repository],
134+
addInMemoryEventSourcedRepository: false,
135+
);
136+
137+
$ecotoneLite->sendCommand(new PlaceOrder());
138+
139+
$this->assertNotEmpty($repository->getEvents());
140+
```
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,13 @@ class BasketWasCreated
6161
```
6262

6363
This way Ecotone will do the mapping before storing an Event and when retrieving the Event in order to deserialize it to correct class.
64+
65+
## Testing
66+
67+
It's worth to remember that if we want test storing Events using provided Event Named, we need to add them under recognized classes, so Ecotone knows that should scan those classes for Attributes:
68+
69+
```php
70+
$ecotoneLite = EcotoneLite::bootstrapFlowTesting(
71+
[Basket::class, BaskeWasCreated::class],
72+
);
73+
```

modelling/event-sourcing/event-sourcing-introduction/event-sourcing-aggregates/snapshoting.md renamed to modelling/event-sourcing/event-sourcing-introduction/persistence-strategy/snapshoting.md

+11-4
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,13 @@ description: PHP Event Sourcing Snapshoting
77
In general having streams in need for snapshots may indicate that our model needs revisiting. \
88
We may cut the stream on some specific event and begin new one, like at the end of month from all the transactions we generate invoice and we start new stream for next month. \
99
\
10-
However if cutting the stream off is not an option for any reason, we snapshot aggregate state at given point of time, so we can skip previous events. \
11-
This process continues on predefined threshold, like every 1000 events. 
10+
However if cutting the stream off is not an option for any reason, we can use snapshots to avoid loading all events history for given Aggregate. Every given set of events snapshot will be taken, stored and retrieved on next calls, to fetch only events that happened after that.
1211

1312
## Setting up 
1413

1514
`EventSourcingConfiguration` provides the following interface to set up snapshots.
1615

1716
```php
18-
use Ecotone\Messaging\Store\Document\DocumentStore;
19-
2017
class EventSourcingConfiguration
2118
{
2219
public const DEFAULT_SNAPSHOT_TRIGGER_THRESHOLD = 100;
@@ -51,3 +48,13 @@ Ecotone make use of [Document Store](../../../../messaging/document-store.md) to
5148
\
5249
If you want to clean the snapshots, you can do it manually. Snapshots are stored in `aggregate_snapshots` collection.
5350
{% endhint %}
51+
52+
## Snapshot threshold
53+
54+
Threshold states at which interval snapshots should be done. Therefore with below configuration:
55+
56+
```php
57+
->withSnapshotsFor(Ticket::class, 500)
58+
```
59+
60+
snapshots will be done every 500 events. Then when snapshot will be loaded, it will start loading the events from event number 501 for given Aggregate instance.

quick-start-php-ddd-cqrs-event-sourcing/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ If you're looking on way to start and get to familiar with Ecotone. Then Ecotone
1717
* [Subscribe to Mailing list](https://blog.ecotone.tech/#/portal) - Join mailing list to stay up to date with Ecotone changes and latest articles and features.
1818

1919
{% hint style="success" %}
20-
Ecotone does not bind given features to specific Framework. Whatever you use Laravel or Symfony or Lite (no external framework), all examples will be able to work in your Application. \
20+
Some demo and quick-start examples which are done using specific framework integration. However Ecotone does not bind given set of features to specific solution. Whatever you use Laravel or Symfony or Lite (no external framework), all features will in same way. \
2121
\
22-
Therefore feel encouraged to test examples, even if they are in different Framework than one you use, as you will be able to use them same way in your Application.
22+
Therefore feel encouraged to test out examples, even if they are not framework of your choose.
2323
{% endhint %}

0 commit comments

Comments
 (0)