From 5c566843c45b9d11b3c066e8a2796475d295da7c Mon Sep 17 00:00:00 2001 From: David Badura Date: Fri, 12 Jun 2026 19:36:59 +0200 Subject: [PATCH] revised docs --- docs/aggregate-id.md | 9 +++--- docs/aggregate.md | 30 +++++++++--------- docs/cli.md | 5 +-- docs/clock.md | 6 ++-- docs/command-bus.md | 12 +++----- docs/event-bus.md | 4 +-- docs/events.md | 2 +- docs/getting-started.md | 22 +++++++------- docs/index.md | 2 +- docs/message-decorator.md | 10 +++--- docs/message.md | 13 ++++---- docs/normalizer.md | 34 ++++++++++----------- docs/personal-data.md | 8 +++-- docs/query-bus.md | 15 +++++---- docs/repository.md | 15 +++++---- docs/snapshots.md | 18 +++++------ docs/split-stream.md | 10 +++--- docs/store.md | 41 +++++++++++++------------ docs/subscription.md | 64 ++++++++++++++++++++++----------------- docs/testing.md | 18 +++++++---- docs/upcasting.md | 2 +- 21 files changed, 179 insertions(+), 161 deletions(-) diff --git a/docs/aggregate-id.md b/docs/aggregate-id.md index 5b220c57..8d06b688 100644 --- a/docs/aggregate-id.md +++ b/docs/aggregate-id.md @@ -8,7 +8,7 @@ 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 -Performance reasons, the default configuration of the store require an uuid string for `aggregate id`. +For performance reasons, the default configuration of the store requires an uuid string for the `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. ::: @@ -43,7 +43,7 @@ $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). +More information about [uuid versions](https://uuid.ramsey.dev/en/stable/rfc4122.html) can be found in the ramsey/uuid documentation. ::: ## Custom ID @@ -66,8 +66,7 @@ final class Profile extends BasicAggregateRoot ``` :::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). +you need to change the `aggregate_id_type` to `string` in the [store](store.md) configuration. ::: So you can use any string as an id: @@ -141,7 +140,7 @@ class ProfileId implements AggregateRootId use CustomIdBehaviour; } ``` -### Learn more +## Learn more * [How to create an aggregate](aggregate.md) * [How to create an event](events.md) diff --git a/docs/aggregate.md b/docs/aggregate.md index 49f7eda4..e93e2697 100644 --- a/docs/aggregate.md +++ b/docs/aggregate.md @@ -6,7 +6,7 @@ This means it is always possible to build the current state again from the event :::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). +You can find out more about aggregates in [Martin Fowler's article about the DDD Aggregate pattern](https://martinfowler.com/bliki/DDD_Aggregate.html). ::: An aggregate must fulfill a few points so that we can use it in event-sourcing: @@ -14,7 +14,7 @@ An aggregate must fulfill a few points so that we can use it in event-sourcing: * It must implement the `AggregateRoot` interface. * It needs a unique identifier. * It needs to provide the current playhead. -* It must make changes to his state available as events. +* It must make changes to its state available as events. * And rebuild/catchup its state from the events. We can implement this ourselves, or use the `BasicAggregateRoot` implementation that already brings everything with it. @@ -49,7 +49,7 @@ The aggregate is not yet finished and has only been built to the point that you ::: :::tip -Find out more about aggregate IDs [here](aggregate-id.md). +Find out more about [aggregate IDs](aggregate-id.md). ::: We use a so-called named constructor here to create an object of the AggregateRoot. @@ -108,7 +108,7 @@ final class ProfileRegistered } ``` :::note -You can find out more about events [here](events.md). +You can find out more about [events](events.md). ::: After we have defined the event, we have to adapt the profile aggregate: @@ -262,7 +262,7 @@ Our aggregate can now be changed and saved. ::: :::note -You can read more about Repository [here](repository.md). +You can read more about the [repository](repository.md). ::: Here the aggregate is loaded from the `repository` by fetching all events from the database. @@ -313,7 +313,7 @@ if you define the event types in the method using a union type. Sometimes you have events that do not change the state of the aggregate itself, but are still recorded for the future or to subscribe for processor and projection. So that you are not forced to write an apply method for it, -you can suppress the missing apply exceptions these events with the `SuppressMissingApply` attribute. +you can suppress the missing apply exceptions for these events with the `SuppressMissingApply` attribute. ```php use Patchlevel\EventSourcing\Aggregate\BasicAggregateRoot; @@ -366,8 +366,8 @@ When all events are suppressed, debugging becomes more difficult if you forget a ## Shared apply context 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. +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. The `SharedApplyContext` attribute allows you to declare that several aggregates share the same apply context. With this configuration, a missing apply is only reported if none of the shared aggregates handle the event. @@ -435,7 +435,7 @@ final class GuestList extends BasicAggregateRoot } ``` :::tip -You can find more about splitting aggregates [here](aggregate.md#splitting-aggregates). +You can find more about [splitting aggregates](aggregate.md#splitting-aggregates). ::: ## Business rules @@ -463,7 +463,7 @@ final class Profile extends BasicAggregateRoot public function changeName(string $name): void { if (strlen($name) < 3) { - throw new NameIsToShortException($name); + throw new NameIsTooShortException($name); } $this->recordThat(new NameChanged($name)); @@ -493,7 +493,7 @@ final class Name public function __construct(private string $value) { if (strlen($value) < 3) { - throw new NameIsToShortException($value); + throw new NameIsTooShortException($value); } } @@ -568,7 +568,7 @@ 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](normalizer.md). ::: There are also cases where business rules have to be defined depending on the aggregate state. @@ -623,7 +623,7 @@ final class Hotel extends BasicAggregateRoot An aggregate should always be deterministic. In other words, whenever I execute methods on the aggregate, I always get the same result. This also makes testing much easier. -But that often doesn't seem to be possible, e.g. if you want to save a createAt date. +But that often doesn't seem to be possible, e.g. if you want to save a createdAt date. But you can pass this information by yourself. ```php @@ -683,7 +683,7 @@ Now you can pass the `SystemClock` to determine the current time. Or for test purposes the `FrozenClock`, which always returns the same time. :::note -You can find out more about clock [here](clock.md). +You can find out more about the [clock](clock.md). ::: ## Splitting Aggregates @@ -834,7 +834,7 @@ 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. +Suppressing missing apply methods needs to be defined in the root aggregate. ::: And the `Order` aggregate root looks like this: diff --git a/docs/cli.md b/docs/cli.md index 8c6dff40..832f4c67 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -33,7 +33,8 @@ To manage your subscriptions there are the following cli commands. * SubscriptionBootCommand: `event-sourcing:subscription:boot` * SubscriptionPauseCommand: `event-sourcing:subscription:pause` -* SubscriptionReactiveCommand: `event-sourcing:subscription:reactive` +* SubscriptionReactivateCommand: `event-sourcing:subscription:reactivate` +* SubscriptionRefreshCommand: `event-sourcing:subscription:refresh` * SubscriptionRemoveCommand: `event-sourcing:subscription:remove` * SubscriptionRunCommand: `event-sourcing:subscription:run` * SubscriptionSetupCommand: `event-sourcing:subscription:setup` @@ -41,7 +42,7 @@ To manage your subscriptions there are the following cli commands. * SubscriptionTeardownCommand: `event-sourcing:subscription:teardown` :::note -You can find out more about subscriptions [here](subscription.md). +You can find out more about [subscriptions](subscription.md). ::: ## Inspector commands diff --git a/docs/clock.md b/docs/clock.md index ee99fc14..4a8884f6 100644 --- a/docs/clock.md +++ b/docs/clock.md @@ -2,8 +2,8 @@ We are using the clock to get the current datetime. This is needed to create the `recorded_on` datetime for the event stream. We have two implementations of the clock, one for the production and one for the tests. -But you can also create your own implementation that is PSR-20 compatible. -For more information see [here](https://github.com/php-fig/fig-standards/blob/master/proposed/clock.md). +But you can also create your own implementation that is compatible with the +[PSR-20 clock specification](https://www.php-fig.org/psr/psr-20/). ## SystemClock @@ -62,7 +62,7 @@ $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. +The instance of the frozen datetime will be cloned internally, so it's not the same instance but equal. ::: ## Learn more diff --git a/docs/command-bus.md b/docs/command-bus.md index 9560cbc1..36e12ec3 100644 --- a/docs/command-bus.md +++ b/docs/command-bus.md @@ -137,7 +137,7 @@ final class Profile extends BasicAggregateRoot } ``` :::tip -You can find more information about aggregates [here](aggregate.md). +You can find more information about [aggregates](aggregate.md). ::: #### Update Aggregate @@ -178,10 +178,6 @@ final class Profile extends BasicAggregateRoot #[Handle] public function changeName(ChangeProfileName $command): void { - if (!$nameValidator($command->name)) { - throw new InvalidArgument(); - } - $this->recordThat(new NameChanged($command->name)); } @@ -197,7 +193,7 @@ you can use the [Auto Initialize](aggregate.md#auto-initialize) feature. You can inject services into aggregate handler methods. Starting with the second parameter, it automatically tries to inject the service using a service locator. -Standard, it uses the fully qualified class name from the parameter type hint to find the service. +By default, it uses the fully qualified class name from the parameter type hint to find the service. ```php use Patchlevel\EventSourcing\Aggregate\BasicAggregateRoot; @@ -296,7 +292,7 @@ use Patchlevel\EventSourcing\CommandBus\CommandBus; use Patchlevel\EventSourcing\CommandBus\InstantRetryCommandBus; use Patchlevel\EventSourcing\Repository\AggregateOutdated; -/** @var CommandBus $store */ +/** @var CommandBus $commandBus */ $commandBus = new InstantRetryCommandBus( $commandBus, 3, // maximum number of retries, default is 3 @@ -339,7 +335,7 @@ There are different types of providers that you can use to register handlers. ### Service Handler Provider -The classically way to handle commands is to use services. +The classic way to handle commands is to use services. The `ServiceHandlerProvider` is used to handle commands by invoking methods on services. ```php diff --git a/docs/event-bus.md b/docs/event-bus.md index e27af201..f29a0ca5 100644 --- a/docs/event-bus.md +++ b/docs/event-bus.md @@ -79,7 +79,7 @@ $listenerProvider = new class implements ListenerProvider { }; ``` :::tip -You can use `$listenerDiscriptor->name()` to get the name of the listener. +You can use `$listenerDescriptor->name()` to get the name of the listener. ::: ## Listener @@ -142,4 +142,4 @@ You can't use the `Subscribe` attribute with the psr-14 event bus. * [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 decorate messages](message-decorator.md) diff --git a/docs/events.md b/docs/events.md index 41e6b334..6979adbe 100644 --- a/docs/events.md +++ b/docs/events.md @@ -114,7 +114,7 @@ and so you don't have to specify them. If you want to configure the Normalizer, ::: :::note -You can find out more about normalizer [here](normalizer.md). +You can find out more about [normalizer](normalizer.md). ::: ## Event Registry diff --git a/docs/getting-started.md b/docs/getting-started.md index f152e2b6..1dceccf8 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -7,7 +7,7 @@ We keep the example small, so we can only create hotels and let guests check in First we define the events that happen in our system. -A hotel can be created with a `name` and a `id`: +A hotel can be created with a `name` and an `id`: ```php use Patchlevel\EventSourcing\Aggregate\Uuid; @@ -56,7 +56,7 @@ final class GuestIsCheckedOut } ``` :::note -You can find out more about events [here](events.md). +You can find out more about [events](events.md). ::: ## Define aggregates @@ -148,7 +148,7 @@ final class Hotel extends BasicAggregateRoot } ``` :::note -You can find out more about aggregates [here](aggregate.md). +You can find out more about [aggregates](aggregate.md). ::: ## Define projections @@ -178,7 +178,7 @@ final class HotelProjector /** @return list */ public function getHotels(): array { - return $this->db->fetchAllAssociative(sprintf('SELECT id, name, guests FROM %s;'), self::TABLE); + return $this->db->fetchAllAssociative(sprintf('SELECT id, name, guests FROM %s;', self::TABLE)); } #[Subscribe(HotelCreated::class)] @@ -226,7 +226,7 @@ final class HotelProjector } ``` :::note -You can find out more about projector [here](subscription.md). +You can find out more about [projectors](subscription.md). ::: ## Processor @@ -257,7 +257,7 @@ final class SendCheckInEmailProcessor } ``` :::note -You can find out more about processor [here](subscription.md). +You can find out more about [processors](subscription.md). ::: ## Configuration @@ -326,7 +326,7 @@ $repositoryManager = new RunSubscriptionEngineRepositoryManager( $hotelRepository = $repositoryManager->get(Hotel::class); ``` :::note -You can find out more about stores [here](store.md). +You can find out more about [stores](store.md). ::: :::note @@ -334,7 +334,7 @@ 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). +Learn more about the [subscription engine](subscription.md). ::: ## Database setup @@ -369,7 +369,7 @@ $schemaDirector->create(); $engine->setup(skipBooting: true); ``` :::note -you can use the predefined [cli commands](cli.md) for this. +You can use the predefined [cli commands](cli.md) for this. ::: ## Usage @@ -392,11 +392,11 @@ $hotel2 = $hotelRepository->load(Uuid::fromString('d0d0d0d0-d0d0-d0d0-d0d0-d0d0d $hotel2->checkIn('David'); $hotelRepository->save($hotel2); -$hotels = $hotelProjection->getHotels(); +$hotels = $hotelProjector->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). +You can find more about this in the [aggregate id](aggregate-id.md) documentation. ::: ## Result diff --git a/docs/index.md b/docs/index.md index 582e9d96..b75d1513 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,7 +13,7 @@ powered by the reliable Doctrine ecosystem and focused on developer experience. * Versioned and managed lifecycle of [subscriptions](subscription.md) like projections and processors * 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) +* Simple setup with [schema management](store.md) and [doctrine migration](store.md) * Built in [cli commands](cli.md) with [symfony](https://symfony.com/) * and much more... diff --git a/docs/message-decorator.md b/docs/message-decorator.md index 52b5c56f..bac58e80 100644 --- a/docs/message-decorator.md +++ b/docs/message-decorator.md @@ -67,13 +67,13 @@ $repositoryManager = new DefaultRepositoryManager( $repository = $repositoryManager->get(Profile::class); ``` :::note -You can find out more about repository [here](repository.md). +You can find out more about the [repository](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` -to add data `withHeader` and to read this data later on `header`. +You can also use this feature to add your own metadata to your events. For this, the `Message` has extra methods: +`withHeader` to add data and `header` to read this data later on. ```php use Patchlevel\EventSourcing\Attribute\Header; @@ -98,11 +98,11 @@ final class OnSystemRecordedDecorator implements MessageDecorator } ``` :::note -The Message is immutable, for more information look up [here](message.md). +The message is immutable, more information can be found in the [message](message.md) documentation. ::: :::tip -You can also set multiple headers with `withHeaders` which expects an hashmap. +You can also set multiple headers with `withHeaders` which expects a list of headers. ::: ## Learn more diff --git a/docs/message.md b/docs/message.md index b93f457a..3df8f312 100644 --- a/docs/message.md +++ b/docs/message.md @@ -160,7 +160,7 @@ $translator = new FilterEventTranslator(static function (object $event) { ``` ### Exclude Events with Header -With this translator you can exclude event with specific header. +With this translator you can exclude events with a specific header. ```php use Patchlevel\EventSourcing\Message\Translator\ExcludeEventWithHeaderTranslator; @@ -174,6 +174,7 @@ With this translator you can only allow events with a specific header. ```php use Patchlevel\EventSourcing\Message\Translator\IncludeEventWithHeaderTranslator; +use Patchlevel\EventSourcing\Store\ArchivedHeader; $translator = new IncludeEventWithHeaderTranslator(ArchivedHeader::class); ``` @@ -204,7 +205,7 @@ $translator = new UntilEventTranslator(new DateTimeImmutable('2020-01-01 12:00:0 This translator can be used to recalculate the playhead. The playhead must always be in ascending order so that the data is valid. -Some translator can break this order and the translator `RecalculatePlayheadTranslator` can fix this problem. +Some translators can break this order and the `RecalculatePlayheadTranslator` can fix this problem. ```php use Patchlevel\EventSourcing\Message\Translator\RecalculatePlayheadTranslator; @@ -212,7 +213,7 @@ use Patchlevel\EventSourcing\Message\Translator\RecalculatePlayheadTranslator; $translator = new RecalculatePlayheadTranslator(); ``` :::warning -The `RecalculatePlayheadTranslator` is and need to be stateful. +The `RecalculatePlayheadTranslator` is stateful and needs to be. You can't reuse the translator for multiple streams. ::: @@ -239,7 +240,7 @@ $translator = new ChainTranslator([ You can also write a custom translator. The translator gets a message and can return `n` messages. There are the following possibilities: -* Return only the message to an array to leave it unchanged. +* Return only the message in an array to leave it unchanged. * Put another message in the array to swap the message. * Return an empty array to remove the message. * Or return multiple messages to enrich the stream. @@ -299,7 +300,7 @@ The initial state is the state that is used at the beginning of the reduction. use Patchlevel\EventSourcing\Message\Reducer; $state = (new Reducer()) - ->initialState(['count' => 0]) + ->initState(['count' => 0]) ->reduce($messages); // state is ['count' => 0] ``` ### When @@ -312,7 +313,7 @@ use Patchlevel\EventSourcing\Message\Message; use Patchlevel\EventSourcing\Message\Reducer; $state = (new Reducer()) - ->initialState([ + ->initState([ 'names' => [], ]) ->when( diff --git a/docs/normalizer.md b/docs/normalizer.md index 1d7b6f17..bbd90669 100644 --- a/docs/normalizer.md +++ b/docs/normalizer.md @@ -5,14 +5,14 @@ For example DateTime, enums or value objects. Here you can use the normalizer to define how the data should be saved and loaded. :::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 exists as a separate library. +You can find out more details in the [hydrator](https://github.com/patchlevel/hydrator) documentation. ::: ## Usage You have a lot of options to use the normalizer. -First of all and simplest, you can let guess the normalizer from the type hint. +First of all and simplest, you can let the hydrator guess the normalizer from the type hint. ```php final class DTO @@ -76,7 +76,7 @@ final class Item } ``` :::note -With the `ObjectNormalizer`, you can seraialize and deserialize recursively. +With the `ObjectNormalizer`, you can serialize and deserialize recursively. ::: ### Event @@ -88,13 +88,13 @@ The whole thing is then loaded again from the DB and denormalized in the propert use Patchlevel\EventSourcing\Attribute\Event; use Patchlevel\Hydrator\Normalizer\DateTimeImmutableNormalizer; -#[Event('hotel.create')] -final class CreateHotel +#[Event('hotel.created')] +final class HotelCreated { public function __construct( public readonly string $name, #[DateTimeImmutableNormalizer] - public readonly DateTimeImmutable $createAt, + public readonly DateTimeImmutable $createdAt, ) { } } @@ -120,24 +120,24 @@ final class Hotel extends BasicAggregateRoot { private string $name; #[DateTimeImmutableNormalizer] - private DateTimeImmutable $createAt; + private DateTimeImmutable $createdAt; // ... } ``` :::note -You can learn more about snapshots [here](snapshots.md). +You can learn more about [snapshots](snapshots.md). ::: ## Built-in Normalizer -For some the standard cases we already offer built-in normalizers. +For some standard cases we already offer built-in normalizers. ### Array If you have a list of objects that you want to normalize, then you must normalize each object individually. That's what the `ArrayNormalizer` does for you. -In order to use the `ArrayNormaliser`, you still have to specify which normaliser should be applied to the individual +In order to use the `ArrayNormalizer`, you still have to specify which normalizer should be applied to the individual objects. Internally, it basically does an `array_map` and then runs the specified normalizer on each element. ```php @@ -190,7 +190,7 @@ You can read about how the format is structured in the [php docs](https://www.ph ### DateTime -The `DateTime` Normalizer works exactly like the DateTimeNormalizer. Only for DateTime objects. +The `DateTimeNormalizer` works exactly like the `DateTimeImmutableNormalizer`. Only for DateTime objects. ```php use Patchlevel\Hydrator\Normalizer\DateTimeNormalizer; @@ -288,7 +288,7 @@ final class DTO You can let the hydrator guess the normalizer from the type hint. ::: -Optional you can also define the type of the id. +Optionally you can also define the type of the id. ```php use Patchlevel\EventSourcing\Aggregate\Uuid; @@ -314,7 +314,7 @@ final class DTO public ComplexObject $object; } ``` -Optional you can also define the type of the object. +Optionally you can also define the type of the object. ```php use Patchlevel\Hydrator\Normalizer\ObjectNormalizer; @@ -338,7 +338,7 @@ final class Name public function __construct(private string $value) { if (strlen($value) < 3) { - throw new NameIsToShortException($value); + throw new NameIsTooShortException($value); } } @@ -431,13 +431,13 @@ The whole thing looks like this } ``` :::tip -You can also rename properties to events without having a backwards compatibility break by keeping the serialized name. +You can also rename properties in 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 +if you have a backwards compatibility break in the property name. ::: ## Ignore diff --git a/docs/personal-data.md b/docs/personal-data.md index 4f5c5dc1..a080eab4 100644 --- a/docs/personal-data.md +++ b/docs/personal-data.md @@ -72,6 +72,8 @@ The default fallback value is `null`. You can change this by setting the `fallback` parameter or using the `fallbackCallable` parameter. ```php +use Patchlevel\EventSourcing\Aggregate\Uuid; +use Patchlevel\Hydrator\Attribute\DataSubjectId; use Patchlevel\Hydrator\Attribute\PersonalData; final class ProfileChanged @@ -143,8 +145,8 @@ $schemaDirector = new DoctrineSchemaDirector( Now we have to put the whole thing together in a Personal Data Payload Cryptographer. ```php -use Patchlevel\EventSourcing\Cryptography\Store\CipherKeyStore; use Patchlevel\Hydrator\Cryptography\PersonalDataPayloadCryptographer; +use Patchlevel\Hydrator\Cryptography\Store\CipherKeyStore; /** @var CipherKeyStore $cipherKeyStore */ $cryptographer = PersonalDataPayloadCryptographer::createWithDefaultSettings($cipherKeyStore); @@ -168,7 +170,7 @@ DefaultEventSerializer::createFromPaths( ); ``` :::note -More information about the events can be found [here](events.md). +More information can be found in the [events](events.md) documentation. ::: ### Snapshot Store Integration @@ -188,7 +190,7 @@ $snapshotStore = DefaultSnapshotStore::createDefault( ); ``` :::note -More information about the snapshot store can be found [here](snapshots.md). +More information can be found in the [snapshots](snapshots.md) documentation. ::: :::success diff --git a/docs/query-bus.md b/docs/query-bus.md index 9f61c537..ae131b52 100644 --- a/docs/query-bus.md +++ b/docs/query-bus.md @@ -1,13 +1,13 @@ # Query Bus The Query Bus is another optional component in the Event Sourcing library that coordinates the data flow in the system. -Unlike the command bus, the query bus intention is not to perform actions on the system but instead retrieve information -from the system. It allows to fully utilize the read write split and the usage of small, independent and tailored +Unlike the command bus, the query bus's intention is not to perform actions on the system but instead retrieve information +from the system. It allows you to fully utilize the read write split and the usage of small, independent and tailored projections. ## Query -First, you need to create a simple data transfer object which will be our query. It represent our intention to retrieve +First, you need to create a simple data transfer object which will be our query. It represents our intention to retrieve data from the system. ```php @@ -26,6 +26,8 @@ The next step is to create a handler which has a method which can handle the que data. ```php +use Patchlevel\EventSourcing\Attribute\Answer; + final class QueryProfileHandler { #[Answer] @@ -44,7 +46,7 @@ To use Service Handler you need to register the handler in the `ServiceHandlerPr ::: :::tip -A class can have multiple methods which answers different queries. +A class can have multiple methods which answer different queries. ::: ### Projector @@ -53,6 +55,7 @@ Another way to handle queries is to answer them directly in the corresponding pr as when using a dedicated class. The method which should handle the query will be marked with the `#[Answer]` attribute. ```php +use Patchlevel\EventSourcing\Attribute\Answer; use Patchlevel\EventSourcing\Attribute\Projector; #[Projector('profiles')] @@ -68,8 +71,8 @@ final class ProfileProjector } ``` :::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. +Using small dedicated projections for each usecase is best practice. Using them directly as query handlers is +endorsed and can reduce fragmentation of the system. ::: ## Setup diff --git a/docs/repository.md b/docs/repository.md index 9cbd8a2b..79deb558 100644 --- a/docs/repository.md +++ b/docs/repository.md @@ -1,7 +1,7 @@ # Repository A `repository` takes care of storing and loading the `aggregates`. -He is also responsible for building [messages](message.md) from the events +It is also responsible for building [messages](message.md) from the events and optionally dispatching them to the event bus. ## Create a repository @@ -66,13 +66,12 @@ You may encounter [at least once](https://softwaremill.com/message-delivery-and- ::: :::note -You can find out more about event bus [here](event-bus.md). +You can find out more about the [event bus](event-bus.md). ::: :::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). +that's why we recommend the [subscription engine](subscription.md). ::: ### Snapshots @@ -106,7 +105,7 @@ $repositoryManager = new DefaultRepositoryManager( $repository = $repositoryManager->get(Profile::class); ``` :::note -You can find out more about snapshots [here](snapshots.md). +You can find out more about [snapshots](snapshots.md). ::: ### Decorator @@ -135,7 +134,7 @@ $repositoryManager = new DefaultRepositoryManager( $repository = $repositoryManager->get(Profile::class); ``` :::note -You can find out more about message decorator [here](message-decorator.md). +You can find out more about the [message decorator](message-decorator.md). ::: :::tip @@ -151,7 +150,7 @@ saving it or checking whether it exists. An `aggregate` can be `saved`. All new events that have not yet been written to the database are fetched from the aggregate. -These events are then also append to the database. +These events are then appended to the database. ```php use Patchlevel\EventSourcing\Aggregate\Uuid; @@ -227,7 +226,7 @@ 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. +This means that the state of the aggregate is not rebuilt either. ::: ## Custom Repository diff --git a/docs/snapshots.md b/docs/snapshots.md index 3fd1d25b..4cc5da18 100644 --- a/docs/snapshots.md +++ b/docs/snapshots.md @@ -6,7 +6,7 @@ But if the number gets bigger at some point, then loading and rebuilding can bec The `snapshot` system can be used to control this. :::tip -Use snapshots only if you have a performance problems, +Use snapshots only if you have a performance problem, because it introduces additional complexity. In our benchmarks we can load 10 000 events for one aggregate in 50ms. @@ -56,7 +56,7 @@ $repositoryManager = new DefaultRepositoryManager( ); ``` :::note -You can read more about Repository [here](repository.md). +You can read more about the [repository](repository.md). ::: Next we need to tell the Aggregate to take a snapshot of it. We do this using the snapshot attribute. @@ -105,12 +105,12 @@ Or the snapshot version needs to be changed so that the previous snapshot is inv ::: :::warning -In the end it the complete aggregate must be serializeable as json, also the aggregate Id. +In the end the complete aggregate must be serializable as json, including the aggregate id. ::: :::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). +You can find more about this in the [normalizer](normalizer.md) documentation. ::: ### Snapshot batching @@ -118,7 +118,7 @@ You can find more about normalizer also [here](normalizer.md). Since the loading of events in itself is quite fast and only becomes noticeably slower with thousands of events, we do not need to create a snapshot after each event. That would also have a negative impact on performance. Instead, we can also create a snapshot after `n` events. -The remaining events that are not in the snapshot are then loaded from store. +The remaining events that are not in the snapshot are then loaded from the store. ```php use Patchlevel\EventSourcing\Aggregate\BasicAggregateRoot; @@ -161,7 +161,7 @@ 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. +you should consider using [split streams](split-stream.md) if it makes 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. ::: @@ -181,7 +181,7 @@ Here are a few listed: ### psr-6 -A `Psr6SnapshotAdapter`, the associated documentation can be found [here](https://www.php-fig.org/psr/psr-6/). +A `Psr6SnapshotAdapter`, based on the [PSR-6 caching standard](https://www.php-fig.org/psr/psr-6/). ```php use Patchlevel\EventSourcing\Snapshot\Adapter\Psr6SnapshotAdapter; @@ -192,7 +192,7 @@ $adapter = new Psr6SnapshotAdapter($cache); ``` ### psr-16 -A `Psr16SnapshotAdapter`, the associated documentation can be found [here](https://www.php-fig.org/psr/psr-16/). +A `Psr16SnapshotAdapter`, based on the [PSR-16 caching standard](https://www.php-fig.org/psr/psr-16/). ```php use Patchlevel\EventSourcing\Snapshot\Adapter\Psr16SnapshotAdapter; @@ -203,7 +203,7 @@ $adapter = new Psr16SnapshotAdapter($cache); ``` ### in memory -A `InMemorySnapshotAdapter` that can be used for test purposes. +An `InMemorySnapshotAdapter` that can be used for test purposes. ```php use Patchlevel\EventSourcing\Snapshot\Adapter\InMemorySnapshotAdapter; diff --git a/docs/split-stream.md b/docs/split-stream.md index 3254cd22..f9ca74b2 100644 --- a/docs/split-stream.md +++ b/docs/split-stream.md @@ -1,14 +1,14 @@ # Split Stream -In some cases the business has rules which implies a restart of the event stream for an aggregate +In some cases the business has rules which imply a restart of the event stream for an aggregate since the past events are not relevant for the current state. A bank is often used as an example. A bank account has hundreds of transactions, but every bank makes a balance report at the end of the year. In this step the current account balance is persisted. This event is perfect to split the stream and start aggregating from this point. -Not only that some businesses requires such an action -it also increases the performance for aggregate which would have a really long event stream. +Not only do some businesses require such an action, +it also increases the performance for aggregates which would have a really long event stream. In the background the library will mark all past events as archived and will not load them anymore for building the aggregate. @@ -41,7 +41,7 @@ $repositoryManager = new DefaultRepositoryManager( ); ``` :::note -You can find out more about decorator [here](message-decorator.md). +You can find out more about the [message decorator](message-decorator.md). ::: :::tip @@ -70,7 +70,7 @@ 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! +The event needs all data which is relevant for the aggregate, since all past events will not be loaded! Keep this in mind if you want to use this feature. ::: diff --git a/docs/store.md b/docs/store.md index d09288cd..da6f37a5 100644 --- a/docs/store.md +++ b/docs/store.md @@ -4,7 +4,7 @@ In the end, the messages have to be saved somewhere. Each message contains an event and the associated headers. :::note -More information about the message can be found [here](message.md). +More information can be found in the [message](message.md) documentation. ::: The store is optimized to efficiently store and load events for aggregates. @@ -37,8 +37,8 @@ $store = new DoctrineDbalStore( ); ``` :::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) +You can find out more about [how to create a connection](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html) +in the doctrine dbal documentation. ::: Following options are available in `DoctrineDbalStore`: @@ -68,14 +68,14 @@ The table structure of the `DoctrineDbalStore` looks like this: :::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. +You can change the type with the `aggregate_id_type` option to `string` if you want to use a custom id. ::: ### StreamDoctrineDbalStore We offer a new store called `StreamDoctrineDbalStore`. This store is decoupled from the aggregate and can be used to store events from other sources. -The difference to the `DoctrineDbalStore` is that the `StreamDoctrineDbalStore` merge the aggregate id +The difference to the `DoctrineDbalStore` is that the `StreamDoctrineDbalStore` merges the aggregate id and the aggregate name into one column named `stream`. Additionally, the column `playhead` is nullable. This store introduces two new methods `streams` and `remove`. @@ -97,8 +97,8 @@ $store = new StreamDoctrineDbalStore( ); ``` :::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) +You can find out more about [how to create a connection](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html) +in the doctrine dbal documentation. ::: Following options are available in `StreamDoctrineDbalStore`: @@ -109,7 +109,7 @@ Following options are available in `StreamDoctrineDbalStore`: | locking | bool | true | If the store should use locking for writing | | lock_id | int | 133742 | The id of the lock | | lock_timeout | int | -1 | The timeout of the lock. -1 means no timeout | -| keep_index | bool | false | By message save the index header will be kept | +| keep_index | bool | false | If enabled, the index header is kept on save | The table structure of the `StreamDoctrineDbalStore` looks like this: @@ -143,7 +143,7 @@ You can pass messages to the constructor to initialize the store with some event Last but not least, we offer two read-only stores. One for the `DoctrineDbalStore` and one for the `StreamDoctrineDbalStore`. -It passes all methods to the underlying store, but throws an `StoreIsReadOnly` exception when trying to execute write +It passes all methods to the underlying store, but throws a `StoreIsReadOnly` exception when trying to execute write operations. ```php @@ -187,7 +187,7 @@ $schemaDirector = new DoctrineSchemaDirector( ); ``` :::note -How to setup cli commands for schema director can be found [here](cli.md). +How to setup [cli commands](cli.md) for the schema director can be found in the cli documentation. ::: #### Create schema @@ -296,7 +296,7 @@ Here you can find more information on how to ::: :::note -How to setup cli commands for doctrine migration can be found [here](cli.md). +How to setup [cli commands](cli.md) for doctrine migration can be found in the cli documentation. ::: ## Usage @@ -339,6 +339,7 @@ use Patchlevel\EventSourcing\Store\Criteria\ArchivedCriterion; use Patchlevel\EventSourcing\Store\Criteria\Criteria; use Patchlevel\EventSourcing\Store\Criteria\EventsCriterion; use Patchlevel\EventSourcing\Store\Criteria\FromIndexCriterion; +use Patchlevel\EventSourcing\Store\Criteria\FromPlayheadCriterion; $criteria = new Criteria( new AggregateNameCriterion('profile'), @@ -349,7 +350,7 @@ $criteria = new Criteria( new EventsCriterion(['profile.created', 'profile.name_changed']), ); ``` -Or you can the criteria builder to create the criteria. +Or you can use the criteria builder to create the criteria. ```php use Patchlevel\EventSourcing\Store\Criteria\CriteriaBuilder; @@ -383,7 +384,7 @@ foreach ($stream as $message) { } ``` :::note -You can find more information about the `Message` object [here](message.md). +You can find more information about the [message](message.md) object in the message documentation. ::: :::warning @@ -434,11 +435,11 @@ $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. +The store locks the table for writing during each save by default. ::: :::tip -Use transactional method if you want call multiple save methods in a transaction. +Use the transactional method if you want to call multiple save methods in one transaction. ::: ### Update @@ -495,19 +496,19 @@ $store->transactional(static function () use ($command, $bankAccountRepository): }); ``` :::note -The store lock the table for writing during the transaction by default. +The store locks 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 only want to save one aggregate, you don't have to use the transactional method. +The save method in store and repository is already transactional. ::: ## Learn more * [How to create events](events.md) * [How to use repositories](repository.md) -* [How to create message](message.md) +* [How to create messages](message.md) * [How to create projections](subscription.md) * [How to upcast events](upcasting.md) -* [How configure cli commands](cli.md) +* [How to configure cli commands](cli.md) diff --git a/docs/subscription.md b/docs/subscription.md index 829b1e10..4fea2afb 100644 --- a/docs/subscription.md +++ b/docs/subscription.md @@ -3,7 +3,7 @@ One core concept of event sourcing is the ability to react and process events in a different way. This is where subscriptions and the subscription engine come into play. -There are different types of subscriptions. In most cases, we are talking about projector and processor. +There are different types of subscriptions. In most cases, we are talking about projectors and processors. But you can use it for anything like migration, report or something else. For this, we use the event store to get the events and process them. @@ -15,7 +15,7 @@ Internally, the subscription engine does this by tracking where each subscriber ## Subscriber If you want to react to events, you have to create a subscriber. -Each subscriber need a unique ID and a run mode. +Each subscriber needs a unique ID and a run mode. ```php use Patchlevel\EventSourcing\Attribute\Subscriber; @@ -27,7 +27,7 @@ final class DoStuffSubscriber } ``` :::note -For each subsciber ID, the engine will create a subscription. +For each subscriber 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. ::: @@ -56,7 +56,7 @@ final class ProfileProjector } } ``` -Mostly you want process the events from the beginning. +Mostly you want to process the events from the beginning. For this reason, it is also possible to use the `Projector` attribute. It extends the `Subscriber` attribute with a default group and run mode. @@ -102,8 +102,8 @@ final class WelcomeEmailProcessor } } ``` -Mostly you want process the events from now, -because you don't want to email users who already have an account since a long time. +Mostly you want to process the events from now, +because you don't want to email users who already have an account for a long time. For this reason, it is also possible to use the `Processor` attribute. It extends the `Subscriber` attribute with a default group and run mode. @@ -158,7 +158,9 @@ If you want to subscribe on all events, you can pass `*` or `Subscribe::ALL` ins ```php use Patchlevel\EventSourcing\Attribute\Subscribe; +use Patchlevel\EventSourcing\Attribute\Subscriber; use Patchlevel\EventSourcing\Message\Message; +use Patchlevel\EventSourcing\Subscription\RunMode; #[Subscriber('welcome_email', RunMode::FromNow)] final class WelcomeSubscriber @@ -224,9 +226,11 @@ This service only has access to the messages before the current message. Here is an example how you can use it in a projector. ```php +use Patchlevel\EventSourcing\Attribute\Projector; use Patchlevel\EventSourcing\Attribute\Subscribe; +use Patchlevel\EventSourcing\Message\Message; use Patchlevel\EventSourcing\Message\Reducer; -use Patchlevel\EventSourcing\Subscription\Lookup; +use Patchlevel\EventSourcing\Subscription\Lookup\Lookup; #[Projector('public_profile')] final class PublicProfileProjection @@ -270,13 +274,13 @@ final class PublicProfileProjection } ``` :::note -More about reducers you can find [here](message.md#reducer) +More information can be found in the [reducer](message.md#reducer) documentation. ::: ##### Recorded On Resolver The recorded on resolver resolves the recorded on date. -It looks for a parameter with the instance of the `DateTimeImmutable`. +It looks for a parameter with the type `DateTimeImmutable`. ```php use Patchlevel\EventSourcing\Attribute\Subscribe; @@ -301,7 +305,7 @@ This can be useful for providing direct access to custom headers or other data. ### Setup Subscribers can have one `setup` method that is executed when the subscription is created. -For this there is the attributes `Setup`. The method name itself doesn't matter. +For this there is the attribute `Setup`. The method name itself doesn't matter. This is especially helpful for projectors, as they can create the necessary structures for the projection here. ```php @@ -345,7 +349,7 @@ The limit is usually 64 characters. ### Teardown Subscribers can have one `teardown` method that is executed when the subscription is removed. -For this there is the attributes `Teardown`. +For this there is the attribute `Teardown`. ```php use Doctrine\DBAL\Connection; @@ -374,7 +378,7 @@ 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. +Another option is to use the `Cleanup` method. ::: :::note @@ -383,7 +387,7 @@ You can not mix the `cleanup` method with the `teardown` method. ### Cleanup -Alternativ, you can use a `cleanup` method for cleanup tasks. +Alternatively, you can use a `cleanup` method for cleanup tasks. Unlike Teardown, this method is called when the subscription is created. The tasks are then saved in the Subscription Store. When removing the subscription, the subscriber is not necessary anymore, @@ -415,7 +419,7 @@ You can not mix the `cleanup` method with the `teardown` method. #### Dbal Cleanup Tasks -Default, we provide the following cleanup tasks for `doctrine/dbal`: +By default, we provide the following cleanup tasks for `doctrine/dbal`: | Task | Description | |-----------------|------------------------------| @@ -513,7 +517,7 @@ final class ProfileSubscriber } ``` :::note -The different attributes has different default group. +The different attributes have different default groups. * `Subscriber` - `default` * `Projector` - `projector` @@ -541,7 +545,7 @@ final class WelcomeEmailSubscriber } ``` :::tip -If you want create projections and run from the beginning, you can use the `Projector` attribute. +If you want to create projections and run from the beginning, you can use the `Projector` attribute. ::: #### From Now @@ -561,7 +565,7 @@ final class WelcomeEmailSubscriber } ``` :::tip -If you want process events from now, you can use the `Processor` attribute. +If you want to process events from now, you can use the `Processor` attribute. ::: #### Once @@ -590,7 +594,9 @@ We preconfigured two strategies for you: `default` and `no_retry`. ```php use Patchlevel\EventSourcing\Attribute\RetryStrategy; use Patchlevel\EventSourcing\Attribute\Subscribe; +use Patchlevel\EventSourcing\Attribute\Subscriber; use Patchlevel\EventSourcing\Message\Message; +use Patchlevel\EventSourcing\Subscription\RunMode; #[Subscriber('welcome_email', RunMode::FromNow)] #[RetryStrategy('default')] @@ -615,6 +621,7 @@ To achieve this, you can implement the `BatchableSubscriber` interface. ```php use Doctrine\DBAL\Connection; use Patchlevel\EventSourcing\Attribute\Projector; +use Patchlevel\EventSourcing\Attribute\Subscribe; use Patchlevel\EventSourcing\Subscription\Subscriber\BatchableSubscriber; #[Projector('profile_1')] @@ -703,7 +710,7 @@ The subscription engine manages individual subscribers and keeps the subscriptio Internally, the subscription engine does this by tracking where each subscriber is in the event stream and keeping all subscriptions up to date. -He also takes care that new subscribers are booted and old ones are removed again. +It 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 @@ -814,7 +821,7 @@ There are two options to reactivate the subscription: If an error occurs in a subscriber, then the subscription is set to Error. This can happen in the create process, in the boot process or in the run process. -This subscription will then no longer boot/run until the subscription is reactivate or retried. +This subscription will then no longer boot/run until the subscription is reactivated or retried. The subscription engine has a retry strategy to retry subscriptions that have an error. It tries to reactivate the subscription after a certain time and a certain number of attempts. @@ -864,12 +871,14 @@ Then it loads with a filter only the relevant messages. ```php use Patchlevel\EventSourcing\Metadata\Event\EventMetadataFactory; +use Patchlevel\EventSourcing\Store\Store; use Patchlevel\EventSourcing\Subscription\Engine\EventFilteredStoreMessageLoader; +use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberAccessorRepository; /** * @var Store $store * @var EventMetadataFactory $eventMetadataFactory - * @var SubscriberRepository $subscriberRepository + * @var SubscriberAccessorRepository $subscriberRepository */ $messageLoader = new EventFilteredStoreMessageLoader( $store, @@ -942,7 +951,7 @@ $schemaDirector = new DoctrineSchemaDirector( ); ``` :::note -You can find more about schema configurator [here](store.md) +You can find more about the schema configurator in the [store](store.md) documentation. ::: ### Retry Strategy @@ -1004,7 +1013,7 @@ This is what our default configuration looks like if you do not define the retry ::: :::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 defining the name in the constructor as second parameter. ::: ### Cleanup Handler @@ -1133,6 +1142,7 @@ use Patchlevel\EventSourcing\Subscription\Engine\MessageLoader; use Patchlevel\EventSourcing\Subscription\RetryStrategy\RetryStrategyRepository; use Patchlevel\EventSourcing\Subscription\Store\DoctrineSubscriptionStore; use Patchlevel\EventSourcing\Subscription\Subscriber\MetadataSubscriberAccessorRepository; +use Psr\Log\LoggerInterface; /** * @var MessageLoader $messageLoader @@ -1171,7 +1181,7 @@ You can use the `CatchUpSubscriptionEngine` in your tests to process the events ::: :::note -Learn more about the worker [here](./cli#subscription-commands). +Learn more about the worker in the [subscription commands](cli.md#subscription-commands) documentation. ::: ### Throw on error Subscription Engine @@ -1188,13 +1198,13 @@ $throwOnErrorSubscriptionEngine = new ThrowOnErrorSubscriptionEngine($subscripti ``` :::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. +The subscription engine has a built-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. -This means that a worker to run the subscriptions are not needed. +This means that a worker to run the subscriptions is not needed. ```php use Patchlevel\EventSourcing\Repository\RepositoryManager; @@ -1205,7 +1215,7 @@ use Patchlevel\EventSourcing\Subscription\Repository\RunSubscriptionEngineReposi * @var SubscriptionEngine $subscriptionEngine * @var RepositoryManager $defaultRepositoryManager */ -$eventBus = new RunSubscriptionEngineRepositoryManager( +$repositoryManager = new RunSubscriptionEngineRepositoryManager( $defaultRepositoryManager, $subscriptionEngine, ['id1', 'id2'], // filter subscribers by id @@ -1220,7 +1230,7 @@ Internally, the events are saved in a transaction to ensure data consistency. ::: :::note -More about repository manager and repository can be found [here](repository.md). +More about the repository manager can be found in the [repository](repository.md) documentation. ::: :::tip diff --git a/docs/testing.md b/docs/testing.md index d332df5e..03e45d9a 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -1,4 +1,4 @@ -# Tests +# Testing The library's design promotes easily testable code, and we offer several helpers to simplify the testing process even further. If you need additional support, we also provide @@ -141,11 +141,10 @@ These methods automatically invoke the appropriate functions defined via attribu ```php use Patchlevel\EventSourcing\PhpUnit\Test\SubscriberUtilities; +use PHPUnit\Framework\TestCase; final class ProfileSubscriberTest extends TestCase { - use SubscriberUtilities; - public function testProfileCreated(): void { $subscriber = new ProfileSubscriber(/* inject deps or mock tests as needed */); @@ -210,11 +209,11 @@ final class ProfileTest extends AggregateRootTestCase } ``` :::note -You can find out more about the clock [here](clock.md). +You can find out more about the [clock](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 `FrozenClock` in your integration tests to test the time-based behavior of your application. ::: ## Tests with UUID @@ -243,5 +242,12 @@ 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. +and supports only uuid version 7, which is used by the library. ::: + +## Learn more + +* [How to create an aggregate](aggregate.md) +* [How to use the clock](clock.md) +* [How to use subscriptions](subscription.md) +* [How to use the command bus](command-bus.md) diff --git a/docs/upcasting.md b/docs/upcasting.md index 09a308f4..e7d122c8 100644 --- a/docs/upcasting.md +++ b/docs/upcasting.md @@ -35,7 +35,7 @@ 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. +Keep in mind that all events are passed to the upcaster, so an early return for unrelated events is recommended. ::: ## Adjust event name