In Domain-Driven Design, why might a team introduce a dedicated factory method or factory class to create an aggregate instead of just calling `new Order(...)` directly wherever an order is needed?
answer
- single choke point for creation
- illegal states unrepresentable
- private constructor + public factory
- proportional to complexity, not mandatory everywhere
basics
~20 sBecause creating some objects takes several steps and rules that must all be checked together; a factory does that work in one place so every part of the code creates the object correctly and never ends up half-built or invalid.
solid answer
~30 sA factory centralizes the logic needed to build a valid aggregate: validating inputs, computing derived fields, assigning identity, and wiring child entities/value objects together. Aggregates in DDD must never exist in an invalid state, so if construction requires several steps or cross-field checks, a plain public constructor called from many places risks callers skipping a step or getting the order wrong. A factory (method or standalone class) is the single choke point where those rules live, so the rest of the codebase just asks for 'a new Order' and always gets a consistent, invariant-satisfying object back.
go deeper
Should be able to say a factory bundles creation steps in one place and mention that skipping it lets bad objects get created; doesn't need to discuss private constructors or Result types.
Should articulate the 'illegal states unrepresentable' idea and know the constructor is typically made non-public so the factory is the only entry point.
Should reason about proportionality (when a factory is overkill) and describe concrete failure modes like duplicated validation logic drifting.
Should connect factory design to aggregate boundary design as a joint decision and discuss how this scales across a codebase with many call sites and evolving business rules.
## What an aggregate promises An **aggregate** in DDD is a cluster of entities and value objects treated as one consistency unit, guarded by a root entity that enforces the business rules (**invariants**) that must always hold for that cluster. One of the strongest guarantees a well-designed aggregate offers is that it can never be observed in an invalid state by anything outside it - not right after construction, not mid-update. That guarantee is easy to break at exactly the moment of creation, because creating a real aggregate often takes several dependent steps: - validating raw input - deriving fields from other fields - generating or reserving an identity - creating child value objects - wiring everything together in the right order A public no-op constructor that just assigns whatever it's handed puts all of that responsibility on every single call site. If ten different places in the codebase construct an `Order`, and only nine of them remember to also compute the tax line and set the initial status, the tenth becomes a silent time bomb - a supposedly valid aggregate that is actually broken, discovered only later when some downstream code trips over the missing field. ## How a factory closes the gap A factory exists to close that gap by becoming the one and only sanctioned path to a valid instance. Concretely this means: - the constructor itself becomes **private or package-private** (so it cannot be called from outside) - a **factory method or factory class** becomes the public entry point Callers pass in raw, possibly-untrusted input; the factory validates it, performs whatever computation or lookups are needed, and only then invokes the constructor with data it has already proven consistent. If validation fails, the factory refuses to produce an object at all - it throws, returns a Result/Either type, or otherwise signals failure - so there is no window in which an invalid aggregate escapes into the rest of the system. This is often summarized as **'make illegal states unrepresentable'**: by removing every other route to construction, the type system and the factory together guarantee that any `Order` object you can get your hands on is, by definition, one that passed its rules. ## The trade-off The trade-off is indirection and a small amount of ceremony. A trivial aggregate with one or two fields and no cross-field rules gains little from a factory - a public constructor with parameter validation inline is often perfectly fine, and DDD is explicit that factories are for complex creation, not a mandatory ritual for every object. Overusing factories on simple aggregates adds a layer developers must learn to navigate for no real safety benefit, and can obscure what should be a two-line constructor behind a static method and a builder-style call. The judgment call is proportional to complexity: does creation involve more than trivial field assignment - multiple related invariants, computed values, identity assignment, or coordination between two or more child entities? If yes, a factory earns its keep. ## Failure modes when this is skipped Failure modes when this is skipped in production tend to look the same regardless of language or framework: - aggregates that pass unit tests (because tests happen to call the constructor correctly) but produce corrupted data once real, messier input reaches a different call site - duplicate, slightly-divergent validation logic scattered across controllers, batch jobs, and test fixtures that each reinvent 'how do I build an Order' and drift out of sync over time - invariant violations that only surface downstream, e.g., a report job crashing on a null total because one code path forgot to set it Retrofitting a factory after the fact is also more painful than starting with one, since every existing call site that used the constructor directly has to be found and migrated, and any bad data already persisted has to be cleaned up or defended against. ## Where it shows up A concrete, widely-cited framing comes from Eric Evans's original DDD book, which treats a factory for assembling an Aggregate as being just as deliberate a design decision as choosing the aggregate boundary itself - the two are meant to be designed together, because the factory is what encodes 'how do you legally get one of these.' A practical real-world analogue is an e-commerce `Order` aggregate whose factory, all before the constructor is ever invoked: 1. computes line totals 2. applies applicable promotions 3. generates an order number from a sequence 4. sets the initial 'pending' status Callers just supply a cart and a customer and always receive a fully-formed, rule-compliant `Order`.
- What specifically goes wrong if you skip the factory and just expose a public constructor on a complex aggregate?Every call site becomes responsible for correctly performing all the validation, derivation, and wiring steps itself, and any site that gets it wrong - or a future site added by someone unaware of the rules - produces a silently invalid aggregate. Duplicated construction logic also drifts out of sync over time as rules change in one place but not another. The bug typically surfaces far from its cause, e.g., a report or export job crashing on missing state.
- Does every aggregate need a factory?No - DDD explicitly reserves factories for aggregates whose creation is non-trivial: multiple invariants, derived fields, generated identity, or coordination across child entities. A simple aggregate with a couple of fields and no cross-field rules is fine with a validating constructor; adding a factory there is unnecessary ceremony.
A factory method is like a car manufacturer's assembly line rather than handing every customer a box of loose parts and a wrench - the line guarantees every car that rolls off has its safety checks done and bolts torqued in the right order, instead of trusting each buyer to assemble it correctly themselves.
saying these in an interview costs you the question
- Says factories are only about hiding 'new' for style reasons
- Can't explain what breaks if construction is skipped or done out of order
- Claims every DDD aggregate must have a factory regardless of complexity
- Confuses this with the GoF Factory Method/Abstract Factory patterns without mentioning the invariant-guarding purpose