How does the GRASP "Creator" heuristic coexist with dependency injection and a composition root? Which objects should still be created by the class that owns them, and which should be injected?
answer
- inject behaviour, construct data
- composition root = long-lived collaborators only
- entities/VOs/events/DTOs → Creator
- bridge = injected factory (assisted injection)
- container-managed entities = smell
basics
~20 sInject long-lived collaborators — services, repositories, gateways, clients — wired once at startup. Keep creating short-lived, data-carrying objects (entities, value objects, events, DTOs) inside the class that owns or has their data, as Creator says. When both are needed, inject a factory.
solid answer
~60 sThe two ideas govern different populations of objects. A **composition root** (a single place near the entry point where the object graph is assembled) and a DI container handle **stateless, long-lived collaborators** whose implementation should be substitutable: repositories, HTTP clients, mailers, policy strategies. **Creator** governs **short-lived, per-operation, data-carrying objects**: entities, value objects, domain events, commands, results. Those cannot come from a container because they depend on runtime arguments, are created many times per request, and carry state — a container would either have to know the data or return shared mutable instances. Practical rule: *if it has runtime data, construct it; if it has behaviour and no request-specific state, inject it.* When a short-lived object genuinely needs an injected collaborator (id generator, clock, tax service), do not inject that service into the entity; either resolve the value in the application layer and pass it as a parameter, or inject an abstract **factory/provider** that the owner calls with runtime arguments. This preserves both Creator's coupling argument and DI's substitutability, and keeps domain objects deterministic and trivially unit-testable.
go deeper
Say services are injected and simple data objects are created with new by whoever has the data.
Give the taxonomy (services vs entities/value objects/events) and the rule 'inject behaviour, construct data'; mention the composition root.
Add the injected-factory bridge, why containers can't create entities, and the anti-patterns (service locator in entities, container-managed entities).
Discuss creation policy across a system: composition-root boundaries per module, assisted-injection factories as the sanctioned bridge, the drift toward anemic domains in DI-heavy codebases, and testing/serialization consequences of dependency-bearing entities.
## Two different questions - **Creator** asks: *which class should call the constructor of this domain object?* Answer: the one that aggregates/contains/records/closely uses it or holds its initializing data. - **Dependency injection** asks: *how does a class obtain the collaborators it needs to do its job, without hard-coding their implementations?* Answer: they are supplied from outside — constructor parameters, usually wired at a **composition root**, the single place (main, module config, container configuration) where the whole object graph is assembled. They conflict only if you assume every object must come from one mechanism. It must not. ## The taxonomy that resolves it | Object kind | Examples | Lifetime | Created by | Rationale | |---|---|---|---|---| | **Services / collaborators** | repository, mailer, HTTP gateway, cache, policy strategy | app lifetime or request scope | composition root / container | stateless, substitutable, few instances, must be mockable | | **Domain entities & aggregates** | `Order`, `Account` | per operation | application service loads or creates; the aggregate creates its parts (Creator) | carry identity + runtime data | | **Value objects** | `Money`, `DateRange`, `EmailAddress` | transient, many | whoever has the data (Creator) | pure data; injection is meaningless | | **Events / commands / DTOs** | `OrderPlaced`, `PlaceOrderCommand` | transient | the class that has the data (Creator) | serialized data carriers | | **Per-operation stateful helpers** | a parser instance, a batch accumulator | per call | the using class (Creator) | needs runtime args | > **Heuristic:** *behaviour without request state → inject; data with runtime values → construct.* ## The bridge: injected factories What if a short-lived object needs both runtime data and an injected collaborator? Three options, best first: 1. **Resolve the value outside and pass it in.** The application layer asks the clock/id generator/tax service, then calls `order.addLine(product, qty, taxRate, now)` or passes a small context object. The entity stays pure — deterministic, testable without stubs. This is by far the most common right answer. 2. **Inject an abstract factory into the class that needs it.** The container supplies `LineFactory`; the owner calls `lineFactory.create(product, qty)` with the runtime arguments. The container provides the *dependencies*, the caller provides the *data*. Many containers/frameworks generate such "assisted injection" factories. 3. **Inject the service into the domain object itself.** Usually a smell: it makes entities non-serializable, hard to reconstitute from persistence, and non-deterministic in tests. Reserve for rare cases (double dispatch to a domain service passed as a *method* parameter is often better). ## Anti-patterns to name - **Service Locator inside entities** — the object pulls dependencies from a global registry. Hides dependencies, defeats compile-time checking, makes tests order-dependent. - **Container-managed entities** — registering `Order` in the DI container. Either it becomes a shared mutable singleton or the container must be handed request data it should not know. - **Everything injected** — injecting a `MoneyFactory` to create a two-field value object; ceremony with no substitutability benefit. - **Nothing injected** — entities calling `new SmtpClient()` or `Instant.now()` internally: untestable, non-deterministic, infrastructure leaking into the domain. - **Newing services deep in the call stack** — the mirror image: Creator says nothing about services, and hard-coded service construction destroys substitutability. ## Why Creator still matters in a DI-heavy codebase DI answers *wiring*, not *modelling*. Even with a perfect container, someone must decide whether the controller, the service, or the aggregate builds the order line — and the wrong choice still leaks invariants, exposes collections, and spreads change. In fact heavy DI codebases fail Creator most often: because services are ubiquitous and easy to reach, teams drift into building all domain objects in services with setters (an **anemic domain model**), leaving entities as data bags. ## Testing consequences - Objects created per Creator are constructed directly in tests — fast, no mocking framework, no container bootstrap. - Injected collaborators are replaced with fakes at the boundary. - If a test needs a container to build a value object, the taxonomy has been violated. ## Rule of thumb to quote > Inject **behaviour**; construct **data**. Wire long-lived graphs once at the composition root; let the owner create everything that lives and dies inside a single operation.
- An entity needs the current time and a generated identifier when it creates a part. What's the cleanest approach?Resolve both in the application layer and pass them as parameters (or a small context object). This keeps the entity deterministic and unit-testable without stubbing a clock, and avoids injecting infrastructure into the domain. Injecting a Clock/IdGenerator into the entity is a fallback, and a service locator inside the entity is the worst option.
- Why can't a DI container simply create entities too?Entities depend on runtime data (ids, quantities, user input) that the container doesn't have, and they are created many times per request with distinct state. A container would either need the request data pushed into it or return shared mutable instances, which breaks identity and thread safety.
- How does over-reliance on DI lead to an anemic domain model?When every operation is easy to place in an injected service, teams build domain objects with public setters from services and keep behaviour outside them. Entities degrade into data bags, invariants scatter across services, and Creator's benefit — creation and rules living with the owner — is lost.