Tactical Design
The building blocks used inside one bounded context: entities, value objects, aggregates, domain events, services, repositories, factories and specifications. This is the level where DDD turns into code you actually write.
part ofSoftware design & architectureoverview, primer and where to startread it →on this pageshowhide
explore
- Entities & Value Objects6 questions
- Aggregates5 questions
- Domain Events6 questions
- Domain Services6 questions
- Repositories (DDD)6 questions
- Factories (DDD)6 questions
- Specification Pattern6 questions
- Application vs Domain Services6 questions
- Supple Design6 questions
questions
page 2 of 2Why should Value Objects be immutable, and what does 'side-effect-free behavior' mean in practice — for example, should a method like `Money.add(Money other)` mutate the receiver or return a new instance?
basics
~20 sValue Objects should never change after creation. Any 'change' should produce a brand-new object instead of altering the original. So Money.add() should return a new Money with the summed amount, not modify the original — that way nothing else holding a reference to the original gets silently surprised.
Specifications are traditionally described as having three uses: validation, selection, and construction-to-order. What does 'construction-to-order' mean, and how does it differ from just validating an object after it's built?
basics
~20 sValidation checks an object after it exists. Construction-to-order uses the same rule before building, to describe exactly what a new object should look like so a factory can build one that already satisfies the rule, instead of building something and then rejecting it.
When a domain operation can't be made fully side-effect-free -- for example, Account.debit(amount) must mutate the balance -- how can assertions be used to keep that side effect's contract explicit, and what's the practical failure mode if a team relies on Java's assert keyword for this in production?
basics
~20 sYou write down, in the code, exactly what must be true before and after the change (e.g. 'balance can't go negative') so anyone reading it knows the rule without guessing. But Java's built-in assert is switched off by default in production, so if that's your only check, the rule silently stops being enforced.
In Domain-Driven Design tactical design, why is it considered a red flag for one aggregate root's method to directly call methods on another aggregate root within the same operation, and what should happen instead?
basics
~20 sIf one aggregate's code directly calls another aggregate during an operation, you've merged their boundaries and lost the point of splitting them. Coordination should instead happen one level up, in application code that orchestrates the use case.
You're modeling a 'FundsTransfer' operation between two Account aggregates in a banking domain. Walk through the reasoning for why this operation should become a domain service rather than a method on the Account entity, and what the domain service's method signature and statelessness constraints should look like.
basics
~20 sNeither Account owns a transfer between two accounts, so it doesn't fit as one entity's method. A stateless domain service takes both accounts and the amount, applies debit/credit rules, and returns the results — holding no data between calls.
Why might a domain model raise a specific event like 'CustomerAddressChanged' or 'CustomerEmailVerified' instead of a single generic 'CustomerUpdated' event carrying a diff of changed fields? What's the trade-off?
basics
~20 sSpecific events tell listeners exactly what happened, so they can react directly without inspecting the data. A generic 'Updated' event is easier to add but forces every listener to figure out what actually changed by digging through the payload.
Should a domain event's payload carry the full state of the aggregate at the time it was raised, or just the minimal data needed to describe what changed, like IDs and the specific delta? What are the trade-offs?
basics
~20 sA 'thin' event carries just the key IDs and what changed, like an order ID and new status. A 'fat' event carries the whole aggregate's data. Thin events are smaller and less likely to leak stale info; fat events save listeners an extra lookup but can go stale or expose more than needed.
A codebase has a class called OrderService with roughly 40 methods covering discount calculation, order validation, tax computation, and shipment scheduling, injected wherever an Order is touched. Is this a Domain Service in the DDD sense? What's wrong with it, and how would you fix it?
basics
~20 sNo - a real Domain Service covers one business process, not everything vaguely Order-related. This is a dumping ground that hollowed out Order. Fix it: move Order's own logic back onto Order, split the rest into small, named services.
A system originally identifies `User` entities by email address (using the email as the natural key/identity). Later, the business adds an 'email change' feature. What breaks because of this identity choice, and how does a surrogate key like a UUID fix it?
basics
~20 sIf a user's identity IS their email, then changing the email means the user 'becomes a different user' as far as the system is concerned — any records referencing the old email (orders, permissions, links) lose their connection. A separate, unchanging ID (like a random UUID) fixes this because the ID never changes even when the email does.
You're reviewing a pull request where a developer added a standalone factory class, complete with an interface and a builder-style fluent API, just to construct a value object that holds a single validated `email: String` field. What's the concern, and when does that level of ceremony actually pay for itself?
basics
~20 sFor something that small, a factory is overkill - a simple constructor or static function that checks the email format is enough. Factories earn their cost only when creation is genuinely complicated, like needing several steps, other data, or business rules that could easily be gotten wrong.
What practical failure modes appear when a repository's 'collection-like' illusion breaks down at scale - for example with large aggregates, deep object graphs, or eager/lazy loading choices hidden behind a simple findById call?
basics
~20 sWhen aggregates get big, a method that looks as simple as 'find this order' can secretly load a huge amount of data or run many slow queries behind the scenes - the simple-looking interface hides a performance problem that only shows up once you have a lot of data or traffic.
When an application needs complex queries - filtering, sorting, pagination, cross-aggregate reporting - how can a repository support that without leaking infrastructure query mechanics (SQL fragments, ORM criteria objects) into the domain model?
basics
~20 sInstead of letting callers build raw database queries through the repository, you give the repository a small number of named methods that describe what business question is being asked (like 'find overdue orders'), or you move heavy reporting needs to a completely separate read-only layer that doesn't pretend to be the domain repository.
When you use a composite Specification purely for validation (e.g., rejecting an invalid domain object before it's saved), how do you get a useful, itemized error message instead of just a single true/false result?
basics
~20 sA single yes/no doesn't tell the user WHY something failed. So instead of just isSatisfiedBy returning false, you add a way for each sub-rule to report its own failure reason, and collect all of them into a list the user can actually read.
'Closure of operations' is a Supple Design pattern where an operation's return type is the same type as its arguments -- for example, Money.add(Money other): Money rather than Money.add(Money other): BigDecimal. What problem does this solve, and when does forcing closure actually hurt the design?
basics
~20 sIf combining two things of a type gives you back the same type, you can chain operations endlessly (add, add, add) without ever stepping outside that type into something less meaningful, like a raw number that's lost its currency and rules.
Can a Domain Service depend on a Repository interface, such as one used to check that no other Customer already has a given email address? Where's the line between a legitimate domain-service dependency and infrastructure concerns leaking into the domain layer?
basics
~20 sYes, when a rule needs to look across many records, like checking an email is unique. The line: depend on an interface defined by the domain, never a concrete database or HTTP class, and never let query details leak in.
As a tech lead reviewing a design that proposes composable Specification objects for every filtering and validation need across a large codebase, what trade-offs and failure modes would make you push back, and when is a simpler predicate/query-object approach the better call?
basics
~20 sSpecifications add extra classes and indirection. If a team uses them for every tiny check, the codebase gets harder to read and navigate for little benefit. Use them where rules are genuinely reused, combined, or need to become queries - not everywhere.
In Domain-Driven Design, for a domain with a hard, non-negotiable invariant — such as a bank account balance that must never go negative under any concurrent transaction — how do you weigh keeping that invariant inside one aggregate against the throughput cost, and what alternatives exist if a single-aggregate design becomes a bottleneck?
basics
~20 sWhen a rule can never be broken, even briefly, keep its data in one aggregate so one lock guards it. If that's too slow, speed up or queue that path — don't loosen the guarantee.
In a system where placing an order should raise an OrderPlaced domain event, should the domain logic that decides to raise that event publish it directly to an event bus/message broker itself, or should it merely return/collect the event for the application service to publish after the transaction commits? Explain the reasoning.
basics
~20 sThe business logic should just record that an event happened — not broadcast it. The application service should send it, and only after the database changes are safely committed, so nobody hears about something that later gets rolled back.
When is raising a domain event the wrong choice inside an aggregate - i.e., when should a state change just be handled with a direct method call or a straightforward invariant check instead?
basics
~20 sIf nothing outside the aggregate actually needs to react, and it's a simple internal check like 'balance can't go negative,' don't make it an event - just check it directly in the method. Events are for facts something else cares about, not for every little internal rule.
A team wants to keep a full history of every state an `Order` entity passed through (e.g., for auditing or event sourcing) by storing immutable snapshots of the Order's data at each transition. Does storing these snapshots turn Order into a Value Object, and how should the snapshot type itself be modeled?
basics
~20 sNo — Order is still an Entity because it still has one identity across its whole life. The snapshots are a different, separate thing: each snapshot is a Value Object (immutable, no identity of its own) that just describes 'what the Order looked like at this specific point in time,' tagged with the Order's id and a timestamp.
Your team enforces a strict rule that each transaction may modify at most one aggregate. A new Order aggregate is created by reserving stock across several Product aggregates and then persisting the Order. How should the factory/creation logic be structured so it respects the one-aggregate-per-transaction rule while still guaranteeing the Order is never created with stock it can't actually claim?
basics
~20 sSplit it into steps: first reserve or hold the stock on each product (separate small transactions or a reservation step), then create the Order only if all reservations succeed, using its own transaction. Don't try to change the order and all the products at once - use short-lived holds and a way to release them if something fails partway through.
In a system with both a rich write-side aggregate repository and heavy reporting/read needs, how do you decide the boundary between what stays on the aggregate repository versus what moves to a separate read model, and what keeps that boundary from eroding as the system grows?
basics
~20 sYou draw a line based on whether something needs a full business object to make decisions with (that stays with the repository) or just needs to show/report data (that moves to a simpler, faster read-only path) - and you keep re-checking that line as the system grows, because there's constant pressure to blur it for convenience.
Supple Design's 'standalone classes' principle pushes for minimizing a class's dependencies on other concrete types to reduce the mental overhead of understanding it in isolation. At what point does chasing standalone classes across a large domain model start working against you, and how do you recognize that trade-off point in practice?
basics
~20 sA class that depends on almost nothing else is easy to understand and test on its own. But if you go too far and strip out every relationship, you can end up hiding real connections between concepts that the domain actually needs, making the bigger picture harder to see.
showing 31–53 of 53