prooph / event-sourcing Goto Github PK
View Code? Open in Web Editor NEWProvides basic functionality for event sourced aggregates.
Home Page: http://getprooph.org
License: BSD 3-Clause "New" or "Revised" License
Provides basic functionality for event sourced aggregates.
Home Page: http://getprooph.org
License: BSD 3-Clause "New" or "Revised" License
I use PHPStorm live templates heavily and tried custom class templates (file templates) recently. You can do a lot of stuff with it. I generate immutable data types with a combination of live templates and class templates.
So my idea is to provide blueprints for event sourced aggregates and messages as PHPStorm templates. It's possible to share them so ppl can import the templates and customize them if needed. I'd say you get best of both worlds and PHPStorm is a de facto standard these days.
Thoughts?
[Symfony\Component\Debug\Exception\FatalThrowableError]
Type error: Argument 1 passed to Prooph\SnapshotStore\Snapshot::__construct() must be of the type string, object given, called in /home-projects/api-plhw-development/deploy/relea
ses/20161219092409UTC/vendor/prooph/event-sourcing/src/Aggregate/SnapshotReadModel.php on line 91
https://github.com/prooph/event-sourcing/blob/develop/src/Aggregate/SnapshotReadModel.php#L86-L88
Tell me that should be get_class($aggregateRoot)
and I'll PR it
What is it good for? It's not used anymore!
see: https://github.com/prooph/event-sourcing/blob/master/src/Util/MapIterator.php
Remove this class?
Currenty we use something like this:
protected function apply(AggregateChanged $e)
{
$handler = $this->determineEventHandlerMethodFor($e);
if (! method_exists($this, $handler)) {
throw new \RuntimeException(sprintf(
'Missing event handler method %s for aggregate root %s',
$handler,
get_class($this)
));
}
$this->{$handler}($e);
}
New approach with be like this:
protected function apply(AggregateChanged $event) {
switch (get_class($event)) {
case ManagerWasBlocked::class:
$this->whenManagerWasBlocked($event); // delegate to when...-method
break;
case ManagerWasDeleted::class:
$this->state = ManagerState::DELETED(); // handle immediately
break;
default:
throw new \UnexpectedValueException('Unknown domain event');
}
}
Reasons:
refers to: prooph/service-bus#109
I've followed the example in the quickstart on the latest 5.0.0 beta. The comments say that an aggregate loaded from a repository will automatically commit their events. I cannot get this behaviour without calling saveAggregateRoot
.
I'm using Postgres and have configured the event store, transaction manager and service bus. Everything works except the automatic committing.
Perhaps I'm missing something?
I would like to throw in an idea for improvement around event ids.
Currently, if you want to employ different uuid generation strategy, for example COMB, ramsey uuid lib has to be configured statically beforehand.
I think it would be beneficial to delay event uuid generation (currently happens in DomainMessage::init()
) until it is requested by something, which should not occur before extractor is run in most cases.
That would allow us to explicitly supply uuid generator in a clean way from infrastructure with a proper dependency injection:
public function extractPendingStreamEvents($anEventSourcedAggregateRoot): array
{
if (null === $this->pendingEventsExtractor) {
$this->pendingEventsExtractor = function (): array {
return $this->popRecordedEvents();
};
}
$events = $this->pendingEventsExtractor->call($anEventSourcedAggregateRoot);
if ($this->uuidGenerator) {
foreach ($events as $event) {
$event->applyUuidGenerator($this->uuidGenerator);
}
}
return $events;
}
public function applyUuidGenerator(UuidFactoryInterface $factory) : void
{
if (! $this->uuid) {
$this->uuid = $factory->uuid4();
}
// noop otherwise
}
Tested with 5.0.0-beta1 Release
If I try to save a a aggregate root not having pending events with AggregateRepository::saveAggregateRoot
, an exception is thrown by method AggregateRepository::isFirstEvent
, since this method requires a Message
object as first argument.
$firstEvent
would be null
if there is no pending event in $domainEvents
.
$firstEvent = $domainEvents[0];
if ($this->isFirstEvent($firstEvent) && $this->oneStreamPerAggregate) {
$createStream = true;
}
I would prefer to throw a NoPendingEventsException, or not to do anything If there is no pending event.
Maybe an additional unit test is required for that use case.
We encountered an interesting error in our application today. It turned out to be our fault but I think prooph could handle such case a bit better.
What happened was basically this:
final class Foo extends AggregateRoot
{
/** @var FooId */
private $fooId;
/** @var BarId */
private $barId;
protected function aggregateId(): string
{
return (string) $this->barId;
}
}
The bug in the code above is that aggregateId
should return (string) $this->fooId
instead. $this->barId
was null so it returned an empty string.
The result was that the event was saved in the event store but _aggregate_id
in the event metadata was an empty string.
Then database failed on this unique check when we saved a second aggregate root of the same type.
I think there should have been an exception thrown when trying to save the first aggregate root - maybe here or in the aggregate translator? I'm not exactly sure where to put the check.
Two years ago I created this issue about a possible Blueprint instead of a event-sourcing library.
I'd like discuss this option again. In the last two years @sandrokeil and me focused on developing a modeling tool: InspectIO. It ships with a customizable code generator. The PHP version works with PHP AST and @sandrokeil put a lot of effort into making it as flexible as possible while still being easy to use.
With this toolbox in place we could think about a new prooph/event-sourcing and also a lightweight prooph/service-bus. Both based on code generation instead of library code that you have to extend from.
A set of defaults could guide people in the right direction but should offer enough freedom to change things they want to do different.
What do you think?
/cc @prolic
see: 818fca6
@sandrokeil wanna take over the job and revert that pr?
Would it be allowed to update the Prooph/Common dependency to version 3? We are planning to use Prooph in a Symfony application but this library does not depend on Zend Framework as far as I can see and hence we would not like to pull in those dependencies.
I saw that Prooph/Common version 3 and higher no longer uses the Zend Framework dependencies and it would be nice if this library would use that version
If you call AggregateRepository::saveAggregateRoot, the aggregate got saved and then removed from the identiy map.
I see no reason to do this, because all new events are popped from the stream when saving. One of the main advantages of event sourcing is, that it is append only and because of that its easy to cache.
So e.g. you have a domain event, that triggers some action, lets say on a external API and based on the result it will raise a command on the same aggregate, that caused the event.
Now the aggregate would have to replay the event stream again. In worst case this could happend a few times.
Instead of removing an aggregate from the identity map i would consider its better to add it on creation and keep it on the identity map if its already exists.
IMHO the way the identiy map is implemented right now, it should be without any use.
I have been studying and playing with the code for a bit and cant see a way to add multiple event handlers. Each AggregateRoot contains its own Event handlers (eg whenUserWasRenamed). Part of the spec for CQRS-ES is that you can have multiple read models. I create a new AggregateRoot that had the same event listeners but the AggregateRoot failed to create and only returned null.
What am i missing?
Thank you
The aggregateId method from EventProducerTrait and EventSourcedTrait are not related to that traits, but to the abstract AggregateRoot class which uses this traits.
So we should move this method up.
Already done in this PR: https://github.com/prooph/event-sourcing/pull/73/files
But suspended until we release the next major version
cc @prolic
This package needs a new beta release, since the current one uses $stream->streamEvents()
instead of Iterator.
Use the EventStore::loadEventsByMetadataFrom method in combination with a snapshot_version metadata key to fetch all events since the last snapshot. The snapshot_version needs to be passed to all events, so the AggregateRoot or UoW should hold the current snapshot version and automatically pass it to all new Events.
The only way currently to use the AggregateRepositoryFactory
is to create a class that inherits from AggregateRepository
.
This has multiple drawbacks. Two of them are:
getAggregateRoot
).Technical it's possible to create an instance of AggregateRepository
(no subclass of it) and put it as dependency into my repository and use it (as AggregateRepository::saveAggregateRoot()
and AggregateRepository::getAggregateRoot()
are public functions).
The only things thats prevent me from doing it is this check in AggregateRepositoryFactory
:
if (! \is_subclass_of($repositoryClass, AggregateRepository::class)) {
throw ConfigurationException::configurationError(\sprintf(
'Repository class %s must be a sub class of %s',
$repositoryClass,
AggregateRepository::class
));
}
I think the check here should use is_a()
, to allow me to create not only children of AggregateRepositories, but also direct instances of AggregateRepository.
If you can confirm that it is a valid approach to create the factory without creating subclasses of it, i could create a pull request for it.
Very often when handling a command I need to check if an aggregate with the given ID exists. I'm mostly using AggregateRepository::getAggregateRoot()
for that purpose but in my opinion it is not a good method for this case. I don't need the aggregate itself or any of its events after all so getAggregateRoot
is actually doing a lot of useless work.
I was unable to find any more suitable method for this. Can you tell me how do you solve this in your applications? Do you think introducing a new method to AggregateRepository
would be a good idea? Any tip how to implement it? EventStore
doesn't seem to have a good method for this either (hasStream
doesn't help because I don't use one-stream-per-aggregate strategy).
Coming up prooph/event-store v7 no longer contains Aggregate related classes. They are all moved to prooph/event-sourcing. Now we have two AggregateTranslator
implementations in one package.
Originally the ConfigurableAggregateTranslator
was meant to be a default implementation if one don't want to use prooph/event-sourcing.
With the new structure the ConfigurableAggregateTranslator
is no longer needed. prooph/event-sourcing uses the decorator pattern and has its own AggregateTranslator in place.
W could create a gist of the ConfigurableAggregateTranslator
instead and link it in the docs if one don't like the decorator approach.
and use after_success instead of after_script for it
Currently:
$aggregateType = AggregateType::fromAggregateRootClass($config['aggregate_type']);
see https://github.com/prooph/event-sourcing/blob/master/src/Container/Aggregate/AggregateRepositoryFactory.php#L85
Maybe it should be AggregateType::fromString($config['aggregate_type']);
or maybe like this:
if (class_exists($config['aggregate_type'])){
$aggregateType = AggregateType::fromAggregateRootClass($config['aggregate_type']);
} else {
$aggregateType = AggregateType::fromString($config['aggregate_type']);
}
Maybe this can be considered a BC? I don't know.
Thoughts?
By accident I used an ID of a differently AR type to query with the AggregateRepository::get(id) with an ID for a different AR type will return the found AR off the incorrect type.
I got saved by the Type hinting, but shouldn't AggregateRepository return null in such a case? I think it is due to caching and/or snapshots, which bypasses detecting the types.
Not if this is a severe issue, posting for reference...
PHP Fatal error: Uncaught TypeError: Return value of HF\Api\Infrastructure\Repository\Dossier\DossierAggregateRepository::get() must be an instance of HF\Api\Domain\Dossier\Aggregate\Dossier or null, instance of HF\Api\Domain\Dossier\Aggregate\Order returned in /home-projects/api-plhw-testing/deploy/releases/20181023091400UTC/src/Infrastructure/Repository/Dossier/DossierAggregateRepository.php:37
Stack trace:
#0 /home-projects/api-plhw-testing/deploy/releases/20181023091400UTC/src/Domain/Dossier/Command/Handler/RemoveAttachmentFromDossierHandler.php(44): HF\Api\Infrastructure\Repository\Dossier\DossierAggregateRepository->get(Object(HF\Api\Domain\Dossier\VO\DossierId))
#1 /home-projects/api-plhw-testing/deploy/releases/20181023091400UTC/vendor/prooph/service-bus/src/CommandBus.php(47): HF\Api\Domain\Dossier\Command\Handler\RemoveAttachmentFromDossierHandler->__invoke(Object(HF\Api\Domain\Dossier\Command\RemoveAttachmentFromDossier))
#2 /home-projects/api-plhw-testing/deploy/releases/20181023091400UTC/vendor/prooph/common/src/ in /home-projects/api-plhw-testing/deploy/releases/20181023091400UTC/vendor/prooph/service-bus/src/Exception/MessageDispatchException.php on line 26
I'm using Prooph in a project which is running on php7.0. Hence I'm locked on prooph/event-sourcing:v4.x
.
Do you accept updates on older versions? Changes would be to upgrade ramsey/uuid
which is being used in the v4
.
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.