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

Eventsauce Outbox Laravel Package

andreo/eventsauce-outbox

Extended outbox components for EventSauce on PHP 8.2+. Includes an outbox-aware aggregate repository, a forwarding message consumer to dispatch to your queue, and a Symfony Console command to consume relays with batch/commit, sleep, and limit options.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup for Laravel Integration

  1. Install the Package

    composer require andreo/eventsauce-outbox
    

    Note: Requires PHP 8.2+ and Symfony Console (install via composer require symfony/console).

  2. Register EventSauce Dependencies Configure EventSauce’s core components (MessageRepository, AggregateRootRepository) in Laravel’s config/app.php or a service provider:

    $this->app->bind(EventSauce\EventSourcing\MessageRepository::class, function ($app) {
        return new EventSauce\EventSourcing\MessageRepository(
            // Your EventSauce config
        );
    });
    
  3. Create a Custom Outbox Repository Extend the package’s repository to work with Laravel’s container:

    use Andreo\EventSauce\Outbox\Repository\EventSourcedAggregateRootRepositoryForOutbox;
    use Illuminate\Contracts\Container\Container;
    
    $repository = new EventSourcedAggregateRootRepositoryForOutbox(
        aggregateRootClassName: MyAggregate::class,
        messageRepository: $this->app->make(EventSauce\EventSourcing\MessageRepository::class),
        regularRepository: $this->app->make(EventSauce\EventSourcing\AggregateRootRepository::class),
        container: $this->app // Laravel's container for service resolution
    );
    
  4. First Use Case: Dispatch Events to a Queue Use the ForwardingMessageConsumer to forward events to Laravel’s queue system:

    use Andreo\EventSauce\Outbox\MessageConsumer\ForwardingMessageConsumer;
    use EventSauce\EventSourcing\MessageDispatcher;
    
    $dispatcher = new ForwardingMessageConsumer(
        messageDispatcher: $this->app->make(MessageDispatcher::class)
    );
    
  5. Run the Outbox Consumer via Artisan Create a Laravel Artisan command to replace the Symfony command:

    php artisan make:command ProcessOutboxMessages
    

    Configure it to use the package’s OutboxMessagesConsumeCommand logic:

    use Andreo\EventSauce\Outbox\Command\OutboxMessagesConsumeCommand;
    use Symfony\Component\DependencyInjection\ServiceLocator;
    
    protected function handle(): void {
        $relays = new ServiceLocator(); // Replace with Laravel-compatible service locator
        $relays->set('foo-relay-id', $this->app->make(\EventSauce\MessageOutbox\OutboxRelay::class));
    
        $command = new OutboxMessagesConsumeCommand(
            relays: $relays,
            logger: $this->app->make(\Psr\Log\LoggerInterface::class)
        );
    
        $command->run(
            relays: ['foo-relay-id'],
            run: true,
            batchSize: 100,
            commitSize: 1,
            sleep: 1,
            limit: -1
        );
    }
    

    Run it with:

    php artisan process:outbox-messages
    

Implementation Patterns

Core Workflow: Event Dispatch via Outbox

  1. Event Production When an aggregate root emits events, they are stored in EventSauce’s MessageRepository and automatically enqueued in the outbox (via the custom repository).

    $aggregate = $repository->retrieve(MyAggregate::class, $id);
    $aggregate->apply(new MyEvent());
    $repository->persist($aggregate); // Events are outboxed here
    
  2. Asynchronous Processing Use Laravel’s queue system to process outboxed messages. Create a queue job to handle the ForwardingMessageConsumer:

    use EventSauce\EventSourcing\Message;
    
    class DispatchOutboxMessagesJob implements ShouldQueue {
        public function handle(): void {
            $consumer = new ForwardingMessageConsumer(
                messageDispatcher: $this->app->make(MessageDispatcher::class)
            );
            $consumer->consume(); // Processes messages from the outbox
        }
    }
    

    Dispatch this job periodically via a Laravel scheduler:

    $schedule->command('process:outbox-messages')->everyMinute();
    
  3. Batch Processing Configure the OutboxMessagesConsumeCommand with optimal batch sizes for your queue system:

    $command->run(
        batchSize: 50, // Fetch 50 messages at once
        commitSize: 10 // Commit 10 messages as a transaction
    );
    

Integration with Laravel Queues

To forward outboxed messages to Laravel’s queue, create a custom MessageDispatcher adapter:

use EventSauce\EventSourcing\MessageDispatcher;
use Illuminate\Bus\Dispatcher as LaravelDispatcher;
use Illuminate\Contracts\Queue\ShouldQueue;

