[{"data":1,"prerenderedAt":640},["ShallowReactive",2],{"page-\u002Fv1\u002Fcore\u002Fcross-module-events":3},{"id":4,"title":5,"body":6,"description":633,"extension":634,"meta":635,"navigation":98,"path":636,"seo":637,"stem":638,"__hash__":639},"content\u002Fv1\u002Fcore\u002Fcross-module-events.md","Cross-module events",{"type":7,"value":8,"toc":623},"minimark",[9,14,26,32,46,51,146,156,162,166,264,269,287,298,307,311,387,391,431,499,503,532,536,604,619],[10,11,13],"h1",{"id":12},"cross-module-events-async","Cross-module events (async)",[15,16,17,21,22,25],"p",{},[18,19,20],"strong",{},"Purpose:"," when module A finished work and module B must react ",[18,23,24],{},"later",", without returning a value into A’s Use Case.",[15,27,28,31],{},[18,29,30],{},"Illustrative example:"," Ordering order fulfilled → Warehouse deducts reserved stock.",[15,33,34,35,40,41,45],{},"Operational detail (idempotency, outbox, duplicates): ",[36,37,39],"a",{"href":38},"\u002Fv1\u002Fcore\u002Fevent-delivery","Event delivery",". Contract shape: ",[36,42,44],{"href":43},"\u002Fv1\u002Fcore\u002Fcross-module-contracts","Cross-module contracts",".",[47,48,50],"h2",{"id":49},"high-level-shape","High-level shape",[52,53,58],"pre",{"className":54,"code":55,"language":56,"meta":57,"style":57},"language-mermaid shiki shiki-themes github-light github-dark","flowchart LR\n    subgraph Publisher[\"Publisher — Ordering\"]\n        PUC[Use Case] --> EVT[Domain Event\u003Cbr\u002F>rich happy-path payload]\n        EVT --> DISP[EventDispatcherInterface\u003Cbr\u002F>Shared \u002F kernel port]\n    end\n\n    DISP --> BUS[(Queue \u002F bus)]\n\n    subgraph Consumer[\"Consumer — Warehouse\"]\n        LIST[Infrastructure\u003Cbr\u002F>translation listener]\n        IUC[Inbound Use Case]\n        BUS --> LIST\n        LIST -->|map to local Application DTO| IUC\n    end\n","mermaid","",[59,60,61,69,75,81,87,93,100,106,111,117,123,129,135,141],"code",{"__ignoreMap":57},[62,63,66],"span",{"class":64,"line":65},"line",1,[62,67,68],{},"flowchart LR\n",[62,70,72],{"class":64,"line":71},2,[62,73,74],{},"    subgraph Publisher[\"Publisher — Ordering\"]\n",[62,76,78],{"class":64,"line":77},3,[62,79,80],{},"        PUC[Use Case] --> EVT[Domain Event\u003Cbr\u002F>rich happy-path payload]\n",[62,82,84],{"class":64,"line":83},4,[62,85,86],{},"        EVT --> DISP[EventDispatcherInterface\u003Cbr\u002F>Shared \u002F kernel port]\n",[62,88,90],{"class":64,"line":89},5,[62,91,92],{},"    end\n",[62,94,96],{"class":64,"line":95},6,[62,97,99],{"emptyLinePlaceholder":98},true,"\n",[62,101,103],{"class":64,"line":102},7,[62,104,105],{},"    DISP --> BUS[(Queue \u002F bus)]\n",[62,107,109],{"class":64,"line":108},8,[62,110,99],{"emptyLinePlaceholder":98},[62,112,114],{"class":64,"line":113},9,[62,115,116],{},"    subgraph Consumer[\"Consumer — Warehouse\"]\n",[62,118,120],{"class":64,"line":119},10,[62,121,122],{},"        LIST[Infrastructure\u003Cbr\u002F>translation listener]\n",[62,124,126],{"class":64,"line":125},11,[62,127,128],{},"        IUC[Inbound Use Case]\n",[62,130,132],{"class":64,"line":131},12,[62,133,134],{},"        BUS --> LIST\n",[62,136,138],{"class":64,"line":137},13,[62,139,140],{},"        LIST -->|map to local Application DTO| IUC\n",[62,142,144],{"class":64,"line":143},14,[62,145,92],{},[15,147,148,151,152,155],{},[18,149,150],{},"Critical boundary:"," only the ",[18,153,154],{},"translation listener"," (Infrastructure) may import the publisher’s event class. Consumer Application\u002FDomain stay publisher-agnostic.",[157,158,159],"blockquote",{},[15,160,161],{},"Naming: older text may say “ACL listener.” That means the same Infrastructure translation boundary — not a sync ACL port.",[47,163,165],{"id":164},"sequence-fulfill-then-deduct","Sequence — fulfill then deduct",[52,167,169],{"className":54,"code":168,"language":56,"meta":57,"style":57},"sequenceDiagram\n    autonumber\n    participant SUC as Ordering Use Case\n    participant DISP as EventDispatcherInterface\n    participant Q as Queue\n    participant L as Warehouse translation listener\n    participant IUC as DeductStockForFulfilledOrderUseCase\n    participant IR as Warehouse repository port\n\n    SUC->>SUC: persist fulfilled state\n    SUC->>DISP: dispatch OrderFulfilled\u003Cbr\u002F>eventId + schemaVersion + payload\n    DISP->>Q: enqueue\n    Note over SUC: Ordering request can finish\u003Cbr\u002F>without waiting for Warehouse\n    Q->>L: handle(OrderFulfilled)\n    L->>L: map event → local DTO\u003Cbr\u002F>idempotent on eventId\n    L->>IUC: __invoke(DTO)\n    IUC->>IR: deduct \u002F confirm reservation\n    IUC-->>L: done\n",[59,170,171,176,181,186,191,196,201,206,211,215,220,225,230,235,240,246,252,258],{"__ignoreMap":57},[62,172,173],{"class":64,"line":65},[62,174,175],{},"sequenceDiagram\n",[62,177,178],{"class":64,"line":71},[62,179,180],{},"    autonumber\n",[62,182,183],{"class":64,"line":77},[62,184,185],{},"    participant SUC as Ordering Use Case\n",[62,187,188],{"class":64,"line":83},[62,189,190],{},"    participant DISP as EventDispatcherInterface\n",[62,192,193],{"class":64,"line":89},[62,194,195],{},"    participant Q as Queue\n",[62,197,198],{"class":64,"line":95},[62,199,200],{},"    participant L as Warehouse translation listener\n",[62,202,203],{"class":64,"line":102},[62,204,205],{},"    participant IUC as DeductStockForFulfilledOrderUseCase\n",[62,207,208],{"class":64,"line":108},[62,209,210],{},"    participant IR as Warehouse repository port\n",[62,212,213],{"class":64,"line":113},[62,214,99],{"emptyLinePlaceholder":98},[62,216,217],{"class":64,"line":119},[62,218,219],{},"    SUC->>SUC: persist fulfilled state\n",[62,221,222],{"class":64,"line":125},[62,223,224],{},"    SUC->>DISP: dispatch OrderFulfilled\u003Cbr\u002F>eventId + schemaVersion + payload\n",[62,226,227],{"class":64,"line":131},[62,228,229],{},"    DISP->>Q: enqueue\n",[62,231,232],{"class":64,"line":137},[62,233,234],{},"    Note over SUC: Ordering request can finish\u003Cbr\u002F>without waiting for Warehouse\n",[62,236,237],{"class":64,"line":143},[62,238,239],{},"    Q->>L: handle(OrderFulfilled)\n",[62,241,243],{"class":64,"line":242},15,[62,244,245],{},"    L->>L: map event → local DTO\u003Cbr\u002F>idempotent on eventId\n",[62,247,249],{"class":64,"line":248},16,[62,250,251],{},"    L->>IUC: __invoke(DTO)\n",[62,253,255],{"class":64,"line":254},17,[62,256,257],{},"    IUC->>IR: deduct \u002F confirm reservation\n",[62,259,261],{"class":64,"line":260},18,[62,262,263],{},"    IUC-->>L: done\n",[265,266,268],"h3",{"id":267},"payload-rule-happy-path","Payload rule (happy path)",[15,270,271,272,275,276,279,280,283,284,45],{},"Publish a ",[18,273,274],{},"rich"," event: everything the consumer needs for the normal reaction, plus ",[59,277,278],{},"eventId",", ",[59,281,282],{},"occurredAt",", and ",[59,285,286],{},"schemaVersion",[15,288,289,290,293,294,297],{},"Do ",[18,291,292],{},"not"," publish only an ID and force the consumer to call back into Ordering ",[18,295,296],{},"Domain"," on every message.",[15,299,300,303,304,306],{},[18,301,302],{},"Allowed later:"," a separate consumer-owned query\u002FACL port for corrections or rebuilds — see ",[36,305,39],{"href":38},". That must not replace rich events on the happy path.",[47,308,310],{"id":309},"layer-placement","Layer placement",[312,313,314,330],"table",{},[315,316,317],"thead",{},[318,319,320,324,327],"tr",{},[321,322,323],"th",{},"Piece",[321,325,326],{},"Module \u002F layer",[321,328,329],{},"Notes",[331,332,333,347,358,374],"tbody",{},[318,334,335,339,344],{},[336,337,338],"td",{},"Event class",[336,340,341,342],{},"Publisher ",[18,343,296],{},[336,345,346],{},"Pure PHP; no framework serialization traits on the Domain event",[318,348,349,352,355],{},[336,350,351],{},"Dispatch",[336,353,354],{},"Publisher Use Case via Shared port",[336,356,357],{},"Not a framework facade inside the Use Case",[318,359,360,363,369],{},[336,361,362],{},"Translation listener",[336,364,365,366],{},"Consumer ",[18,367,368],{},"Infrastructure",[336,370,371,372],{},"Maps foreign event → local DTO; may record ",[59,373,278],{},[318,375,376,379,384],{},[336,377,378],{},"Inbound Use Case",[336,380,365,381],{},[18,382,383],{},"Application",[336,385,386],{},"Same rules as any Use Case",[47,388,390],{"id":389},"acl-vs-event","ACL vs Event",[52,392,394],{"className":54,"code":393,"language":56,"meta":57,"style":57},"flowchart TD\n    Q{Does the publisher Use Case need\u003Cbr\u002F>the peer's return value to continue?}\n    Q -->|Yes| ACL[Sync ACL]\n    Q -->|No — fire and forget \u002F eventual| EV[Domain Event]\n    EV --> MULTI{Many steps \u002F compensations?}\n    MULTI -->|Yes| ORCH[See orchestration]\n    MULTI -->|No| DONE[Single event + inbound Use Case]\n",[59,395,396,401,406,411,416,421,426],{"__ignoreMap":57},[62,397,398],{"class":64,"line":65},[62,399,400],{},"flowchart TD\n",[62,402,403],{"class":64,"line":71},[62,404,405],{},"    Q{Does the publisher Use Case need\u003Cbr\u002F>the peer's return value to continue?}\n",[62,407,408],{"class":64,"line":77},[62,409,410],{},"    Q -->|Yes| ACL[Sync ACL]\n",[62,412,413],{"class":64,"line":83},[62,414,415],{},"    Q -->|No — fire and forget \u002F eventual| EV[Domain Event]\n",[62,417,418],{"class":64,"line":89},[62,419,420],{},"    EV --> MULTI{Many steps \u002F compensations?}\n",[62,422,423],{"class":64,"line":95},[62,424,425],{},"    MULTI -->|Yes| ORCH[See orchestration]\n",[62,427,428],{"class":64,"line":102},[62,429,430],{},"    MULTI -->|No| DONE[Single event + inbound Use Case]\n",[312,432,433,443],{},[315,434,435],{},[318,436,437,440],{},[321,438,439],{},"Situation",[321,441,442],{},"Bridge",[331,444,445,455,465,474,483],{},[318,446,447,450],{},[336,448,449],{},"Check stock while placing the order",[336,451,452],{},[18,453,454],{},"ACL",[318,456,457,460],{},[336,458,459],{},"After fulfillment succeeded, deduct reserved stock",[336,461,462],{},[18,463,464],{},"Event",[318,466,467,470],{},[336,468,469],{},"Validate member tier before confirming",[336,471,472],{},[18,473,454],{},[318,475,476,479],{},[336,477,478],{},"Notify another system that shipment was created",[336,480,481],{},[18,482,464],{},[318,484,485,488],{},[336,486,487],{},"Reserve → pay → confirm across modules",[336,489,490,493,494,498],{},[18,491,492],{},"Orchestration"," (",[36,495,497],{"href":496},"\u002Fv1\u002Fcore\u002Forchestration","docs",")",[47,500,502],{"id":501},"replacing-the-publisher-with-http-webhooks","Replacing the publisher with HTTP \u002F webhooks",[52,504,506],{"className":54,"code":505,"language":56,"meta":57,"style":57},"flowchart TD\n    A[Today: queue listener receives Domain Event] --> B{Ordering becomes external?}\n    B -->|Yes| C[HTTP webhook \u002F message consumer in Warehouse Infrastructure]\n    C --> D[Same inbound Use Case]\n    D --> E[Warehouse Application\u002FDomain unchanged]\n",[59,507,508,512,517,522,527],{"__ignoreMap":57},[62,509,510],{"class":64,"line":65},[62,511,400],{},[62,513,514],{"class":64,"line":71},[62,515,516],{},"    A[Today: queue listener receives Domain Event] --> B{Ordering becomes external?}\n",[62,518,519],{"class":64,"line":77},[62,520,521],{},"    B -->|Yes| C[HTTP webhook \u002F message consumer in Warehouse Infrastructure]\n",[62,523,524],{"class":64,"line":83},[62,525,526],{},"    C --> D[Same inbound Use Case]\n",[62,528,529],{"class":64,"line":89},[62,530,531],{},"    D --> E[Warehouse Application\u002FDomain unchanged]\n",[47,533,535],{"id":534},"anti-patterns","Anti-patterns",[312,537,538,548],{},[315,539,540],{},[318,541,542,545],{},[321,543,544],{},"Anti-pattern",[321,546,547],{},"Fix",[331,549,550,558,566,577,585,593],{},[318,551,552,555],{},[336,553,554],{},"Listener calls publisher Use Case \u002F repository",[336,556,557],{},"Map from event payload (or dedicated correction ACL)",[318,559,560,563],{},[336,561,562],{},"Domain Event carries ORM models",[336,564,565],{},"Use primitives \u002F Domain DTOs",[318,567,568,574],{},[336,569,570,571],{},"Consumer Application imports ",[59,572,573],{},"OrderFulfilled",[336,575,576],{},"Keep import in Infrastructure listener only",[318,578,579,582],{},[336,580,581],{},"Empty event + “consumer will query Ordering Domain”",[336,583,584],{},"Enrich the event",[318,586,587,590],{},[336,588,589],{},"Synchronous dispatch hiding a required ACL",[336,591,592],{},"If you need the result, use ACL",[318,594,595,600],{},[336,596,597,598],{},"No idempotency on ",[59,599,278],{},[336,601,602],{},[36,603,39],{"href":38},[15,605,606,607,610,611,610,615,45],{},"Next: ",[36,608,609],{"href":38},"event delivery"," · ",[36,612,614],{"href":613},"\u002Fv1\u002Fcore\u002Fshared-kernel","shared kernel",[36,616,618],{"href":617},"\u002Fv1\u002Fcore\u002Fdecision-tree","decision tree",[620,621,622],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":57,"searchDepth":71,"depth":71,"links":624},[625,626,629,630,631,632],{"id":49,"depth":71,"text":50},{"id":164,"depth":71,"text":165,"children":627},[628],{"id":267,"depth":77,"text":268},{"id":309,"depth":71,"text":310},{"id":389,"depth":71,"text":390},{"id":501,"depth":71,"text":502},{"id":534,"depth":71,"text":535},"Async Domain Events — rich happy-path payload, consumer translation listener, inbound Use Case.","md",{},"\u002Fv1\u002Fcore\u002Fcross-module-events",{"title":5,"description":633},"v1\u002Fcore\u002Fcross-module-events","YhrZog1L3bkfc0MeqFaWObwAfk5L_JsN0IEZsGKC01M",1786241411981]