eventsaucephp / eventsauce Goto Github PK
View Code? Open in Web Editor NEWA pragmatic event sourcing library for PHP with a focus on developer experience.
Home Page: https://eventsauce.io/
License: MIT License
A pragmatic event sourcing library for PHP with a focus on developer experience.
Home Page: https://eventsauce.io/
License: MIT License
I just followed the command in installation guide but I get this error
I just started with
$ composer init
and ran the rest of the command. Are there other steps that I need to do? Also this is the content of my compose.json
{
"name": "naren/kpaunchha.pv",
"authors": [
{
"name": "Naren",
"email": "[email protected]"
}
],
"require": {
"eventsauce/eventsauce": "^0.2.2"
},
"minimum-stability" : "dev"
}
I even changed the minimum stability
to dev
just to make sure but not working. Looking forward to using this.
Hey there, I'm quite sure this is intentional and I'm aware that I can easily swap out the class.
But I'd love to understand it.
If I retrieve
an UUID in ConstructingAggregateRootRepository
and there are no messages for it I'm getting an empty object.
(Correct type, but with no messages applied)
I'd expect a AggregateRootNotFound
-Exception or something like this.
Why do you return an empty Aggregate here?
Thanks for clarification :-)
Wonderful library.
// ConstructingAggregateRootRepository
public function retrieve(AggregateRootId $aggregateRootId): object
{
// ...
return $className::reconstituteFromEvents($aggregateRootId, $events);
}
// ConstructionBehaviour
public static function reconstituteFromEvents(AggregateRootId $aggregateRootId, Generator $events): AggregateRoot
{
$aggregateRoot = new static($aggregateRootId);
/** @var object $event */
foreach ($events as $event) {
$aggregateRoot->apply($event);
++$aggregateRoot->aggregateRootVersion;
}
return $aggregateRoot;
}
I was check the docs and i notice an odd difference but im not 100% sure it is valid or not. There is table schema defined in the docs:
https://eventsauce.io/docs/message-storage/repository-table-schema/
When reviewing the DoctrineUuidV4MessageRepository
it seems that the DefaultTableSchema
has a reference to an id
field which is not in the schema definition. Problem is that i am not 100% sure what the definition of this ID field should be. I assume part of the PK as an unsigned AI BIGINT?
We finally got around to upgrade to 0.7 and use the new snapshotting functionality.
I like the new implementation, thanks @frankdejonge
In our previous homegrown snapshot implementation we stored the aggregate type and code version of the aggregate so we could easily remove previous/outdated snapshots in our database.
The eventsauce docs describe it on the snapshot page as well:
"Versioning Snapshots
Snapshots are stored in the database. When your aggregate root evolves, so must your snapshots. A good practise is to version your snapshots. Storing a version along with your snapshot allows you to filter out any outdated ones when trying to fetch your aggregate root."
However the Snapshot object does not really provide a way to pass this information.
I've now solved this by passing the aggregateRoot to the snapshot persist function and grabbing both type and code version. Would extending the interface for the snapshot repository make sense?
Something like?
interface SnapshotRepository
{
public function persist(Snapshot $snapshot, AggregateRootWithSnapshotting $aggregateRoot): void;
public function retrieve(AggregateRootId $id): ?Snapshot;
}
My Repository then does something like this (VersionedSnapshots is extending the AggregateRootWithSnapshotting interface with a method to grab the current version of the code) :
/**
* @throws \Doctrine\ODM\MongoDB\MongoDBException
*/
public function persist(Snapshot $snapshot, VersionedSnapshots $aggregateRoot = null): void
{
$collection = $this->collectionManager->getCollection(Snapshots::class);
$data = [
'aggregate_root_id' => $snapshot->aggregateRootId()->toString(),
'aggregate_root_id_serialized' => serialize($snapshot->aggregateRootId()),
'aggregate_root_version' => $snapshot->aggregateRootVersion(),
'state' => json_encode($snapshot->state()),
];
if (null !== $aggregateRoot) {
// the $aggregateRoot should always be passed to this function, but the interface doesn't include it
$data['aggregate_snapshot_code_version'] = $aggregateRoot->getAggregateSnapshotVersion();
$data['aggregate_type'] = get_class($aggregateRoot);
}
$collection->replaceOne(['aggregate_root_id' => $snapshot->aggregateRootId()->toString()], $data, ['upsert' => true]);
}
Or should i solve this in a different way?
At first I would like to tell that you made a great job with your EventSauce project.
I am completely new to this project and I would like to advise one big change it the code: to separate all unit tests to new directory tests
(at the same level as src
is):
src/
tests/
The benefits can be really obvious:
src
As we speak only about unit tests there shouldn't be any problems with backward compatibility or other things.
That way you could do:
class AggregateRootExample implements AggregateRoot
{
/**
* @use AggregateRootBehaviour<AggregateRootIdExample>
*/
use AggregateRootBehaviour
}
And tie the aggregate root with the type of ID it uses.
DoctrineOutboxRepository and some other Doctrine specific EventSauce classes are using backticks in sql statements. Postgresql doesn't support it, instead double quotes should be used.
db_1 [590] ERROR: syntax error at or near "\`" at character 27
db_1 [590] STATEMENT: INSERT INTO event_outbox (`payload`) VALUES ($1)
For a project I'm working on I had the need to use event-based modelling but had no need for an aggregate root. In this specific case used both the MessageDispatcher and MessageRepository implementations and created my own base test case. For this a "classic" event store might be a good encapsulation. But it leaves open a few cases to discuss.
Hi,
I think it helps to read documentation pages faster if they have next
and back
navigation buttons on each page at the end.
Is it possible to add such buttons?
As the list of events grows for an aggregate root, just loading the aggregate root and having all those events replayed is just going to get slower and slower. If I am missing something please help me understand.
Hi,
Is there another way to apply events outside the aggregator?
For example, using a handler class instead applyProcessStarted method.
Best,
Right now AggregateRootTestCase
contains methods to test interactions with AggregateRoots. The way to use this let a testclass extend AggregateRootTestCase
.
Laravel apps have their own root TestCase with lots of methods to test the app. It would be nice if you we could use all methods in AggregateRootTestCase
while still using the default Laravel TestCase.
On first though this could be made possible if all methods currently present in AggregateRootTestCase
would be move to a trait called TestsAggregateRoot
(of something similar). Then we could just use that trait in Laravel tests.
The AggregateRootTestCase
provided by EventSauce could also use the same trait, so there would be no breaking changes. I can whip up a PR if you agree with this change.
Hey Frank
First of all, let me say thank you for this library and all the other OSS stuff you do! I've just started using EventSauce and I'm starting to get familiar with all the concepts. I love all the flexibility and the way you focussed on providing the building blocks so I can do whatever I want myself :) Great stuff! โค๏ธ
One thing I'm missing from the AggregateRootRepository
is to retrieve an aggregate in a specific version. I think this is one of the areas where event sourcing really shines: comparing versions. I know it's basically doable by injecting the MessageRespository
myself and ignoring all the versions after n
but it feels like a little design issue because I would need to inject both, the correct AggregateRootRepository
which itself depends on the MessageRepository
as well as the repository itself. Or well, I would need to create my own implementation of AggregateRootRepository
which decorates e.g. the EventSourcedAggregateRootRepository
. Is that what I'm supposed to do?
So basically: Did I miss anything built-in or is there really no way to fetch that currently? ๐
See https://github.com/EventSaucePHP/EventSauce/blob/main/docs/docs/architecture.md#message-serializer.
The core ships with a default (JSON based) serializer.
This no longer appears to be the case? At least, a search of the codebase has no mention of JSON.
I am not really sure how to reconcile this issue, but the Trait throws InvalidAggregateRootReconstitutionException
, however, the interface explicitly states that the method does not throw any exception.
This makes development very difficult for several reasons:
AggregateRootRepository::retrieve
whether history for aggregate root is required or not.The only idea I got is to add AggregateRootRepository::retrieveWithRequiredHistory
that can have the exception hint.
I can see that there is a concept of a Aggregate Root version, but I'm not sure if it is used for preventing concurrency issues, i.e. two or more events occurring at the same time? Actually I'm not sure what is its purpose. Other solutions such as Prooph use unique key constraints, something like: UNIQUE KEY `ix_unique_event` (`aggregate_type`, `aggregate_id`, `aggregate_version`)
. Other languages rely on manual checks before persisting events: https://github.com/gregoryyoung/m-r/blob/master/SimpleCQRS/EventStore.cs#L52.
What I'm also missing in the documentation is a sample DB schema and recommended way for persisting aggregates, for instance single table strategy, separate table for every aggregate type, and similar.
When I have multiple connections in my app ab -n100 -c100 -t5 http://localhost/...
I noticed that the aggregate version behavior is lost
In the collection, several records are created with the same version
Is this expected or could it be an implementation issue?
I'm using version 1.2 of the package
Hacktoberfest is upon us. In order to make Hacktoberfest a positive experience for everybody, this issue describes what kind of PR's are welcomed with open arms.
Documentation has always been one of the things I've focused on, however, it's always something that can be improved upon. This is especially true for documentation contributions. Did you wish something was covered more (or at all) in the documentation? Please contribute! The more topics covered, the beter!
Happy Hacktoberfest!
PS: Meaningless drive-by PR's will be closed, let's keep this productive for all parties involved.
If #27 is completed we've one step away from allowing full decoupling from the library. If the AggregateRootRepository receives a AggregateRootFactory
it could become unaware of how aggregate roots are reconstituted. This allows people to create their own hydration strategies. This allows them to wrap their own aggregate root in the library specific aggregate root, thereby allowing decoupling from the lib. This still allows us to use the aggregate root interface for message decoration.
I am upgrading to EventSauce 1.x and was prompted to switch to MessageRepositoryForDoctrineV2, which is fundamentally incompatible with the old DoctrineMessageRepository
due to this method.
I cannot comprehend why using a binary UUID is preferable to a true UUID type, except for the people using MySQL/MariaDB. There does not appear to be any documentation for this change, nor any suggestion for how to migrate my existing tables to use this schema, even if I wanted to.
And to make the situation worse, this method is marked as private
so there is no way for me to extend the default implementation in a way that would make it compatible with my existing tables. It appears my only recourse is to copy/paste this whole file into my project and alter it.
EventSauce is fantastic, but this change is essentially broken by design.
Hey @frankdejonge!
First, I feel the need to say that "pragmatic event sourcing for PHP" is the PERFECT description of what 0.6 has achieved so far. I've worked with Prooph, Broadway and implemented my own ES stack 3 or 4 times after coming to feel that every one of them will be different and "building a ES framework is impossible!". I believe you have struck the best balance in small, focused interfaces to pull off the core without all the bloat and over-architecture present in other similar "frameworks".
Second, I want to contribute and promote this package. What do you need help with? As far as I can tell, the library is more-or-less feature complete (certainly enough for my own needs), but what is your "wish-list" of things you would add? Do we want some example projects? If so, do you have any suggestions on domains more complex than "To-do" or "banking", which seem to be the only event-sourced applications folks are building in the open ๐.
Anywho, thanks for putting time into this and I look forward to working with the project!
Hi @frankdejonge et al,
Since I'm on composer v2 it skips the commands_and_events.php
files because it does not comply with psr-4 autoloading standard.
That's technically a correct error as the file defines multiple classes.
Any recommendation on how to handle this? I expect more people are or will be running into this.
I've tried loading the file using composer's files
directive, but no luck there either.
Here's an excerpt:
"autoload": {
"files": [
"src/Domain/SomeModule/commands_and_events.php"
],
"psr-4": {
"App\\": "src/App/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"Domain\\": "src/Domain/",
"Support\\": "src/Support/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
Hello, the current definition of MessageSerializer looks like this:
interface MessageSerializer
{
public function serializeMessage(Message $message): array;
public function unserializePayload(array $payload): Generator;
}
When it is used outside the MessageRepository it needs to be wrapped with iterator_to_array($serializer->unserializePayload($payload))[0]
or call $serializer->unserializePayload($payload)->current()
. In my opinion the signature should be unserializePayload(array $payload): Message;
, since it is returning only one message and not a sequence. Message
and Payload
could also be left out of method names.
Any opinions on that?
The only example of a snapshot repository you provide is InMemorySnapshotRepository. Where can we find an example of how to persist the snapshot to a database, does a simple serialize(), unserialize() work for the Snapshot object?
Here's an interesting question, perhaps an edge case. When using the \EventSauce\EventSourcing\ConstructingAggregateRootRepository::retrieve
method with an AggregateRootId
that does not actually exist (i.e. has no events) does not result in an error at all: it merely creates an instance of the AggregateRoot
with no events.
I imagine a simple check in \EventSauce\EventSourcing\AggregateRootBehaviour::reconstituteFromEvents
would fix this, for example:
/** @var AggregateRootBehaviour $aggregateRoot */
$aggregateRoot = new static($aggregateRootId);
$appliedEvents = 0;
/** @var object $event */
foreach ($events as $event) {
$aggregateRoot->apply($event);
$appliedEvents++;
}
if ($appliedEvents === 0) {
throw new \RuntimeException('noooo');
}
/* @var AggregateRoot $aggregateRoot */
return $aggregateRoot;
Would be cleaner to check for the presence of events before the loop and return early, but since a generator is being used, have to check after the loop.
If this is something desirable, let me know and I'll put together a patch.
Quick question, but is it possible to push lambdas or closures onto the command queue instead of command objects?
Firstly I have to say that this is my first issue on GitHub that describes an error. Please don't be offended :). I wanted to use this library in my sample project but after digging into the code, I decided against it. I really like its api, so I can wait until this issue is closed.
This library only wraps messages in a transaction. While this is fine if you don't want immediately consistent read models, this is important to understand the real issue.
// from DoctrineMessageRepository.php
$this->connection->beginTransaction();
$this->connection->prepare($sql)->execute($params);
$this->connection->commit();
The consistency issues start in the next block of code.
// from ConstructingAggregateRootRepository.php
$this->repository->persist(...$messages);
$this->dispatcher->dispatch(...$messages);
After the messages have been persisted successfully, some of the following errors may occur. I take RabbitMq as an example, because you listed it in your documentation. But this can be replaced with any message broker, even sql implementations.
If one of the errors above occur, the application is in an inconsistent state because the handlers are not informed that something happened. This isn't even eventual consistency, because it never happened from the view of the handlers.
There are several solutions to this problem.
Firstly, if the library wants to provide immediately consistent read models, like stated in the documentation, it should call the handlers within the same transaction where the messages are persisted. Of course, this only works if the same database and connection is used.
Secondly, all messages which are persisted successfully should be published to all interested handlers in another process. But it's important, that it only publishes messages which are persisted successfully. This process is also called "store and forward". I've seen recommendations to read uncommitted transactions for not reading the events out of order, but this is also faulty because the last sql statement "COMMIT;" eventually never reaches the server and the handlers already read that uncommitted event. The best I've seen is the following approach. It's implemented in my sample project's event store. Actually, the logic is spread over several classes. For the sake of simplicity, I copied the code so it works procedurally. This approach works only for auto increment event ids. Of course you can also create a global event id, like uuidv4. The auto increment id is only important for the following code.
// This script was written down quickly. It's not tested.
// The client should specify the batch size.
$batchSize = 1000;
// The client should specify the throttle time.
$throttleTimeInMicroseconds = 100000;
$lastStoredEventId = 0; // Retrieve the pointer somewhere.
// Build the handlers somewhere.
// This can be read model builders,
// or something that forwards the events to another bounded context / application.
$storedEventSubscribers = [];
while(true) {
$events = $eventStore->storedEventsSince($lastStoredEventId, $batchSize);
if (count($events) !== 0) {
$lastProcessedEventId = $lastStoredEventId;
foreach ($events as $event) {
// This following if statement can happen, when two transactions are open,
// but the second is faster than the first.
if ($event->id() !== $lastProcessedEventId + 1) {
// Throw an exception to the client of this library.
// The client knows best how to handle it.
// Maybe it's not relevant in the clients particular situation.
// Maybe the client needs to wait a short amount of
// time until the previous message comes.
// Maybe it never arrives because the first transaction was a failing one.
// However, the client knows best how to handle it.
throw new OutOfOrderException();
}
$storedEventPublisher->publish($event);
$lastProcessedEventId = $event->id();
}
$lastStoredEventId = $lastProcessedEventId;
// The $lastStoredEventId should be persisted somewhere (not only in memory).
// If the process is crashed, this process can pick up where its left off.
} else {
// Don't stress the sql server if no messages are available.
usleep($throttleTimeInMicroseconds);
}
}
There are some caveats with this approach. This is at most once consistency. It happens definitely, but maybe more than one times. All handlers should be idempotent.
Messaging is hard, you may provide a link to a good book so users don't blindly think it works just because it does work in the happy path. Maybe I've overlooked the part where you talk about immediately and eventual consistency approaches in your documentation. I think you should mention these and provide links for further reading.
I can imagine that the library is used productively. Please inform the users about it if you see an issue here.
Some book recommendations
Trying to upgrade to EventSauce 1.x and discovered that Message::withHeader no longer accepts an array as a value, which is not mentioned in the upgrade documentation and seems to be an arbitrary restriction, given that an array
can be encoded to JSON without issues. Before typing was added, this method had a mixed type and worked with arrays. As such, I believe this is a regression.
We use withHeader
via a decorator to attach context variables to the message, such as the requesting IP address, user ID, etc. for audit purposes. Currently this is 9 separate values in the array and it would be burdensome to use separate headers for every one of the values. The code looks like:
return $message->withHeader(self::CONTEXT_HEADER, $this->context->export());
EventSauce/src/AggregateRootBehaviour.php
Line 56 in d03a649
Hello,
I'm facing an issue when a child aggregate needs to record its own events at instantiation.
Let's take the following exemple:
class Family extends AggregateRoot {
protected $child;
public static function create()
{
$self = new self();
$self->recordThat(new ParentCreated());
$self->recordThat(new ChildCreated());
return $self;
}
public function applyChildCreated(ChildCreated $event)
{
$this->child = new Child();
}
}
and
class Child extends SubAggregate {
public function __construct()
{
$this->recordThat(new ChildCried());
}
}
When i test this with a BDD approach, i'm expecting this:
$this
->given()
->when(new CreateFamily()) // which call $family = Family::create(); + $aggregateRepository->persist($family);
->then(
new ParentCreated(),
new ChildCreated(),
new ChildCried()
)
but i got this
$this
->given()
->when(new CreateFamily()) // which call $family = Family::create(); + $aggregateRepository->persist($family);
->then(
new ParentCreated(),
new ChildCried(),
new ChildCreated()
)
I noticed that to fix it, i needed to move around the content of the recordThat
method to be like (so event recorded before apply the event.
protected function recordThat(object $event): void
{
$this->recordedEvents[] = $event;
$this->apply($event);
}
I noticed that prooph does it in that order (https://github.com/prooph/event-sourcing/blob/master/src/Aggregate/EventProducerTrait.php#L51) as well as in the book DDD in PHP:
class AggregateRoot {
protected function recordApplyAndPublishThat(DomainEvent $domainEvent) {
$this->recordThat($domainEvent);
$this->applyThat($domainEvent);
$this->publishThat($domainEvent);
}
protected function recordThat(DomainEvent $domainEvent){
$this->recordedEvents[] = $domainEvent;
}
}
is there a reason why $this->apply($event);
is called before $this->recordedEvents[] = $event;
in EventSauce.io ? What would be the impact to move it around ?
The Event
interface is only used for serialization and "intentional" hinting. If we remove the event interface. Since we already have a MessageSerializer
the construction could become an implementation detail for the rest of the library. This means if people don't want to be tied to the library, they won't have to be. The AggregateRootRepository
and AggregateRoot
interface then only function as a reference implementation (easy to swap out when needed/wanted).
Pretty much the possibility to be fully decoupled from the entire library.
In order to have the most minimal event interface we could move the timeOfRecording (PointInTime) to the headers. The default header message decorator would then account for filling in the default. Because the PointInTime is microsecond precise we'd still have the sequential integrity (when needed by the repository).
Thoughts?
[Additional Thoughts]
This also makes it more natural to use domain specific date/time objects when relevant to the business without having people use the pointInTime in their domain, effectively tying them to the package.
I will have a new challenge to get started soon.
The idea is to implement the event sourcing in Monolith so we can upstream their data to a new service.
I think that projection can be used as a contract in between.
What Do you think about it?
I would like to hear your thoughts.
Hi,
The order of the parameters in the DefaultHeadersDecorator is deprecated.
My console shows warnings about that .
See - https://php.watch/versions/8.0/deprecate-required-param-after-optional
Best Regards.
Hi all,
I'm working on a project and I'm bumping into the following issue.
First some background information; there exist some entities and relations seen in Figure 1. Events
are the "governing" entity where currently a lot of logic is situated. First Categories
are created within an Event
. Then Persons
can be added to Events
with a chosen Category
. We wish to separate our business rules more, as this is just a fraction of the domain we're examining.
We have event stormed with our development team and came to the conclusion we have the following events and aggregates, as seen in Figure 2. Again, this is just a fraction.
Figure 2: Domain events and aggregates
Suppose I would like to test the following behaviour: an event is started, and a person is added. This is quite simple and follows the examples similarily.
public function test_adding_a_person_to_an_event()
{
$this->given(
new EventStarted(),
)->when(
new AddPerson($this->aggregateRootId),
)->then(
new PersonAdded(),
);
}
But now, we have the requirement that a Person
must have a Category
. How would we pass the reference to the Category
?
public function test_adding_a_person_to_an_event_with_category()
{
$this->given(
new EventStarted(),
new CategoryCreated()
)->when(
new AddPerson($this->aggregateRootId, $categoryId), // <- what is $categoryId?
)->then(
new PersonAdded(),
);
}
Next to that, once we have a Person
in place, I would too like to test the following behaviour. Here we have two aggregate roots that are used, the Event
and the Person
.
public function test_adding_a_person_to_an_event_and_adding_information()
{
$this->given(
new EventStarted(),
new PersonAdded(),
)->when(
new AddInformation($this->aggregateRootId), // <- $this->aggregateRootId is from the Event, not the Person
)->then(
new InformationAdded(),
);
}
I found the expressive testing that EventSauce facilitates very pleasing and it works well, however, I'm uncertain if I'm doing something wrong, or that this is just not (currently) supported in EventSauce.
Hi :)
Event Sauce is great!
The snapshot documentation is a bit inconsistent.
On page example is:
/**
* bool type for state is incompatible with the AggregateRootWithSnapshotting interface.
* Maybe better you should use mixed type here and in interface?
*/
protected static function reconstituteFromSnapshotState(AggregateRootId $id, bool $state): AggregateRootWithSnapshotting
{
}
Best regards. Andrew
๐ / ๐
Input:
namespace: AcmeDomain\ProcessName
aggregate_root_id:
type: ProcessId
name: processId
events:
StartProcess:
fields:
reason:
type: string
Expected output:
<?php
namespace AcmeDomain\ProcessName;
use EventSauce\EventSourcing\AggregateRootId;
use EventSauce\EventSourcing\Command;
use EventSauce\EventSourcing\Event;
use EventSauce\EventSourcing\PointInTime;
final class StartProcess implements Event
{
/**
* @var ProcessId
*/
private $processId;
/**
* @var string
*/
private $reason;
/**
* @var PointInTime
*/
private $timeOfRecording;
public function __construct(
ProcessId $processId,
PointInTime $timeOfRecording,
string $description
) {
$this->processId = $processId;
$this->timeOfRecording = $timeOfRecording;
$this->description = $description;
}
public function aggregateRootId(): AggregateRootId
{
return $this-> processId();
}
public function processId(): ProcessId
{
return $this->processId;
}
public function reason(): string
{
return $this->reason;
}
public function timeOfRecording(): PointInTime
{
return $this->timeOfRecording;
}
public static function fromPayload(
array $payload,
ProcessId $processId,
PointInTime $timeOfRecording): Event
{
return new EventWithDescription(
$processId,
$timeOfRecording,
(string) $payload['reason']
);
}
public function toPayload(): array
{
return [
'reason' => (string) $this->description,
];
}
}
I'm applying a rename event to an item persisted in the DB with aggregate root version of 2, however EventSauce is setting the next version to 0. I've found the cause (I guess) is line 83 in file AggregateRootBehaviour.php
public static function reconstituteFromEvents(AggregateRootId $aggregateRootId, Generator $events): AggregateRoot
{
/** @var AggregateRoot&static $aggregateRoot */
$aggregateRoot = new static($aggregateRootId);
/** @var object $event */
foreach ($events as $event) {
$aggregateRoot->apply($event);
}
$aggregateRoot->aggregateRootVersion = $events->getReturn() ?: 0;
/* @var AggregateRoot $aggregateRoot */
return $aggregateRoot;
}
$aggregateRoot->aggregateRootVersion = $events->getReturn() ?: 0;
Doing a dump of $events->getReturn()
is giving null
, so the aggregateRootVersion is set to 0.
Here are my $events
App\Models\Catalog\Events\Imported^ {#721
-name: "First Item"
}
App\Models\Catalog\Events\Renamed^ {#735
-name: "Second Time"
}
App\Models\Catalog\Events\Renamed^ {#740
-name: "55"
}
What is going wrong here that $events->getReturn()
returns null?
Cheers
Hi,
I am using MariaDB 10.4.11 and it does not support the timezone identifier in a Datetime column. The PointInTime class used in this project is final and always adds the timezone as seen here: https://github.com/EventSaucePHP/EventSauce/blob/master/src/PointInTime.php#L14
Any suggestion on what type of column to use then?
I'd like to be able to only have date, time and microseconds.. timezone is always UTC for me
While using this library, I ran into the issue that, when I use the Doctrine Message Repository, persisting events fails because MySQL fails to accept the Date Time format.
After verifying this, I found that the DATE_TIME_FORMAT constant in the PointInTime class uses O
as the timezone format (+0000
) instead of P
(+00:00). When I change this, inserting events in MySQL works, and the MySQL documentation suggests that the P modifier seems to be the accepted format (https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_convert-tz).
Before issuing a PR to change this, I would like to ask whether this format worked in a prior version; or whether this works on Postgre and was not verified on MySQL (for example).
When using the test helper ->when()
there is a separate method ->expectToFail()
. But there is no opposite of it.
Any exception that is thrown in the events in when will just be caught in AggregateRootTestCase
:
} catch (Exception $exception) {
$this->caughtException = $exception;
}
This way when using asserts on the retrieved aggregate root the values will just be wrong but I don't know about a failing command. Am I missing something or is there now way around that at them moment?
Would be awesome if this would be provided.
According to https://eventsauce.io/docs/advanced/custom-repository/ the example implementation should use an string. But it looks like that aggregateRootId is an object or should be an object when following this guide: https://eventsauce.io/docs/event-sourcing/create-an-aggregate-root/
public function persist(Message ... $messages)
{
foreach ($messages as $message) {
$aggregateRootId = $message->header(Header::AGGREGATE_ROOT_ID);
$version = $message->header(Header::AGGREGATE_ROOT_VERSION);
if ( ! is_dir(__DIR__.'/'.$aggregateRootId)) {
mkdir(__DIR__.'/'.$aggregateRootId);
}
$payload = $this->serializer->serializeMessage($message);
file_put_contents(__DIR__."/{$aggregateRootId}/{$version}.json", json_encode($payload, JSON_PRETTY_PRINT));
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.