{
    "slug": "anemic_domain_model",
    "term": "Anemic Domain Model (Anti-Pattern)",
    "category": "quality",
    "difficulty": "advanced",
    "short": "Domain objects with only data (getters/setters) and no behaviour — business logic scattered across service classes.",
    "long": "Martin Fowler describes the Anemic Domain Model as an anti-pattern where domain objects are data containers and all logic lives in service classes that operate on them. This is procedural programming in OO clothing: objects don't encapsulate their own behaviour, leading to scattered logic, duplication, and loss of the Tell Don't Ask principle. The alternative is a rich domain model where objects contain both data and the behaviour that operates on that data, as in DDD entities and value objects.",
    "aliases": [
        "anemic model",
        "data class",
        "behaviour-free model"
    ],
    "tags": [
        "ddd",
        "oop",
        "anti-pattern",
        "architecture"
    ],
    "misconception": "Separating data (models) from logic (services) is always clean architecture. When domain logic lives entirely in service classes and models are just property bags, you lose encapsulation, invariant enforcement, and the benefits of object-oriented design.",
    "why_it_matters": "Anemic domain models push business logic into service classes, scattering it across the codebase — the domain model becomes a passive data bag that requires external orchestration to do anything meaningful.",
    "common_mistakes": [
        "Creating model classes with only getters and setters and putting all logic in Service or Manager classes.",
        "Confusing an anemic model with a DTO — DTOs are intentionally data-only; domain models should encapsulate behaviour.",
        "Believing that a 'thin model, fat service' architecture is always good — it inverts OOP intent for domain objects.",
        "Not noticing the pattern until the service layer becomes unmaintainably large and all logic is duplicated."
    ],
    "when_to_use": [
        "Complex domains with business rules that should live close to the data they operate on.",
        "When service classes are growing with repeated logic that belongs to the entity itself.",
        "Domain-driven design contexts where entities enforce their own invariants.",
        "Preventing the same business rule from being duplicated across multiple service classes."
    ],
    "avoid_when": [
        "Simple CRUD applications with no domain logic — rich domain models add complexity where there is nothing to encapsulate.",
        "Read-only reporting models where the data is never mutated through domain rules.",
        "Transaction scripts are already clear and maintainable — do not introduce domain objects for their own sake."
    ],
    "related": [
        "tell_dont_ask",
        "value_object",
        "domain_driven_design",
        "single_responsibility"
    ],
    "prerequisites": [
        "domain_driven_design",
        "single_responsibility",
        "tell_dont_ask"
    ],
    "refs": [
        "https://martinfowler.com/bliki/AnemicDomainModel.html"
    ],
    "bad_code": "// Anemic model — data bag, all logic in services\nclass Order {\n    public int \\$status;   // magic number\n    public float \\$total;\n    public array \\$items;  // zero behaviour\n}\nclass OrderService {\n    public function cancel(Order \\$o): void {\n        if (\\$o->status !== 2) throw new \\Exception('Cannot cancel');\n        \\$o->status = 5; // magic numbers everywhere\n    }\n}",
    "good_code": "// Rich model — behaviour lives with data\nclass Order {\n    private OrderStatus \\$status;\n\n    public function cancel(): void {\n        if (!\\$this->status->canCancel()) throw new CannotCancelException();\n        \\$this->status = OrderStatus::Cancelled;\n        \\$this->recordEvent(new OrderCancelled(\\$this->id));\n    }\n\n    public function isPaid(): bool { return \\$this->status === OrderStatus::Paid; }\n}",
    "quick_fix": "Move business logic from service classes back into the domain objects that own the data — if your Order class only has getters/setters, it's anemic",
    "severity": "medium",
    "effort": "high",
    "created": "2026-03-15",
    "updated": "2026-04-19",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/anemic_domain_model",
        "html_url": "https://codeclaritylab.com/glossary/anemic_domain_model",
        "json_url": "https://codeclaritylab.com/glossary/anemic_domain_model.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[Anemic Domain Model (Anti-Pattern)](https://codeclaritylab.com/glossary/anemic_domain_model) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/anemic_domain_model"
            }
        }
    }
}