Build a transactional outbox
The outbox row is written in the Order transaction, while the relay records a consumed key in an inbox. This makes intent durable but is not a broker, a scalable relay, or an exactly-once guarantee.
Before you begin
Run composer run reference:test:outbox. Choose this tier only when an external integration needs durable intent.
Relay with an inbox guard
examples/reference-app/app/Pulsar/Services/Internal/Modules/Orders/UseCases/RelayOutbox.php — Transactional relay and inbox deduplication.
php
<?php
namespace App\Pulsar\Services\Internal\Modules\Orders\UseCases;
use Illuminate\Support\Facades\DB;
final class RelayOutbox
{
// #region transactional-outbox-relay
public function execute(string $messageKey): bool
{
return DB::transaction(function () use ($messageKey): bool {
if (DB::table('inbox_messages')->where('message_key', $messageKey)->exists()) {
return false;
}
DB::table('inbox_messages')->insert(['message_key' => $messageKey, 'created_at' => now(), 'updated_at' => now()]);
DB::table('outbox_messages')->where('idempotency_key', $messageKey)->update(['relayed_at' => now(), 'updated_at' => now()]);
return true;
});
}
// #endregion
}Verify and troubleshoot
The test proves atomic write, relay retry, and one inbox row. Production relays still need locking, retention, poison-record handling, monitoring, and throughput design.