7 min readCalm pace · scan the outline anytime

Cross-module events

Async Domain Events — rich happy-path payload, consumer translation listener, inbound Use Case.

Cross-module events (async)

Purpose: when module A finished work and module B must react later, without returning a value into A’s Use Case.

Illustrative example: Ordering order fulfilled → Warehouse deducts reserved stock.

Operational detail (idempotency, outbox, duplicates): Event delivery. Contract shape: Cross-module contracts.

High-level shape

Critical boundary: only the translation listener (Infrastructure) may import the publisher’s event class. Consumer Application/Domain stay publisher-agnostic.

Naming: older text may say “ACL listener.” That means the same Infrastructure translation boundary — not a sync ACL port.

Sequence — fulfill then deduct

Payload rule (happy path)

Publish a rich event: everything the consumer needs for the normal reaction, plus eventId, occurredAt, and schemaVersion.

Do not publish only an ID and force the consumer to call back into Ordering Domain on every message.

Allowed later: a separate consumer-owned query/ACL port for corrections or rebuilds — see Event delivery. That must not replace rich events on the happy path.

Layer placement

PieceModule / layerNotes
Event classPublisher DomainPure PHP; no framework serialization traits on the Domain event
DispatchPublisher Use Case via Shared portNot a framework facade inside the Use Case
Translation listenerConsumer InfrastructureMaps foreign event → local DTO; may record eventId
Inbound Use CaseConsumer ApplicationSame rules as any Use Case

ACL vs Event

SituationBridge
Check stock while placing the orderACL
After fulfillment succeeded, deduct reserved stockEvent
Validate member tier before confirmingACL
Notify another system that shipment was createdEvent
Reserve → pay → confirm across modulesOrchestration (docs)

Replacing the publisher with HTTP / webhooks

Anti-patterns

Anti-patternFix
Listener calls publisher Use Case / repositoryMap from event payload (or dedicated correction ACL)
Domain Event carries ORM modelsUse primitives / Domain DTOs
Consumer Application imports OrderFulfilledKeep import in Infrastructure listener only
Empty event + “consumer will query Ordering Domain”Enrich the event
Synchronous dispatch hiding a required ACLIf you need the result, use ACL
No idempotency on eventIdEvent delivery

Next: event delivery · shared kernel · decision tree.

Modular Hexagonal Domain-Driven Design
Core 1.0.0-draft