[{"data":1,"prerenderedAt":257},["ShallowReactive",2],{"page-\u002Fv1\u002Fcore\u002Freads-and-reporting":3},{"id":4,"title":5,"body":6,"description":249,"extension":250,"meta":251,"navigation":252,"path":253,"seo":254,"stem":255,"__hash__":256},"content\u002Fv1\u002Fcore\u002Freads-and-reporting.md","Reads and reporting",{"type":7,"value":8,"toc":236},"minimark",[9,13,22,40,45,100,104,109,116,119,123,130,133,137,152,159,163,201,205,225],[10,11,5],"h1",{"id":12},"reads-and-reporting",[14,15,16,17,21],"p",{},"Write-side replaceability is wasted if reports join every module’s tables through ORM relations. Treat cross-context ",[18,19,20],"strong",{},"reads"," as deliberately designed contracts.",[14,23,24,25,30,31,30,35,39],{},"Related: ",[26,27,29],"a",{"href":28},"\u002Fv1\u002Fcore\u002Fports-and-persistence","Ports & persistence"," · ",[26,32,34],{"href":33},"\u002Fv1\u002Fcore\u002Fcross-module-acl","ACL",[26,36,38],{"href":37},"\u002Fv1\u002Fcore\u002Fcross-module-events","Events",".",[41,42,44],"h2",{"id":43},"ownership","Ownership",[46,47,48,61],"table",{},[49,50,51],"thead",{},[52,53,54,58],"tr",{},[55,56,57],"th",{},"Data",[55,59,60],{},"Owner",[62,63,64,73,88],"tbody",{},[52,65,66,70],{},[67,68,69],"td",{},"Authoritative write model",[67,71,72],{},"The bounded-context module that defines the invariant",[52,74,75,82],{},[67,76,77,78,81],{},"Cross-module ",[18,79,80],{},"foreign keys"," for business joins",[67,83,84,87],{},[18,85,86],{},"Avoid"," as a coupling mechanism (Core FR-6)",[52,89,90,93],{},[67,91,92],{},"Denormalised read model \u002F projection",[67,94,95,96,99],{},"The module (or host reporting app) that ",[18,97,98],{},"needs"," the screen — fed by Events or sync ACL",[41,101,103],{"id":102},"patterns-allowed","Patterns (allowed)",[105,106,108],"h3",{"id":107},"_1-sync-acl-read","1. Sync ACL read",[14,110,111,112,115],{},"UI → Use Case → local read port → ACL → peer façade method that returns a ",[18,113,114],{},"small DTO"," (not Entities, not ORM).",[14,117,118],{},"Use for: eligibility checks, availability, “display peer label for this code.”",[105,120,122],{"id":121},"_2-event-sourced-projection-preferred-for-listsdashboards","2. Event-sourced projection (preferred for lists\u002Fdashboards)",[14,124,125,126,129],{},"Publisher emits rich events → consumer\u002Freporting Infrastructure translation listener → updates a ",[18,127,128],{},"local"," read table owned by the reader.",[14,131,132],{},"Use for: warehouse “open orders” board, directory “member activity” summaries.",[105,134,136],{"id":135},"_3-host-reporting-composition-outer-layer","3. Host reporting composition (outer layer)",[14,138,139,140,143,144,147,148,151],{},"A reporting UI\u002Fmodule in ",[18,141,142],{},"Infrastructure\u002FUI"," may call ",[18,145,146],{},"multiple"," ACL façades or read replicas ",[18,149,150],{},"as an outer composer"," — still without putting foreign Domain types into another module’s Application\u002FDomain.",[14,153,154,155,158],{},"Do ",[18,156,157],{},"not"," hide that composition inside a random Domain Entity.",[41,160,162],{"id":161},"patterns-forbidden","Patterns (forbidden)",[46,164,165,175],{},[49,166,167],{},[52,168,169,172],{},[55,170,171],{},"Smell",[55,173,174],{},"Fix",[62,176,177,185,193],{},[52,178,179,182],{},[67,180,181],{},"ORM associations \u002F joins across module namespaces for policy",[67,183,184],{},"ACL or projection",[52,186,187,190],{},[67,188,189],{},"Shared “ReportModels” with everyone’s columns",[67,191,192],{},"Owned projection per consumer",[52,194,195,198],{},[67,196,197],{},"Use Case running raw SQL across peer schemas",[67,199,200],{},"Peer façade \u002F projection",[41,202,204],{"id":203},"practical-guidance","Practical guidance",[206,207,208,216,222],"ol",{},[209,210,211,212,215],"li",{},"Screens that need ",[18,213,214],{},"one peer field"," → ACL read or denormalise a code + label via Event.",[209,217,211,218,221],{},[18,219,220],{},"many rows from peers"," → projection updated asynchronously.",[209,223,224],{},"Analytics warehouses \u002F BI → export from each module or from projections; out of Core scope, but still not an excuse for Domain coupling.",[14,226,227,228,30,232,39],{},"Next: ",[26,229,231],{"href":230},"\u002Fv1\u002Fcookbook\u002Fidentity-directory","Identity ↔ Directory cookbook",[26,233,235],{"href":234},"\u002Fv1\u002Fcore\u002Fanti-patterns","anti-patterns",{"title":237,"searchDepth":238,"depth":238,"links":239},"",2,[240,241,247,248],{"id":43,"depth":238,"text":44},{"id":102,"depth":238,"text":103,"children":242},[243,245,246],{"id":107,"depth":244,"text":108},3,{"id":121,"depth":244,"text":122},{"id":135,"depth":244,"text":136},{"id":161,"depth":238,"text":162},{"id":203,"depth":238,"text":204},"Cross-context queries without ORM relations across modules — ACL reads, projections, and owned read models.","md",{},true,"\u002Fv1\u002Fcore\u002Freads-and-reporting",{"title":5,"description":249},"v1\u002Fcore\u002Freads-and-reporting","EKc9SDnWdNEVQUOv9kUOBadDUpHvvEzXx-E73v4lAA4",1786241412317]