skip to content

Factories (DDD)

Creating an aggregate can involve more setup than a constructor should carry, so a factory encapsulates it and guarantees the invariants hold from the first moment. You will also cover reconstitution, the separate path used when rebuilding an aggregate from storage.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

6

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?

level: juniorimportance: must knowfreq 55%

answer

  1. single choke point for creation
  2. illegal states unrepresentable
  3. private constructor + public factory
  4. proportional to complexity, not mandatory everywhere

basics

~20 s

Because 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 s

A 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

for a junior

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.

for a middle

Should articulate the 'illegal states unrepresentable' idea and know the constructor is typically made non-public so the factory is the only entry point.

for a senior

Should reason about proportionality (when a factory is overkill) and describe concrete failure modes like duplicated validation logic drifting.

for a principal

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

context

open as a page

When designing a factory for an aggregate, how do you decide between putting a static/companion factory method directly on the aggregate root versus writing a separate, standalone Factory class?

level: middleimportance: must knowfreq 45%

basics

~20 s

Use a method on the object itself when creation only needs data already going into that object; use a separate factory class when creation needs outside help, like other aggregates, ID generators, or several related objects built together.

open as a page

When an application loads an existing aggregate back from a database row or an event stream, why is this 'reconstitution' path usually kept separate from the factory method used to create a brand-new aggregate, even though both end up producing the same aggregate type?

level: middleimportance: must knowfreq 50%

basics

~20 s

Making something new has to check business rules like 'is this order allowed to exist yet,' but loading something that already existed and was already checked once shouldn't re-run those creation-time checks - it should just rebuild the object from stored data.

open as a page

A factory for an Account aggregate needs to enforce that the account's opening balance is never negative AND that its owner's email is not already used by another account. What's the difference in how a factory should handle these two invariants, and why does that difference matter?

level: seniorimportance: must knowfreq 40%

basics

~20 s

The balance check only needs the data you're already given, so the factory can check it instantly by itself. The email-uniqueness check needs to look at other accounts somewhere else, so the factory has to ask something outside itself - and that check can go stale between asking and actually saving.

open as a page

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?

level: seniorimportance: should knowfreq 35%

basics

~20 s

For 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.

open as a page

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?

level: principalimportance: nice to knowfreq 20%

basics

~20 s

Split 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.

open as a page