Giter Club home page Giter Club logo

laravel-event-sourcing's Introduction

Event sourcing for Artisans 📽

Latest Version on Packagist Tests Total Downloads

This package aims to be the entry point to get started with event sourcing in Laravel. It can help you with setting up aggregates, projectors, and reactors.

If you've never worked with event sourcing, or are uncertain about what aggregates, projectors and reactors are head over to the getting familiar with event sourcing section in our docs.

Event sourcing might be a good choice for your project if:

  • your app needs to make decisions based on the past
  • your app has auditing requirements: the reason why your app is in a certain state is equally as important as the state itself
  • you foresee that there will be a reporting need in the future, but you don't know yet which data you need to collect for those reports

If you want to skip to reading code immediately, here are some example apps. In each of them, you can create accounts and deposit or withdraw money.

Event sourcing in Laravel course

If you want to learn more about event sourcing, check out our course on event sourcing in Laravel

Support us

We invest a lot of resources into creating best in class open source packages. You can support us by buying one of our paid products.

We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on our contact page. We publish all received postcards on our virtual postcard wall.

Documentation

You can find installation instructions and detailed instructions on how to use this package at the dedicated documentation site.

Upgrading from laravel-event-projector

This package supercedes laravel-event-projector. It has the same API. Upgrading from laravel-event-projector to laravel-event-sourcing is easy. Take a look at our upgrade guide.

Changelog

Please see CHANGELOG for more information what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you've found a bug regarding security please mail [email protected] instead of using the issue tracker.

Postcardware

You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using.

Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium.

We publish all received postcards on our company website.

Credits

The aggregate root functionality is heavily inspired by Frank De Jonge's excellent EventSauce package. A big thank you to Dries Vints for giving lots of valuable feedback while we were developing the package.

Footnotes

1 Quote taken from Event Sourcing made Simple

License

The MIT License (MIT). Please see License File for more information.

laravel-event-sourcing's People

Contributors

27pchrisl avatar adrianb93 avatar adrianmrn avatar avosalmon avatar ayoobmh avatar brendt avatar colinc avatar deanlj avatar dilab avatar erikgaal avatar freekmurze avatar gdebrauwer avatar immeyti avatar inmanturbo avatar inxilpro avatar juukie avatar laravel-shift avatar morrislaptop avatar nielsvanpach avatar pascalbaljet avatar robertbaelde avatar rodrigopedra avatar rubenvanassche avatar salehhashemi1992 avatar sebastiandedeyne avatar soarecostin avatar sohaibafifi avatar telkins avatar unreasonablymundane avatar wouterbrouwers 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

laravel-event-sourcing's Issues

Question - more complex business rules

Hi Guys, I really love the idea of event sourcing but am just starting with it. I have watched your video about 10 times and read the docs many times, as well as a lot of other resources on event sourcing across the web. I get the concept but the implementation is much harder.

Your examples with bank balance are easy to follow, understand and implement but I would love to see how more complex business rules could be implemented.

I know this is not an issue and I have started a question on stack overflow - https://stackoverflow.com/questions/58215808/laravel-event-sourcing-spatie-how-to-deal-with-more-complex-business-rules

I would love to get your thoughts on this!

Update docs: meaning of "You should make sure that the queue will process only one job at a time."

So in the docs on https://docs.spatie.be/laravel-event-sourcing/v1/installation-setup/ there's this section

It’s recommended that should set up a queue. Specify the connection name in the queue key of the event-sourcing config file. This queue will be used to guarantee that the events will be processed by all projectors in the right order. You should make sure that the queue will process only one job at a time. In a local environment, where events have a very low chance of getting fired concurrently, it’s probably ok to just use the sync driver.

It's probably helpful to provide a couple of examples of how to do this, e.g. for horizon I believe it's a case of adding something like the following to the horizon.php config file.

'environments' => [
    'production' => [
        //...
        'event-sourcing-supervisor-1' => [
            'connection' => 'redis',
            'queue' => [env('EVENT_PROJECTOR_QUEUE_NAME')],
            'balance' => 'simple',
            'processes' => 1,
            'tries' => 3,
        ],
    ],

    'local' => [
        //...
        'event-sourcing-supervisor-1' => [
            'connection' => 'redis',
            'queue' => [env('EVENT_PROJECTOR_QUEUE_NAME')],
            'balance' => 'simple',
            'processes' => 1,
            'tries' => 3,
        ],
    ],
]

