5 min readCalm pace · scan the outline anytime

Laravel cross-module bridges

Laravel-host ACL and Domain Event wiring — ServiceProvider binds, listeners, queues — without breaking Core replaceability.

Laravel cross-module bridges

Core rules for ACL and Events are unchanged. This page shows Laravel wiring only, using greenfield Ordering / Warehouse names.

Golden rules (host reminder)

  1. Application/Domain of module A never import Application/Domain of module B.
  2. Need peer answer now → local port + Infrastructure ACL adapter → thin peer *ModuleInterface.
  3. Need peer side effect later → Domain Event → consumer Infrastructure translation listener → inbound Use Case.
  4. Bind ports in the module ServiceProvider (composition root).
  5. Prefer ACL validate before local DB commit; never one transaction across two modules’ tables (transactions).

Full Core diagrams: ACL · Events · Contracts · Event delivery.

Sync ACL — bind example

// Ordering Application/Providers
$this->app->bind(
    WarehouseAvailabilityPortInterface::class,
    WarehouseAvailabilityAclAdapter::class,
);
// Ordering Infrastructure — only layer that may import Warehouse façade
final class WarehouseAvailabilityAclAdapter implements WarehouseAvailabilityPortInterface
{
    public function __construct(
        private WarehouseAvailabilityModuleInterface $warehouse,
    ) {}

    public function isAvailable(string $itemCode, int $quantity): bool
    {
        return $this->warehouse->isStockAvailable($itemCode, $quantity);
    }
}

Use Cases inject WarehouseAvailabilityPortInterface only. Keep the Warehouse façade thin (contracts).

Async Events — listener placement

PieceLaravel place
Domain Event classOrdering/Domain/Events/OrderFulfilled.php (pure PHP)
DispatchVia Shared EventDispatcherInterface adapter (not Event facade in Use Cases)
Translation listenerWarehouse/Infrastructure/.../OrderingOrderFulfilledTranslationListener.php
Inbound Use CaseWarehouse/Application/UseCases/.../DeductStockForFulfilledOrderUseCase.php
IdempotencyConsumer Infra store of processed eventId (same DB transaction as side effect when possible)
RegistrationModule provider / EventServiceProvider discovery — wiring only

Prefer queued listeners for side effects so Ordering’s request does not wait on Warehouse.

Payload contract

Published events are a public contract. Include eventId, occurredAt, schemaVersion, and a rich happy-path payload. Prefer additive evolution.

Persist the Ordering write and an outbox row in one DB transaction; a scheduled/queued job publishes to the Laravel event bus / queue afterward. Prevents “committed without dispatch.” Table shape is host-specific; the Core rule is in Event delivery.

Replaceability on Laravel

TodayTomorrow
In-process thin Warehouse façadeHTTP client inside the same ACL adapter class (or sibling)
Queue translation listener on OrderFulfilledWebhook controller in Warehouse Infrastructure calling the same inbound Use Case

Ordering / Warehouse Application + Domain stay untouched.

Forbidden on Laravel hosts

SmellFix
use App\Modules\Warehouse\Application\... in Ordering Use CaseLocal ACL port
Event::dispatch inside DomainShared dispatcher port
Domain Event using SerializesModels with EloquentPrimitives / Domain DTOs
Filament Resource importing peer Models for policyOwn Use Case + ACL
Modular Hexagonal Domain-Driven Design
Core 1.0.0-draft