class LaravelQueueMessageDispatcher implements MessageDispatcher {
    public function __construct(
        private LaravelDispatcher $laravelDispatcher
    ) {}

    public function dispatch(Message $message): void {
        $this->laravelDispatcher->dispatch(
            new DispatchEventJob($message)
        );
    }
}

Relay Configuration

Define relays in Laravel’s service container (e.g., config/eventsauce.php):

'relays' => [
    'foo-relay-id' => [
        'class' => \EventSauce\MessageOutbox\OutboxRelay::class,
        'config' => [
            'queue' => 'events',
            'connection' => 'redis'
        ]
    ]
],

Transaction Management

Ensure outbox operations are transactional. Use Laravel’s database transactions with EventSauce’s:

use Illuminate\Support\Facades\DB;

DB::transaction(function () use ($repository, $aggregate) {
    $aggregate->apply(new MyEvent());
    $repository->persist($aggregate); // Outboxed within this transaction
});

Gotchas and Tips

Pitfalls

  1. Symfony Dependency Overhead

    • The package relies on Symfony’s ServiceLocator and Console. To avoid tight coupling:
      • Replace ServiceLocator with Laravel’s container or a custom wrapper:
        use Illuminate\Support\ServiceLocator as LaravelServiceLocator;
        
        $locator = new LaravelServiceLocator();
        $locator->add('foo-relay-id', $this->app->make(\EventSauce\MessageOutbox\OutboxRelay::class));
        
      • Use the Symfony Bridge if needed.
  2. Transaction Conflicts

    • EventSauce and Laravel may handle transactions differently. Ensure:
      • EventSauce’s MessageRepository commits before Laravel’s transaction ends.
      • Avoid nested transactions if your database doesn’t support them (e.g., MySQL with InnoDB).
  3. Queue Serialization

    • Laravel’s queue system may struggle with EventSauce’s Message objects. Use a serializer adapter:
      use EventSauce\EventSourcing\Message;
      use Illuminate\Contracts\Queue\ShouldQueue;
      
      class DispatchEventJob implements ShouldQueue {
          public function __construct(private Message $message) {}
      
          public function handle(): void {
              // Deserialize and process the message
          }
      
          public function serialize(): string {
              return serialize([
                  'event' => $this->message->event(),
                  'metadata' => $this->message->metadata(),
              ]);
          }
      
          public static function deserialize($data): self {
              $data = unserialize($data);
              return new self(
                  new Message(
                      $data['event'],
                      $data['metadata']
                  )
              );
          }
      }
      
  4. Outbox Table Schema

    • The package assumes EventSauce manages the outbox table. If you need to customize it:
      • Extend EventSauce’s schema migrations or create a Laravel migration that aligns with the expected structure:
        Schema::create('outbox_messages', function (Blueprint $table) {
            $table->id();
            $table->string('aggregate_id');
            $table->string('aggregate_type');
            $table->json('event');
            $table->json('metadata');
            $table->boolean('published')->default(false);
            $table->timestamps();
        });
        
  5. Relay Id Mismatches

    • The OutboxMessagesConsumeCommand requires relay IDs registered in the ServiceLocator. If using Laravel’s container:
      • Bind relays explicitly:
        $this->app->bind(\EventSauce\MessageOutbox\OutboxRelay::class, function ($app) {
            return new \EventSauce\MessageOutbox\OutboxRelay(
                // Your relay config
            );
        });
        
      • Or use a custom service locator that resolves Laravel bindings.

Debugging Tips

  1. Log Unpublished Messages Add logging to the ForwardingMessageConsumer to track failed dispatches:
    use Psr\Log\LoggerInterface;
    
    $consumer = new ForwardingMessageConsumer(
        messageDispatcher: $dispatcher,
        logger: $this->app->make(LoggerInterface::class)
    );
    
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.
daikazu/eloquent-salesforce-objects
unseen-codes/chat
romalytar/yammi-jobs-monitoring-laravel
kisame76/filament-db-table-state
nqxcode/laravel-lucene-search
dpfx/laravel-livewire-wizards
workos/workos-php-laravel
sofa/laravel-global-scope
nawasara/auth-primitives
adhocrat-io/arkhe-main
make-dev/orca-harpoon
itsemon245/lamet
baks-dev/dashboard
amoifr/pickle-panther-bundle
make-dev/orca
dmstr/symfony-system-resources-bundle
dmstr/symfony-job-queue-bundle
dmstr/openapi-json-schema-bundle
dmstr/keycloak-security-bundle
dmstr/doctrine-audit-log-bundle