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

Stupid Event Sourcing Symfony Laravel Package

damgru/stupid-event-sourcing-symfony

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require damgru/stupid-event-sourcing-symfony
    

    Add the bundle to config/bundles.php (Symfony) or config/app.php (Laravel via Symfony bridge):

    Damgru\StupidEventSourcingSymfony\StupidEventSourcingBundle::class => ['all' => true],
    
  2. Database Configuration Ensure your config/database.php (Laravel) or .env (Symfony) includes a events connection pointing to your event storage table (e.g., events table with id, aggregate_id, aggregate_type, event_name, payload, occurred_on).

  3. First Event Define an event class (e.g., app/Events/UserRegistered.php):

    namespace App\Events;
    
    class UserRegistered
    {
        public function __construct(public string $userId, public string $email) {}
    }
    

    Publish it via the command:

    php artisan events:publish UserRegistered
    

    (Laravel) or php bin/console damgru:events:publish UserRegistered (Symfony).

  4. First Aggregate Create an aggregate root (e.g., app/Domain/User.php):

    namespace App\Domain;
    
    use Damgru\StupidEventSourcing\AggregateRoot;
    use App\Events\UserRegistered;
    
    class User extends AggregateRoot
    {
        public function register(string $email): void
        {
            $this->recordThat(new UserRegistered($this->id, $email));
        }
    }
    

    Use the aggregate in a service:

    $user = User::retrieve($userId);
    $user->register($email);
    $user->persist(); // Saves events to DB
    

Implementation Patterns

Workflows

  1. Event Sourcing Workflow

    • Command Handling: Use Symfony Messenger or Laravel’s HandleEvents trait to react to events.
      use Damgru\StupidEventSourcingSymfony\EventListener\HandleEvents;
      
      class UserRegisteredListener implements HandleEvents
      {
          public function handle(UserRegistered $event): void
          {
              // Side effects (e.g., send welcome email)
          }
      }
      
    • Projection: Build read models by replaying events:
      $projection = new UserProjection();
      $projection->replayEventsForAggregate($userId);
      
  2. CQRS Integration

    • Separate write (event-sourced) and read (projection) models.
    • Use Laravel’s query builder or Symfony’s Doctrine to query projections.
  3. Testing

    • Unit Tests: Mock AggregateRoot and verify events:
      $user = new User();
      $user->register('[email protected]');
      $this->assertCount(1, $user->pullUncommittedEvents());
      
    • Integration Tests: Use EventStoreTestCase (if provided) or manually replay events:
      $events = EventStore::findEventsForAggregate($userId);
      $projection = new UserProjection();
      foreach ($events as $event) {
          $projection->apply($event);
      }
      

Integration Tips

  • Laravel-Specific:
    • Bind the EventStore to the container:
      $this->app->bind(EventStore::class, function ($app) {
          return new EventStore($app->make(Connection::class));
      });
      
    • Use Laravel’s Bus or Events facade to dispatch events:
      event(new UserRegistered($userId, $email));
      
  • Symfony-Specific:
    • Configure the bundle in config/packages/damgru_stupid_event_sourcing.yaml:
      damgru_stupid_event_sourcing:
          connection: events
      
    • Use dependency injection for aggregates:
      public function __construct(private EventStore $eventStore) {}
      

Gotchas and Tips

Pitfalls

  1. Event Schema Mismatches

    • Ensure payload column in the events table uses json or jsonb type (PostgreSQL) or longtext (MySQL).
    • Fix: Run migrations or alter the table:
      Schema::table('events', function (Blueprint $table) {
          $table->json('payload')->change();
      });
      
  2. Aggregate ID Collisions

    • Use UUIDs or composite keys (e.g., class@id) for aggregate_id to avoid conflicts.
    • Tip: Override AggregateRoot::id() to enforce UUIDs:
      public function id(): string
      {
          return (string) Str::uuid();
      }
      
  3. Event Ordering

    • Events are stored in occurred_on order. If using multiple processes, ensure clocks are synchronized or use a sequence number.
  4. Projection Lag

    • Projections may fall behind if event processing is slow. Use batch replay or async workers (e.g., Laravel Queues/Symfony Messenger).

Debugging

  • Missing Events:
    • Check if persist() is called on the aggregate root.
    • Verify the events table has the expected records:
      SELECT * FROM events WHERE aggregate_id = 'user@123';
      
  • Serialization Errors:
    • Ensure all event properties are serializable (no closures, resources, or non-PHP-serializable objects).
    • Fix: Use #[AsArray] (Symfony) or #[Serializable] (PHP 8.1+) attributes or implement JsonSerializable.

Extension Points

  1. Custom Event Store
    • Extend EventStore to add features (e.g., snapshotting):
      class CustomEventStore extends EventStore
      {
          public function snapshot(AggregateRoot $aggregate): void
          {
              // Custom logic
          }
      }
      
  2. Event Subscribers
    • Use Symfony’s EventSubscriberInterface or Laravel’s ObservesEvents:
      class AuditSubscriber implements EventSubscriberInterface
      {
          public static function getSubscribedEvents(): array
          {
              return [
                  UserRegistered::class => 'onUserRegistered',
              ];
          }
      
          public function onUserRegistered(UserRegistered $event): void
          {
              // Log or audit
          }
      }
      
  3. Event Versioning
    • Store event versions in the payload to handle schema changes:
      class UserRegistered implements Serializable
      {
          public function __construct(
              public string $userId,
              public string $email,
              public int $version = 1
          ) {}
      }
      
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.
adachsoft/changelog-linter
adachsoft/collection
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
adachsoft/php-code-style