I've opened an issue rather than a PR as I can not think of a great way to word this.

Making sure projector get handled in the right order

Maybe this is something i could not find on the docs, so my case is i have 2 projectors listening to the same event

one projector to update a model second one to log the status of that model

so for me the second projector happen first, storing the wrong(old) values on the log.

So of course i would like to make a pr maybe i can update the docs or the example to make rhis more clear, but i dont understand how to achieve this.

Regards

DateTimeImmutable Serialization issue

My Event has a DateTimeImmutable object:

<?php

namespace App\Bookings\Event;

use App\ValueObject\BookingSlot;
use DateTimeImmutable;
use Spatie\EventSourcing\ShouldBeStored;

final class BookingStarted implements ShouldBeStored
{
    public int $userId;
    public int $locationId;
    public string $carAggregateId;
    public DateTimeImmutable $bookingSlot;

    public function __construct(int $userId,
                                int $locationId,
                                string $carAggregateId,
                                DateTimeImmutable $bookingSlot)
    {
        $this->userId = $userId;
        $this->locationId = $locationId;
        $this->carAggregateId = $carAggregateId;
        $this->bookingSlot = $bookingSlot;
    }


}

When playing the event in my Unit Test:

 $bookingStarted = new BookingStarted(
            $userId = 100,
            $locationId = 2,
            $carAggregateId = Uuid::uuid4()->toString(),
            new \DateTimeImmutable('now');
        );

BookingRoot::fake($bookingStarted)
  ->start();

I have received the following issue:

Caused by
Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException: Cannot create an instance of DateTimeImmutable from serialized data because its constructor requires parameter "time" to be present.

/vagrant/vendor/symfony/serializer/Normalizer/AbstractNormalizer.php:505
/vagrant/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php:237
/vagrant/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php:335
/vagrant/vendor/symfony/serializer/Serializer.php:191
/vagrant/vendor/symfony/serializer/Normalizer/AbstractNormalizer.php:530
/vagrant/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php:486
/vagrant/vendor/symfony/serializer/Normalizer/AbstractNormalizer.php:496
/vagrant/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php:237
/vagrant/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php:335
/vagrant/vendor/symfony/serializer/Serializer.php:191
/vagrant/vendor/symfony/serializer/Serializer.php:142
/vagrant/vendor/spatie/laravel-event-sourcing/src/EventSerializers/JsonEventSerializer.php:39
/vagrant/vendor/spatie/laravel-event-sourcing/src/StoredEvent.php:45
/vagrant/vendor/spatie/laravel-event-sourcing/src/Models/EloquentStoredEvent.php:32
/vagrant/vendor/spatie/laravel-event-sourcing/src/EloquentStoredEventRepository.php:63
/vagrant/vendor/spatie/laravel-event-sourcing/src/EloquentStoredEventRepository.php:71
/vagrant/vendor/spatie/laravel-event-sourcing/src/AggregateRoot.php:50
/vagrant/vendor/spatie/laravel-event-sourcing/src/FakeAggregateRoot.php:30
/vagrant/vendor/spatie/laravel-event-sourcing/src/AggregateRoot.php:111
/vagrant/tests/Unit/Booking/AggregateRoot/BookingRootTest.php:58

Recording Events in a Reactor

I have a reactor that is running a job. When the job is complete, I wanted to record an event to the aggregate as to whether it succeeded or not.

Issues:

  • When I record an event to an aggregate in a reactor, an infinite loop is caused.
  • If I remove the reactor before persisting the new event, a duplicate of the originally reacted to event is persisted.

I can't find a way to make this work. I'm sure this won't be a problem if I queue the job, but my tests suite runs jobs synchronously.

Examples of the issues:

// Infinite loop happens:
public function onSomethingStarted(SomethingStarted $event, StoredEvent $storedEvent, string $aggregateUuid): void
{
    try {
        Something::uuid($aggregateUuid)->doSomething();

        ExampleAggregate::retrieve($aggregateUuid)->recordThat(new SomethingCompleted)->persist();
    } catch (Exception $error) {
        ExampleAggregate::retrieve($aggregateUuid)->recordThat(new SomethingFailed)->persist();
    }
}

// Two `SomethingStarted` events get recorded:
public function onSomethingStarted(SomethingStarted $event, StoredEvent $storedEvent, string $aggregateUuid): void
{
    Projectionist::withoutEventHandler(ExampleReactor::class);

    try {
        Something::uuid($aggregateUuid)->doSomething();

        ExampleAggregate::retrieve($aggregateUuid)->recordThat(new SomethingCompleted)->persist();
    } catch (Exception $error) {
        ExampleAggregate::retrieve($aggregateUuid)->recordThat(new SomethingFailed)->persist();
    }

    Projectionist::addReactor(ExampleReactor::class);
}

I'm open to submitting a PR for this if there isn't a way to record an event from a reactor. Might just need some guidance on a preferred approach.

php 7.3

I have a laravel 6 app running on a php7.2, is it really necessary for this package to install to run it on php 7.3?

store unicode into marianDB

This is my env:

  • DB: MarianDB: 10.3.14
  • PHP: 7.4
  • composer:
    -- "require": {
    "php": "^7.2.5",
    "cerbero/laravel-enum": "^1.2",
    "fideloper/proxy": "^4.3.0",
    "guzzlehttp/guzzle": "^6.3",
    "laravel/framework": "7.5.2",
    "laravel/helpers": "^1.2.0",
    "laravel/passport": "^8.4.3",
    "laravel/tinker": "^2.4.0",
    "laravel/ui": "^2.0",
    "league/flysystem-aws-s3-v3": "^1.0",
    "maatwebsite/excel": "^3.1",
    "sentry/sentry-laravel": "1.7.0",
    "spatie/laravel-event-sourcing": "^3.1",
    "spatie/laravel-permission": "^3.6",
    "writingink/wink": "^0.2"
    },

When I store event into stored_events table, data at event_properties column will like this:
{"data":{"_token":"xy6f18aLmexqgW9ugRm3EWDcY9ZaX5mAhiFRmbOs","name":"\u0110\u1ee9c - Customer 3","email":"[email protected]","password":"123456","password_confirmation":"123456","uuid":"fcf7fa80-27f5-413a-b5cf-ce20c5571702"}}

