Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Event Store Laravel Package

event-engine/php-event-store

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps for Laravel Integration

  1. Install the Package

    composer require event-engine/php-event-store
    
  2. Define a Laravel-Compatible Event Store Create a concrete implementation of EventEngine\EventStore\EventStore:

    namespace App\Services;
    
    use EventEngine\EventStore\Event;
    use EventEngine\EventStore\EventStore;
    use Illuminate\Support\Facades\DB;
    
    class DatabaseEventStore implements EventStore
    {
        public function append(Event $event): void
        {
            DB::table('events')->insert([
                'aggregate_id' => $event->getAggregateId(),
                'event_name'  => $event->getName(),
                'data'        => json_encode($event->getData()),
                'version'     => $event->getVersion(),
                'occurred_on' => now(),
            ]);
        }
    
        public function load(string $aggregateId, int $fromVersion = 0, int $toVersion = PHP_INT_MAX): array
        {
            return DB::table('events')
                ->where('aggregate_id', $aggregateId)
                ->whereBetween('version', [$fromVersion, $toVersion])
                ->orderBy('version')
                ->get()
                ->map(fn ($event) => new Event(
                    $event->event_name,
                    json_decode($event->data, true),
                    $event->version,
                    $event->aggregate_id,
                    $event->occurred_on
                ));
        }
    }
    
  3. Bind the Store to Laravel’s Container In AppServiceProvider@boot():

    public function boot()
    {
        $this->app->singleton(EventStore::class, function () {
            return new DatabaseEventStore();
        });
    }
    
  4. First Use Case: Storing and Loading Events

    use EventEngine\EventStore\Event;
    
    // Store an event
    $eventStore = app(EventStore::class);
    $event = new Event('user.registered', ['email' => 'user@example.com'], 1, 'user-123');
    $eventStore->append($event);
    
    // Load events for an aggregate
    $events = $eventStore->load('user-123');
    foreach ($events as $event) {
        // Process events (e.g., rebuild aggregate state)
    }
    
  5. Create a Migration for Events Table

    php artisan make:migration create_events_table
    
    public function up()
    {
        Schema::create('events', function (Blueprint $table) {
            $table->id();
            $table->string('aggregate_id');
            $table->string('event_name');
            $table->json('data');
            $table->unsignedInteger('version');
            $table->timestamp('occurred_on');
            $table->index(['aggregate_id', 'version']);
        });
    }
    

Implementation Patterns

1. Aggregate Root with Event Sourcing

Leverage the load method to reconstruct aggregate state:

namespace App\Domain;

use EventEngine\EventStore\EventStore;
use EventEngine\EventStore\Event;

class UserAggregate
{
    private array $events = [];
    private string $id;
    private int $version = 0;

    public function __construct(private EventStore $eventStore, string $id)
    {
        $this->id = $id;
        $this->loadFromStore();
    }

    private function loadFromStore(): void
    {
        $this->events = $this->eventStore->load($this->id);
        $this->version = count($this->events);
        $this->replayEvents();
    }

    private function replayEvents(): void
    {
        foreach ($this->events as $event) {
            $this->apply($event);
        }
    }

    private function apply(Event $event): void
    {
        $this->version = $event->getVersion();
        switch ($event->getName()) {
            case 'user.registered':
                $this->handleRegistered($event->getData());
                break;
            case 'user.email.updated':
                $this->handleEmailUpdated($event->getData());
                break;
        }
    }

    // Domain methods...
}

2. Event-Driven Laravel Commands

Use the event store to persist command outcomes:

namespace App\Console\Commands;

use App\Domain\UserAggregate;
use EventEngine\EventStore\Event;
use Illuminate\Console\Command;

class RegisterUserCommand extends Command
{
    public function handle()
    {
        $userId = 'user-' . Str::uuid()->toString();
        $aggregate = new UserAggregate(app(EventStore::class), $userId);

        $aggregate->register(
            $this->ask('Email'),
            $this->ask('Name')
        );

        // Events are automatically appended to the store via the aggregate
    }
}

3. Event Listeners for Side Effects

Integrate with Laravel’s event system to trigger side effects:

namespace App\Listeners;

use EventEngine\EventStore\Event;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

class UserRegisteredListener implements ShouldQueue
{
    use InteractsWithQueue;

    public function handle(Event $event)
    {
        if ($event->getName() === 'user.registered') {
            // Send welcome email, log activity, etc.
        }
    }
}

4. CQRS: Read Models from Events

Project events into read models for queries:

namespace App\Services;

use EventEngine\EventStore\EventStore;
use Illuminate\Support\Facades\DB;

class UserReadModel
{
    public function __construct(private EventStore $eventStore)
    {}

    public function sync(string $userId)
    {
        $events = $this->eventStore->load($userId);
        DB::table('user_profiles')->upsert(
            $events->map(fn ($event) => [
                'id' => $userId,
                'email' => $event->getData()['email'] ?? null,
                'name' => $event->getData()['name'] ?? null,
                'updated_at' => $event->getOccurredOn(),
            ])
        );
    }
}

5. Testing Event Sourcing Logic

Mock the event store in tests:

namespace Tests\Unit;

use EventEngine\EventStore\Event;
use EventEngine\EventStore\EventStore;
use Tests\TestCase;

class UserAggregateTest extends TestCase
{
    public function testAggregateReconstruction()
    {
        $mockStore = $this->createMock(EventStore::class);
        $mockStore->method('load')
            ->willReturn([
                new Event('user.registered', ['email' => 'test@example.com'], 1, 'user-1'),
            ]);

        $aggregate = new UserAggregate($mockStore, 'user-1');
        $this->assertEquals('test@example.com', $aggregate->getEmail());
    }
}

6. Laravel Service Provider Integration

Centralize event store configuration:

namespace App\Providers;

use EventEngine\EventStore\EventStore;
use Illuminate\Support\ServiceProvider;

class EventStoreServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(EventStore::class, function ($app) {
            return new \App\Services\DatabaseEventStore(
                $app->make(\Illuminate\Database\Connection::class)
            );
        });
    }
}

Gotchas and Tips

Pitfalls

  1. Versioning Conflicts

    • Issue: If two processes append events for the same aggregate without version checks, events may be out of order.
    • Fix: Implement optimistic locking in your append method:
      public function append(Event $event): void
      {
          $currentVersion = DB::table('events')
              ->where('aggregate_id', $event->getAggregateId())
              ->max('version') ?? 0;
      
          if ($event->getVersion() !== $currentVersion + 1) {
              throw new \RuntimeException('Version conflict');
          }
      
          // Proceed with insertion
      }
      
  2. Event Data Serialization

    • Issue: JSON encoding/decoding may fail for complex objects (e.g., DateTime, resources).
    • Fix: Normalize event data to primitive types or use a serializer:
      use Symfony\Component\Serializer\SerializerInterface;
      
      $serializer = app(SerializerInterface::class);
      $data = $serializer->serialize($eventData, 'json');
      
  3. Performance with Large Aggregates

    • Issue: Loading thousands of events for an aggregate can be slow.
    • Fix: Implement snapshots or pagination:
      public function load(string $aggregateId, int $fromVersion = 0, int $toVersion = PHP_INT_MAX, int $limit = 1000): array
      {
          return DB::table('events')
              ->where('aggregate_id', $aggregateId)
              ->whereBetween('version', [$fromVersion, $toVersion])
              ->orderBy('version')
              ->limit($limit)
              ->get()
              ->map(...);
      }
      
  4. **Laravel’s Queue System Conf

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky