Learn the vocabulary of domain objects that hold only data while their business logic lives elsewhere.
0 / 5 completed
1 / 5
At standup, a dev mentions domain objects that hold only data fields with getters and setters and contain no business logic at all, while every actual behavior and business rule lives in separate service classes that operate on those objects from outside. What is this pattern called?
An anemic domain model is exactly this: it describes domain objects that hold only data fields with getters and setters and contain no business logic at all, while every actual behavior and business rule lives in separate service classes that operate on those objects from outside, leaving the objects themselves 'anemic.' A hash collision is an unrelated hash-table concept about two keys sharing a bucket. This data-only-objects-with-external-logic pattern is exactly why an anemic domain model is considered a smell in object-oriented systems that are supposed to encapsulate behavior with data.
2 / 5
During a design review, the team refactors an anemic domain model by moving an invoice's business rules, like calculating its total or applying a discount, directly into the Invoice class itself instead of a separate InvoiceService. Which capability does this provide?
Moving behavior into the domain object here provides encapsulated behavior alongside the data it operates on, since the Invoice class itself now enforces its own business rules, instead of relying on external service classes that could apply the wrong rule or skip it entirely. Keeping all business rules in a separate InvoiceService while the Invoice class holds only data leaves nothing stopping a caller from mutating the invoice's data directly and bypassing the rules altogether. This encapsulate-behavior-with-its-data behavior is exactly why moving logic out of an anemic domain model and into the domain object itself is the standard fix.
3 / 5
In a code review, a dev notices an Invoice class exposes only public getters and setters for its fields, while a separate InvoiceService class contains all the logic for calculating totals and applying discounts, meaning nothing stops another part of the codebase from mutating the invoice's fields directly and skipping that logic. What does this represent?
This is an anemic domain model, since the Invoice class holds only data with all its business logic pulled out into a separate service, leaving nothing to stop a caller from mutating its fields directly and bypassing the rules. A cache eviction policy is an unrelated concept about discarded cache entries. This data-only-object-with-external-logic pattern is exactly the kind of smell a reviewer flags once a domain object's data can be mutated without its business rules ever being enforced.
4 / 5
An incident report shows an invoice shipped with an incorrect total in production, because a new code path mutated the Invoice object's fields directly without going through the separate InvoiceService that held the actual calculation logic, and nothing in the anemic Invoice class itself enforced the rule. What practice would prevent this?
Moving the calculation logic directly into the Invoice class itself makes the object enforce its own business rules, so it can't be mutated into an inconsistent state by a caller that bypasses a separate service. Continuing to keep the Invoice class as a data-only anemic model with all logic in a separate service regardless of how many code paths end up bypassing that service is exactly what caused the incorrect total described in this incident. This encapsulate-the-logic-in-the-object approach is the standard fix once an anemic domain model is confirmed to let its data be mutated without its rules being enforced.
5 / 5
During a PR review, a teammate asks why the team moves business logic into the domain object itself instead of keeping it in a separate service class, given that a service class also technically contains the logic somewhere in the codebase. What is the reasoning?
Encapsulating logic in the domain object itself guarantees the rule is enforced every time the object's state changes, no matter which caller triggers it, while keeping logic only in a separate service class relies on every caller remembering to go through that service, and any caller that mutates the object's fields directly bypasses the rule entirely. This is exactly why moving away from an anemic domain model toward behavior-rich domain objects is the standard fix for object-oriented systems where consistency matters.