I tried with mysq 5.7, coumn will like this:
{"data": {"name": "Đức customer 5", "uuid": "e86d939b-8141-4d36-9429-17c5b566be0f", "email": "[email protected]", ...

The different is "name" attribute. mysql store "name": "Đức customer 5", but mariaDB store "name":"\u0110\u1ee9c - Customer 3".

I tested with sql command on mariaDB:

insert into stored_events (event_class, event_properties, meta_data)
values ('sg', '{"test":"Đức"}', 'sg')

It still work well!

How to store data like mysql 5.7?

Migration - Needs a "down()"

Not sure how to submit a PR and if it's worth it for a small issue like this.

the file create_stored_events_table.php.stub needs simple down() method to remove the created table.

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('stored_events');
    }

When not using json serialized events, the library still passes through the event properties as json to the desirializer

I'm using a custom serializer that simply uses the php serialize and unserialize function.
This means my event_properties aren't json.

You can see on line 47:

$this->event = app(EventSerializer::class)->deserialize(
self::getActualClassForEvent($this->event_class),
json_encode($this->event_properties)
);
the event_properties are passed through as json_encoded string. I suspect this is done because you cast it in the default model to an array and you need to pass a string to the deserialization method.

In my case the event_properties is a string, so no encoding needed.

CouldNotPersistAggregate with message 'not persist aggregate ActionPointAggregate (uuid: ) because it seems to be changed by another process after it was retrieved in the current process. Expect to persist events after version 0, but version 1 was already persisted.'

Hello,

I am wondering if there is a way to incorporate Spatie Event Sourcing with Behat (Gherkin)?

I am holding onto AggregateRoot references throughout the entire lifecycle of each scenario in order to test the "behaviour" of my business logic.

What I am finding is, due to the way AggregateRoot::fake() is implemented, there can be a clash when attempting to use the testing in the manner in which it was originally designed; assuming targeted for PHPUnit where tearDown() will rollback any data so there would never be a "blank" uuid which would clause version classes among different aggregate roots or events.

The tests can obviously be made to work by actually using the AggregateRoots normally (by providing a UUID and persisting to SQLIte) - but I am wondering if there's another way around this so we can use the testing in the way it was supposed to be used.

Thanks,

Kind Regards,
Michael

[Question] Is it a good idea to convert User registration into event sourcing?

Hello,

I am wondering if it is a good idea to convert User registration into event sourcing.

For example, in order to handle user registration, the RegisterController could be modified to retrieve a UserAggregate and persist the user data.

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    {
        /*return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);*/

        $uuid = Str::uuid();
        UserAggregate::retrieve($uuid)->create($data)->persist();

        return User::uuid($uuid);
    }

The same behavior could be extended to other Auth controllers, like password resets and email verification.

Is this a good idea? Has anybody experienced any issue/stopper by applying these modifications?

Thank you very much.

Projectors and Reactors still trigger in tests despite using Event::fake()

Hi,

according to the docs when testing Aggregates you can prevent Projectors and Reactors from being fired by using Laravels \Illuminate\Support\Facades\Event::fake();. I tried it but they (Projectors and Reactors) are still triggered when building the Aggregate with the given method.

Tried it with a fresh project. Maybe it doesn't work as expected or I did a mistake. If I understood the Event::fake() correctly "global" Events (not related to a Model), are not faked?

Tried the following in a fresh installed project.

Event:

<?php

namespace App\StorableEvents;

use Spatie\EventSourcing\ShouldBeStored;

final class TestHappened implements ShouldBeStored
{
    /** @var string */
    public $text;

    public function __construct(string $text)
    {
        $this->text = $text;
    }
}

Aggregate:

<?php

namespace App\Aggregates;

use Spatie\EventSourcing\AggregateRoot;
use App\StorableEvents\TestHappened;

final class TestRootAggregate extends AggregateRoot
{
    public function doSomething(string $text)
    {
        $this->recordThat(new TestHappened($text));

        return $this;
    }
}

Projector (tested with Reactor too, same code):

<?php

namespace App\Projectors;

use App\StorableEvents\TestHappened;
use Spatie\EventSourcing\Projectors\Projector;
use Spatie\EventSourcing\Projectors\ProjectsEvents;

final class TestProjector implements Projector
{
    use ProjectsEvents;

    protected $handlesEvents = [
        TestHappened::class => 'onTestHappened'
    ];

    public function onTestHappened(TestHappened $event)
    {
        // This should not be echo'd while running the tests.
        echo PHP_EOL.get_class($this).': '.$event->text.PHP_EOL;
    }
}

Test:

<?php

namespace Tests\Unit;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
use App\Aggregates\TestRootAggregate;
use App\StorableEvents\TestHappened;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * A basic test example.
     *
     * @return void
     */
    public function testProjectorIsNotTriggeredWhenEventsAreFaked()
    {
        Event::fake(); // Do not trigger Projectors and Reactors

        TestRootAggregate::fake()
            ->given(new TestHappened('First Test')) // This one should not be projected
            ->when(static function (TestRootAggregate $root) {
                $root->doSomething('Second Test');
            })
            ->assertRecorded(new TestHappened('Second Test'));
    }
    // ...
}

Test result (highlighted output should not be displayed/echoed):
Screenshot

Can't unserialize event which contains Carbon property type

I try to create an event with Illuminate\Support\Carbon property , i get an exception
"message": "Failed to unserialize an event with class App\\Aggregates\\Reward\\Events\\PointGave on stored event with id 6. Are you sure that event class exists? ", "exception": "Spatie\EventSourcing\Exceptions\InvalidStoredEvent", ...

How to resolve this issue without a custom event serializer implementaion ??
Thanks for your answers!

TDD with AggregateRoots

I tried to read the documentation and see the example projects, but I did not figure out the best approach to develop an AggregateRoots in TDD.

I have several issues, maybe many of them not related to the package directly but I think it worth to be discussed here:

