{"post":{"slug":"ddd-aggregates-explained-2026","title":"DDD Aggregates: Drawing Consistency Boundaries","excerpt":"An aggregate defines what must stay consistent together and what can be eventually consistent. Here is how to size them and why large aggregates cause contention.","category":"code","date":"July 8, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">A</span>n aggregate answers one question: what has to be consistent at the same instant? Everything inside the boundary changes together in a single transaction and always satisfies its invariants. Everything outside can be momentarily out of step and catch up shortly.\n\nGetting that boundary right determines both correctness and how well the system handles concurrency, which is why it is the design decision worth the most attention.\n\n## What changed in 2026\n\n- **Smaller aggregates became the consensus.** Repeated experience of contention from large aggregates pushed guidance firmly toward the smallest boundary that preserves genuine invariants.\n- **Event-driven propagation matured.** Reliable mechanisms for updating other aggregates asynchronously made eventual consistency between them more comfortable to adopt.\n- **The distinction from database design sharpened.** Recognition that an aggregate is a domain concept rather than a table structure reduced a common conflation.\n- **Contention became measurable.** Better observability made it easier to see which aggregates were causing lock contention under load.\n\n## Sizing the boundary\n\n| Question | If yes |\n| --- | --- |\n| Must these change atomically to preserve a rule? | Same aggregate |\n| Would a brief inconsistency be acceptable? | Separate aggregates |\n| Is this rule enforced by the business or by convenience? | Only real rules justify the boundary |\n| Do many users modify this concurrently? | Smaller is better; contention scales with size |\n| Is this collection unbounded? | Do not put it inside; it will grow without limit |\n\nThe unbounded collection case is the classic mistake. Modelling a customer as an aggregate containing all their orders means loading every order to change anything about the customer, and it means two people placing orders simultaneously contend on the same aggregate. Orders belong in their own aggregate, referencing the customer by identity.\n\nThe invariant test is the one that matters. Ask whether a rule genuinely must hold at every instant, or whether it merely feels tidy for things to be updated together. Most apparent invariants are the second kind, and they do not justify the coupling.\n\n## Working across aggregates\n\nReference by identity. An aggregate holding a direct object reference to another invites loading both, changing both, and saving both in one transaction — which merges them into one aggregate in practice regardless of how they are drawn.\n\nUpdate one aggregate per transaction. When an action must affect two, change the first and emit an event that triggers the second. That accepts a brief window of inconsistency in exchange for avoiding distributed transactions, and it is the whole point of the boundary.\n\nMake the propagation reliable. An event emitted in-process after a commit can be lost if the process dies, which leaves the second aggregate permanently out of step. The [outbox pattern](/blog/outbox-pattern-explained-2026) is the standard fix, writing the event in the same transaction as the state change.\n\nHandle failure in the downstream update. Eventual consistency means the second update might fail, and a plan for that — retry, then dead letter, as in [dead letter queues explained](/blog/dead-letter-queues-explained-2026) — is what separates eventual consistency from occasional inconsistency.\n\n## Common mistakes\n\n- **Large aggregates.** Contention under concurrency and expensive loads.\n- **Unbounded collections inside an aggregate.** Grows without limit.\n- **Direct object references between aggregates.** Merges them in practice.\n- **Two aggregates in one transaction.** Recreates the coupling the boundary avoided.\n- **Treating tables as aggregates.** They are different concerns; one aggregate may span tables.\n- **Enforcing convenience as an invariant.** Couples things for no real rule.\n\n## FAQ\n\n**How small should an aggregate be?**\nAs small as the real invariants allow. Frequently a single entity with a few value objects.\n\n**What if a rule spans two aggregates?**\nEnforce it eventually, with compensation when violated. If it genuinely must hold instantly, the boundary is drawn wrong.\n\n**Does this require event sourcing?**\nNo. Aggregates and event sourcing are separate ideas that combine well and neither requires the other.\n\n**How does this map to microservices?**\nA service typically owns one or several aggregates, and a bounded context frequently maps to a service. Aggregates are finer-grained than service boundaries.\n\n## Where to go next\n\nFor discovering boundaries, read [event storming guide](/blog/event-storming-guide-2026). For reliable cross-aggregate updates, [outbox pattern explained](/blog/outbox-pattern-explained-2026) and [saga pattern explained](/blog/saga-pattern-explained-2026).","hue":278,"metaTitle":"DDD Aggregates Explained: Consistency Design 2026","metaDescription":"How aggregates define transactional consistency boundaries, why smaller aggregates scale better, referencing by identity, and handling cross-aggregate updates.","keywords":["ddd aggregates explained","consistency boundary design","aggregate root pattern","transactional boundaries","eventual consistency modelling","domain model design","concurrency contention","invariant enforcement","software architecture 2026"],"tldr":["**An aggregate is a consistency boundary** — everything inside it changes together in one transaction.","**Small aggregates scale; large ones create contention** — a big aggregate means a big lock.","**Reference other aggregates by identity, not by object** — holding a direct reference invites transactional coupling.","**Between aggregates, accept eventual consistency** — that is the deliberate trade.","**Skip:** modelling every entity as an aggregate root. Most entities belong inside one."],"readTime":"3 min read","wordCount":708},"related":[{"slug":"domain-driven-design-basics-2026","title":"Domain-Driven Design Basics for 2026: A Practical Primer","excerpt":"A plain-language introduction to domain-driven design in 2026 — bounded contexts, ubiquitous language, and when the pattern earns its complexity.","category":"code","date":"July 1, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":200},{"slug":"bounded-contexts-explained-2026","title":"Bounded Contexts Explained 2026: Drawing Domain Lines","excerpt":"A bounded context is the boundary where a domain model's vocabulary stays consistent, and outside it the same word can mean something else entirely. Here is how to classify subdomains and draw the lines well in 2026.","category":"code","date":"July 30, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":316},{"slug":"domain-driven-design-explained-2026","title":"Domain-Driven Design in 2026: What It Actually Requires","excerpt":"Domain-driven design is not a diagram or a vocabulary list. Here is what it actually obligates a team to do day to day, and what happens when a team skips straight to the tactical patterns.","category":"code","date":"July 31, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":314}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"cobra-coverage-explained-2026","title":"COBRA Coverage: Expensive, and Sometimes the Right Choice","excerpt":"Continuing employer coverage after leaving costs the full premium plus a fee, and there are usually cheaper alternatives. Here is when it still wins.","category":"guides","date":"July 8, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":195},"older":{"slug":"event-storming-guide-2026","title":"Event Storming: Mapping a Domain in a Room","excerpt":"Event storming gets domain experts and engineers to build a shared model of what actually happens. Here is how a session runs and what makes it work.","category":"code","date":"July 8, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":270}}