diff --git a/docs/pages/UPGRADE-3.0.md b/docs/UPGRADE-3.0.md
similarity index 100%
rename from docs/pages/UPGRADE-3.0.md
rename to docs/UPGRADE-3.0.md
diff --git a/docs/pages/aggregate_id.md b/docs/aggregate-id.md
similarity index 81%
rename from docs/pages/aggregate_id.md
rename to docs/aggregate-id.md
index c4c336be4..bc2a34711 100644
--- a/docs/pages/aggregate_id.md
+++ b/docs/aggregate-id.md
@@ -7,12 +7,12 @@ since only an aggregate-wide unique string is expected in the store.
This library provides you with a few options for generating the id.
-!!! warning
+:::warning
+Performance reasons, the default configuration of the store require an uuid string for `aggregate id`.
+But technically, for the library, it can be any string.
+If you want to use a custom id, you have to change the `aggregate_id_type` in the [store](store.md) configuration.
+:::
- Performance reasons, the default configuration of the store require an uuid string for `aggregate id`.
- But technically, for the library, it can be any string.
- If you want to use a custom id, you have to change the `aggregate_id_type` in the [store](store.md) configuration.
-
## Uuid
The easiest way is to use an `uuid` as an aggregate ID.
@@ -41,11 +41,12 @@ use Patchlevel\EventSourcing\Aggregate\Uuid;
$uuid = Uuid::generate();
$uuid = Uuid::fromString('d6e8d7a0-4b0b-4e6a-8a9a-3a0b2d9d0e4e');
```
-!!! Note
- We implemented the version 7 of the uuid, because it is most suitable for event sourcing.
- More information about uuid versions can be found [here](https://uuid.ramsey.dev/en/stable/rfc4122.html).
-
+:::note
+We implemented the version 7 of the uuid, because it is most suitable for event sourcing.
+More information about uuid versions can be found [here](https://uuid.ramsey.dev/en/stable/rfc4122.html).
+:::
+
## Custom ID
If you don't want to use an uuid, you can also use the custom ID implementation.
@@ -64,12 +65,13 @@ final class Profile extends BasicAggregateRoot
private CustomId $id;
}
```
-!!! warning
- If you want to use a custom id that is not an uuid,
- you need to change the `aggregate_id_type` to `string` in the store configuration.
- More information can be found [here](store.md).
-
+:::warning
+If you want to use a custom id that is not an uuid,
+you need to change the `aggregate_id_type` to `string` in the store configuration.
+More information can be found [here](store.md).
+:::
+
So you can use any string as an id:
```php
diff --git a/docs/pages/aggregate.md b/docs/aggregate.md
similarity index 87%
rename from docs/pages/aggregate.md
rename to docs/aggregate.md
index 015f4de52..6c91ee83f 100644
--- a/docs/pages/aggregate.md
+++ b/docs/aggregate.md
@@ -4,11 +4,11 @@ The linchpin of event-sourcing is the aggregate. These aggregates can be imagine
One main difference is that we don't save the current state, but only the individual events that led to the state.
This means it is always possible to build the current state again from the events.
-!!! note
+:::note
+The term aggregate itself comes from DDD and has nothing to do with event sourcing and can be used independently as a pattern.
+You can find out more about Aggregates [here](https://martinfowler.com/bliki/DDD_Aggregate.html).
+:::
- The term aggregate itself comes from DDD and has nothing to do with event sourcing and can be used independently as a pattern.
- You can find out more about Aggregates [here](https://martinfowler.com/bliki/DDD_Aggregate.html).
-
An aggregate must fulfill a few points so that we can use it in event-sourcing:
* It must implement the `AggregateRoot` interface.
@@ -44,14 +44,15 @@ final class Profile extends BasicAggregateRoot
}
}
```
-!!! warning
- The aggregate is not yet finished and has only been built to the point that you can instantiate the object.
-
-!!! tip
+:::warning
+The aggregate is not yet finished and has only been built to the point that you can instantiate the object.
+:::
+
+:::tip
+Find out more about aggregate IDs [here](aggregate-id.md).
+:::
- Find out more about aggregate IDs [here](./aggregate_id.md).
-
We use a so-called named constructor here to create an object of the AggregateRoot.
The constructor itself is protected and cannot be called from outside.
But it is possible to define different named constructors for different use-cases like `import`.
@@ -76,17 +77,18 @@ final class CreateProfileHandler
}
}
```
-!!! warning
- If you look in the database now, you would see that nothing has been saved.
- This is because only events are stored in the database and as long as no events exist,
- nothing happens.
-
-!!! tip
+:::warning
+If you look in the database now, you would see that nothing has been saved.
+This is because only events are stored in the database and as long as no events exist,
+nothing happens.
+:::
+
+:::tip
+A **command bus** system is not necessary, only recommended.
+The interaction can also easily take place in a controller or service.
+:::
- A **command bus** system is not necessary, only recommended.
- The interaction can also easily take place in a controller or service.
-
## Create a new aggregate
In order that an aggregate is actually saved, at least one event must exist in the DB.
@@ -107,10 +109,11 @@ final class ProfileRegistered
}
}
```
-!!! note
- You can find out more about events [here](./events.md).
-
+:::note
+You can find out more about events [here](events.md).
+:::
+
After we have defined the event, we have to adapt the profile aggregate:
```php
@@ -148,10 +151,11 @@ final class Profile extends BasicAggregateRoot
}
}
```
-!!! tip
- Prefixing the apply methods with "apply" improves readability.
-
+:::tip
+Prefixing the apply methods with "apply" improves readability.
+:::
+
In our named constructor `register` we have now created the event and recorded it with the method `recordThat`.
The aggregate remembers all new recorded events in order to save them later.
At the same time, a defined `apply` method is executed directly so that we can change our state.
@@ -160,10 +164,10 @@ So that the AggregateRoot also knows which method it should call,
we have to mark it with the `Apply` attribute. We did that in the `applyProfileRegistered` method.
In there we then change the state of the aggregate by filling the properties with the values from the event.
-!!! success
+:::success
+The aggregate is now ready to be saved!
+:::
- The aggregate is now ready to be saved!
-
### Modify an aggregate
In order to change the state of the aggregates afterwards, only further events have to be defined.
@@ -181,10 +185,11 @@ final class NameChanged
}
}
```
-!!! note
- Events should best be written in the past, as they describe a state that has happened.
-
+:::note
+Events should best be written in the past, as they describe a state that has happened.
+:::
+
After we have defined the event, we can define a new public method called `changeName` to change the profile name.
This method then creates the event `NameChanged` and records it:
@@ -257,14 +262,15 @@ final class ChangeNameHandler
}
}
```
-!!! success
- Our aggregate can now be changed and saved.
-
-!!! note
+:::success
+Our aggregate can now be changed and saved.
+:::
+
+:::note
+You can read more about Repository [here](repository.md).
+:::
- You can read more about Repository [here](./repository.md).
-
Here the aggregate is loaded from the `repository` by fetching all events from the database.
These events are then executed again with the `apply` methods in order to rebuild the current state.
All of this happens automatically in the `load` method.
@@ -303,11 +309,12 @@ final class Profile extends BasicAggregateRoot
}
}
```
-!!! tip
- You don't necessarily need to define multiple `Apply` attributes with the event class
- if you define the event types in the method using a union type.
-
+:::tip
+You don't necessarily need to define multiple `Apply` attributes with the event class
+if you define the event types in the method using a union type.
+:::
+
## Suppress missing apply methods
Sometimes you have events that do not change the state of the aggregate itself,
@@ -359,13 +366,14 @@ final class Profile extends BasicAggregateRoot
}
}
```
-!!! warning
- When all events are suppressed, debugging becomes more difficult if you forget an apply method.
-
+:::warning
+When all events are suppressed, debugging becomes more difficult if you forget an apply method.
+:::
+
## Shared apply context
-When working with [micro-aggregates](./aggregate.md#micro-aggregates),
+When working with [micro-aggregates](aggregate.md#micro-aggregates),
it’s common that events are applied by different aggregates.
As a result, an aggregate may receive events it does not handle, which can lead to multiple “missing apply” warnings.
@@ -391,16 +399,17 @@ final class PersonalInformation extends BasicAggregateRoot
{
}
```
-!!! warning
- You need to define the `SharedApplyContext` attribute on all aggregates that share the apply context.
-
+:::warning
+You need to define the `SharedApplyContext` attribute on all aggregates that share the apply context.
+:::
+
## Stream Name
-!!! warning
+:::warning
+The `stream name` works only with the [StreamDoctrineDbalStore](store.md#streamdoctrinedbalstore).
+:::
- The `stream name` works only with the [StreamDoctrineDbalStore](./store.md#streamdoctrinedbalstore).
-
The stream name is the name of the stream in the event store.
By default, the stream name has the format `aggregateName-aggregateId`.
But you can also define your own stream name with the `Stream` attribute.
@@ -434,10 +443,11 @@ final class GuestList extends BasicAggregateRoot
// ...
}
```
-!!! tip
- You can find more about splitting aggregates [here](./aggregate.md#splitting-aggregates).
-
+:::tip
+You can find more about splitting aggregates [here](aggregate.md#splitting-aggregates).
+:::
+
## Business rules
Usually, aggregates have business rules that must be observed. Like there may not be more than 10 people in a group.
@@ -476,11 +486,12 @@ final class Profile extends BasicAggregateRoot
}
}
```
-!!! danger
- Validations during "apply" should not happen, they will break the rebuilding of the aggregate!
- Instead validate the data *before* the event will be recorded.
-
+:::danger
+Validations during "apply" should not happen, they will break the rebuilding of the aggregate!
+Instead validate the data *before* the event will be recorded.
+:::
+
We have now ensured that this rule takes effect when a name is changed with the method `changeName`.
But when we create a new profile this rule does not currently apply.
@@ -562,23 +573,24 @@ final class NameChanged
}
}
```
-!!! warning
- You need to create a normalizer for the `Name` value object.
- So the payload must be serializable and unserializable as json.
-
-!!! note
+:::warning
+You need to create a normalizer for the `Name` value object.
+So the payload must be serializable and unserializable as json.
+:::
+
+:::note
+You can find out more about normalizer [here](normalizer.md).
+:::
- You can find out more about normalizer [here](./normalizer.md).
-
There are also cases where business rules have to be defined depending on the aggregate state.
Sometimes also from states, which were changed in the same method.
This is not a problem, as the `apply` methods are always executed immediately.
In the next case we throw an exception if the hotel is already overbooked.
Besides that, we record another event `FullyBooked`, if the hotel is fully booked with the last booking.
-With this event we could [notify](./subscription.md) external systems
-or fill a [projection](./subscription.md) with fully booked hotels.
+With this event we could [notify](subscription.md) external systems
+or fill a [projection](subscription.md) with fully booked hotels.
```php
use Patchlevel\EventSourcing\Aggregate\BasicAggregateRoot;
@@ -682,10 +694,10 @@ final class Profile extends BasicAggregateRoot
Now you can pass the `SystemClock` to determine the current time.
Or for test purposes the `FrozenClock`, which always returns the same time.
-!!! note
+:::note
+You can find out more about clock [here](clock.md).
+:::
- You can find out more about clock [here](./clock.md).
-
## Splitting Aggregates
In some cases, it makes sense to split an aggregate into several smaller aggregates.
@@ -694,10 +706,10 @@ We currently support two patterns for this: Micro Aggregates and Child Aggregate
### Micro Aggregates
-!!! warning
+:::warning
+This feature works only with the [StreamDoctrineDbalStore](store.md#streamdoctrinedbalstore).
+:::
- This feature works only with the [StreamDoctrineDbalStore](./store.md#streamdoctrinedbalstore).
-
Micro Aggregates are a pattern to split an aggregate into several smaller aggregates.
Each of these aggregates is saved in the same stream.
This gives the Micro Aggregates the ability to independently manage their state and trigger their events,
@@ -781,18 +793,19 @@ final class Shipping extends BasicAggregateRoot
}
}
```
-!!! tip
- With the [SharedApplyContext](./aggregate.md#shared-apply-context) attribute,
- you can suppress missing applies for events that are handled by other aggregates.
-
+:::tip
+With the [SharedApplyContext](aggregate.md#shared-apply-context) attribute,
+you can suppress missing applies for events that are handled by other aggregates.
+:::
+
### Child Aggregates
-??? example "Experimental"
+:::experimental
+This feature is still experimental and may change in the future.
+Use it with caution.
+:::
- This feature is still experimental and may change in the future.
- Use it with caution.
-
Another way to split an aggregate is to use child aggregates.
The difference to Micro Aggregates, child aggregates can only be accessed by the root aggregate
and are not separate aggregates.
@@ -829,14 +842,15 @@ final class Shipping extends BasicChildAggregate
}
}
```
-!!! warning
- The apply method must be public, otherwise the root aggregate cannot call it.
-
-!!! note
+:::warning
+The apply method must be public, otherwise the root aggregate cannot call it.
+:::
+
+:::note
+Supress missing apply methods need to be defined in the root aggregate.
+:::
- Supress missing apply methods need to be defined in the root aggregate.
-
And the `Order` aggregate root looks like this:
```php
@@ -879,10 +893,10 @@ final class Order extends BasicAggregateRoot
## Auto Initialize
-??? example "Experimental"
-
- This feature is still experimental and may change in the future.
- Use it with caution.
+:::experimental
+This feature is still experimental and may change in the future.
+Use it with caution.
+:::
Sometimes you want to be able to access an aggregate even if it has not yet been created in the system.
In this case, the aggregate should be automatically initialized if it cannot be found in the store.
@@ -919,9 +933,10 @@ final class Profile extends BasicAggregateRoot
}
}
```
-!!! note
- Recording events in the `initialize` method is optional but recommended.
+:::note
+Recording events in the `initialize` method is optional but recommended.
+:::
## Aggregate Root Registry
@@ -947,8 +962,8 @@ $aggregateRegistry = (new AttributeAggregateRootRegistryFactory())->create([/* p
```
## Learn more
-* [How to create own aggregate id](aggregate_id.md)
+* [How to create own aggregate id](aggregate-id.md)
* [How to store and load aggregates](repository.md)
* [How to snapshot aggregates](snapshots.md)
* [How to create Projections](subscription.md)
-* [How to split streams](split_stream.md)
+* [How to split streams](split-stream.md)
diff --git a/docs/pages/cli.md b/docs/cli.md
similarity index 93%
rename from docs/pages/cli.md
rename to docs/cli.md
index 0c272a6bf..09850ce94 100644
--- a/docs/pages/cli.md
+++ b/docs/cli.md
@@ -23,10 +23,10 @@ The database schema can also be created, updated and dropped.
* SchemaUpdateCommand: `event-sourcing:schema:update`
* SchemaDropCommand: `event-sourcing:schema:drop`
-!!! note
+:::note
+You can also register doctrine migration commands.
+:::
- You can also register doctrine migration commands.
-
## Subscription commands
To manage your subscriptions there are the following cli commands.
@@ -40,10 +40,10 @@ To manage your subscriptions there are the following cli commands.
* SubscriptionStatusCommand: `event-sourcing:subscription:status`
* SubscriptionTeardownCommand: `event-sourcing:subscription:teardown`
-!!! note
+:::note
+You can find out more about subscriptions [here](subscription.md).
+:::
- You can find out more about subscriptions [here](subscription.md).
-
## Inspector commands
The inspector is a tool to inspect the event streams.
@@ -141,11 +141,12 @@ $cli->addCommands([
new Command\VersionCommand($dependencyFactory, 'event-sourcing:migrations:version'),
]);
```
-!!! note
- Here you can find more information on how to
- [configure doctrine migration](https://www.doctrine-project.org/projects/doctrine-migrations/en/3.3/reference/custom-configuration.html).
-
+:::note
+Here you can find more information on how to
+[configure doctrine migration](https://www.doctrine-project.org/projects/doctrine-migrations/en/3.3/reference/custom-configuration.html).
+:::
+
## Learn more
* [How to configure store](store.md)
diff --git a/docs/pages/clock.md b/docs/clock.md
similarity index 93%
rename from docs/pages/clock.md
rename to docs/clock.md
index 4939c3e67..25cb26b1b 100644
--- a/docs/pages/clock.md
+++ b/docs/clock.md
@@ -61,10 +61,11 @@ $clock = new FrozenClock($firstDate);
$clock->sleep(10); // sleep 10 seconds
```
-!!! note
- The instance of the frozen datetime will be cloned internally, so the it's not the same instance but equals.
-
+:::note
+The instance of the frozen datetime will be cloned internally, so the it's not the same instance but equals.
+:::
+
## Learn more
* [How to test with datetime](testing.md)
diff --git a/docs/pages/command_bus.md b/docs/command-bus.md
similarity index 87%
rename from docs/pages/command_bus.md
rename to docs/command-bus.md
index ea11339d7..75940c9e9 100644
--- a/docs/pages/command_bus.md
+++ b/docs/command-bus.md
@@ -38,13 +38,14 @@ final class CreateProfileHandler
}
}
```
-!!! note
- To use Service Handler you need to register the handler in the `ServiceHandlerProvider`.
-
-!!! tip
+:::note
+To use Service Handler you need to register the handler in the `ServiceHandlerProvider`.
+:::
- A class can have multiple handle methods.
+:::tip
+A class can have multiple handle methods.
+:::
### Multiple Handle Attributes
@@ -104,12 +105,12 @@ final class CreateProfileHandler
Another way to handle commands is to use the aggregates themselves.
To do this, you need to mark the method that handles the command with the `#[Handle]` attribute.
-!!! note
+:::note
+The aggregates themselves are of course not a service.
+The AggregateHandlerProvider uses the aggregates to create the handlers for you.
+You can find out more about this in the [providers](command-bus.md#provider) section.
+:::
- The aggregates themselves are of course not a service.
- The AggregateHandlerProvider uses the aggregates to create the handlers for you.
- You can find out more about this in the [providers](./command_bus.md#provider) section.
-
#### Create Aggregate
If you want to create a new aggregate, you need to create a static method that returns a new instance of the aggregate.
@@ -139,10 +140,11 @@ final class Profile extends BasicAggregateRoot
// ... apply methods
}
```
-!!! tip
- You can find more information about aggregates [here](aggregate.md).
-
+:::tip
+You can find more information about aggregates [here](aggregate.md).
+:::
+
#### Update Aggregate
If you want to update an existing aggregate,
@@ -191,10 +193,11 @@ final class Profile extends BasicAggregateRoot
// ... apply methods
}
```
-!!! tip
- If you want to automatically initialize an aggregate if it cannot be found in the store,
- you can use the [Auto Initialize](aggregate.md#auto-initialize) feature.
+:::tip
+If you want to automatically initialize an aggregate if it cannot be found in the store,
+you can use the [Auto Initialize](aggregate.md#auto-initialize) feature.
+:::
#### Inject Service
@@ -230,14 +233,15 @@ final class Profile extends BasicAggregateRoot
// ... apply methods
}
```
-!!! note
- The service must be registered in the service locator.
-
-!!! tip
+:::note
+The service must be registered in the service locator.
+:::
+
+:::tip
+You can inject multiple services into the handler method.
+:::
- You can inject multiple services into the handler method.
-
Or you can inject the service manually using the `#[Inject]` attribute.
There you can specify the service name that should be injected.
@@ -270,10 +274,11 @@ final class Profile extends BasicAggregateRoot
// ... apply methods
}
```
-!!! note
- Injection in handler methods is only possible with the `AggregateHandlerProvider`.
-
+:::note
+Injection in handler methods is only possible with the `AggregateHandlerProvider`.
+:::
+
## Setup
We provide a `SyncCommandBus` that you can use to dispatch commands.
@@ -322,20 +327,21 @@ final class CreateProfile
}
}
```
-!!! tip
- You can override the default values for the maximum number of retries and the conditions
- by passing them to the `InstantRetry` attribute.
-
- ```php
- use Patchlevel\EventSourcing\Attribute\InstantRetry;
-
- #[InstantRetry(3, [AggregateOutdated::class])]
- final class CreateProfile
- {
- }
- ```
-
+:::tip
+You can override the default values for the maximum number of retries and the conditions
+by passing them to the `InstantRetry` attribute.
+
+```php
+use Patchlevel\EventSourcing\Attribute\InstantRetry;
+
+#[InstantRetry(3, [AggregateOutdated::class])]
+final class CreateProfile
+{
+}
+```
+:::
+
## Provider
There are different types of providers that you can use to register handlers.
@@ -399,10 +405,11 @@ $provider = new AggregateHandlerProvider(
]), // or other psr-11 compatible container
);
```
-!!! tip
- You can find suitable implementations of psr-11 containers on [packagist](https://packagist.org/search/?tags=PSR-11).
-
+:::tip
+You can find suitable implementations of psr-11 containers on [packagist](https://packagist.org/search/?tags=PSR-11).
+:::
+
### Chain Handler Provider
The `ChainHandlerProvider` allows you to combine multiple handler providers.
@@ -420,5 +427,5 @@ $provider = new ChainHandlerProvider([
* [How to use aggregates](aggregate.md)
* [How to use events](events.md)
* [How to use clock](clock.md)
-* [How to use aggregate id](aggregate_id.md)
-* [How to use query bus](query_bus.md)
+* [How to use aggregate id](aggregate-id.md)
+* [How to use query bus](query-bus.md)
diff --git a/docs/pages/event_bus.md b/docs/event-bus.md
similarity index 79%
rename from docs/pages/event_bus.md
rename to docs/event-bus.md
index 9294561c7..d31310e18 100644
--- a/docs/pages/event_bus.md
+++ b/docs/event-bus.md
@@ -2,15 +2,15 @@
Optionally you can use an event bus to dispatch events to listeners.
-For all events that are persisted (when the `save` method has been executed on the [repository](./repository.md)),
+For all events that are persisted (when the `save` method has been executed on the [repository](repository.md)),
the event wrapped in a message will be dispatched to the `event bus`.
All listeners are then called for each message.
-!!! tip
+:::tip
+It is recommended to use the [subscription engine](subscription.md) to process the messages.
+It is more powerful and flexible than the event bus.
+:::
- It is recommended to use the [subscription engine](subscription.md) to process the messages.
- It is more powerful and flexible than the event bus.
-
## Event Bus
The library delivers a light-weight event bus for which you can register listeners and dispatch events.
@@ -20,10 +20,11 @@ use Patchlevel\EventSourcing\EventBus\DefaultEventBus;
$eventBus = DefaultEventBus::create([$mailListener]);
```
-!!! note
- The order in which the listeners are executed is determined by the order in which they are passed to the factory.
-
+:::note
+The order in which the listeners are executed is determined by the order in which they are passed to the factory.
+:::
+
Internally, the event bus uses the `Consumer` to consume the messages and call the listeners.
## Consumer
@@ -55,10 +56,11 @@ $eventBus = new DefaultEventBus(
new DefaultConsumer($listenerProvider),
);
```
-!!! tip
- The `DefaultEventBus::create` method uses the `DefaultConsumer` and `AttributeListenerProvider` by default.
-
+:::tip
+The `DefaultEventBus::create` method uses the `DefaultConsumer` and `AttributeListenerProvider` by default.
+:::
+
### Custom listener provider
You can also use your own listener provider.
@@ -78,10 +80,11 @@ $listenerProvider = new class implements ListenerProvider {
}
};
```
-!!! tip
- You can use `$listenerDiscriptor->name()` to get the name of the listener.
-
+:::tip
+You can use `$listenerDiscriptor->name()` to get the name of the listener.
+:::
+
## Listener
You can listen for specific events with the attribute `Subscribe`.
@@ -100,10 +103,11 @@ final class WelcomeSubscriber
}
}
```
-!!! tip
- If you use psalm, you can use the [event sourcing plugin](https://github.com/patchlevel/event-sourcing-psalm-plugin) for better type support.
-
+:::tip
+If you use psalm, you can use the [event sourcing plugin](https://github.com/patchlevel/event-sourcing-psalm-plugin) for better type support.
+:::
+
### Listen on all events
If you want to listen on all events, you can pass `*` or `Subscribe::ALL` instead of the event class.
@@ -132,14 +136,15 @@ use Patchlevel\EventSourcing\EventBus\Psr14EventBus;
$eventBus = new Psr14EventBus($psr14EventDispatcher);
```
-!!! warning
- You can't use the `Subscribe` attribute with the psr-14 event bus.
-
+:::warning
+You can't use the `Subscribe` attribute with the psr-14 event bus.
+:::
+
## Learn more
* [How to use messages](message.md)
* [How to use events](events.md)
* [How to use the subscription engine](subscription.md)
* [How to use repositories](repository.md)
-* [How to use decorate messages](message_decorator.md)
+* [How to use decorate messages](message-decorator.md)
diff --git a/docs/pages/events.md b/docs/events.md
similarity index 79%
rename from docs/pages/events.md
rename to docs/events.md
index 4541319fb..fc7af7e19 100644
--- a/docs/pages/events.md
+++ b/docs/events.md
@@ -27,21 +27,22 @@ final class ProfileCreated
}
}
```
-!!! warning
-
- The payload must be serializable and unserializable as json.
-
-!!! tip
-
- An event should be named in the past because it has already happened.
-
- Best practice is to prefix the event names with the aggregate name, lowercase everything, and replace spaces with underscores.
- Here are some examples:
-
- * `profile.created`
- * `profile.name_changed`
- * `hotel.guest_checked_out`
-
+
+:::warning
+The payload must be serializable and unserializable as json.
+:::
+
+:::tip
+An event should be named in the past because it has already happened.
+
+Best practice is to prefix the event names with the aggregate name, lowercase everything, and replace spaces with underscores.
+Here are some examples:
+
+* `profile.created`
+* `profile.name_changed`
+* `hotel.guest_checked_out`
+:::
+
## Alias
You also have the option to set aliases for the events.
@@ -57,16 +58,16 @@ final class ProfileRegistered
```
When saving, the name will always be used. However, when loading, aliases will also be taken into account.
-!!! note
+:::note
+In the database, the name of the event is always stored,
+allowing the class to be renamed without encountering any issues.
+:::
- In the database, the name of the event is always stored,
- allowing the class to be renamed without encountering any issues.
-
-!!! tip
+:::tip
+If you want to make significant changes to an event,
+you can take a look at the [Upcaster](upcasting.md).
+:::
- If you want to make significant changes to an event,
- you can take a look at the [Upcaster](upcasting.md).
-
## Serializer
So that the events can be saved in the database, they must be serialized and deserialized.
@@ -108,15 +109,16 @@ final class ProfileCreated
}
}
```
-!!! tip
- Built-in normalizers like `IdNormalizer` and `DateTimeImmutableNormalizer` can be inferred from the type hint
- and so you don't have to specify them. If you want to configure the Normalizer, you still have to do it.
-
-!!! note
+:::tip
+Built-in normalizers like `IdNormalizer` and `DateTimeImmutableNormalizer` can be inferred from the type hint
+and so you don't have to specify them. If you want to configure the Normalizer, you still have to do it.
+:::
+
+:::note
+You can find out more about normalizer [here](normalizer.md).
+:::
- You can find out more about normalizer [here](normalizer.md).
-
## Event Registry
The library needs to know about all events
diff --git a/docs/pages/getting_started.md b/docs/getting-started.md
similarity index 88%
rename from docs/pages/getting_started.md
rename to docs/getting-started.md
index 05337de53..43235ad3b 100644
--- a/docs/pages/getting_started.md
+++ b/docs/getting-started.md
@@ -55,10 +55,11 @@ final class GuestIsCheckedOut
}
}
```
-!!! note
- You can find out more about events [here](events.md).
-
+:::note
+You can find out more about events [here](events.md).
+:::
+
## Define aggregates
Next we need to define the hotel aggregate.
@@ -147,10 +148,11 @@ final class Hotel extends BasicAggregateRoot
}
}
```
-!!! note
- You can find out more about aggregates [here](aggregate.md).
-
+:::note
+You can find out more about aggregates [here](aggregate.md).
+:::
+
## Define projections
So that we can see all the hotels on our website and also see how many guests are currently visiting the hotels,
@@ -225,10 +227,11 @@ final class HotelProjector
}
}
```
-!!! note
- You can find out more about projector [here](subscription.md).
-
+:::note
+You can find out more about projector [here](subscription.md).
+:::
+
## Processor
In our example we also want to email the head office as soon as a guest is checked in.
@@ -256,18 +259,19 @@ final class SendCheckInEmailProcessor
}
}
```
-!!! note
- You can find out more about processor [here](subscription.md).
-
+:::note
+You can find out more about processor [here](subscription.md).
+:::
+
## Configuration
After we have defined everything, we still have to plug the whole thing together:
-!!! tip
+:::tip
+If you use symfony, you can use our [symfony bundle](https://event-sourcing-bundle.patchlevel.io/latest/installation/) to skip this step.
+:::
- If you use symfony, you can use our [symfony bundle](https://event-sourcing-bundle.patchlevel.io/latest/installation/) to skip this step.
-
```php
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Tools\DsnParser;
@@ -324,18 +328,19 @@ $repositoryManager = new RunSubscriptionEngineRepositoryManager(
$hotelRepository = $repositoryManager->get(Hotel::class);
```
-!!! note
- You can find out more about stores [here](store.md).
+:::note
+You can find out more about stores [here](store.md).
+:::
-!!! note
+:::note
+The `RunSubscriptionEngineRepositoryManager` is a decorator that triggers the
+Subscription Engine when an Aggregate is saved. Normally, you'd use the
+`DefaultRepositoryManager` and a worker to run the Subscription Engine.
+
+Learn more [here](subscription.md).
+:::
- The `RunSubscriptionEngineRepositoryManager` is a decorator that triggers the
- Subscription Engine when an Aggregate is saved. Normally, you'd use the
- `DefaultRepositoryManager` and a worker to run the Subscription Engine.
-
- Learn more [here](subscription.md).
-
## Database setup
So that we can actually write the data to a database,
@@ -367,10 +372,11 @@ $schemaDirector->create();
/** @var SubscriptionEngine $engine */
$engine->setup(skipBooting: true);
```
-!!! note
- you can use the predefined [cli commands](cli.md) for this.
-
+:::note
+you can use the predefined [cli commands](cli.md) for this.
+:::
+
## Usage
We are now ready to use the Event Sourcing System. We can load, change and save aggregates.
@@ -393,20 +399,21 @@ $hotelRepository->save($hotel2);
$hotels = $hotelProjection->getHotels();
```
-!!! note
- You can also use other forms of IDs such as uuid version 6 or a custom format.
- You can find more about this [here](aggregate_id.md).
-
+:::note
+You can also use other forms of IDs such as uuid version 6 or a custom format.
+You can find more about this [here](aggregate-id.md).
+:::
+
## Result
-!!! success
+:::success
+We have successfully implemented and used event sourcing.
+
+Feel free to browse further in the documentation for more detailed information.
+If there are still open questions, create a ticket on Github and we will try to help you.
+:::
- We have successfully implemented and used event sourcing.
-
- Feel free to browse further in the documentation for more detailed information.
- If there are still open questions, create a ticket on Github and we will try to help you.
-
## Learn more
* [How to create an aggregate](aggregate.md)
diff --git a/docs/pages/index.md b/docs/index.md
similarity index 81%
rename from docs/pages/index.md
rename to docs/index.md
index 99a5e8bd5..b68c6fe10 100644
--- a/docs/pages/index.md
+++ b/docs/index.md
@@ -9,9 +9,9 @@ powered by the reliable Doctrine ecosystem and focused on developer experience.
* Based on [doctrine dbal](https://github.com/doctrine/dbal) and their ecosystem
* Developer experience oriented and fully typed
* Automatic [snapshot](snapshots.md)-system to boost your performance
-* [Split](split_stream.md) big aggregates into multiple streams
+* [Split](split-stream.md) big aggregates into multiple streams
* Versioned and managed lifecycle of [subscriptions](subscription.md) like projections and processors
-* Safe usage of [Personal Data](personal_data.md) with crypto-shredding
+* Safe usage of [Personal Data](personal-data.md) with crypto-shredding
* Smooth [upcasting](upcasting.md) of old events
* Simple setup with [scheme management](store.md) and [doctrine migration](store.md)
* Built in [cli commands](cli.md) with [symfony](https://symfony.com/)
@@ -27,7 +27,6 @@ composer require patchlevel/event-sourcing
* [Symfony](https://github.com/patchlevel/event-sourcing-bundle)
* [Psalm](https://github.com/patchlevel/event-sourcing-psalm-plugin)
-!!! tip
-
- Start with the [quickstart](./getting_started.md) to get a feeling for the library.
-
\ No newline at end of file
+:::tip
+Start with the [quickstart](getting-started.md) to get a feeling for the library.
+:::
\ No newline at end of file
diff --git a/docs/pages/message_decorator.md b/docs/message-decorator.md
similarity index 90%
rename from docs/pages/message_decorator.md
rename to docs/message-decorator.md
index cff647a56..c0709d6aa 100644
--- a/docs/pages/message_decorator.md
+++ b/docs/message-decorator.md
@@ -10,7 +10,7 @@ We offer a few decorators that you can use.
### SplitStreamDecorator
-In order to use the [split stream](split_stream.md) feature, the `SplitStreamDecorator` must be added.
+In order to use the [split stream](split-stream.md) feature, the `SplitStreamDecorator` must be added.
```php
use Patchlevel\EventSourcing\Metadata\Event\AttributeEventMetadataFactory;
@@ -66,10 +66,11 @@ $repositoryManager = new DefaultRepositoryManager(
$repository = $repositoryManager->get(Profile::class);
```
-!!! note
- You can find out more about repository [here](repository.md).
-
+:::note
+You can find out more about repository [here](repository.md).
+:::
+
## Create own decorator
You can also use this feature to add your own metadata to your events. For this the have an extra methods on `Message`
@@ -97,14 +98,15 @@ final class OnSystemRecordedDecorator implements MessageDecorator
}
}
```
-!!! note
- The Message is immutable, for more information look up [here](message.md).
-
-!!! tip
+:::note
+The Message is immutable, for more information look up [here](message.md).
+:::
+
+:::tip
+You can also set multiple headers with `withHeaders` which expects an hashmap.
+:::
- You can also set multiple headers with `withHeaders` which expects an hashmap.
-
## Learn more
* [How to create messages](message.md)
diff --git a/docs/pages/message.md b/docs/message.md
similarity index 89%
rename from docs/pages/message.md
rename to docs/message.md
index e0c3921ed..288ee1686 100644
--- a/docs/pages/message.md
+++ b/docs/message.md
@@ -11,11 +11,12 @@ use Patchlevel\EventSourcing\Message\Message;
$message = Message::create(new NameChanged('foo'));
```
-!!! note
- You don't have to create the message yourself, it is automatically created, saved and dispatched in
- the [repository](repository.md).
-
+:::note
+You don't have to create the message yourself, it is automatically created, saved and dispatched in
+the [repository](repository.md).
+:::
+
You can add a header using `withHeader`:
```php
@@ -32,10 +33,11 @@ $message = Message::create(new NameChanged('foo'))
recordedOn: $clock->now(),
));
```
-!!! note
- The message object is immutable. It creates a new instance with the new data.
-
+:::note
+The message object is immutable. It creates a new instance with the new data.
+:::
+
You can also access the headers:
```php
@@ -80,15 +82,16 @@ use Patchlevel\EventSourcing\Message\Message;
$message = Message::create(new NameChanged('foo'))
->withHeader(new ApplicationHeader('app'));
```
-!!! warning
- The header needs to be serializable. The library uses the hydrator to serialize and deserialize the headers.
- So you can add normalize attributes to the properties if needed.
-
-!!! note
+:::warning
+The header needs to be serializable. The library uses the hydrator to serialize and deserialize the headers.
+So you can add normalize attributes to the properties if needed.
+:::
+
+:::note
+You can read about how to pass additional headers to the message object in the [message decorator](message-decorator.md) docs.
+:::
- You can read about how to pass additional headers to the message object in the [message decorator](message_decorator.md) docs.
-
You can also access your custom headers:
```php
@@ -211,15 +214,16 @@ use Patchlevel\EventSourcing\Message\Translator\RecalculatePlayheadTranslator;
$translator = new RecalculatePlayheadTranslator();
```
-!!! warning
- The `RecalculatePlayheadTranslator` is and need to be stateful.
- You can't reuse the translator for multiple streams.
-
-!!! tip
+:::warning
+The `RecalculatePlayheadTranslator` is and need to be stateful.
+You can't reuse the translator for multiple streams.
+:::
+
+:::tip
+If you migrate your event stream, you can use the `RecalculatePlayheadTranslator` to fix the playhead.
+:::
- If you migrate your event stream, you can use the `RecalculatePlayheadTranslator` to fix the playhead.
-
### Chain
If you want to group your translator, you can use one or more `ChainTranslator`.
@@ -277,15 +281,16 @@ final class SplitProfileCreatedTranslator implements Translator
}
}
```
-!!! warning
- Since we changed the number of messages, we have to recalculate the playhead.
-
-!!! tip
+:::warning
+Since we changed the number of messages, we have to recalculate the playhead.
+:::
+
+:::tip
+You don't have to migrate the store directly for every change,
+but you can also use the [upcasting](upcasting.md) feature.
+:::
- You don't have to migrate the store directly for every change,
- but you can also use the [upcasting](upcasting.md) feature.
-
## Reducer
The `Reducer` is a construct that allows you to reduce messages to a state.
@@ -375,8 +380,8 @@ $state = (new Reducer())
```
## Learn more
-* [How to decorate messages](message_decorator.md)
+* [How to decorate messages](message-decorator.md)
* [How to load aggregates](repository.md)
* [How to store messages](store.md)
* [How to use subscriptions](subscription.md)
-* [How to use the event bus](event_bus.md)
+* [How to use the event bus](event-bus.md)
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
deleted file mode 100644
index d691bbab5..000000000
--- a/docs/mkdocs.yml
+++ /dev/null
@@ -1,128 +0,0 @@
-# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json
-
-site_url: https://event-sourcing.patchlevel.io/
-repo_url: https://github.com/patchlevel/event-sourcing
-repo_name: patchlevel/event-sourcing
-edit_uri: edit/2.0.x/docs/pages/
-docs_dir: pages
-site_name: Event Sourcing for PHP
-site_description: An event sourcing library for php, complete with all the essential features, powered by the reliable Doctrine ecosystem and focused on developer experience.
-site_author: patchlevel
-
-extra:
- meta:
- image: img/patchlevel-banner.png
- social:
- - icon: fontawesome/brands/github
- link: https://github.com/patchlevel
- - icon: fontawesome/solid/globe
- link: https://patchlevel.de
- version:
- provider: mike
- canonical_version: latest
- analytics:
- provider: custom
-
-extra_css:
- - stylesheets/extra.css
-
-theme:
- name: material
- custom_dir: overrides
- logo: assets/logo.png
- favicon: /favicon.ico
- features:
- - navigation.sections
- - navigation.top
- - navigation.indexes
- - navigation.footer
- - content.code.annotate
- - content.code.copy
- palette:
- - media: "(prefers-color-scheme: light)"
- scheme: default
- primary: black
- accent: blue
- toggle:
- icon: material/brightness-7
- name: Switch to dark mode
- - media: "(prefers-color-scheme: dark)"
- scheme: slate
- primary: black
- accent: blue
- toggle:
- icon: material/brightness-4
- name: Switch to light mode
-
-plugins:
- - search:
- - mike:
- canonical_version: latest
- - privacy:
-
-markdown_extensions:
- - meta
- - pymdownx.details
- - pymdownx.highlight:
- anchor_linenums: true
- extend_pygments_lang:
- - name: php
- lang: php
- options:
- startinline: true
- - pymdownx.inlinehilite
- - pymdownx.snippets:
- auto_append:
- - docs/includes/links.md
- - admonition
- - pymdownx.emoji:
- emoji_index: !!python/name:material.extensions.emoji.twemoji
- emoji_generator: !!python/name:materialx.emoji.to_svg
- - def_list
- - pymdownx.tasklist:
- custom_checkbox: true
- - pymdownx.superfences:
- custom_fences:
- - name: mermaid
- class: mermaid
- format: !!python/name:pymdownx.superfences.fence_code_format
-
-nav:
- - Introduction: index.md
- - Getting Started: getting_started.md
- - Basics:
- - Aggregate: aggregate.md
- - Events: events.md
- - Repository: repository.md
- - Message: message.md
- - Store: store.md
- - Subscription: subscription.md
- - Command Bus: command_bus.md
- - Event Bus: event_bus.md
- - Query Bus: query_bus.md
- - Advanced:
- - Aggregate ID: aggregate_id.md
- - Normalizer: normalizer.md
- - Snapshots: snapshots.md
- - Personal Data: personal_data.md
- - Upcasting: upcasting.md
- - Message Decorator: message_decorator.md
- - Split Stream: split_stream.md
- - Time / Clock: clock.md
- - Testing: testing.md
- - CLI: cli.md
- - Supported Versions: supported-versions.md
- - Our BC Promise: our-backward-compatibility-promise.md
- - Upgrade from 2.x: UPGRADE-3.0.md
- - Links:
- - Blog: https://patchlevel.de/blog
- - Symfony Bundle: https://patchlevel.github.io/event-sourcing-bundle-docs/latest/
- - Admin Bundle: https://github.com/patchlevel/event-sourcing-admin-bundle
- - Psalm Plugin: https://github.com/patchlevel/event-sourcing-psalm-plugin
- - Hydrator: https://github.com/patchlevel/hydrator
-
-validation:
- omitted_files: warn
- absolute_links: warn # Or 'relative_to_docs' - new in MkDocs 1.6
- unrecognized_links: warn
- #anchors: warn
\ No newline at end of file
diff --git a/docs/pages/normalizer.md b/docs/normalizer.md
similarity index 80%
rename from docs/pages/normalizer.md
rename to docs/normalizer.md
index c798a0f22..ca6cb47ce 100644
--- a/docs/pages/normalizer.md
+++ b/docs/normalizer.md
@@ -4,11 +4,11 @@ Sometimes you also want to add more complex data in events as payload or in aggr
For example DateTime, enums or value objects.
Here you can use the normalizer to define how the data should be saved and loaded.
-!!! note
+:::note
+The underlying system called hydrator exists as a library.
+You can find out more details [here](https://github.com/patchlevel/hydrator).
+:::
- The underlying system called hydrator exists as a library.
- You can find out more details [here](https://github.com/patchlevel/hydrator).
-
## Usage
You have a lot of options to use the normalizer.
@@ -28,11 +28,11 @@ Most built-in normalizers can be inferred from the type hint:
* `Enum` => `EnumNormalizer`
* `AggregateRootId` => `IdNormalizer`
-!!! note
+:::note
+`ObjectNormalizer` will not be inferred. You have to specify it yourself.
+This should prevent you from accidentally serializing objects that you don't want to serialize.
+:::
- `ObjectNormalizer` will not be inferred. You have to specify it yourself.
- This should prevent you from accidentally serializing objects that you don't want to serialize.
-
The other way is to specify the normalizer to the properties directly.
This example is equivalent to the previous one.
@@ -75,10 +75,11 @@ final class Item
}
}
```
-!!! note
- With the `ObjectNormalizer`, you can seraialize and deserialize recursively.
-
+:::note
+With the `ObjectNormalizer`, you can seraialize and deserialize recursively.
+:::
+
### Event
For the event, the properties are normalized to a payload and saved in the DB at the end.
@@ -99,10 +100,11 @@ final class CreateHotel
}
}
```
-!!! note
- If you have personal data, you can use [crypto-shredding](personal_data.md).
-
+:::note
+If you have personal data, you can use [crypto-shredding](personal-data.md).
+:::
+
### Aggregate
For the aggregates it is very similar to the events. However, the normalizer is only used for the snapshots.
@@ -125,10 +127,11 @@ final class Hotel extends BasicAggregateRoot
// ...
}
```
-!!! note
- You can learn more about snapshots [here](snapshots.md).
-
+:::note
+You can learn more about snapshots [here](snapshots.md).
+:::
+
## Built-in Normalizer
For some the standard cases we already offer built-in normalizers.
@@ -150,10 +153,11 @@ final class DTO
public array $dates;
}
```
-!!! note
- The keys from the arrays are taken over here.
-
+:::note
+The keys from the arrays are taken over here.
+:::
+
### DateTimeImmutable
With the `DateTimeImmutable` Normalizer, as the name suggests,
@@ -168,10 +172,11 @@ final class DTO
public DateTimeImmutable $date;
}
```
-!!! tip
- You can let the hydrator guess the normalizer from the type hint.
-
+:::tip
+You can let the hydrator guess the normalizer from the type hint.
+:::
+
You can also define the format. Either describe it yourself as a string or use one of the existing constants.
The default is `DateTimeImmutable::ATOM`.
@@ -184,10 +189,11 @@ final class DTO
public DateTimeImmutable $date;
}
```
-!!! note
- You can read about how the format is structured in the [php docs](https://www.php.net/manual/de/datetime.format.php).
-
+:::note
+You can read about how the format is structured in the [php docs](https://www.php.net/manual/de/datetime.format.php).
+:::
+
### DateTime
The `DateTime` Normalizer works exactly like the DateTimeNormalizer. Only for DateTime objects.
@@ -201,10 +207,11 @@ final class DTO
public DateTime $date;
}
```
-!!! tip
- You can let the hydrator guess the normalizer from the type hint.
-
+:::tip
+You can let the hydrator guess the normalizer from the type hint.
+:::
+
You can also specify the format here. The default is `DateTime::ATOM`.
```php
@@ -216,15 +223,16 @@ final class DTO
public DateTime $date;
}
```
-!!! warning
- It is highly recommended to only ever use DateTimeImmutable objects and the DateTimeImmutableNormalizer.
- This prevents you from accidentally changing the state of the DateTime and thereby causing bugs.
-
-!!! note
+:::warning
+It is highly recommended to only ever use DateTimeImmutable objects and the DateTimeImmutableNormalizer.
+This prevents you from accidentally changing the state of the DateTime and thereby causing bugs.
+:::
+
+:::note
+You can read about how the format is structured in the [php docs](https://www.php.net/manual/de/datetime.format.php).
+:::
- You can read about how the format is structured in the [php docs](https://www.php.net/manual/de/datetime.format.php).
-
### DateTimeZone
To normalize a `DateTimeZone` one can use the `DateTimeZoneNormalizer`.
@@ -238,10 +246,11 @@ final class DTO
public DateTimeZone $timeZone;
}
```
-!!! tip
- You can let the hydrator guess the normalizer from the type hint.
-
+:::tip
+You can let the hydrator guess the normalizer from the type hint.
+:::
+
### Enum
Backed enums can also be normalized.
@@ -255,10 +264,11 @@ final class DTO
public Status $status;
}
```
-!!! tip
- You can let the hydrator guess the normalizer from the type hint.
-
+:::tip
+You can let the hydrator guess the normalizer from the type hint.
+:::
+
You can also specify the enum class.
```php
@@ -284,10 +294,11 @@ final class DTO
public Uuid $id;
}
```
-!!! tip
- You can let the hydrator guess the normalizer from the type hint.
-
+:::tip
+You can let the hydrator guess the normalizer from the type hint.
+:::
+
Optional you can also define the type of the id.
```php
@@ -383,10 +394,11 @@ class NameNormalizer implements Normalizer
}
}
```
-!!! warning
- The important thing is that the result of Normalize is serializable!
-
+:::warning
+The important thing is that the result of Normalize is serializable!
+:::
+
Now we can also use the normalizer directly.
```php
@@ -396,10 +408,11 @@ final class DTO
public Name $name;
}
```
-!!! tip
- Every normalizer, including the custom normalizer, can be used both for the events and for the snapshots.
-
+:::tip
+Every normalizer, including the custom normalizer, can be used both for the events and for the snapshots.
+:::
+
Or define it on class level, so you don't have to specify it for each property.
```php
@@ -430,16 +443,17 @@ The whole thing looks like this
"profile_name": "David"
}
```
-!!! tip
- You can also rename properties to events without having a backwards compatibility break by keeping the serialized name.
-
-!!! note
+:::tip
+You can also rename properties to events without having a backwards compatibility break by keeping the serialized name.
+:::
+
+:::note
+NormalizedName also works for snapshots.
+But since a snapshot is just a cache, you can also just invalidate it,
+if you have backwards compatibility break in the property name
+:::
- NormalizedName also works for snapshots.
- But since a snapshot is just a cache, you can also just invalidate it,
- if you have backwards compatibility break in the property name
-
## Ignore
You can also ignore properties with the `Ignore` attribute.
@@ -459,4 +473,4 @@ final class DTO
* [How to define aggregates](aggregate.md)
* [How to define events](events.md)
* [How to snapshot aggregates](snapshots.md)
-* [How to work with personal data](personal_data.md)
+* [How to work with personal data](personal-data.md)
diff --git a/docs/pages/our-backward-compatibility-promise.md b/docs/our-backward-compatibility-promise.md
similarity index 94%
rename from docs/pages/our-backward-compatibility-promise.md
rename to docs/our-backward-compatibility-promise.md
index 70f7f1ffd..35f90d3c4 100644
--- a/docs/pages/our-backward-compatibility-promise.md
+++ b/docs/our-backward-compatibility-promise.md
@@ -40,8 +40,7 @@ compatibility promise.
In our docs the features are marked like this:
-??? example "Experimental"
-
- This feature is still experimental and may change in the future.
- Use it with caution.
-
\ No newline at end of file
+:::experimental
+This feature is still experimental and may change in the future.
+Use it with caution.
+:::
\ No newline at end of file
diff --git a/docs/overrides/assets/favicon.png b/docs/overrides/assets/favicon.png
deleted file mode 100644
index 86e8fb7be..000000000
Binary files a/docs/overrides/assets/favicon.png and /dev/null differ
diff --git a/docs/overrides/assets/logo.png b/docs/overrides/assets/logo.png
deleted file mode 100644
index be170fedb..000000000
Binary files a/docs/overrides/assets/logo.png and /dev/null differ
diff --git a/docs/overrides/main.html b/docs/overrides/main.html
deleted file mode 100644
index da67f541c..000000000
--- a/docs/overrides/main.html
+++ /dev/null
@@ -1,20 +0,0 @@
-{% extends "base.html" %}
-
-{% block extrahead %}
-
-
-
-
-
-
-
-
-
-{% endblock %}
-
-{% block outdated %}
-You're not viewing the latest version.
-
- Click here to go to latest.
-
-{% endblock %}
\ No newline at end of file
diff --git a/docs/overrides/partials/integrations/analytics/custom.html b/docs/overrides/partials/integrations/analytics/custom.html
deleted file mode 100644
index f72401389..000000000
--- a/docs/overrides/partials/integrations/analytics/custom.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/pages/navigation.json b/docs/pages/navigation.json
deleted file mode 100644
index 09f5e3831..000000000
--- a/docs/pages/navigation.json
+++ /dev/null
@@ -1,134 +0,0 @@
-[
- {
- "slug": "event-sourcing",
- "title": "Introduction",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/getting_started",
- "title": "Getting Started",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/basics",
- "title": "Basics",
- "subEntries": [
- {
- "slug": "event-sourcing/aggregate",
- "title": "Aggregate",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/events",
- "title": "Events",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/repository",
- "title": "Repository",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/message",
- "title": "Message",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/store",
- "title": "Store",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/subscription",
- "title": "Subscription",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/command_bus",
- "title": "Command Bus",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/event_bus",
- "title": "Event Bus",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/query_bus",
- "title": "Query Bus",
- "subEntries": null
- }
- ]
- },
- {
- "slug": "event-sourcing/advanced",
- "title": "Advanced",
- "subEntries": [
- {
- "slug": "event-sourcing/aggregate_id",
- "title": "Aggregate ID",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/normalizer",
- "title": "Normalizer",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/snapshots",
- "title": "Snapshots",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/personal_data",
- "title": "Personal Data",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/upcasting",
- "title": "Upcasting",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/message_decorator",
- "title": "Message Decorator",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/split_stream",
- "title": "Split Stream",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/clock",
- "title": "Time / Clock",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/testing",
- "title": "Testing",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/cli",
- "title": "CLI",
- "subEntries": null
- }
- ]
- },
- {
- "slug": "event-sourcing/supported-versions",
- "title": "Supported Versions",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/our-backward-compatibility-promise",
- "title": "Our BC Promise",
- "subEntries": null
- },
- {
- "slug": "event-sourcing/UPGRADE-3.0",
- "title": "Upgrade from 2.x",
- "subEntries": null
- }
-]
diff --git a/docs/pages/stylesheets/extra.css b/docs/pages/stylesheets/extra.css
deleted file mode 100644
index 26494c71e..000000000
--- a/docs/pages/stylesheets/extra.css
+++ /dev/null
@@ -1,43 +0,0 @@
-:root > * {
- --md-code-hl-number-color: #6897BB;
- --md-code-hl-special-color: #C7CDD7;
- --md-code-hl-function-color: #FFC66D;
- --md-code-hl-constant-color: #9876AA;
- --md-code-hl-keyword-color: #CC7832;
- --md-code-hl-string-color: #6A8759;
- --md-code-hl-name-color: red;
- --md-code-hl-operator-color: #C7CDD7;
- --md-code-hl-punctuation-color: #C7CDD7;
- --md-code-hl-comment-color: #629755;
- --md-code-hl-generic-color: red;
- --md-code-hl-variable-color: #9876AA;
-
- --md-code-fg-color: #C7CDD7;
- --md-code-bg-color: rgb(39, 42, 53);
- --md-code-hl-color: red;
-}
-
-p {
- margin-block-start: 2em;
- margin-block-end: 2em;
-}
-
-.md-typeset code {
- padding: .2em .4em;
- font-size: .9em;
- line-height: 1.8;
- border-radius: .2rem;
-}
-
-[data-md-color-scheme=default] .md-typeset code:not(pre code) {
- background-color: #f5f5f5;
- color: #36464e;
-}
-
-.admonition-title {
- margin-bottom: -1em !important;
-}
-
-.md-nav__link--active {
- font-weight: bold;
-}
\ No newline at end of file
diff --git a/docs/pages/personal_data.md b/docs/personal-data.md
similarity index 88%
rename from docs/pages/personal_data.md
rename to docs/personal-data.md
index e89afffe2..7fd4d2b88 100644
--- a/docs/pages/personal_data.md
+++ b/docs/personal-data.md
@@ -39,10 +39,11 @@ final class EmailChanged
}
}
```
-!!! tip
- You can use the `DataSubjectId` in aggregates for snapshots too.
-
+:::tip
+You can use the `DataSubjectId` in aggregates for snapshots too.
+:::
+
### PersonalData
Next, you have to mark the properties that should be encrypted with the `#[PersonalData]` attribute.
@@ -63,10 +64,11 @@ final class EmailChanged
}
}
```
-!!! tip
- You can use the `PersonalData` in aggregates for snapshots too.
-
+:::tip
+You can use the `PersonalData` in aggregates for snapshots too.
+:::
+
If the information could not be decrypted, then a fallback value will be used.
The default fallback value is `null`.
You can change this by setting the `fallback` parameter or using the `fallbackCallable` parameter.
@@ -92,14 +94,15 @@ final class ProfileChanged
}
}
```
-!!! danger
- You have to deal with this case in your business logic such as aggregates and subscriptions.
-
-!!! note
+:::danger
+You have to deal with this case in your business logic such as aggregates and subscriptions.
+:::
+
+:::note
+The normalized data is encrypted. This means that this happens after the `extract` or before the `hydrate`.
+:::
- The normalized data is encrypted. This means that this happens after the `extract` or before the `hydrate`.
-
## Setup
In order for the system to work, a few things have to be done.
@@ -149,10 +152,11 @@ use Patchlevel\Hydrator\Cryptography\PersonalDataPayloadCryptographer;
/** @var CipherKeyStore $cipherKeyStore */
$cryptographer = PersonalDataPayloadCryptographer::createWithDefaultSettings($cipherKeyStore);
```
-!!! tip
- You can specify the cipher method with the second parameter.
-
+:::tip
+You can specify the cipher method with the second parameter.
+:::
+
### Event Serializer Integration
The last step is to integrate the cryptographer into the event store.
@@ -167,10 +171,11 @@ DefaultEventSerializer::createFromPaths(
cryptographer: $cryptographer,
);
```
-!!! note
- More information about the events can be found [here](./events.md).
-
+:::note
+More information about the events can be found [here](events.md).
+:::
+
### Snapshot Store Integration
And for the snapshot store.
@@ -187,14 +192,15 @@ $snapshotStore = DefaultSnapshotStore::createDefault(
$cryptographer,
);
```
-!!! note
- More information about the snapshot store can be found [here](./snapshots.md).
-
-!!! success
+:::note
+More information about the snapshot store can be found [here](snapshots.md).
+:::
+
+:::success
+Now you can save and read events with personal data.
+:::
- Now you can save and read events with personal data.
-
## Remove personal data
To remove personal data, you can either remove the key manually or do it with a processor.
diff --git a/docs/project.json b/docs/project.json
new file mode 100644
index 000000000..96a7c9610
--- /dev/null
+++ b/docs/project.json
@@ -0,0 +1,110 @@
+{
+ "navigation": [
+ {
+ "title": "Introduction",
+ "file": "index.md"
+ },
+ {
+ "title": "Getting Started",
+ "file": "getting-started.md"
+ },
+ {
+ "title": "Basics",
+ "subEntries": [
+ {
+ "title": "Aggregate",
+ "file": "aggregate.md"
+ },
+ {
+ "title": "Events",
+ "file": "events.md"
+ },
+ {
+ "title": "Repository",
+ "file": "repository.md"
+ },
+ {
+ "title": "Message",
+ "file": "message.md"
+ },
+ {
+ "title": "Store",
+ "file": "store.md"
+ },
+ {
+ "title": "Subscription",
+ "file": "subscription.md"
+ },
+ {
+ "title": "Command Bus",
+ "file": "command-bus.md"
+ },
+ {
+ "title": "Event Bus",
+ "file": "event-bus.md"
+ },
+ {
+ "title": "Query Bus",
+ "file": "query-bus.md"
+ }
+ ]
+ },
+ {
+ "title": "Advanced",
+ "subEntries": [
+ {
+ "title": "Aggregate ID",
+ "file": "aggregate-id.md"
+ },
+ {
+ "title": "Normalizer",
+ "file": "normalizer.md"
+ },
+ {
+ "title": "Snapshots",
+ "file": "snapshots.md"
+ },
+ {
+ "title": "Personal Data",
+ "file": "personal-data.md"
+ },
+ {
+ "title": "Upcasting",
+ "file": "upcasting.md"
+ },
+ {
+ "title": "Message Decorator",
+ "file": "message-decorator.md"
+ },
+ {
+ "title": "Split Stream",
+ "file": "split-stream.md"
+ },
+ {
+ "title": "Time / Clock",
+ "file": "clock.md"
+ },
+ {
+ "title": "Testing",
+ "file": "testing.md"
+ },
+ {
+ "title": "CLI",
+ "file": "cli.md"
+ }
+ ]
+ },
+ {
+ "title": "Supported Versions",
+ "file": "supported-versions.md"
+ },
+ {
+ "title": "Our BC Promise",
+ "file": "our-backward-compatibility-promise.md"
+ },
+ {
+ "title": "Upgrade from 2.x",
+ "file": "UPGRADE-3.0.md"
+ }
+ ]
+}
diff --git a/docs/pages/query_bus.md b/docs/query-bus.md
similarity index 86%
rename from docs/pages/query_bus.md
rename to docs/query-bus.md
index fba5c77e0..817d3f885 100644
--- a/docs/pages/query_bus.md
+++ b/docs/query-bus.md
@@ -35,18 +35,19 @@ final class QueryProfileHandler
}
}
```
-!!! warning
- A query can only be answered by one method.
-
-!!! note
+:::warning
+A query can only be answered by one method.
+:::
- To use Service Handler you need to register the handler in the `ServiceHandlerProvider`.
-
-!!! tip
+:::note
+To use Service Handler you need to register the handler in the `ServiceHandlerProvider`.
+:::
+
+:::tip
+A class can have multiple methods which answers different queries.
+:::
- A class can have multiple methods which answers different queries.
-
### Projector
Another way to handle queries is to answer them directly in the corresponding projectors. The configuration is the same
@@ -67,11 +68,12 @@ final class ProfileProjector
// projector related methods to maintain the state of profiles
}
```
-!!! tip
- Using small dedicated projections for each usecase is best practice. Using them directly as query handlers are
- endoresed and can reduce fragmentation of the system.
-
+:::tip
+Using small dedicated projections for each usecase is best practice. Using them directly as query handlers are
+endoresed and can reduce fragmentation of the system.
+:::
+
## Setup
We provide a `SyncQueryBus` that you can use to dispatch queries.
@@ -122,4 +124,4 @@ $provider = new ChainHandlerProvider([
* [How to use aggregates](aggregate.md)
* [How to use events](events.md)
* [How to use subscriptions](subscription.md)
-* [How to use command bus](command_bus.md)
+* [How to use command bus](command-bus.md)
diff --git a/docs/pages/repository.md b/docs/repository.md
similarity index 73%
rename from docs/pages/repository.md
rename to docs/repository.md
index 5285658d7..75c02c98c 100644
--- a/docs/pages/repository.md
+++ b/docs/repository.md
@@ -31,10 +31,11 @@ $repositoryManager = new DefaultRepositoryManager(
$repository = $repositoryManager->get(Profile::class);
```
-!!! note
- The same repository instance is always returned for a specific aggregate.
-
+:::note
+The same repository instance is always returned for a specific aggregate.
+:::
+
### Event Bus
You can pass an event bus to the `DefaultRepositoryManager` to dispatch events synchronously.
@@ -60,21 +61,22 @@ $repositoryManager = new DefaultRepositoryManager(
$repository = $repositoryManager->get(Profile::class);
```
-!!! warning
- If you use the event bus, you should be aware that the events are dispatched synchronously.
- You may encounter [at least once](https://softwaremill.com/message-delivery-and-deduplication-strategies/) problems.
-
-!!! note
+:::warning
+If you use the event bus, you should be aware that the events are dispatched synchronously.
+You may encounter [at least once](https://softwaremill.com/message-delivery-and-deduplication-strategies/) problems.
+:::
+
+:::note
+You can find out more about event bus [here](event-bus.md).
+:::
- You can find out more about event bus [here](event_bus.md).
-
-!!! tip
+:::tip
+In most cases it is better to react to events asynchronously,
+that's why we recommend the subscription engine.
+More information can be found [here](subscription.md).
+:::
- In most cases it is better to react to events asynchronously,
- that's why we recommend the subscription engine.
- More information can be found [here](subscription.md).
-
### Snapshots
Loading events for an aggregate is superfast.
@@ -105,10 +107,11 @@ $repositoryManager = new DefaultRepositoryManager(
$repository = $repositoryManager->get(Profile::class);
```
-!!! note
- You can find out more about snapshots [here](snapshots.md).
-
+:::note
+You can find out more about snapshots [here](snapshots.md).
+:::
+
### Decorator
If you want to add more metadata to the message, like e.g. an application id, then you can use decorators.
@@ -134,14 +137,15 @@ $repositoryManager = new DefaultRepositoryManager(
$repository = $repositoryManager->get(Profile::class);
```
-!!! note
- You can find out more about message decorator [here](message_decorator.md).
-
-!!! tip
+:::note
+You can find out more about message decorator [here](message-decorator.md).
+:::
+
+:::tip
+If you have multiple decorators, you can use the `ChainMessageDecorator` to chain them.
+:::
- If you have multiple decorators, you can use the `ChainMessageDecorator` to chain them.
-
## Use the repository
Each `repository` has three methods that are responsible for loading an `aggregate`,
@@ -163,24 +167,25 @@ $profile = Profile::create($id, 'david.badura@patchlevel.de');
/** @var Repository $repository */
$repository->save($profile);
```
-!!! warning
-
- All events are written to the database with one transaction in order to ensure data consistency.
- If an exception occurs during the save process,
- the transaction is rolled back and the aggregate is not valid anymore.
- You can not save the aggregate again and you need to load it again.
-
-!!! note
-
- Due to the nature of the aggregate having a playhead,
- we have a unique constraint that ensures that no race condition happens here.
- An `AggregateOutdated` exception is thrown if a conflict occurs.
-
-!!! tip
-
- If you use the Command Bus, you can use the [RetryOutdatedAggregateCommandBus](command_bus.md#retry-outdated-aggregate-command-bus)
- to retry the command when an `AggregateOutdated` exception occurs automatically.
-
+
+:::warning
+All events are written to the database with one transaction in order to ensure data consistency.
+If an exception occurs during the save process,
+the transaction is rolled back and the aggregate is not valid anymore.
+You can not save the aggregate again and you need to load it again.
+:::
+
+:::note
+Due to the nature of the aggregate having a playhead,
+we have a unique constraint that ensures that no race condition happens here.
+An `AggregateOutdated` exception is thrown if a conflict occurs.
+:::
+
+:::tip
+If you use the Command Bus, you can use the [RetryOutdatedAggregateCommandBus](command-bus.md#retry-outdated-aggregate-command-bus)
+to retry the command when an `AggregateOutdated` exception occurs automatically.
+:::
+
### Load an aggregate
An `aggregate` can be loaded using the `load` method.
@@ -195,19 +200,20 @@ $id = Uuid::fromString('229286ff-6f95-4df6-bc72-0a239fe7b284');
/** @var Repository $repository */
$profile = $repository->load($id);
```
-!!! warning
-
- When the method is called, the aggregate is always reloaded and rebuilt from the database.
-!!! note
+:::warning
+When the method is called, the aggregate is always reloaded and rebuilt from the database.
+:::
- You can only fetch one aggregate at a time and don't do any complex queries either.
- Projections are used for this purpose.
+:::note
+You can only fetch one aggregate at a time and don't do any complex queries either.
+Projections are used for this purpose.
+:::
-!!! tip
-
- If you want to automatically initialize an aggregate if it cannot be found in the store,
- you can use the [Auto Initialize](aggregate.md#auto-initialize) feature.
+:::tip
+If you want to automatically initialize an aggregate if it cannot be found in the store,
+you can use the [Auto Initialize](aggregate.md#auto-initialize) feature.
+:::
### Has an aggregate
@@ -225,11 +231,12 @@ if ($repository->has($id)) {
// ...
}
```
-!!! note
- The query is fast and does not load any event.
- This means that the state of the aggregate is not rebuild either.
-
+:::note
+The query is fast and does not load any event.
+This means that the state of the aggregate is not rebuild either.
+:::
+
## Custom Repository
In clean code you want to have explicit type hints for the repositories
@@ -276,6 +283,6 @@ class ProfileRepository
* [How to create an event](events.md)
* [How to work with the store](store.md)
* [How to use snapshots](snapshots.md)
-* [How to split streams](split_stream.md)
-* [How to use the event bus](event_bus.md)
+* [How to split streams](split-stream.md)
+* [How to use the event bus](event-bus.md)
* [How to create messages](message.md)
diff --git a/docs/requirements.txt b/docs/requirements.txt
deleted file mode 100644
index 22b21d497..000000000
--- a/docs/requirements.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-mkdocs==1.6.1
-mike==2.2.0
-markdown==3.10.2
-mkdocs-material==9.7.6
-
-# Markdown extensions
-Pygments==2.20.0
-pymdown-extensions==10.21.3
-
-# MkDocs plugins
-mkdocs-material-extensions==1.3.1
diff --git a/docs/pages/snapshots.md b/docs/snapshots.md
similarity index 80%
rename from docs/pages/snapshots.md
rename to docs/snapshots.md
index 30b9c1842..42f1fe89f 100644
--- a/docs/pages/snapshots.md
+++ b/docs/snapshots.md
@@ -5,14 +5,14 @@ This is not a problem if there are a few hundred.
But if the number gets bigger at some point, then loading and rebuilding can become slow.
The `snapshot` system can be used to control this.
-!!! tip
-
- Use snapshots only if you have a performance problems,
- because it introduces additional complexity.
-
- In our benchmarks we can load 10 000 events for one aggregate in 50ms.
- Of course, this can vary from system to system.
-
+:::tip
+Use snapshots only if you have a performance problems,
+because it introduces additional complexity.
+
+In our benchmarks we can load 10 000 events for one aggregate in 50ms.
+Of course, this can vary from system to system.
+:::
+
Normally, the events are all applied again on the aggregate in order to rebuild the current state.
With a `snapshot`, we can shorten the way in which we temporarily save the current state of the aggregate.
When loading it is checked whether the snapshot exists.
@@ -55,10 +55,11 @@ $repositoryManager = new DefaultRepositoryManager(
$snapshotStore,
);
```
-!!! note
- You can read more about Repository [here](./repository.md).
-
+:::note
+You can read more about Repository [here](repository.md).
+:::
+
Next we need to tell the Aggregate to take a snapshot of it. We do this using the snapshot attribute.
There we also specify where it should be saved.
@@ -99,20 +100,21 @@ final class Profile extends BasicAggregateRoot
// ...
}
```
-!!! danger
- If anything changes in the properties of the aggregate, then the cache must be cleared.
- Or the snapshot version needs to be changed so that the previous snapshot is invalid.
-
-!!! warning
+:::danger
+If anything changes in the properties of the aggregate, then the cache must be cleared.
+Or the snapshot version needs to be changed so that the previous snapshot is invalid.
+:::
+
+:::warning
+In the end it the complete aggregate must be serializeable as json, also the aggregate Id.
+:::
- In the end it the complete aggregate must be serializeable as json, also the aggregate Id.
-
-!!! note
+:::note
+The [hydrator](https://github.com/patchlevel/hydrator) is used internally and you can use all of its features.
+You can find more about normalizer also [here](normalizer.md).
+:::
- The [hydrator](https://github.com/patchlevel/hydrator) is used internally and you can use all of its features.
- You can find more about normalizer also [here](normalizer.md).
-
### Snapshot batching
Since the loading of events in itself is quite fast and only becomes noticeably slower with thousands of events,
@@ -154,18 +156,19 @@ final class Profile extends BasicAggregateRoot
// ...
}
```
-!!! warning
-
- If the snapshots are discarded, a load peak can occur since the aggregates have to be rebuilt.
- You should update the snapshot version only when necessary.
-
-!!! tip
-
- If you have aggregates with a lot of events,
- you should consider using [split streams](split_stream.md) if it make sense in your domain.
- Then the load peak is not so high anymore,
- because only the events from new stream start are loaded to rebuild the aggregate.
-
+
+:::warning
+If the snapshots are discarded, a load peak can occur since the aggregates have to be rebuilt.
+You should update the snapshot version only when necessary.
+:::
+
+:::tip
+If you have aggregates with a lot of events,
+you should consider using [split streams](split-stream.md) if it make sense in your domain.
+Then the load peak is not so high anymore,
+because only the events from new stream start are loaded to rebuild the aggregate.
+:::
+
## Adapter
We offer a few `SnapshotAdapter` implementations that you can use.
@@ -229,11 +232,12 @@ use Patchlevel\EventSourcing\Snapshot\SnapshotStore;
*/
$snapshotStore->save($aggregate);
```
-!!! danger
- If the state of an aggregate is saved as a snapshot without being saved to the event store (database),
- it can lead to data loss or broken aggregates!
-
+:::danger
+If the state of an aggregate is saved as a snapshot without being saved to the event store (database),
+it can lead to data loss or broken aggregates!
+:::
+
### Load
You can also load an aggregate from the snapshot store:
@@ -251,14 +255,14 @@ The method returns the Aggregate if it was loaded successfully.
If the aggregate was not found, then a `SnapshotNotFound` is thrown.
And if the version is no longer correct and the snapshot is therefore invalid, then a `SnapshotVersionInvalid` is thrown.
-!!! warning
+:::warning
+The aggregate may be in an old state as the snapshot may lag behind.
+You still have to bring the aggregate up to date by loading the missing events from the event store.
+:::
- The aggregate may be in an old state as the snapshot may lag behind.
- You still have to bring the aggregate up to date by loading the missing events from the event store.
-
## Learn more
* [How to define aggregates](aggregate.md)
* [How to store and load aggregates](repository.md)
-* [How to split streams](split_stream.md)
-* [How to work with personal data](personal_data.md)
+* [How to split streams](split-stream.md)
+* [How to work with personal data](personal-data.md)
diff --git a/docs/pages/split_stream.md b/docs/split-stream.md
similarity index 78%
rename from docs/pages/split_stream.md
rename to docs/split-stream.md
index e1237160f..57c8ef310 100644
--- a/docs/pages/split_stream.md
+++ b/docs/split-stream.md
@@ -40,14 +40,15 @@ $repositoryManager = new DefaultRepositoryManager(
new SplitStreamDecorator($eventMetadataFactory),
);
```
-!!! note
- You can find out more about decorator [here](./message_decorator.md).
-
-!!! tip
+:::note
+You can find out more about decorator [here](message-decorator.md).
+:::
+
+:::tip
+You can use multiple decorators with the `ChainMessageDecorator`.
+:::
- You can use multiple decorators with the `ChainMessageDecorator`.
-
## Usage
To use this feature you need to mark the event which should split the stream.
@@ -69,22 +70,23 @@ final class BalanceReported
}
}
```
-!!! warning
- The event needs all data which is relevant the aggregate to be used since all past event will not be loaded!
- Keep this in mind if you want to use this feature.
-
-!!! note
+:::warning
+The event needs all data which is relevant the aggregate to be used since all past event will not be loaded!
+Keep this in mind if you want to use this feature.
+:::
+
+:::note
+This impacts only the aggregate loaded by the repository. Subscriptions will still receive all events.
+:::
- This impacts only the aggregate loaded by the repository. Subscriptions will still receive all events.
-
-!!! tip
+:::tip
+You can combine this feature with the snapshot feature to increase the performance even more.
+:::
- You can combine this feature with the snapshot feature to increase the performance even more.
-
## Learn more
-* [How to use message decorator](message_decorator.md)
+* [How to use message decorator](message-decorator.md)
* [How to define events](events.md)
* [How to define aggregates](aggregate.md)
* [How to store and load aggregates](repository.md)
diff --git a/docs/pages/store.md b/docs/store.md
similarity index 86%
rename from docs/pages/store.md
rename to docs/store.md
index 60cfe28cb..cc4569b11 100644
--- a/docs/pages/store.md
+++ b/docs/store.md
@@ -3,10 +3,10 @@
In the end, the messages have to be saved somewhere.
Each message contains an event and the associated headers.
-!!! note
+:::note
+More information about the message can be found [here](message.md).
+:::
- More information about the message can be found [here](message.md).
-
The store is optimized to efficiently store and load events for aggregates.
## Configure Store
@@ -36,11 +36,12 @@ $store = new DoctrineDbalStore(
DefaultEventSerializer::createFromPaths(['src/Event']),
);
```
-!!! note
- You can find out more about how to create a connection
- [here](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html)
-
+:::note
+You can find out more about how to create a connection
+[here](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html)
+:::
+
Following options are available in `DoctrineDbalStore`:
| Option | Type | Default | Description |
@@ -66,11 +67,11 @@ The table structure of the `DoctrineDbalStore` looks like this:
| archived | bool | If the event is archived |
| custom_headers | json | Custom headers for the event |
-!!! note
+:::note
+The default type of the `aggregate_id` column is `uuid` if the database supports it and `string` if not.
+You can change the type with the `aggregate_id_type` to `string` if you want use custom id.
+:::
- The default type of the `aggregate_id` column is `uuid` if the database supports it and `string` if not.
- You can change the type with the `aggregate_id_type` to `string` if you want use custom id.
-
### StreamDoctrineDbalStore
We offer a new store called `StreamDoctrineDbalStore`.
@@ -96,11 +97,12 @@ $store = new StreamDoctrineDbalStore(
DefaultEventSerializer::createFromPaths(['src/Event']),
);
```
-!!! note
- You can find out more about how to create a connection
- [here](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html)
-
+:::note
+You can find out more about how to create a connection
+[here](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html)
+:::
+
Following options are available in `StreamDoctrineDbalStore`:
| Option | Type | Default | Description |
@@ -135,10 +137,11 @@ use Patchlevel\EventSourcing\Store\InMemoryStore;
$store = new InMemoryStore();
```
-!!! tip
- You can pass messages to the constructor to initialize the store with some events.
-
+:::tip
+You can pass messages to the constructor to initialize the store with some events.
+:::
+
### ReadOnlyStore & StreamReadOnlyStore
Last but not least, we offer two read-only stores.
@@ -162,10 +165,10 @@ $readOnlyStore = new StreamReadOnlyStore($store);
With the help of the `SchemaDirector`, the database structure can be created, updated and deleted.
-!!! tip
+:::tip
+You can also use doctrine migration to create and keep your schema in sync.
+:::
- You can also use doctrine migration to create and keep your schema in sync.
-
### Doctrine Schema Director
The `SchemaDirector` is responsible for creating, updating and deleting the database schema.
@@ -186,10 +189,11 @@ $schemaDirector = new DoctrineSchemaDirector(
$store,
);
```
-!!! note
- How to setup cli commands for schema director can be found [here](cli.md).
-
+:::note
+How to setup cli commands for schema director can be found [here](cli.md).
+:::
+
#### Create schema
You can create the table from scratch using the `create` method.
@@ -290,15 +294,16 @@ $dependencyFactory->setService(
$schemaProvider,
);
```
-!!! note
- Here you can find more information on how to
- [configure doctrine migration](https://www.doctrine-project.org/projects/doctrine-migrations/en/3.3/reference/custom-configuration.html).
-
-!!! note
+:::note
+Here you can find more information on how to
+[configure doctrine migration](https://www.doctrine-project.org/projects/doctrine-migrations/en/3.3/reference/custom-configuration.html).
+:::
+
+:::note
+How to setup cli commands for doctrine migration can be found [here](cli.md).
+:::
- How to setup cli commands for doctrine migration can be found [here](cli.md).
-
## Usage
The store has a few methods to interact with the database.
@@ -382,15 +387,16 @@ foreach ($stream as $message) {
$message->event(); // get the event
}
```
-!!! note
- You can find more information about the `Message` object [here](message.md).
-
-!!! warning
+:::note
+You can find more information about the `Message` object [here](message.md).
+:::
+
+:::warning
+The stream cannot rewind, so you can only iterate over it once.
+If you want to iterate over it again, you have to call the `load` method again.
+:::
- The stream cannot rewind, so you can only iterate over it once.
- If you want to iterate over it again, you have to call the `load` method again.
-
### Count
You can count the number of events in the store with the `count` method.
@@ -432,15 +438,16 @@ $store->save($message);
$store->save($message1, $message2, $message3);
$store->save(...$messages);
```
-!!! note
- The saving happens in a transaction, so all messages are saved or none.
- The store lock the table for writing during each save by default.
-
-!!! tip
+:::note
+The saving happens in a transaction, so all messages are saved or none.
+The store lock the table for writing during each save by default.
+:::
+
+:::tip
+Use transactional method if you want call multiple save methods in a transaction.
+:::
- Use transactional method if you want call multiple save methods in a transaction.
-
### Update
It is not possible to update events.
@@ -456,10 +463,11 @@ use Patchlevel\EventSourcing\Store\StreamStore;
/** @var StreamStore $store */
$store->remove('profile-*');
```
-!!! note
- The method is only available in the `StreamStore` like `StreamDoctrineDbalStore`.
-
+:::note
+The method is only available in the `StreamStore` like `StreamDoctrineDbalStore`.
+:::
+
### List Streams
You can list all streams with the `streams` method.
@@ -470,10 +478,11 @@ use Patchlevel\EventSourcing\Store\StreamStore;
/** @var StreamStore $store */
$streams = $store->streams(); // ['profile-1', 'profile-2', 'profile-3']
```
-!!! note
- The method is only available in the `StreamStore` like `StreamDoctrineDbalStore`.
-
+:::note
+The method is only available in the `StreamStore` like `StreamDoctrineDbalStore`.
+:::
+
### Transaction
There is also the possibility of executing a function in a transaction.
@@ -494,15 +503,16 @@ $store->transactional(static function () use ($command, $bankAccountRepository):
$bankAccountRepository->save($accountTo);
});
```
-!!! note
- The store lock the table for writing during the transaction by default.
-
-!!! tip
+:::note
+The store lock the table for writing during the transaction by default.
+:::
+
+:::tip
+If you want save only one aggregate, so you don't have to use the transactional method.
+The save method in store/repository is already transactional.
+:::
- If you want save only one aggregate, so you don't have to use the transactional method.
- The save method in store/repository is already transactional.
-
## Learn more
* [How to create events](events.md)
diff --git a/docs/pages/subscription.md b/docs/subscription.md
similarity index 86%
rename from docs/pages/subscription.md
rename to docs/subscription.md
index 157cea6b5..bd46b6e8c 100644
--- a/docs/pages/subscription.md
+++ b/docs/subscription.md
@@ -26,17 +26,18 @@ final class DoStuffSubscriber
{
}
```
-!!! note
- For each subsciber ID, the engine will create a subscription.
- If the subscriber ID changes, a new subscription will be created.
- In some cases like projections, you want to change the subscriber ID to rebuild the projection.
-
-!!! tip
+:::note
+For each subsciber ID, the engine will create a subscription.
+If the subscriber ID changes, a new subscription will be created.
+In some cases like projections, you want to change the subscriber ID to rebuild the projection.
+:::
+
+:::tip
+You can use specific attributes for specific subscribers like `Projector` or `Processor`.
+So you don't have to define the group and run mode every time.
+:::
- You can use specific attributes for specific subscribers like `Projector` or `Processor`.
- So you don't have to define the group and run mode every time.
-
### Projector
You can create projections and read models with a subscriber.
@@ -73,17 +74,18 @@ final class ProfileProjector
}
}
```
-!!! warning
- PostgreSQL, MySQL and MariaDB don't support transactions for DDL statements.
- So you must use a different database connection for your subscriptions.
-
-!!! tip
+:::warning
+PostgreSQL, MySQL and MariaDB don't support transactions for DDL statements.
+So you must use a different database connection for your subscriptions.
+:::
+
+:::tip
+Add a version as suffix to the subscriber id
+so you can increment it when the subscription changes.
+Like `profile_1` to `profile_2`.
+:::
- Add a version as suffix to the subscriber id
- so you can increment it when the subscription changes.
- Like `profile_1` to `profile_2`.
-
### Processor
The other way to react to events is to take actions like sending an email, dispatch commands or change other aggregates.
@@ -147,11 +149,12 @@ final class DoStuffSubscriber
}
}
```
-!!! tip
- If you are using psalm then you can install the event sourcing [plugin](https://github.com/patchlevel/event-sourcing-psalm-plugin)
- to make the event method return the correct type.
-
+:::tip
+If you are using psalm then you can install the event sourcing [plugin](https://github.com/patchlevel/event-sourcing-psalm-plugin)
+to make the event method return the correct type.
+:::
+
### Subscribe all events
If you want to subscribe on all events, you can pass `*` or `Subscribe::ALL` instead of the event class.
@@ -269,10 +272,11 @@ final class PublicProfileProjection
// ... setup, teardown, ...
}
```
-!!! note
- More about reducers you can find [here](./message.md#reducer)
-
+:::note
+More about reducers you can find [here](message.md#reducer)
+:::
+
##### Recorded On Resolver
The recorded on resolver resolves the recorded on date.
@@ -325,23 +329,24 @@ final class ProfileProjector
}
}
```
-!!! danger
-
- PostgreSQL, MySQL and MariaDB don't support transactions for DDL statements.
- So you must use a different database connection in your projectors,
- otherwise you will get an error when the subscription tries to create the table.
-
-!!! warning
-
- If you change the subscriber id, you must also change the table/collection name.
- The subscription engine will create a new subscription with the new subscriber id.
- That means the setup method will be called again and the table/collection will conflict with the old existing projection.
-
-!!! note
-
- Most databases have a limit on the length of the table/collection name.
- The limit is usually 64 characters.
-
+
+:::danger
+PostgreSQL, MySQL and MariaDB don't support transactions for DDL statements.
+So you must use a different database connection in your projectors,
+otherwise you will get an error when the subscription tries to create the table.
+:::
+
+:::warning
+If you change the subscriber id, you must also change the table/collection name.
+The subscription engine will create a new subscription with the new subscriber id.
+That means the setup method will be called again and the table/collection will conflict with the old existing projection.
+:::
+
+:::note
+Most databases have a limit on the length of the table/collection name.
+The limit is usually 64 characters.
+:::
+
### Teardown
Subscribers can have one `teardown` method that is executed when the subscription is removed.
@@ -366,21 +371,22 @@ final class ProfileProjector
}
}
```
-!!! danger
- PostgreSQL, MySQL and MariaDB don't support transactions for DDL statements.
- So you must use a different database connection in your projectors,
- otherwise you will get an error when the subscription tries to create the table.
-
-!!! warning
+:::danger
+PostgreSQL, MySQL and MariaDB don't support transactions for DDL statements.
+So you must use a different database connection in your projectors,
+otherwise you will get an error when the subscription tries to create the table.
+:::
+
+:::warning
+A teardown can only be performed for a subscription if the code for the subscriber with that subscriber ID still exists.
+A another option is to use the `Cleanup` option.
+:::
- A teardown can only be performed for a subscription if the code for the subscriber with that subscriber ID still exists.
- A another option is to use the `Cleanup` option.
-
-!!! note
+:::note
+You can not mix the `cleanup` method with the `teardown` method.
+:::
- You can not mix the `cleanup` method with the `teardown` method.
-
### Cleanup
Alternativ, you can use a `cleanup` method for cleanup tasks.
@@ -409,10 +415,11 @@ final class ProfileProjector
}
}
```
-!!! note
- You can not mix the `cleanup` method with the `teardown` method.
-
+:::note
+You can not mix the `cleanup` method with the `teardown` method.
+:::
+
#### Dbal Cleanup Tasks
Default, we provide the following cleanup tasks for `doctrine/dbal`:
@@ -422,16 +429,16 @@ Default, we provide the following cleanup tasks for `doctrine/dbal`:
| `DropIndexTask` | Drops an index from a table. |
| `DropTableTask` | Drops a table. |
-!!! note
+:::note
+If you are passing connection registry, you can use the connection name as parameter.
+The `connectionName` parameter is optional and defaults to the default connection.
+:::
- If you are passing connection registry, you can use the connection name as parameter.
- The `connectionName` parameter is optional and defaults to the default connection.
-
-!!! tip
+:::tip
+You can create your own cleanup tasks and handler.
+For more information, see [Cleanup Handler](#cleanup-handler).
+:::
- You can create your own cleanup tasks and handler.
- For more information, see [Cleanup Handler](#cleanup-handler).
-
### On Failed
The subscription engine has a [retry strategy](#retry-strategy) to retry subscriptions that have an error.
@@ -463,14 +470,15 @@ final class InvoiceProcessor
}
}
```
-!!! warning
- Currently, the `OnFailed` method is only available for non-batchable subscribers.
-
-!!! note
+:::warning
+Currently, the `OnFailed` method is only available for non-batchable subscribers.
+:::
+
+:::note
+The `OnFailed` method is called after the retry strategy has decided that the subscription should be set to failed.
+:::
- The `OnFailed` method is called after the retry strategy has decided that the subscription should be set to failed.
-
### Versioning
As soon as the structure of a projection changes, or you need other events from the past,
@@ -486,17 +494,18 @@ final class ProfileSubscriber
// ...
}
```
-!!! warning
- If you change the `subscriberID`, you must also change the table/collection name.
- Otherwise the table/collection will conflict with the old subscription.
-
-!!! tip
+:::warning
+If you change the `subscriberID`, you must also change the table/collection name.
+Otherwise the table/collection will conflict with the old subscription.
+:::
+
+:::tip
+Add a version as suffix to the subscriber id
+so you can increment it when the subscription changes.
+Like `profile_1` to `profile_2`.
+:::
- Add a version as suffix to the subscriber id
- so you can increment it when the subscription changes.
- Like `profile_1` to `profile_2`.
-
### Grouping
You can also group subscribers together and filter them in the subscription engine.
@@ -512,14 +521,15 @@ final class ProfileSubscriber
// ...
}
```
-!!! note
-
- The different attributes has different default group.
-
- * `Subscriber` - `default`
- * `Projector` - `projector`
- * `Processor` - `processor`
-
+
+:::note
+The different attributes has different default group.
+
+* `Subscriber` - `default`
+* `Projector` - `projector`
+* `Processor` - `processor`
+:::
+
### Run Mode
The run mode determines how the subscriber should behave.
@@ -540,10 +550,11 @@ final class WelcomeEmailSubscriber
// ...
}
```
-!!! tip
- If you want create projections and run from the beginning, you can use the `Projector` attribute.
-
+:::tip
+If you want create projections and run from the beginning, you can use the `Projector` attribute.
+:::
+
#### From Now
Certain subscribers operate exclusively on post-release events, disregarding historical data.
@@ -560,10 +571,11 @@ final class WelcomeEmailSubscriber
// ...
}
```
-!!! tip
- If you want process events from now, you can use the `Processor` attribute.
-
+:::tip
+If you want process events from now, you can use the `Processor` attribute.
+:::
+
#### Once
This mode is useful for subscribers that only need to run once.
@@ -682,21 +694,21 @@ The method `forceCommit` is called after each handled event,
and you can decide whether the batch commit process should start now.
This helps to determine the batch size and thus avoid memory overflow.
-!!! danger
+:::danger
+Make sure to fully process the data in `commitBatch` and close any open transactions.
+Otherwise, it may lead to inconsistent data.
+:::
- Make sure to fully process the data in `commitBatch` and close any open transactions.
- Otherwise, it may lead to inconsistent data.
-
-!!! note
+:::note
+The position of the subscriber is only updated after a successful commit.
+In case of an error, the position remains at the state before the batch started.
+:::
- The position of the subscriber is only updated after a successful commit.
- In case of an error, the position remains at the state before the batch started.
-
-!!! tip
+:::tip
+Use `forceCommit` to prevent memory leaks.
+This allows you to decide when it's suitable to process the data and then release the memory.
+:::
- Use `forceCommit` to prevent memory leaks.
- This allows you to decide when it's suitable to process the data and then release the memory.
-
## Subscription Engine
The subscription engine manages individual subscribers and keeps the subscriptions running.
@@ -706,13 +718,13 @@ and keeping all subscriptions up to date.
He also takes care that new subscribers are booted and old ones are removed again.
If something breaks, the subscription engine marks the individual subscriptions as faulty and retries them.
-!!! tip
+:::tip
+The Subscription Engine was inspired by the following two blog posts:
+
+* [Projection Building Blocks: What you'll need to build projections](https://barryosull.com/blog/projection-building-blocks-what-you-ll-need-to-build-projections/)
+* [Managing projectors is harder than you think](https://barryosull.com/blog/managing-projectors-is-harder-than-you-think/)
+:::
- The Subscription Engine was inspired by the following two blog posts:
-
- * [Projection Building Blocks: What you'll need to build projections](https://barryosull.com/blog/projection-building-blocks-what-you-ll-need-to-build-projections/)
- * [Managing projectors is harder than you think](https://barryosull.com/blog/managing-projectors-is-harder-than-you-think/)
-
## Subscription ID
The subscription ID is taken from the associated subscriber and corresponds to the subscriber ID.
@@ -841,10 +853,10 @@ The subscription engine needs a message loader to load the messages.
We provide two implementations by default.
Which one has a better performance depends on the use case.
-!!! tip
+:::tip
+We recommend the `GapResolverStoreMessageLoader` as it handles gaps in the stream.
+:::
- We recommend the `GapResolverStoreMessageLoader` as it handles gaps in the stream.
-
#### Store Message Loader
The store message loader loads all the messages from the event store.
@@ -941,10 +953,11 @@ $schemaDirector = new DoctrineSchemaDirector(
]),
);
```
-!!! note
- You can find more about schema configurator [here](./store.md)
-
+:::note
+You can find more about schema configurator [here](store.md)
+:::
+
### Retry Strategy
The subscription engine uses a retry strategy to retry subscriptions that have an error.
@@ -999,14 +1012,15 @@ $retryStrategyRepository = new RetryStrategyRepository([
'no_retry' => new NoRetryStrategy(),
]);
```
-!!! note
- This is what our default configuration looks like if you do not define the retry strategy.
-
-!!! tip
+:::note
+This is what our default configuration looks like if you do not define the retry strategy.
+:::
+
+:::tip
+You can change the default retry strategy by define the name in the constructor as second parameter.
+:::
- You can change the default retry strategy by define the name in the constructor as second parameter.
-
### Cleanup Handler
You can also create your own cleanup tasks with associated handlers.
@@ -1022,10 +1036,11 @@ final class DropCollection
}
}
```
-!!! warning
- The task class must be serializable. It will be stored in the subscription store.
-
+:::warning
+The task class must be serializable. It will be stored in the subscription store.
+:::
+
The next step is to create a handler for the task.
The handler must implement the `CleanupHandler` interface.
@@ -1065,10 +1080,11 @@ $cleaner = new DefaultCleaner([
new MongodbCleanupTaskHandler($mongodbDatabase),
]);
```
-!!! warning
- You need to pass the Cleaner to the Subscription Engine.
-
+:::warning
+You need to pass the Cleaner to the Subscription Engine.
+:::
+
#### Dbal Cleanup Task Handler
We provide a Dbal cleanup task handler by default.
@@ -1166,10 +1182,11 @@ use Patchlevel\EventSourcing\Subscription\Engine\SubscriptionEngine;
/** @var SubscriptionEngine $subscriptionEngine */
$catchupSubscriptionEngine = new CatchUpSubscriptionEngine($subscriptionEngine);
```
-!!! tip
- You can use the `CatchUpSubscriptionEngine` in your tests to process the events immediately.
-
+:::tip
+You can use the `CatchUpSubscriptionEngine` in your tests to process the events immediately.
+:::
+
### Throw on error Subscription Engine
This is another decorator for the subscription engine. It throws an exception if a subscription is in error state.
@@ -1182,11 +1199,12 @@ use Patchlevel\EventSourcing\Subscription\Engine\ThrowOnErrorSubscriptionEngine;
/** @var SubscriptionEngine $subscriptionEngine */
$throwOnErrorSubscriptionEngine = new ThrowOnErrorSubscriptionEngine($subscriptionEngine);
```
-!!! warning
- This is only for testing or development. Don't use it in production.
- The subscription engine has an build in retry strategy to retry subscriptions that have failed.
-
+:::warning
+This is only for testing or development. Don't use it in production.
+The subscription engine has an build in retry strategy to retry subscriptions that have failed.
+:::
+
### Run Subscription Engine after save
You can trigger the subscription engine after calling the `save` method on the repository.
@@ -1209,21 +1227,22 @@ $eventBus = new RunSubscriptionEngineRepositoryManager(
100, // limit the number of messages
);
```
-!!! danger
- By using this, you can't wrap the repository in a transaction.
- A rollback is not supported and can break the subscription engine.
- Internally, the events are saved in a transaction to ensure data consistency.
-
-!!! note
+:::danger
+By using this, you can't wrap the repository in a transaction.
+A rollback is not supported and can break the subscription engine.
+Internally, the events are saved in a transaction to ensure data consistency.
+:::
- More about repository manager and repository can be found [here](./repository.md).
-
-!!! tip
+:::note
+More about repository manager and repository can be found [here](repository.md).
+:::
+
+:::tip
+You can perfectly use it in development or testing.
+Especially in combination with the `CatchUpSubscriptionEngine` and `ThrowOnErrorSubscriptionEngine` decorators.
+:::
- You can perfectly use it in development or testing.
- Especially in combination with the `CatchUpSubscriptionEngine` and `ThrowOnErrorSubscriptionEngine` decorators.
-
## Usage
The Subscription Engine has a few methods needed to use it effectively.
@@ -1237,10 +1256,11 @@ $criteria = new SubscriptionEngineCriteria(
groups: ['default'],
);
```
-!!! note
- An `OR` check is made for the respective criteria and all criteria are checked with an `AND`.
-
+:::note
+An `OR` check is made for the respective criteria and all criteria are checked with an `AND`.
+:::
+
### Setup
New subscriptions need to be set up before they can be used.
@@ -1254,10 +1274,11 @@ use Patchlevel\EventSourcing\Subscription\Engine\SubscriptionEngineCriteria;
/** @var SubscriptionEngine $subscriptionEngine */
$subscriptionEngine->setup(new SubscriptionEngineCriteria());
```
-!!! tip
- You can skip the booting step with the second boolean parameter named `skipBooting`.
-
+:::tip
+You can skip the booting step with the second boolean parameter named `skipBooting`.
+:::
+
### Boot
You can boot the subscriptions with the `boot` method.
@@ -1361,6 +1382,6 @@ $subscriptionEngine->refresh(new SubscriptionEngineCriteria());
```
## Learn more
-* [How to use CLI commands](./cli.md)
-* [How to create Messages](./message.md)
-* [How to Test](./testing.md)
+* [How to use CLI commands](cli.md)
+* [How to create Messages](message.md)
+* [How to Test](testing.md)
diff --git a/docs/pages/supported-versions.md b/docs/supported-versions.md
similarity index 92%
rename from docs/pages/supported-versions.md
rename to docs/supported-versions.md
index 76d7b4c04..e4c8c8aa6 100644
--- a/docs/pages/supported-versions.md
+++ b/docs/supported-versions.md
@@ -17,4 +17,4 @@ not be used anymore.
## Versioning
For more information about our versioning you should
-read [our backward compatibility promise](./our-backward-compatibility-promise.md).
+read [our backward compatibility promise](our-backward-compatibility-promise.md).
diff --git a/docs/pages/testing.md b/docs/testing.md
similarity index 95%
rename from docs/pages/testing.md
rename to docs/testing.md
index 55182ad7b..39a8fc860 100644
--- a/docs/pages/testing.md
+++ b/docs/testing.md
@@ -209,14 +209,15 @@ final class ProfileTest extends AggregateRootTestCase
}
}
```
-!!! note
- You can find out more about the clock [here](clock.md).
-
-!!! tip
+:::note
+You can find out more about the clock [here](clock.md).
+:::
+
+:::tip
+You can use the FreezeClock in you integration tests to test the time-based behavior of your application.
+:::
- You can use the FreezeClock in you integration tests to test the time-based behavior of your application.
-
## Tests with UUID
Uuids are randomly generated and can be a problem in tests.
@@ -241,8 +242,8 @@ final class ProfileTest extends TestCase
}
}
```
-!!! warning
- The `IncrementalRamseyUuidFactory` is only for testing purposes
- and supports only the version 7 what is used by the library.
-
+:::warning
+The `IncrementalRamseyUuidFactory` is only for testing purposes
+and supports only the version 7 what is used by the library.
+:::
\ No newline at end of file
diff --git a/docs/pages/upcasting.md b/docs/upcasting.md
similarity index 93%
rename from docs/pages/upcasting.md
rename to docs/upcasting.md
index 602ac577a..9042996d9 100644
--- a/docs/pages/upcasting.md
+++ b/docs/upcasting.md
@@ -34,10 +34,11 @@ final class ProfileCreatedEmailLowerCastUpcaster implements Upcaster
}
}
```
-!!! warning
- You need to consider that other events are passed to the Upcaster. So and early out is here endorsed.
-
+:::warning
+You need to consider that other events are passed to the Upcaster. So and early out is here endorsed.
+:::
+
## Adjust event name
Sometimes your event name was not the best choice and you want to change it.
@@ -65,10 +66,11 @@ final class EventNameRenameUpcaster implements Upcaster
}
}
```
-!!! tip
- Events can also have [aliases](./events.md#alias). This is usually sufficient.
-
+:::tip
+Events can also have [aliases](events.md#alias). This is usually sufficient.
+:::
+
## Configure
After we have defined the upcasting rules, we also have to pass the whole thing to the serializer.