Git Product home page Git Product logo

event-sourcing's Introduction

ProophEventSourcing

Simple and lightweight event sourcing library with out of the box support for ProophEventStore

Build Status Coverage Status Gitter

Important

This library will receive support until December 31, 2019 and will then be deprecated.

For further information see the official announcement here: https://www.sasaprolic.com/2018/08/the-future-of-prooph-components.html

Installation

You can install ProophEventSourcing via composer by adding "prooph/event-sourcing": "^5.0" as requirement to your composer.json.

Usage

Our quickstart should give you a starting point. It's a very small domain but shows you the useage of ProophEventSourcing and the integration with ProophEventStore.

ProophEventStore Integration

ProophEventSourcing ships with a ProophEventStore AggregateTranslator to connect the store with the bundled AggregateRoot.

Support

Used Third-Party Libraries

event-sourcing's People

Contributors

basz avatar brammm avatar codeliner avatar fritz-gerneth avatar geekcom avatar jdrieghe avatar jpkleemans avatar oqq avatar orkin avatar prolic avatar sandrokeil avatar xerkus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

event-sourcing's Issues

Add snapshot strategy

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.

Aggregates not automatically saving

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?

Update Prooph/Common dependency version to ^3

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

Blueprint idea

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?

Exceptions will be thrown if aggregate has no pending events

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.

Multiple Event Handlers

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

Change event handling methods

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:

  • performance
  • possibilty to call when-event-name-methods within is still possible
  • use of custom event names (f.e. using dots) is easy

refers to: prooph/service-bus#109

New beta release

This package needs a new beta release, since the current one uses $stream->streamEvents() instead of Iterator.

Snapshot args incorrect

[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

AggregateType in RepositoryFactory

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?

@codeliner @basz @lunetics

Keep/add the aggregate in the indentity map after saving

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.

Remove ConfigurableAggregateTranslator?

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.

Prooph Event Sourcing Revival

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

How to check if an aggregate ID exists?

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).

Allow encapsulation of AggregateRepository

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:

  • My repositories suddenly exposes more public functions than i like (for example getAggregateRoot).
  • it it makes testing more difficult

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.

Missing aggregate ID

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.

Keeping up with PHP versions

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.

Delay event uuid generation until it is requested or external generator supplied

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
}

Use DossierAggregateRepository::get() with ID of different AR Type

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.