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

Laravel Eventsauce Laravel Package

spatie/laravel-eventsauce

Integrate EventSauce event sourcing into Laravel with migrations, models, and queued jobs. Generate aggregate roots, repositories, events and commands via Artisan. Store domain messages per aggregate and dispatch consumers synchronously or through queues.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require spatie/laravel-eventsauce
    php artisan vendor:publish --provider="Spatie\LaravelEventSauce\EventSauceServiceProvider"
    

    Publish the config to config/event-sauce.php and update as needed.

  2. Prerequisites: Ensure you understand EventSauce core concepts (aggregates, events, commands, projections). Familiarize yourself with Laravel’s Artisan commands and migrations.

  3. First Use Case: Generate an aggregate root and repository:

    php artisan make:aggregate-root "MyDomain/MyAggregate"
    

    This creates:

    • App/MyDomain/MyAggregate.php (aggregate root)
    • App/MyDomain/MyAggregateRepository.php (repository)
    • A migration for my_aggregate_domain_messages (EventSauce’s storage table).
  4. Key Files to Review:

    • config/event-sauce.php: Configure storage (e.g., pdo, redis), event bus, and projections.
    • app/Providers/EventSauceServiceProvider.php: Bind repositories and configure projections.

Implementation Patterns

Core Workflow: Event Sourcing in Laravel

  1. Define Aggregates: Use the make:aggregate-root command to scaffold aggregates. Example:

    // App/MyDomain/MyAggregate.php
    namespace App\MyDomain;
    
    use Spatie\LaravelEventSauce\AggregateRoot;
    
    class MyAggregate extends AggregateRoot
    {
        public function apply(MyEvent $event)
        {
            // Update aggregate state based on events
        }
    
        public function handle(MyCommand $command)
        {
            // Emit events in response to commands
            $this->recordThat(new MyEvent());
        }
    }
    
  2. Handle Commands: Dispatch commands via Laravel’s Bus or manually:

    use App\MyDomain\MyCommand;
    use App\MyDomain\MyAggregateRepository;
    
    $repository = app(MyAggregateRepository::class);
    $aggregate = $repository->retrieve('aggregate-id');
    $aggregate->handle(new MyCommand());
    $repository->persist($aggregate);
    
  3. Projections: Project events to read models using Projector classes. Example:

    // App/MyDomain/Projectors/MyProjector.php
    namespace App\MyDomain\Projectors;
    
    use Spatie\LaravelEventSauce\Projector;
    
    class MyProjector extends Projector
    {
        public function whenMyEventOccurred(MyEvent $event)
        {
            // Update read model (e.g., Eloquent, database, cache)
            MyReadModel::updateFromEvent($event);
        }
    }
    

    Register projectors in EventSauceServiceProvider:

    public function boot()
    {
        $this->eventSauce->addProjector(MyProjector::class);
    }
    
  4. Leverage Laravel Features:

    • Jobs: Use Laravel’s queues to process commands asynchronously:
      $this->dispatch(new HandleMyCommand($aggregateId, $commandData));
      
      Implement HandleMyCommand as a job.
    • Migrations: EventSauce tables are auto-generated via migrations. Customize via make:aggregate-root or manually.
    • Testing: Use Laravel’s testing tools to mock repositories and projectors:
      $this->partialMock(MyAggregateRepository::class, function ($mock) {
          $mock->shouldReceive('retrieve')->andReturn($aggregate);
      });
      
  5. Event Publishing: Publish events to the bus for projections:

    $this->eventSauce->publish($event);
    

Gotchas and Tips

Pitfalls

  1. Event Versioning: EventSauce requires strict event versioning. If you modify an event class (e.g., add a property), update its version in the class docblock:

    /**
     * @version 2
     */
    class MyEvent {}
    
    • Fix: Run php artisan eventsauce:generate-events to regenerate event classes if versions mismatch.
  2. Repository Persistence: Always call $repository->persist($aggregate) after handling commands. Forgetting this will lose events.

    • Debug: Check the domain_messages table for missing entries.
  3. Projection Conflicts: Projections may fail if read models aren’t idempotent. Ensure when*Occurred methods handle duplicate events gracefully.

    • Tip: Use Laravel’s DB::transaction for atomic projection updates.
  4. Storage Configuration:

    • PDO: Ensure your database connection is configured in .env (e.g., DB_CONNECTION=mysql).
    • Redis: For high throughput, configure Redis as the event store:
      'storage' => [
          'pdo' => [
              'enabled' => false,
          ],
          'redis' => [
              'enabled' => true,
              'connection' => 'cache',
          ],
      ],
      
  5. Aggregate Retrieval:

    • Use retrieve() for existing aggregates. If the aggregate doesn’t exist, retrieve() returns null.
    • Tip: Initialize new aggregates with a command handler:
      if (!$aggregate = $repository->retrieve($id)) {
          $aggregate = MyAggregate::recreate($id);
      }
      

Debugging

  1. Event Logs: Enable debug logging in config/event-sauce.php:

    'debug' => env('APP_DEBUG', false),
    

    Check Laravel logs (storage/logs/laravel.log) for event bus errors.

  2. Projection Debugging: Use Laravel’s Log facade to trace projector execution:

    \Log::info('Projecting event', ['event' => $event->toArray()]);
    
  3. Command Handling: Validate commands before processing to avoid invalid state:

    public function handle(MyCommand $command)
    {
        if (!$this->isValid($command)) {
            throw new \InvalidArgumentException('Invalid command');
        }
        // ...
    }
    

Extension Points

  1. Custom Storage: Extend Spatie\LaravelEventSauce\EventSauce to support alternative storage backends (e.g., DynamoDB):

    $this->app->bind(EventSauce::class, function ($app) {
        return new EventSauce(new CustomStorage());
    });
    
  2. Event Serialization: Override event serialization for custom formats (e.g., JSON:API):

    class MyEvent implements \JsonSerializable
    {
        public function jsonSerialize()
        {
            return ['data' => $this->data];
        }
    }
    
  3. Aggregate Lifecycle: Hook into aggregate creation/deletion:

    // In EventSauceServiceProvider
    $this->eventSauce->onAggregateCreated(function ($aggregate) {
        \Log::info("Aggregate created: {$aggregate->aggregateId()}");
    });
    
  4. Testing: Use EventSauceTestCase for isolated tests:

    use Spatie\LaravelEventSauce\Testing\EventSauceTestCase;
    
    class MyAggregateTest extends EventSauceTestCase
    {
        public function testCommandHandling()
        {
            $this->assertTrue(true); // Test logic here
        }
    }
    
  5. Performance:

    • Batch Processing: Use Laravel’s chunk() for large projections:
      DomainMessage::chunk(100, function ($messages) {
          foreach ($messages as $message) {
              $this->projector->project($message);
          }
      });
      
    • Caching: Cache read models to reduce projection load:
      cache()->remember("read-model:{$aggregateId}", now()->addHours(1), function () {
          return MyReadModel::find($aggregateId);
      });
      
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport