[{"data":1,"prerenderedAt":433},["ShallowReactive",2],{"page-\u002Fv1\u002Fcore\u002Ftransactions-and-failures":3},{"id":4,"title":5,"body":6,"description":426,"extension":427,"meta":428,"navigation":167,"path":429,"seo":430,"stem":431,"__hash__":432},"content\u002Fv1\u002Fcore\u002Ftransactions-and-failures.md","Transactions and failures",{"type":7,"value":8,"toc":419},"minimark",[9,13,22,40,45,104,108,115,223,236,243,260,264,318,322,329,358,364,368,406,415],[10,11,5],"h1",{"id":12},"transactions-and-failures",[14,15,16,17,21],"p",{},"Modular Hexagonal DDD does ",[18,19,20],"strong",{},"not"," promise a distributed two-phase commit across modules. Each Use Case owns a clear consistency boundary.",[14,23,24,25,30,31,30,35,39],{},"Related: ",[26,27,29],"a",{"href":28},"\u002Fv1\u002Fcore\u002Fcross-module-acl","ACL"," · ",[26,32,34],{"href":33},"\u002Fv1\u002Fcore\u002Fevent-delivery","Event delivery",[26,36,38],{"href":37},"\u002Fv1\u002Fcore\u002Forchestration","Orchestration",".",[41,42,44],"h2",{"id":43},"ownership-rule","Ownership rule",[46,47,48,61],"table",{},[49,50,51],"thead",{},[52,53,54,58],"tr",{},[55,56,57],"th",{},"Rule",[55,59,60],{},"Detail",[62,63,64,84,94],"tbody",{},[52,65,66,72],{},[67,68,69],"td",{},[18,70,71],{},"One Use Case, one primary write set",[67,73,74,75,79,80,83],{},"Prefer a single Shared ",[76,77,78],"code",{},"DatabaseTransactionInterface"," (or host unit of work) around ",[18,81,82],{},"this module’s"," writes",[52,85,86,91],{},[67,87,88],{},[18,89,90],{},"Peers are remote",[67,92,93],{},"Treat sync ACL like an external call — even when in-process today",[52,95,96,101],{},[67,97,98],{},[18,99,100],{},"No cross-module DB transaction",[67,102,103],{},"Do not open one SQL transaction that writes Ordering and Warehouse tables together",[41,105,107],{"id":106},"sync-acl-fail-before-you-commit","Sync ACL — fail before you commit",[14,109,110,111,114],{},"When the publisher needs a peer answer ",[18,112,113],{},"before"," it is safe to commit:",[116,117,122],"pre",{"className":118,"code":119,"language":120,"meta":121,"style":121},"language-mermaid shiki shiki-themes github-light github-dark","sequenceDiagram\n    autonumber\n    participant UC as PlaceOrderUseCase\n    participant TX as DatabaseTransactionInterface\n    participant ACL as WarehouseAvailability port\n    participant R as Ordering repository\n\n    UC->>ACL: check availability (no Ordering write yet)\n    alt unavailable \u002F peer error\n        ACL-->>UC: failure\n        UC-->>UC: abort — nothing committed\n    else available\n        UC->>TX: begin\n        UC->>R: persist order\n        UC->>TX: commit\n    end\n","mermaid","",[76,123,124,132,138,144,150,156,162,169,175,181,187,193,199,205,211,217],{"__ignoreMap":121},[125,126,129],"span",{"class":127,"line":128},"line",1,[125,130,131],{},"sequenceDiagram\n",[125,133,135],{"class":127,"line":134},2,[125,136,137],{},"    autonumber\n",[125,139,141],{"class":127,"line":140},3,[125,142,143],{},"    participant UC as PlaceOrderUseCase\n",[125,145,147],{"class":127,"line":146},4,[125,148,149],{},"    participant TX as DatabaseTransactionInterface\n",[125,151,153],{"class":127,"line":152},5,[125,154,155],{},"    participant ACL as WarehouseAvailability port\n",[125,157,159],{"class":127,"line":158},6,[125,160,161],{},"    participant R as Ordering repository\n",[125,163,165],{"class":127,"line":164},7,[125,166,168],{"emptyLinePlaceholder":167},true,"\n",[125,170,172],{"class":127,"line":171},8,[125,173,174],{},"    UC->>ACL: check availability (no Ordering write yet)\n",[125,176,178],{"class":127,"line":177},9,[125,179,180],{},"    alt unavailable \u002F peer error\n",[125,182,184],{"class":127,"line":183},10,[125,185,186],{},"        ACL-->>UC: failure\n",[125,188,190],{"class":127,"line":189},11,[125,191,192],{},"        UC-->>UC: abort — nothing committed\n",[125,194,196],{"class":127,"line":195},12,[125,197,198],{},"    else available\n",[125,200,202],{"class":127,"line":201},13,[125,203,204],{},"        UC->>TX: begin\n",[125,206,208],{"class":127,"line":207},14,[125,209,210],{},"        UC->>R: persist order\n",[125,212,214],{"class":127,"line":213},15,[125,215,216],{},"        UC->>TX: commit\n",[125,218,220],{"class":127,"line":219},16,[125,221,222],{},"    end\n",[14,224,225,228,229,232,233,235],{},[18,226,227],{},"Prefer:"," run ",[18,230,231],{},"read\u002Fvalidate"," ACL calls ",[18,234,113],{}," opening the write transaction (or before the first irreversible side effect).",[14,237,238,239,242],{},"If you must call ACL ",[18,240,241],{},"inside"," a transaction (e.g. reserve then write):",[244,245,246,250,257],"ul",{},[247,248,249],"li",{},"Keep the peer call short.",[247,251,252,253,256],{},"On peer failure → roll back ",[18,254,255],{},"this"," module’s transaction.",[247,258,259],{},"Never leave “Ordering committed + Warehouse half-reserved” without a defined compensation (see below).",[41,261,263],{"id":262},"failure-modes","Failure modes",[46,265,266,276],{},[49,267,268],{},[52,269,270,273],{},[55,271,272],{},"Situation",[55,274,275],{},"Expected behaviour",[62,277,278,286,294,306],{},[52,279,280,283],{},[67,281,282],{},"Peer ACL timeout \u002F 5xx",[67,284,285],{},"Fail the Use Case; caller retries or shows error — do not pretend success",[52,287,288,291],{},[67,289,290],{},"Peer says “no” (business)",[67,292,293],{},"Domain\u002FApplication policy: reject or alternate path — still no silent partial write",[52,295,296,299],{},[67,297,298],{},"Peer succeeded, local commit fails",[67,300,301,302,305],{},"Compensate peer if the peer call was a ",[18,303,304],{},"write"," (release reservation); prefer peer APIs that are confirm\u002Fcancel capable",[52,307,308,311],{},[67,309,310],{},"Local commit succeeded, async consumer fails",[67,312,313,314,317],{},"Eventual consistency + retry \u002F dead-letter (",[26,315,316],{"href":33},"event delivery",")",[41,319,321],{"id":320},"compensating-actions","Compensating actions",[14,323,324,325,328],{},"For multi-step sync writes across modules, prefer ",[18,326,327],{},"explicit"," steps:",[330,331,332,338,344,351],"ol",{},[247,333,334,337],{},[18,335,336],{},"Reserve"," (peer write ACL) → returns a reservation code",[247,339,340,343],{},[18,341,342],{},"Commit local"," aggregate referencing that code",[247,345,346,347,350],{},"On local failure → ",[18,348,349],{},"Release"," (peer write ACL)",[247,352,353,354,357],{},"On success → later ",[18,355,356],{},"Confirm"," via Event or second ACL",[14,359,360,361,39],{},"That is still ACL + Events — not a shared transaction. Longer chains → ",[26,362,363],{"href":37},"orchestration",[41,365,367],{"id":366},"what-not-to-do","What not to do",[46,369,370,380],{},[49,371,372],{},[52,373,374,377],{},[55,375,376],{},"Anti-pattern",[55,378,379],{},"Why",[62,381,382,390,398],{},[52,383,384,387],{},[67,385,386],{},"Single DB transaction spanning two modules’ tables",[67,388,389],{},"Hidden deploy\u002Fruntime coupling; fails the HTTP-replace mental model",[52,391,392,395],{},[67,393,394],{},"Catch peer errors and commit anyway “to fix later” without an outbox\u002Fevent",[67,396,397],{},"Silent inconsistency",[52,399,400,403],{},[67,401,402],{},"UI retries that double-create without idempotency keys",[67,404,405],{},"Duplicate aggregates",[14,407,408,409,30,411,39],{},"Next: ",[26,410,363],{"href":37},[26,412,414],{"href":413},"\u002Fv1\u002Fcore\u002Fanti-patterns","anti-patterns",[416,417,418],"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":121,"searchDepth":134,"depth":134,"links":420},[421,422,423,424,425],{"id":43,"depth":134,"text":44},{"id":106,"depth":134,"text":107},{"id":262,"depth":134,"text":263},{"id":320,"depth":134,"text":321},{"id":366,"depth":134,"text":367},"Per-Use-Case transaction ownership, sync ACL failure modes, and compensations — without distributed transactions.","md",{},"\u002Fv1\u002Fcore\u002Ftransactions-and-failures",{"title":5,"description":426},"v1\u002Fcore\u002Ftransactions-and-failures","WMQX_ptLCV1uZlRP8awJDZ_qHdN803ocu1CFf-NZjLA",1786241412195]