In the example project, you use primitive types as an AggregateRoot function params like $amount, or $name but for more complex examples the event may need 5+ pieces of information to be passed from the controller or the client-side.

When we write the tests, we use Model Factories to easily initiate a model data, how can we benefit from the same concept to generate the payloads of the events.

In addition to that, I did not figure out how can I make some assertion about the AggregateRoot internal functions like:

$this->assertEquals(1000,$root->initial_balance) after a root = WalletAggregateRoot::fake().... When I dd the root it seems that the aggregate root is these but private so we cannot access it or make any assertion?

Also, how can I test actions that need two aggregates collaboration? for example, in the larabank if I need to transfer amount between account to account, the transfer command will lead to invoke the deposit and withdraw from two different aggregate, and these internal events will not appear in the current testing mechanism.

I think these issues can be solved if you provide a solid test suite for the larabank example especially if you add an event that includes 5+ element on it to make the example more real world.

If any of the issues above not clear, I can provide you with some code snippets. 👍

Please publish a new version

Right now after installation things fail, as the currently published version does not publish the snapshots table migration.

There has been a pr merged to add this in, but the latest version on composer does not have this.

Question about folder structure when using artisan make

By looking at the banking example, all the code of a domain is grouped in a domain folder under app\Domain\Account. When I installed the package in my Laravel 6 application it installed v3, I've noticed that that banking app is using 1.0. When I created my aggregate and event it ended up in app\Aggregates and the events also in their own folder. I prefer to keep things that belong to a domain in one folder, like the banking app. Is there any way of do this or can I simply move the classes around after I created them with make?

I'm new to Laravel and the ES library but I've wrote my own framework agnostic implementation before. https://github.com/world-Architects/event-sourcing I think I might contribute to the Lara ES repository, so far I like what I see. 👍

The only thing I don't like is that the persistence is built into the aggregate instead of passing the aggregate to a repository. This feels somehow wrong to me personally because it adds coupling and IMHO inverses the responsibility.

Release of version 1.1.0 is actually version 2

Hi,

according to the changelog, 1.1.0 is supposed to contain the change countAllStartingFrom. But after updating with composer to 1.1.0 the package now requires PHP 7.4 (the major change of v2). It also doesn't contain the changes related to countAllStartingFrom.

It looks like the tag has been related to a wrong commit, it points to a merge commit on the master branch.

I'm not sure, what the best way would be to solve this. It's just that at the moment 1.1.0 doesn't contain the desired feature, and depends on PHP 7.4 ...

Scheduling commands or events

Hey, some ES/CQRS implementations provide scheduling capabilities out-of-the-box.

Use case example:

  • an order is placed
  • the order is delivered to the buyer (at this moment, we schedule a job to ask the buyer for feedback in 1 hour)
  • user is asked for feedback

Reference from Elixir's Commanded: https://hexdocs.pm/commanded_scheduler/Commanded.Scheduler.html

Are there any plans to incorporate such feature into this library? Or could you provide any guidance on how you'd accomplish the same result by integrating this library with some scheduler tool?

Edit: I'm not familiar with PHP or the Laravel ecosystem.

Integration with hyn/multi-tenant and PHPUnit

Hi!

I have a weird issue when using this package with hyn/multi-tenant. When I am running tests with PHPUnit everything fails after the first test.

