The read model
Keep a read shape for answering questions separate from the write shape that guards rules.
Everything so far has been the write model — the aggregate and all that surrounds it — built for one job: changing state without breaking a rule. But that shape, perfect for guarding an invariant, is a poor way to answer a question. Loading the whole Workspace aggregate just to show “3 of 5 seats used” is heavy and beside the point — and some questions, like “every pending invite across all my workspaces,” fit no single aggregate at all.
So for reading we keep a second shape: the read model — a projection of the same facts, shaped to fit the question, often flattened or precomputed for a single screen. It enforces no rules; there’s nothing to protect, because reads change nothing. A query reads from a read model and nothing else.
Split the two because their jobs pull in opposite directions: force one model to do both and the aggregate fills with display getters irrelevant to its rules while the reads crawl through machinery they never needed.
Kept apart, each stays simple — the write side guards, the read side answers, often kept current by reacting to the events the write side emits: when TeammateInvited, bump the “seats used” projection. That is another thread into Module 5.
This is first a conceptual split; its own storage is the heavier move, reached for only when reads outgrow the write store.