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)
- Application/Domain of module A never import Application/Domain of module B.
- Need peer answer now → local port + Infrastructure ACL adapter → thin peer
*ModuleInterface. - Need peer side effect later → Domain Event → consumer Infrastructure translation listener → inbound Use Case.
- Bind ports in the module ServiceProvider (composition root).
- 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
| Piece | Laravel place |
|---|---|
| Domain Event class | Ordering/Domain/Events/OrderFulfilled.php (pure PHP) |
| Dispatch | Via Shared EventDispatcherInterface adapter (not Event facade in Use Cases) |
| Translation listener | Warehouse/Infrastructure/.../OrderingOrderFulfilledTranslationListener.php |
| Inbound Use Case | Warehouse/Application/UseCases/.../DeductStockForFulfilledOrderUseCase.php |
| Idempotency | Consumer Infra store of processed eventId (same DB transaction as side effect when possible) |
| Registration | Module 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.
Outbox (recommended on Laravel)
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
| Today | Tomorrow |
|---|---|
| In-process thin Warehouse façade | HTTP client inside the same ACL adapter class (or sibling) |
Queue translation listener on OrderFulfilled | Webhook controller in Warehouse Infrastructure calling the same inbound Use Case |
Ordering / Warehouse Application + Domain stay untouched.
Forbidden on Laravel hosts
| Smell | Fix |
|---|---|
use App\Modules\Warehouse\Application\... in Ordering Use Case | Local ACL port |
Event::dispatch inside Domain | Shared dispatcher port |
Domain Event using SerializesModels with Eloquent | Primitives / Domain DTOs |
| Filament Resource importing peer Models for policy | Own Use Case + ACL |