Illuminate\Contracts\Container\BindingResolutionException: Unresolvable dependency resolving [Parameter #0 [ <required> string $storedEventRepository ]] in class Spatie\EventSourcing\EventSubscriber

Looks like the singleton binding for the EventSubscriber isn't used/ran after the first test is run. The class doesn't receive the required $storedEventRepository parameter when created, which should contain the value in the config file (config('event-sourcing.stored_event_repository')).

If I hardcode a fallback inside the class the tests run, but this isn't a viable option obviously...

namespace Spatie\EventSourcing;

final class EventSubscriber
{
    /** @var \Spatie\EventSourcing\StoredEventRepository */
    private $repository;

    public function __construct(string $storedEventRepository = '')
    {
        if ( ! $storedEventRepository ) {
            $storedEventRepository = config('event-sourcing.stored_event_repository');
        }
        $this->repository = app($storedEventRepository);
    }

    // Other methods
}

My test isn't related to any functionnality provided by either of the two packages.

I know this is most likely not an issue with this package in particular, nor with hyn/multi-tenant but maybe a functionality that is used by both packages and therefore creates a conflict?

Hoping you have an idea of where this could come from, because I'm shooting dd() in the dark right now 😅

Happy holidays!

composer require laravel-event-sourcing problem

when i try to install the package with composer in my laravel project i get this problem

Illuminate\Foundation\ComposerScripts::postAutoloadDump

@php artisan package:discover --ansi
PHP TypeError: Argument 1 passed to Spatie\EventSourcing\EventSubscriber::__construct() must be of the type string, null given, called in /var/www/html/livebox-laravel/vendor/spatie/laravel-event-sourcing/src/EventSourcingServiceProvider.php on line 59 in /var/www/html/livebox-laravel/vendor/spatie/laravel-event-sourcing/src/EventSubscriber.php on line 10

Question - Implementing rollback/undo functionality

Hi,

thanks for another awesome package. I have a question which I can't wrap my head around.

I would like to enable end-user to be able to go back to any point in time by giving him an option to go back to some specific point in the stored_events table and "reset" state to that point.

How would you approach this problem? I am thinking of replaying certain aggregate root to a specific point and find out the actual state at that point and then adding another event to reset state. Does that sound fine?

Thanks for your answers!

Issue when using with Telescope ViewWatcher

Hi!

If I use the laravel-event-sourcing package with Laravel Telescope, I get the following error when trying to open any page on the site:

ReflectionException
Function Spatie\EventSourcing\EventSubscriber@handle() does not exist

in: vendor/laravel/telescope/src/Watchers/ViewWatcher.php:123

This happens after installing both packages freshly with their default configurations.

The error goes away, if I disable Telescope's view watcher.

I would be happy to send a PR to solve this, but I couldn't identify the root of the issue.

Used versions:
Laravel: 6.2
Telescope: 2.1
laravel-event-sourcing: 2.0, but it also happens with v1

Question - Could it be slow to recover large amounts of events?

I have a project focused on money transactions, I just read the excellent documentation of this repository and I think it could be of great help in my project.

But I have some doubts, the database of this type of projects generally grows very fast, I feel that aggregators when reconstructing all events (for example, 1M of transactions) would be somewhat slow.

Otherwise, if everything is super fast, would it be convenient to create a separate project to record the events and use the projectors?

I had never used event sourcing and I think it's great, I hope I don't bother with the questions.

Greetings.

Aggregate not replaying events since V3 upgrade.

Hello,

The issue seems to be that since I have upgraded to version 3 and followed the steps in the upgrade guide, the events based on an aggregate are now no longer being replayed.

I am assuming that this is because the aggregate_version column in the database has a NULL value for every stored event. Is there a way already available to fix this, or is there a suggested way that this could be done.

I appreciate any feedback.

Thanks.

installation failing

On require spatie/laravel-event-sourcing, this is what I get.
image

Until I delete the package, every command, e.g. php artisan serve, will result in an error:
image

Any idea?

Utilise composer's PSR-4 autoload for projector/reactor auto-discovery?

Would it be a useful idea to leverage composer's PSR-4 autoload for paths to scan for projectors and reactors? For instance, I have:

{
    // ...
    "autoload": {
        "classmap": [
            "database/seeds",
            "database/factories"
        ],
        "psr-4": {
            "App\\": "app/",
            "Domain\\": "src/"
        }
    },
    // ...
}

Also, might it not be more efficient to use composer's autoload as a source instead of using Finder to traverse the filesystem?

Just a thought.

Simple fix to migration

Add to create_stored_events_table.php.stub so rollback and refresh works :)

public function down()
{
Schema::dropIfExists('stored_events');
}

Adding a bisect command

I was thinking whether it would be beneficial to add some kind of event-projector:bisect command, which you could pass two event UUIDs to debug bugs, similar to what git does with commits.

Option to get all events for one account

Hi there, this is more a question than it is an issue.
I'm trying to figure out how I can get a list of all events for specific account.
An example from Larabank. I would like to create a list of transaction for specific account, when user deposited or withdraw the money from the account to give a user option to see the activity on their account.
By using event replay I can see that it's possible, but I'm not sure how to get to that data.
Thanks.

