Why DDD Insists on a Single Repository per Aggregate
The article explains that in Domain‑Driven Design a Repository should target the aggregate, not individual tables, because the aggregate defines a consistency boundary; having separate repositories for master and detail tables breaks this boundary, allowing inconsistent writes and bypassing the aggregate root’s validation.
Repository Targets the Aggregate, Not the Table
In DDD, a Repository is different from a traditional DAO. A DAO is table‑oriented—one DAO per table. A Repository is aggregate‑oriented—one Repository per aggregate.
An aggregate may correspond to a single table or to multiple tables. In the common document model, the aggregate consists of a master table and a detail table. The Repository’s save method receives an aggregate‑root object; internally it handles the write order of the master and detail tables, transaction management, and foreign‑key back‑filling. Callers do not need to know whether the data spans one table or many—they only interact with the aggregate root.
Similarly, getById queries the master table, then batch‑fetches related detail rows, groups them by the master ID, and assembles a complete aggregate‑root object. The caller receives a fully populated aggregate root without extra queries or manual assembly.
The Repository encapsulates persistence for the aggregate. If a separate Repository is created for the detail entity, callers can bypass the aggregate root and manipulate detail data directly, breaking the aggregate’s ability to maintain consistency.
Consistency Boundary Is the Root Reason
The reason the master and detail tables belong to the same aggregate is not merely the foreign‑key relationship in the database, but the business fact that they are often modified together.
For example, when a document changes status (e.g., from "pending shipment" to "returned"), the master table’s status field must be updated and the quantity field in each detail row must be synchronized. Both actions must occur within the same transaction; otherwise the master and detail data become inconsistent.
The master and detail rows share a "win‑together, lose‑together" relationship, and this consistency boundary is the fundamental justification for the aggregate.
A Repository saves not several tables but a consistency boundary.
Because the aggregate is a single unit, its persistence should be managed by one Repository. During save, the master and detail are stored together; during update, changes to both are synchronized to the database, including added, modified, or removed detail rows. This responsibility can only be handled by the same Repository.
Creating a separate Repository for the detail would allow the master and detail to be written by different callers at different times, breaking the consistency boundary.
Child Entity Writes Must Go Through the Aggregate Root
In DDD, child entities are not aggregate roots and have no independent lifecycle. They can be queried individually but cannot be modified independently of the aggregate root. Most business operations revolve around the whole document, such as "fetch all details of this document" or "change the quantity of a specific line item to 50".
In code, adding, modifying, or deleting a detail is performed via methods on the aggregate root: addItem, updateItem, removeItem. External code cannot bypass the aggregate root to manipulate child entities directly.
The aggregate root can also enforce business validation. For instance, removeItem may first check that the document is in a "pending" state; once submitted, detail rows cannot be deleted. This validation requires both the document’s status and the detail list, which are both under the aggregate root’s control. Allowing external direct access would scatter such validation logic across many call sites, making maintenance painful.
Persistence of child entities follows the aggregate root. When repository.save(order) is called, the implementation stores both master and detail together. When repository.update(order) runs, changes to both are synchronized. The detail does not need its own DomainRepository.
Read Operations Are an Exception
The discussion above concerns write models. Write operations must strictly go through the aggregate root—there is no negotiation.
For read models, DDD does not require going through the aggregate root. The Repository implementation can expose convenience query methods, such as fetching detail lists by document ID or retrieving a specific detail by a field. These read‑only methods do not violate the write‑side constraints because they do not change data state.
In admin back‑ends with complex multi‑condition pagination, cross‑aggregate joins may be needed. The CQRS practice is: write operations strictly use the domain model, while read operations may query directly via Repository or, when necessary, perform cross‑aggregate SQL. Relaxed reads do not compromise aggregate integrity because data is only read, not modified.
Conclusion
The principle "one aggregate, one Repository" is not a dogma of DDD but a design decision derived from the consistency boundary. Data that must be changed together belongs in the same aggregate, and a single Repository manages its persistence. Child‑entity write operations must always pass through the aggregate root.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
samdeepthink
Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
