Weave 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

Event Sourcing Laravel Package

averor/event-sourcing

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require averor/event-sourcing
    

    Ensure averor/message-bus is also installed (this package extends it).

  2. Publish Config

    php artisan vendor:publish --provider="Averor\EventSourcing\EventSourcingServiceProvider"
    

    Review config/event-sourcing.php for default settings (e.g., event storage, projection paths).

  3. Define an Event

    namespace App\Events;
    
    use Averor\EventSourcing\Event;
    
    class UserRegistered implements Event
    {
        public function __construct(
            public string $userId,
            public string $email
        ) {}
    }
    
  4. First Event Sourcing Workflow

    use Averor\EventSourcing\EventSourcedEntity;
    use App\Events\UserRegistered;
    
    class User extends EventSourcedEntity
    {
        protected $entityId;
    
        public function register(string $email): void
        {
            $this->recordThat(new UserRegistered($this->entityId, $email));
        }
    }
    
  5. Replay Events (Projection)

    use Averor\EventSourcing\Projection;
    
    class UserProjection implements Projection
    {
        public function __invoke(UserRegistered $event)
        {
            // Update your read model (e.g., database, cache)
        }
    }
    

Implementation Patterns

Core Workflows

1. Event Sourcing with Aggregates

  • Aggregate Root Pattern: Use EventSourcedEntity as the base class for domain aggregates.
    class Order extends EventSourcedEntity
    {
        public function place(array $items): void
        {
            $this->recordThat(new OrderPlaced(
                $this->entityId,
                $items,
                now()
            ));
        }
    }
    
  • Load from Events:
    $order = Order::retrieve($orderId); // Reconstructs state from stored events
    

2. Projections for Read Models

  • Define Projections:
    class OrderProjection implements Projection
    {
        public function __invoke(OrderPlaced $event)
        {
            OrderReadModel::create([
                'id' => $event->orderId,
                'status' => 'placed',
                'created_at' => $event->occurredOn,
            ]);
        }
    }
    
  • Register Projections:
    EventSourcing::addProjection(OrderPlaced::class, OrderProjection::new());
    

3. Handling Event Streams

  • Publish Events:
    EventSourcing::publish(new UserRegistered($userId, $email));
    
  • Subscribe to Streams:
    EventSourcing::subscribeToStream($userId, function ($event) {
        // Handle real-time events (e.g., WebSocket updates)
    });
    

4. Event Versioning

  • Versioned Events:
    class UserRegistered implements Event
    {
        public static function version(): string
        {
            return '1.0';
        }
    }
    
  • Migrate Events: Use EventSourcing::migrate() to handle schema changes in stored events.

Integration Tips

With Laravel Queues

  • Dispatch Events to Queue:
    EventSourcing::publish(new UserRegistered($userId, $email))
        ->onQueue('events');
    
  • Process Events Asynchronously:
    class ProcessEventsJob implements ShouldQueue
    {
        public function handle()
        {
            EventSourcing::process();
        }
    }
    

With Database Storage

  • Custom Storage Adapter:
    EventSourcing::setStorage(new DatabaseEventStorage(
        new Connection('mysql')
    ));
    

Testing

  • Mock Event Storage:
    $storage = Mockery::mock(StorageInterface::class);
    EventSourcing::setStorage($storage);
    
  • Test Projections:
    $this->assertDatabaseHas('order_read_models', [
        'id' => $orderId,
        'status' => 'placed',
    ]);
    

Gotchas and Tips

Pitfalls

  1. Event Ordering

    • Events must be processed strictly in order for consistency.
    • Fix: Use a single-threaded queue worker or a database-backed event store.
  2. Projection Side Effects

    • Projections can silently fail if not wrapped in transactions.
    • Fix: Use EventSourcing::processWithTransaction() or wrap in a DB transaction.
  3. Event Versioning Mismatches

    • Migrating events requires careful handling of backward/forward compatibility.
    • Fix: Test migrations with historical event data.
  4. Memory Leaks

    • Loading large event streams into memory can crash PHP.
    • Fix: Use chunked loading or a streaming approach.

Debugging

  • Enable Debug Logging:
    EventSourcing::setDebug(true);
    
  • Inspect Event Streams:
    $events = EventSourcing::getEventsForStream($streamId);
    dd($events);
    
  • Check Projection Execution:
    EventSourcing::addProjectionListener(OrderPlaced::class, function ($event) {
        logger()->debug("Projection triggered for OrderPlaced", ['event' => $event]);
    });
    

Configuration Quirks

  • Default Storage The package defaults to an in-memory store. For production, configure a persistent store (e.g., database, Redis):

    'storage' => [
        'driver' => 'database',
        'table' => 'event_store',
    ],
    
  • Projection Paths Auto-discovery of projections requires proper namespace configuration:

    'projections' => [
        'paths' => [
            app_path('Projections'),
        ],
    ],
    

Extension Points

  1. Custom Event Handlers Extend EventHandlerInterface to add pre/post-processing:

    class LoggingHandler implements EventHandlerInterface
    {
        public function handle($event)
        {
            logger()->info("Event published", ['event' => $event]);
        }
    }
    EventSourcing::addEventHandler(new LoggingHandler());
    
  2. Event Serialization Override serialization for complex events:

    class ComplexEvent implements Event
    {
        public static function serialize($event): string
        {
            return json_encode([
                'data' => $event->data,
                'metadata' => $event->metadata,
            ]);
        }
    }
    
  3. Custom Projection Storage Implement ProjectionStorageInterface for non-database projections (e.g., Elasticsearch):

    class ElasticsearchProjectionStorage implements ProjectionStorageInterface
    {
        public function save($projection, $event)
        {
            // Index event in Elasticsearch
        }
    }
    
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.
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk