giudicelli/distributed-architecture-queue
Laravel package for building queue-driven distributed architectures. Helps coordinate queued jobs and messaging across services or nodes, supporting scalable, decoupled application workflows with structured queue integration.
Reality check first. This package has 0 stars, 0 dependents, and a one-line description. That is not a reason to avoid it, but it does mean you are the QA team. Before writing a single line of application code, spend 20 minutes reading the repository:
composer.json — check the require block (does it pin illuminate/* to a version you actually run?) and the autoload.psr-4 namespace. That namespace is your entry point.src/ — look for a ServiceProvider, a config/ file, and any Migrations/ directory. Those three things tell you almost everything about how the package expects to be installed.README.md and tests/ — if there are no tests, budget time to write an integration test of your own before trusting it in production.composer.lock / last commit date — an untouched repo is often fine for a small utility and dangerous for anything touching queue semantics.Assume the class names below are illustrative until you confirm them against src/.
Install and publish:
composer require giudicelli/distributed-architecture-queue
# If the provider is not auto-discovered, register it in bootstrap/providers.php:
# Giudicelli\DistributedQueue\DistributedQueueServiceProvider::class,
php artisan vendor:publish --provider="Giudicelli\DistributedQueue\DistributedQueueServiceProvider"
php artisan migrate # only if the package ships migrations
Configure the driver. A "distributed architecture queue" almost always means one of two things: a queue backed by a shared store (Redis/database) so multiple application nodes pull from the same pool, or a wrapper that adds cross-node coordination (locking, deduplication, idempotency keys) on top of Laravel's existing queue. Check which before assuming. Look for a config key like distributed-queue.default or distributed-queue.driver and point it at your shared Redis connection in .env:
QUEUE_CONNECTION=redis
REDIS_HOST=your-shared-redis
DISTRIBUTED_QUEUE_CONNECTION=default
First use case: exactly-once-ish processing across nodes. The canonical reason to reach for this package is a job that must not run twice when two workers race — payment capture, invoice generation, webhook fan-out. Write the smallest version of that and watch it work:
// app/Jobs/ProcessOrder.php
class ProcessOrder implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 5;
public array $backoff = [10, 30, 120, 300];
public function __construct(public int $orderId) {}
public function handle(): void
{
// Your idempotent domain logic here.
Order::findOrFail($this->orderId)->process();
}
public function uniqueId(): string
{
return 'order:'.$this->orderId;
}
}
Dispatch it, then run two workers on two different terminals against the same Redis and confirm the job lands once. If the package provides a deduplicating dispatch helper, that is the first thing to read the source of.
Treat the package as a coordination layer, not a queue replacement. You almost certainly still use php artisan queue:work, Horizon, or a supervisor-managed worker. The package sits between your job and the broker. That means your existing operational tooling (queue:failed, queue:retry, Horizon metrics) should keep working — verify that early, because it is the single best test of whether the package is well-behaved.
Worker topology. Distributed queue semantics only make sense with more than one consumer. Run at least three workers in staging, ideally on separate hosts or containers with separate clocks, so you exercise the real failure modes:
# host A
php artisan queue:work redis --queue=high,default --tries=5 --timeout=120 --max-time=3600
# host B — identical command
Use --max-time so workers recycle and don't hold a stale lock or DB connection forever. If you use Horizon, keep the supervision config and let the package's driver sit underneath; do not run two supervisors against the same queue.
Idempotency is your job, not the package's. Whatever locking or deduplication it advertises, the safe pattern is to make the handler idempotent independently: check-then-act inside a DB transaction, or an upsert with a unique constraint on the natural key.
public function handle(): void
{
DB::transaction(function () {
$invoice = Invoice::firstOrCreate(
['order_id' => $this->orderId],
['status' => 'pending', 'amount' => $this->amount]
);
if ($invoice->wasRecentlyCreated) {
$invoice->charge();
}
});
}
That transaction is what saves you when the lock expires mid-job and a second worker picks up the same payload.
Chain and batch with awareness of ordering. Bus::chain() and Bus::batch() behave differently once work is spread across nodes: chains keep ordering because each job dispatches the next, but batches across nodes can complete out of order. If order matters, use a chain; if throughput matters, use a batch and tolerate reordering.
Separate queues by failure cost. Put coordination-sensitive jobs on their own queue so a noisy neighbour can't starve them:
ProcessOrder::dispatch($orderId)->onQueue('critical');
Then give that queue dedicated workers and a distinct retry policy. Mixing a 2-second email job with a 90-second payment job on one queue is how you get lock timeouts.
Testing. Use Queue::fake() for unit tests as usual, but add one integration test against a real Redis (or your CI's ephemeral instance) that dispatches the same job twice concurrently and asserts one effect. That single test is worth more than a hundred fakes for a package like this.
Zero dependents means zero field reports. You will not find a StackOverflow answer when it breaks. Read the handle path end-to-end before production, and pin the version exactly ("giudicelli/distributed-architecture-queue": "1.2.3", not ^1.2) so a surprise push can't change behaviour under you. Fork it if the license permits; you want the ability to patch.
Lock TTL versus job timeout is the classic footgun. If the package uses a Redis lock or a "reserved" marker, its TTL must exceed your worker's --timeout. Set --timeout to something less than retry_after in config/queue.php (Laravel throws a warning if you get this backwards), and set any package-level lock TTL comfortably above the worst-case job duration. A lock that expires mid-job gives you two concurrent executions and a very confusing bug report.
Clock skew across nodes. Anything based on timestamps — visibility timeouts, "reserved at", scheduled release — degrades when hosts drift. Run NTP, and avoid designing around sub-second timing guarantees between nodes.
Silent no-ops on dispatch. If the package intercepts dispatch to deduplicate, a duplicate dispatch() returns without error and without a job. That is correct behaviour but easy to misread as "the queue is broken." Log inside your dedup key generator during rollout, or add a temporary listener on Illuminate\Queue\Events\JobProcessing to see what actually gets consumed.
Debugging workflow. When a job vanishes: (1) php artisan queue:failed — is it there? (2) check the raw Redis keys (redis-cli keys '*queue*', llen queues:default) to confirm the payload was written; (3) tail the worker with -vvv; (4) if the package exposes lock keys, inspect their TTL with redis-cli ttl <key> while a job is running. In that order — most "distributed queue bugs" are ordinary queue bugs wearing a costume.
Config caching. php artisan config:cache will freeze whatever you published. If you change driver or connection settings, re-run config:clear before debugging, or you will chase a ghost.
Extension points to look for. A well-built package of this shape lets you swap the lock store and the deduplication key strategy. If those are hardcoded, that is your first fork candidate — you will want to change them within a month.
Have an exit plan. Because everything meaningful is behind dispatch(), migrating off is usually cheap: swap the service provider binding back to Laravel's native queue driver and keep your (already idempotent) jobs. Design your job classes so they don't import package-specific base classes or traits. That constraint costs you nothing today and buys you a clean rollback if the package stops being maintained.
How can I help you explore Laravel packages today?