How Does This Compare To The ActivityLog Package?

We have been using the Activity Log Package since before spatie started creating these event sourcing packages.

I feel that laravel-activity-log and laravel-event-sourcing are used to solved very similar problems, whereas the latter is a bit more elaborate and sticks to a common pattern, but I am not really that deep into event sourcing. So what are the main differences of these two?
Would it even make sense to use both packages within the same app? I guess what we currently do in some cases is actually a very simple form of event sourcing by using the activity-log package. So I am thinking about refactoring our app to rely on the event sourcing package only, because I feel its more strict and expressive, because it uses a common pattern you can remember and refer to.

What do you think?

Installation fails if config is cached

If you've ever run php artisan config:cache prior to installation you will get the following error:

> @php artisan package:discover --ansi

   Symfony\Component\Debug\Exception\FatalThrowableError  : Argument 1 passed to Spatie\EventSourcing\EventSubscriber::__construct() must be of the type string, null given, called in spatie/laravel-event-sourcing/src/EventSourcingServiceProvider.php on line 58

  at spatie/laravel-event-sourcing/src/EventSubscriber.php:9
     5| final class EventSubscriber
     6| {
     7|     private StoredEventRepository $repository;
     8| 
  >  9|     public function __construct(string $storedEventRepository)
    10|     {
    11|         $this->repository = app($storedEventRepository);
    12|     }
    13|

  Exception trace:

  1   Spatie\EventSourcing\EventSubscriber::__construct()
      spatie/laravel-event-sourcing/src/EventSourcingServiceProvider.php:58

  2   Spatie\EventSourcing\EventSourcingServiceProvider::Spatie\EventSourcing\{closure}(Object(Illuminate\Foundation\Application), [])
      laravel/framework/src/Illuminate/Container/Container.php:799

  Please use the argument -v to see more details.
Script @php artisan package:discover --ansi handling the post-autoload-dump event returned with error code 1

Installation failed, reverting ./composer.json to its original content.

I now realize config:cache is recommended for production only:

https://laravel.com/docs/6.x/configuration#configuration-caching
You should typically run the php artisan config:cache command as part of your production deployment routine. The command should not be run during local development as configuration options will frequently need to be changed during the course of your application’s development.

Running php artisan config:clear before installing via composer require... will prevent this issue from occurring, but if you're already hitting this error you will need to run rm -rf vendor && composer update && php artisan config:clear.

I'm not sure if making a note of this is important enough to go in the installation instructions, but if you like I can submit a PR.

`Larabank` examples are invalid URLs in README

README.md file contains invalid urls for Larabanks examples:

I think should be as follows:

- [Larabank built with projectors](https://github.com/spatie/larabank-event-projector)
- [Larabank built with aggregates and projectors](https://github.com/spatie/larabank-event-projector-aggregates)

Missing created_at from snapshots

Seems just found a bug with snapshots during my testing. The created_at attribute is missing and the SQL raises an error.

Illuminate\Database\QueryException: SQLSTATE[HY000]: General error: 1364 Field 'created_at' doesn't have a default value (SQL: insert into snapshots (aggregate_uuid, aggregate_version, state) values (x, x, x)) in file ...

Snap Shots.

I have tried different event sourcing libraries for PHP and I find this easier and straight forward.
I request to add in a "snap shot" feature.

Thanks!

Returning back the data after we create/update it

I'm using spatie/laravel-event-sourcing library.

I'll explain my issue using food and beverage system logic.
Say that I have 2 classes, which is SalesOrder and Table. When SalesOrder is created using the event-sourcing library, I need to return back the created SalesOrder data because I need to assign the SalesOrder's id to the Table's sales_order_id property so that I can link to both SalesOrder->Table and SalesOrder<-Table, and by system I can get the SalesOrder's data only by Table's id.

Is there any ways to return the created/updated data back to my coding section where I need to assign the SalesOrder's id to Table's sales_order_id?

Concurrency transactions

I'm quite interested in spatie/event-sourcing packages, I tried to check how the concurrent operations is handled and looks for me that at the current implementation we use Transactional Locks.

  • I tried to concurrent Withdraw of same account and had to wait until 1st operation is done to process the 2nd operations.

  • I tried to concurrent Add operation for 2 different users and for reading was also had to wait for 1st operation.

How can I reproduce it

Simple, use this example spatie/larabank-aggregates and try to do concurrent operations

The goal

  • The goal of this issue to make sure that I'm right regarding the DB locks
  • What's the next plan to make it more flexibility as well as solves the OCC problem?

SchemalessAttributes needed?

Hi! Love the package!

I'm building custom repositories to store and load events from https://eventstore.com/ so I'm not using Eloquent at all.

I liked the use of StoredEvent, except for the SchemalessAttributes requirement. This type then enforces all of Eloquent to be brought in.

public SchemalessAttributes $meta_data;

At the moment I'm using a workaround, but it's not pretty!

        use Illuminate\Database\Eloquent\Model;
        $emptyModel = new class extends Model { };
        $model = new $emptyModel();
        $model->meta_data = $metadata;

        return new StoredEvent([
            ...
            'meta_data' => new SchemalessAttributes($model, 'meta_data'),
            ...
        ]);

Syntax error, unexpected '=>' (T_DOUBLE_ARROW), expecting ')'

I'm trying to install spatie/laravel-event-sourcing. It has no problem until it reached this point :

@php artisan package:discover --ansi

When it reached this problem, an error showed up, and it said :

In EventSourcingServiceProvider.php line 64:

syntax error, unexpected '=>' (T_DOUBLE_ARROW), expecting ')'

Script @php artisan package:discover --ansi handling the post-autoload-dump event returned with error code 1

Installation failed, reverting ./composer.json to its original content.

I located the file but I'm afraid to change the vendor files, worrying it might ruin the library.

Bring PHP version requirement in line with Laravel

Hello,

What's the reason that your packages require PHP7.4 and do not follow the same requirements as the Laravel framework.

When working in a business environment with large server farms, it's not always possible to keep up to date with the latest PHP versions.

I don't see any reason why older PHP version can't be supported. as there's nothing in PHP7.4 that important to warrant not supporting 7.2 or 7.3

Question - Best practises regarding CRUD events

Due to (strong) auditing requirements I am implementing event-sourcing in my latest client project. I've read the docs and watched the introductionary video, but I'm currently debating on how to go about "best practises" when using this library.

Let's say I got an Invoice model with attributes like hours and rate. Initially I thought about creating the following events:

  • CreateInvoice
  • UpdateInvoice
  • DeleteInvoice

However, looking at the examples provided, you've added events like MoneyAdded and MoneySubtracted.

So here are my questions:

  1. Should events be in the past or present?
    I think of the event store kind of like a git commit history. In which the "common practise" is to write them in present tense as if they're an action ("this commit will add, fix ...").
  2. Should I split up my fields into separate events?
    Instead of a general "Update" event, create extra ones like:
    • UpdateInvoiceHours
    • UpdateInvoiceRate

Some additional questions:

  1. How do you deal with future code changes?
    • What If I have changes in my code structure (event if it's just namespaces moving around), would I have to write a migration that "fixes" my past events?
    • What If I have had an "update" event and now split it up into more detailed events. Do I have to keep the previous instance?

Many thanks in advance!

[Proposal] Allow disabling reactors temporarily

Hi, all.

I'm implementing event sourcing on an existing app using the package and after moving the side-effects to reactors, I had a couple of tests failing. The project isn't using aggregators, but only events and eloquent models for now, and I have an observer that intercepts model changes and dispatches the correct events.

Tests were previously using model factories and I got myself wanting to disable reactors (temporarily). Something like this:

$model = Projectionist::withoutReactors(function () {
  // No reactors will be triggered
  return factory(MyModel::class)->create();
});

Some other possible syntax:

Projectionist::withoutReactors(); // clears up all reactors
Projectionist::withoutReactors([MySideEffect::class]); // disables only this specific reactor

Is this something we can have on the package? If so, I could send a PR.

The existing Projectionist::withoutEventHandlers() doesn't quite work for this case because I want the projectors to run, but not the reactors.

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.