How do you turn what the business tells you into a domain model that holds up once the exceptions arrive?
Model the state changes and invariants rather than the nouns the business names, and go looking for reversal, amendment, partial completion and manual override before committing to a shape. Domain-driven design earns its keep where rules are contested; on CRUD and reporting it is ceremony.
What the interviewer is scoring
- Does the candidate hunt for exceptions before fixing the model rather than after the first release
- Whether invariants are identified explicitly, and used to place transactional boundaries
- That reversal, amendment and partial completion are anticipated instead of added later as boolean flags
- Can the candidate name where domain-driven design is overhead, without dismissing the strategic patterns
- Whether the model is expressed in the business's own words, including the ugly ones
Answer
The business describes the happy path, in nouns
Ask anyone to explain their process and you get a sequence of nouns on a straight line: a customer places an order, the warehouse picks it, a courier delivers it, finance invoices it. That description is true and it is a small fraction of the domain. The rest is what happens when the line bends, and it lives in the heads of people who did not think to mention it because to them it is not an exception, it is Tuesday.
So the first job is not to model. It is to collect the bends deliberately, before any shape is committed to. The most productive interviewing habit is to answer every stated rule with "always?" — an order always ships from one warehouse, always? A customer always has one billing address, always? An invoice always raised after delivery, always? The answer is rarely yes, and each no is a structural fact you would otherwise discover after the schema reached production.
The second habit is to ask about the verbs rather than the entities. Nouns are the residue of processes; elicit the state changes — what happened, who caused it, what it invalidated, what it triggered — and the entities fall out with their lifecycles attached. This is what makes event-oriented discovery workshops such as event storming worth the room booking: they get stakeholders arguing about sequence and causation, which is where the disagreements they did not know they had become visible. A model derived from a noun list produces objects with plausible fields and no behaviour; a model derived from state changes produces objects that know what they are allowed to do.
Find the invariants, and let them draw the boundaries
An invariant is a statement that must be true at every moment a transaction is allowed to complete: an order's line totals sum to its header total; a shipment cannot contain more of an item than was ordered; a ledger's debits equal its credits. Invariants are the useful output of modelling because they are testable, they are the thing the business genuinely cannot tolerate being wrong, and they tell you where consistency boundaries go.
That is the practical payoff. Anything that must hold immediately belongs inside one transactional unit; anything that may be briefly untrue belongs outside it, reconciled by a process. Most bad models get this backwards, either wrapping half the system in one aggregate because everything felt related, or scattering a genuine invariant across three services and then discovering the sum no longer adds up under concurrency. Ask, for each rule, whether the business would accept it being false for thirty seconds. Many rules survive that question — inventory availability, credit exposure and loyalty balances are routinely eventually consistent in real businesses — and the ones that do not are your aggregates.
Use the business's own vocabulary throughout, including the terms that are ugly or historically odd. A model you cannot speak aloud with a stakeholder is a model whose errors nobody can see. If they say "cede", "reserve", "break" or "manifest", model those words rather than substituting a tidier synonym that silently drops the connotations making the term specific.
Four exceptions that break naive models
Whatever the industry, the same four categories are where a model designed around the happy path has to be rewritten rather than extended. Go looking for these on purpose.
Reversal. Something valid was done and must be undone. The naive model deletes or flips a status, which destroys the fact that it ever happened. Domains that involve money have known the answer for centuries: you never erase, you post a compensating entry, and the original plus the reversal are both permanent history. This generalises well beyond accounting, and adopting it early means "what did this look like in March" and "who reversed it" are answerable rather than lost.
Amendment with an effective date. A change agreed today takes effect from a date in the past or the future — a backdated endorsement, a price change effective next quarter, a corrected address that was wrong since January. A model with a single mutable current state cannot express this, and the retrofit is expensive. What it needs is two independent time axes: when a fact was true in the world, and when your system came to know it. Keeping both lets you reproduce what you believed on any past date, which is what an auditor, a regulator or an argument with a customer requires.
Partial completion. Half the order ships, half the invoice is paid, part of the claim is approved. Naive models put a single status enumeration on the parent and then find the parent is simultaneously "shipped" and "not shipped". Model quantity or amount at the level where it varies, and derive the parent's status rather than storing it.
The manual override. Someone with authority does the thing the rules forbid, because a customer matters or a disaster is unfolding. A model that makes this impossible does not prevent it; it makes the operations team achieve it by editing the database, which loses the actor and the reason. Model the override as a legitimate, recorded, authorised action and you keep the invariant explicit while capturing who set it aside and why.
The tell that the shape is wrong is that each new exception arrives as a boolean. When a table accumulates is_manual, is_backdated, is_partial and is_corrected, the flags are not the problem — they are the symptom of a model built around one path with the others bolted to its side, and by the fourth flag the combinations are untestable.
// Shape that dies on its third exception: one mutable state, flags for the rest.
type Policy = { premium: Money; status: 'active' | 'cancelled';
isBackdated: boolean; isManualOverride: boolean };
// Shape that absorbs them: the change is the thing you store, and it carries
// when it takes effect in the world, separately from when you learnt it.
type PolicyChange = {
policyId: PolicyId;
effectiveFrom: Date; // valid time: when it is true for the customer
recordedAt: Instant; // record time: when we knew - lets you replay any past view
change: PremiumAdjusted | CoverAdded | Cancelled | Reinstated;
authorisedBy: UserId; // present because overrides are modelled, not smuggled in
reason: ReasonCode;
};
Where domain-driven design pays, and where it is costume
Domain-driven design is two toolkits, usually adopted in the wrong order. The strategic patterns — ubiquitous language, bounded contexts, and a context map stating how those contexts relate — are cheap, need no framework, and hold nearly all the value. Their central insight is that a concept has no single true model across a company. "Customer" in credit risk and "customer" in marketing are genuinely different things with different rules and lifecycles, and the answer is two models with an explicit translation between them, not a canonical enterprise entity with sixty nullable fields satisfying neither. If a project takes one idea from DDD it should be this one, because it also determines service boundaries and therefore how much cross-team coupling you live with.
The tactical patterns — aggregates, entities, value objects, repositories, domain events — pay when the domain has real invariants and contested rules worth protecting: underwriting, pricing, settlement, clinical workflow, regulated eligibility. There, objects that cannot be constructed in an invalid state genuinely reduce defects, and value objects for money, quantity and identifiers remove a whole class of unit and rounding bugs.
They are ceremony when the domain is thin. A service that stores and returns records, an integration mapping one payload to another, a reporting pipeline, an internal admin tool: these have data and validation, not a domain, and wrapping them in repositories, factories and application services adds indirection with nothing behind it. The commonest failure is importing the tactical vocabulary without the strategic thinking — a team announces it is doing DDD, renames its data access classes to repositories, keeps one shared database and one canonical model across every context, and has taken on the whole cost while buying none of the benefit.
The value of a domain model is proportional to how much of the business's difficulty it absorbs. Where that difficulty is real and lives in the rules, invest, and expect the model to be the most valuable artefact the team owns. Where the difficulty is technical — throughput, latency, integration count — a rich domain model is not the lever, and reaching for it because it sounds sophisticated is how a straightforward system acquires four layers and no clearer behaviour.
Likely follow-ups
- How do you model a correction to something that was already reported to a regulator?
- Two contexts need the same entity with different rules. How do you avoid one shared canonical model?
- What tells you an aggregate boundary is drawn in the wrong place?
- When would you deliberately keep an anaemic model and put the logic in services?
- How do you get exceptions out of stakeholders who insist the process is simple?
Related questions
- The operations team says there is no rule for this, they just use judgement. How do you model that?hardAlso on domain-modelling6 min
- Where does encapsulation or polymorphism change a design, and how do you decide between composition and inheritance?mediumAlso on invariants6 min
- You are building the payer side. How do you configure benefits so a plan change does not need a code release?hardAlso on effective-dating6 min
- Everyone in the business calls it the fax queue, and nothing has been faxed since 2014. Do you keep that name in the code?mediumAlso on domain-modelling4 min
- The business wants to change a rating factor next Monday. Walk me through what actually has to happen.hardAlso on effective-dating5 min
- A broker asks you to backdate a cover change to three months ago. How do you model the policy so that works?hardAlso on effective-dating5 min
- Given package weights in a fixed order, find the smallest ship capacity that delivers them all within D daysmediumAlso on invariants5 min
- Merge a list of overlapping intervals, and justify the key you sorted byeasyAlso on invariants4 min