skip to content

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%

answer

  1. proportional to complexity, not mandatory
  2. single-field VO: constructor or static of() is enough
  3. builder = window of transient invalid state
  4. factory signal gets diluted if overused everywhere

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.

solid answer

~40 s

DDD's factory pattern is meant for complex creation - multiple invariants, derived state, external dependencies, or coordination across entities - not as a mandatory wrapper around every constructor. A single-field value object with one validation rule doesn't need an interface, a separate factory class, or a builder; a static `Email.of(raw)` method or a validating constructor is sufficient and far more readable. The ceremony pays for itself once creation genuinely has multiple steps that must happen together, external state to consult, or more than one legal 'shape' to produce - at that point the indirection buys real safety against half-built or rule-violating objects, which a one-field value object was never at risk of in the first place.

go deeper

for a junior

Should sense intuitively that the PR feels like 'too much for what it does,' even without precise vocabulary for why.

for a middle

Should articulate the proportionality rule - factories for genuinely complex creation, plain constructors/static methods for simple validated values.

for a senior

Should name the concrete costs of over-applying factories (signal dilution, review/onboarding overhead) and the builder's transient-invalid-state risk.

for a principal

Should connect the review comment to a broader team norm/convention question - how the codebase signals 'this construction is complex' consistently, without over- or under-applying ceremony.

## What the pattern is actually scoped to The DDD factory pattern is explicitly scoped to a specific problem: aggregates and, less commonly, entities or value objects whose valid construction is complex enough that leaving it to ad-hoc caller code risks producing an invalid instance. It's easy to lose sight of that scoping condition and start treating 'factory' as a mandatory architectural layer that every domain object must have, purely because it's the 'proper DDD way.' That instinct, applied uniformly, produces exactly the PR in question: an interface plus a standalone factory class plus a fluent builder, all to validate that a string looks like an email address and wrap it in a value object. Every one of those layers has a real cost: - more files to open to understand one concept - more indirection between 'I want an Email' and the three lines of regex or parsing that actually validate it - a steeper on-ramp for anyone new to the codebase who now has to learn the team's factory convention before they can create the simplest possible object ## The proportionality question The proportionality question worth asking on review is: **does construction here involve more than a single local check on the given input?** A value object with one field and one validation rule is a pure function of its input - raw string in, `Email` out, or fail - and a validating constructor or a single static factory method (`Email.of(raw)`) fully captures that with zero extra ceremony; there's no multi-step sequence to get wrong, no derived state to compute, no external dependency to consult, and no risk of a caller 'forgetting a step' because there's only one step. The case for a full factory class strengthens as any of the following become true: - the aggregate has several related invariants that must all hold together and are easy to check out of order or forget one of - some fields are derived from others rather than supplied directly - construction needs an external collaborator (a repository, an ID generator, a clock) - the factory has to choose among multiple concrete types or coordinate creation of more than one aggregate at once None of those conditions apply to a single validated string wrapped in a value object. ## The cost of over-applying, not just under-applying The cost of under-applying factories (skipping one where it's genuinely needed) is well understood - invalid aggregates slipping into the system, duplicated validation logic drifting apart across call sites. The cost of over-applying them is less discussed but just as real, and shows up as a different flavor of production and team-velocity pain: - reviewers spend time reasoning about interfaces and builders that add no behavior beyond what a two-line function would - onboarding new engineers takes longer because the 'how do I make a simple thing' path is buried under a pattern meant for complex things - somewhat ironically, teams that reflexively wrap everything in factories often get numb to what a factory is actually signaling - when everything is wrapped in ceremony, 'this one has a factory' stops meaning 'this one is complex and worth extra care,' and starts meaning nothing at all That erosion of signal is the real cost: factories are useful specifically because they mark 'pay attention here, construction is non-trivial'; applying them uniformly destroys that marker. ## The builder, specifically A related trade-off worth naming explicitly in review: builder-style fluent APIs (chained `.withCustomer(...).withItems(...).build()` calls) are useful when an aggregate has many optional fields or when readability at the call site genuinely benefits from named steps. But they also open a window during which a partially-built object exists in an inconsistent state - all the calls between starting the builder and calling `.build()` - and can hide missing-required-field bugs until `.build()` is called at runtime rather than at compile time. For a one-field value object, a builder introduces exactly the kind of transient invalid state factories exist to prevent, in service of a fluency the object doesn't need because it only ever has one field to set. ## Where the calibration comes from A concrete real-world calibration: teams following DDD closely, such as codebases influenced by Vaughn Vernon's 'Implementing Domain-Driven Design,' reserve dedicated Factory objects for aggregate roots with genuine construction complexity - an aggregate needing an owner, a tenant, and a generated identity, for instance - while letting simple value objects like an email address or a money amount validate themselves in a private constructor plus a small companion `of`/`from` static method, with no separate factory class at all. The right review comment on the PR in question is not 'remove all validation' but 'collapse this down to a single static factory method or validating constructor - the interface, standalone class, and builder aren't earning their keep here.'

  • What's the actual cost of adding a full factory class, interface, and builder to a trivial single-field value object, beyond just 'it's more code'?
    It dilutes the signal that a factory is supposed to send - that construction here is genuinely complex and worth extra scrutiny - so when every object gets the same treatment, reviewers and new engineers can no longer use 'this has a factory' as a cue for where the real invariant-guarding logic lives. It also adds onboarding friction and review overhead disproportionate to the actual risk of misuse.
  • Is a builder-style fluent API ever a bad fit even for a genuinely complex aggregate?
    Yes - builders create a window between the first call and the final build step during which an incomplete, potentially invalid object exists, and missing-required-field mistakes typically surface only at build-time rather than at compile time. For aggregates where missing a required field must be structurally impossible, a single factory method taking all required parameters is often safer than a builder.

Building a doghouse doesn't need architectural blueprints, permits, and a general contractor - that level of process is for building a skyscraper, where skipping it causes real collapse risk; applying skyscraper process to every doghouse just slows everyone down without making the doghouse any sturdier.

saying these in an interview costs you the question

  • Insists every domain object must have a full factory regardless of complexity
  • Can't identify any downside to applying heavy patterns uniformly
  • Defends the builder for a one-field value object without acknowledging the transient-invalid-state window
  • Treats 'more DDD patterns' as inherently better code

context