bugfix666/laravel-outbox
Composer 安装命令:
composer require bugfix666/laravel-outbox
包简介
Transactional Outbox pattern for Laravel
README 文档
README
Transactional Outbox pattern implementation for Laravel 12+ (PHP 8.4+).
Guarantees atomicity and reliable event delivery in distributed systems, especially fintech, e‑commerce, and microservices.
📊 Comparison with Existing Solutions
There are several open‑source implementations of the Transactional Outbox pattern for Laravel. Here’s how laravel-outbox compares to them:
| Feature | This Package | webrek/laravel-outbox | GaiPalyan/laravel-outbox | Dnakitare/laravel-outbox |
|---|---|---|---|---|
| PHP Version | 8.4+ | 8.1+ | 8.1+ | 8.2+ |
| Laravel Version | 12+ | 10+ | 10+ | 10 – 12 |
| PHP 8.4 Features (typed properties, readonly, attributes) | ✅ Full | ❌ | ❌ | ❌ |
| Production‑ready | ✅ Yes | ✅ Yes | ✅ Yes | ⚠️ Beta (0.1.0-beta1) |
| Strict Typing | ✅ Yes | — | — | — |
| Unit & Feature Tests | ✅ 15 tests | ❌ | ❌ | ✅ (limited) |
| Dead Letter Queue (DLQ) | ✅ Yes | ❌ | ❌ | ✅ Yes |
| Inbox Pattern | ❌ (outbox only) | ❌ | ✅ | ❌ |
| Event / Job Interception | ❌ (explicit control) | ❌ | ❌ | ✅ (auto‑intercepts) |
| Atomicity | ✅ (single DB transaction) | ✅ | ✅ | ✅ |
| Custom Publishers | ✅ (via contract) | ✅ | ✅ | — |
| Retry Logic | ✅ (exponential backoff) | ✅ | ✅ | ✅ |
| Worker Locking | ✅ (prevents parallel runs) | ✅ | ❌ | — |
| Cleanup Command | ✅ (outbox:cleanup) |
✅ | ✅ | ✅ |
| Monitoring Events | ✅ (Processed / Failed) |
✅ | ❌ | — |
🚀 Why This Package Stands Out
-
Cutting‑Edge Stack – Built specifically for PHP 8.4+ and Laravel 12+, leveraging the latest language features for cleaner, safer, and more maintainable code.
-
Production‑Ready – Unlike some alternatives that are still in beta, this package is fully tested (15+ unit and feature tests) and ready for mission‑critical applications.
-
Dead Letter Queue (DLQ) – Failed messages after all retries are not lost. They remain in the outbox table for inspection and manual intervention – essential for fintech and reliability‑first systems.
-
Explicit Control, No Magic – Unlike packages that automatically intercept all events/jobs, this package gives you full control over what and when to publish. No surprises, easier debugging, and predictable behavior.
-
Built‑in Monitoring – The
OutboxMessageProcessedandOutboxMessageFailedevents allow seamless integration with logging, metrics (Prometheus, StatsD), and alerting. -
Strict Typing & Modern Practices – Readonly classes, typed properties, constructor property promotion, and console command attributes make the code self‑documenting and robust.
If you need a reliable, modern, and transparent outbox implementation for your fintech or distributed system – this package is the best choice among all existing open‑source solutions.
📦 Features
- ✅ Atomic persistence – business data + outbox record in a single DB transaction.
- ✅ Reliable background processing – worker with locking, batch processing, and retries.
- ✅ Flexible publishers – log, queue, or your own (Kafka, RabbitMQ, SNS, etc.).
- ✅ Built‑in retry logic – exponential backoff (configurable attempts & delay).
- ✅ Monitoring – events for processed/failed messages; easy to integrate with logging or metrics.
- ✅ Cleanup – automated purging of old processed messages.
- ✅ Modern PHP 8.4+ – typed properties, readonly classes, enums, constructor promotion.
- ✅ Laravel 12+ ready – uses new attribute‑based console commands.
🔧 Requirements
- PHP 8.4 or higher
- Laravel 12.0 or higher
- Database that supports transactions (MySQL, PostgreSQL, SQLite, etc.)
📥 Installation
composer require bugfix666/laravel-outbox
🚀 Setup
Publish the configuration and migration files:
php artisan vendor:publish --tag=outbox-config php artisan vendor:publish --tag=outbox-migrations
Run the migration to create the outbox_messages table:
php artisan migrate
⚙️ Configuration
The config file config/outbox.php allows you to tailor the package to your needs:
return [ 'table' => env('OUTBOX_TABLE', 'outbox_messages'), 'connection' => env('OUTBOX_DB_CONNECTION', env('DB_CONNECTION', 'mysql')), // Your custom publisher class (must implement OutboxPublisher) 'publisher' => env('OUTBOX_PUBLISHER', Bugfix666\LaravelOutbox\Publishers\LogPublisher::class), 'worker' => [ 'batch_size' => env('OUTBOX_BATCH_SIZE', 100), 'lock_timeout' => env('OUTBOX_LOCK_TIMEOUT', 10), // seconds 'lock_store' => env('OUTBOX_LOCK_STORE', 'cache'), // cache, redis, etc. ], 'cleanup' => [ 'days' => env('OUTBOX_CLEANUP_DAYS', 7), ], 'retry' => [ 'max_attempts' => env('OUTBOX_MAX_ATTEMPTS', 5), 'delay_seconds' => env('OUTBOX_RETRY_DELAY', 60), // seconds between attempts ], ];
Set your preferred publisher in .env:
OUTBOX_PUBLISHER=App\Publishers\KafkaPublisher OUTBOX_BATCH_SIZE=50 OUTBOX_MAX_ATTEMPTS=3
💡 Basic Usage
1. Store an outbox message inside a transaction
use Bugfix666\LaravelOutbox\Services\OutboxService; use Illuminate\Support\Facades\DB; DB::transaction(function () use ($outboxService) { // 1. Business logic (e.g., create a payment) $payment = Payment::create([ 'amount' => 1000, 'currency' => 'USD', 'status' => 'completed', ]); // 2. Store the outbox message – guaranteed to be persisted atomically $outboxService->store( aggregateType: 'Payment', aggregateId: (string) $payment->id, eventType: 'payment.completed', payload: [ 'payment_id' => $payment->id, 'amount' => $payment->amount, 'user_id' => auth()->id(), ] ); });
2. Run the worker
Start the outbox processor (should be supervised in production):
php artisan outbox:process
You can override batch size on the fly:
php artisan outbox:process --batch-size=200
3. Clean up old processed messages
Schedule the cleanup command to run daily (in routes/console.php):
use Illuminate\Support\Facades\Schedule; Schedule::command('outbox:cleanup')->daily();
Or run manually:
php artisan outbox:cleanup --days=14
🔌 Custom Publisher
By default, the package logs events. To send events to a real broker, create a class that implements Bugfix666\LaravelOutbox\Contracts\OutboxPublisher:
namespace App\Publishers; use Bugfix666\LaravelOutbox\Contracts\OutboxPublisher; use RdKafka\Producer; class KafkaPublisher implements OutboxPublisher { public function __construct(private Producer $producer) {} public function publish(string $eventType, array $payload, ?string $aggregateId = null): bool { $topic = $this->producer->newTopic($eventType); $topic->produce(RD_KAFKA_PARTITION_UA, 0, json_encode($payload)); $this->producer->flush(1000); return true; } }
Then set OUTBOX_PUBLISHER=App\Publishers\KafkaPublisher in your .env.
Tip: For RabbitMQ, use
php-amqplib; for AWS SNS, use the Laravel SNS facade.
🧩 Monitoring and Events
The package fires two events that you can listen to:
Bugfix666\LaravelOutbox\Events\OutboxMessageProcessedBugfix666\LaravelOutbox\Events\OutboxMessageFailed
Example listener:
use Bugfix666\LaravelOutbox\Events\OutboxMessageFailed; class LogOutboxFailure { public function handle(OutboxMessageFailed $event): void { logger()->error('Outbox failure', [ 'id' => $event->message->id, 'attempts' => $event->message->attempts, 'error' => $event->exception->getMessage(), ]); } }
Register your listener in EventServiceProvider.
🔄 Retry Logic
When publishing fails, the worker increments the attempts counter and sets available_at to now() + delay_seconds. The next run will only pick messages where available_at is in the past, giving the broker time to recover.
After max_attempts failures, the message remains unprocessed but is considered failed – you can handle it manually or set up an alert.
🧪 Testing
The package is fully testable. You can mock the publisher and assert that messages are stored and processed correctly.
Example test:
public function test_message_is_stored(): void { DB::transaction(function () { $service = app(OutboxService::class); $message = $service->store('User', '123', 'user.created', ['name' => 'John']); $this->assertDatabaseHas('outbox_messages', ['id' => $message->id]); }); }
📊 Performance Considerations
- Indexes – The migration includes indexes on
(processed_at, created_at),aggregate_type/aggregate_id, andavailable_atto keep queries fast. - Batch size – Tune
batch_sizeaccording to your workload. Larger batches reduce DB round‑trips but increase memory usage. - Partitioning – For extremely high volume, consider table partitioning by
created_at(e.g., daily) to keep the active set small. - Locking – The worker uses
FOR UPDATEto prevent double‑processing. Ensure your database supports row‑level locking.
🛠 Supervisor Configuration
For production, keep the worker running with Supervisor:
[program:outbox-worker] command=php /path/to/your/project/artisan outbox:process directory=/path/to/your/project user=www-data autostart=true autorestart=true redirect_stderr=true stdout_logfile=/path/to/your/project/storage/logs/outbox-worker.log
📖 Full Documentation
🤝 Contributing
- Fork the repository.
- Create a feature branch (
git checkout -b feature/amazing-feature). - Commit your changes (
git commit -m 'Add some amazing feature'). - Push to the branch (
git push origin feature/amazing-feature). - Open a Pull Request.
Please ensure that your code passes the existing tests and is well‑documented.
📝 License
This package is open‑source software licensed under the MIT license.
💬 Support
For questions, bug reports, or feature requests, please open an issue on GitHub.
Happy coding, and may your events always be delivered! 🚀
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 0
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: GPL-3.0-only
- 更新时间: 2026-06-22