{ "category": "Domain Architecture", "check": { "population": "every domain type, business rule and context boundary in the domain layer", "freshness": "a verdict stands until a domain type, a rule's location or a context boundary changes", "refusal": "the layer rule or the domain test fails when a rule or a model leaves its context or its aggregate", "observation": "where each business rule and invariant is enforced, read from source and the domain tests", "evidence": "none: the catalog states this check as a class, so a watched run belongs to each system that adopts it", "authority": "the domain model, which decides the context and the aggregate every rule belongs to" }, "records": [ { "id": "domain-driven-design", "distinctFrom": [ { "id": "architecture:event-driven-architecture", "reason": "Domain-driven design models software on the domain's language, while event-driven architecture connects services through published events." } ], "aliases": ["DDD"], "name": "Domain-Driven Design (DDD)", "definition": "A convention of modeling software on the business domain's own language, split into bounded contexts with a domain model in each.", "type": "style", "scope": [ "domain", "bounded context", "system" ], "requires": [ "Ubiquitous Language", "Bounded Context" ], "reinforces": [ "Domain Model", "Semantic Consistency" ], "enables": ["Domain Alignment"], "conflicts_with": ["Anemic Transaction Script"], "tensions_with": ["Simple CRUD"], "violated_by": [ "architecture:anemic-domain-model", "architecture:fat-controller" ], "detected_by": [ "anemic models", "scattered business rules" ], "measured_by": ["domain logic locality"], "refactored_by": [ "lexicon:extract-domain-model", "architecture:aggregate", "lexicon:split-bounded-context" ], "enforced_by": [ "layer rules", "domain tests" ], "severity": "contextual", "exemplar": { "before": "function updateFoo(row: FooRow, name: string) {\n row.name = name;\n row.updated_at = Date.now();\n return fooTable.save(row);\n}", "after": "class Foo {\n private constructor(readonly id: FooId, private name: string) {}\n rename(name: FooName) { this.name = name.value; }\n}\nfoo.rename(FooName.create(name));", "lang": "ts" } }, { "id": "domain-model", "name": "Domain Model", "definition": "A formal definition of the business concepts, rules and invariants of one context, written as types with behavior.", "type": "artifact", "scope": [ "domain", "bounded context" ], "requires": [ "Ubiquitous Language", "Invariant" ], "reinforces": [ "Correctness", "Semantic Contracts" ], "enables": ["Business Rule Encapsulation"], "conflicts_with": ["Anemic Model"], "tensions_with": ["Persistence Simplicity"], "violated_by": ["architecture:anemic-domain-model"], "detected_by": ["procedural domain logic in services/controllers"], "measured_by": [ "rule locality", "invariant coverage" ], "refactored_by": [ "lexicon:move-logic-to-the-domain", "architecture:value-object", "architecture:aggregate" ], "enforced_by": [ "domain layer rules", "tests" ], "severity": "contextual", "exemplar": { "before": "type Foo = { status: string; count: number };\nfunction closeFoo(foo: Foo) { foo.status = \"closed\"; }", "after": "class Foo {\n #status: \"open\" | \"closed\" = \"open\";\n close() {\n if (this.#status === \"closed\") throw new Error(\"Foo already closed\");\n this.#status = \"closed\";\n }\n}", "lang": "ts" } }, { "id": "bounded-context", "distinctFrom": [ { "id": "lexicon:relationship-semantics", "reason": "A bounded context fixes meaning inside one boundary, while relationship semantics fixes the meaning of each link between boundaries." } ], "name": "Bounded Context", "definition": "A rule or precondition that each domain model holds inside one explicit boundary, where its terms have one meaning.", "type": "constraint", "scope": [ "domain", "service", "team" ], "requires": [ "Explicit Boundaries", "Ubiquitous Language" ], "reinforces": [ "Modularity", "Autonomy" ], "enables": [ "Context Mapping", "Microservices" ], "conflicts_with": ["Shared Global Model"], "tensions_with": ["Cross-Context Reuse"], "violated_by": ["lexicon:shared-global-model"], "detected_by": ["shared domain entities across contexts"], "measured_by": ["context coupling"], "refactored_by": [ "lexicon:split-bounded-context", "architecture:anti-corruption-layer", "architecture:context-mapping" ], "enforced_by": ["package/service boundaries"], "severity": "recommended", "exemplar": { "before": "type FooStatus = \"A\" | \"D\";\nfunction priceBar(status: FooStatus) { return status === \"A\" ? 10 : 0; }", "after": "type FooStatus = \"active\" | \"disabled\";\ntype BarEligibility = \"eligible\" | \"ineligible\";\nfunction toBarEligibility(status: FooStatus): BarEligibility {\n return status === \"active\" ? \"eligible\" : \"ineligible\";\n}", "lang": "ts" } }, { "id": "context-mapping", "name": "Context Mapping", "definition": "The activity of recording how bounded contexts relate, including which one is upstream and how their models translate.", "type": "activity", "scope": [ "bounded contexts", "integration" ], "requires": [ "Bounded Context", "Relationship Semantics" ], "reinforces": [ "Explicit Boundaries", "Integration Clarity" ], "enables": ["Anti-Corruption Layer"], "conflicts_with": ["Implicit Integration"], "tensions_with": ["Documentation Overhead"], "violated_by": ["lexicon:implicit-integration"], "detected_by": [ "unclear ownership", "ambiguous integration flows" ], "measured_by": ["undocumented dependency count"], "refactored_by": [], "enforced_by": [ "architecture docs", "dependency reviews" ], "severity": "recommended", "exemplar": { "before": "fooService.writeDirectly(barDatabase, foo);\nbarService.readDirectly(fooDatabase, foo.id);", "after": "const contextMap = {\n upstream: \"FooContext\",\n downstream: \"BarContext\",\n relationship: \"published-language\",\n} as const;\nfooEvents.publish(toBarIntegrationEvent(foo));", "lang": "ts" } }, { "id": "anti-corruption-layer", "name": "Anti-Corruption Layer", "definition": "A design pattern that translates an external system's model into the local domain's terms at the boundary.", "type": "pattern", "scope": [ "integration", "bounded context boundary" ], "requires": [ "Explicit Boundary", "Translation Model" ], "reinforces": [ "Domain Purity", "Low Coupling" ], "enables": ["Legacy/System Integration"], "conflicts_with": [ "Shared Model Coupling", "Vendor Lock-In Leakage" ], "tensions_with": ["Mapping Overhead"], "violated_by": ["lexicon:direct-external-coupling"], "detected_by": ["external DTOs used in domain layer"], "measured_by": [ "leakage count", "adapter coverage" ], "refactored_by": [ "lexicon:extract-adapter", "lexicon:introduce-boundary-dto" ], "enforced_by": [ "import rules", "layer tests" ], "severity": "recommended", "exemplar": { "before": "function createBar(fooResponse: FooApiResponse) {\n return barService.create({ foo_status: fooResponse.state_code });\n}", "after": "type BarInput = { eligible: boolean };\nfunction fromFoo(response: FooApiResponse): BarInput {\n return { eligible: response.state_code === \"A\" };\n}\nbarService.create(fromFoo(fooResponse));", "lang": "ts" } }, { "id": "explicit-boundaries", "distinctFrom": [ { "id": "architecture:declared-jurisdiction", "reason": "Explicit boundaries declare a module's interface and owner, while declared jurisdiction declares which roots and files a naming gate governs." }, { "id": "architecture:abstraction", "reason": "Explicit boundaries fix where one module ends, while abstraction fixes how a concept is expressed without its detail." }, { "id": "architecture:encapsulation", "reason": "Explicit boundaries govern access between modules, while encapsulation governs changes to one object's state." }, { "id": "architecture:governance", "reason": "Explicit boundaries are the declared interfaces, while governance holds decisions to shared policy." }, { "id": "architecture:single-source-of-truth", "reason": "Explicit boundaries give each module one public interface, while a single source of truth gives each fact one owning definition." }, { "id": "architecture:independence", "reason": "Explicit boundaries declare the interface, while independence is testing and deploying a module without its neighbors." }, { "id": "architecture:modularity", "reason": "Explicit boundaries declare each module's interface and owner, while modularity is the division into modules itself." }, { "id": "architecture:separation-of-concerns", "reason": "Explicit boundaries declare interfaces, while separation of concerns decides which concerns sit on each side of them." }, { "id": "architecture:single-responsibility", "reason": "Explicit boundaries declare interfaces, while single responsibility limits what sits behind one to one reason to change." } ], "name": "Explicit Boundaries", "definition": "A design rule that each module declares its public interface and owner, and other modules reach it only through that interface.", "type": "principle", "scope": [ "module", "component", "service", "domain" ], "requires": [ "Stable Interfaces", "Ownership" ], "reinforces": [ "Modularity", "Encapsulation" ], "enables": [ "Replaceability", "Governance" ], "conflicts_with": ["Boundary Leakage"], "tensions_with": ["Cross-Cutting Concerns"], "violated_by": [ "architecture:boundary-leakage", "architecture:inappropriate-intimacy" ], "detected_by": [ "forbidden imports", "cyclic dependencies" ], "measured_by": ["boundary violation count"], "refactored_by": [ "lexicon:move-behavior-to-its-owner", "lexicon:define-contract", "lexicon:restrict-exports" ], "enforced_by": [ "module rules", "architecture tests" ], "severity": "mandatory", "exemplar": { "before": "import { fooDatabase } from \"../../foo/infrastructure/database\";\nexport function loadBar(id: string) { return fooDatabase.query(id); }", "after": "export interface FooGateway { find(id: FooId): Promise; }\nexport function loadBar(id: FooId, foos: FooGateway) { return foos.find(id); }", "lang": "ts" } }, { "id": "aggregate", "name": "Aggregate", "definition": "A design pattern that groups entities under one root, which is the only entry point and keeps the group's invariants.", "type": "pattern", "scope": [ "domain", "consistency boundary", "bounded context" ], "requires": ["Explicit Boundaries"], "reinforces": [ "Invariant", "Encapsulation" ], "enables": [ "Transactional Consistency Boundary", "Root-Guarded Invariants" ], "conflicts_with": ["Anemic Domain Model"], "tensions_with": ["Aggregate Size"], "violated_by": ["architecture:anemic-domain-model"], "detected_by": ["cross-entity invariant checks scattered in services"], "measured_by": ["out-of-aggregate invariant enforcement count"], "refactored_by": ["lexicon:add-aggregate-invariant"], "enforced_by": ["domain model review"], "severity": "contextual", "exemplar": { "before": "fooOrder.total -= item.price;\nfooOrderItems.delete(item.id);", "after": "class FooOrder {\n #items: FooItem[] = [];\n #total = 0;\n removeItem(id: FooItemId) {\n this.#items = this.#items.filter(item => item.id !== id);\n this.#total = this.#items.reduce((sum, item) => sum + item.price, 0);\n }\n}", "lang": "ts" } }, { "id": "value-object", "name": "Value Object", "definition": "A design pattern that models a domain value as an immutable, self-validating object compared by its attributes.", "type": "pattern", "scope": [ "domain", "modeling", "immutability" ], "requires": ["Value Equality"], "reinforces": [ "Immutability", "Encapsulation" ], "enables": [ "Self-Validating Values", "Side-Effect-Free Equality" ], "conflicts_with": [ "Primitive Obsession", "Data Clumps", "Long Parameter List" ], "tensions_with": ["Object Count"], "violated_by": ["architecture:primitive-obsession"], "detected_by": ["repeated validation of the same primitive shape"], "measured_by": ["primitive-typed domain concept count"], "refactored_by": [], "enforced_by": ["domain model review"], "severity": "recommended", "exemplar": { "before": "function priceFoo(amount: number, currency: string) { return { amount, currency }; }", "after": "class Money {\n private constructor(readonly amount: number, readonly currency: string) {}\n static of(amount: number, currency: string): Money {\n if (amount < 0) throw new Error(\"negative money\");\n return new Money(amount, currency);\n }\n equals(other: Money) { return this.amount === other.amount && this.currency === other.currency; }\n add(other: Money): Money { return Money.of(this.amount + other.amount, this.currency); }\n}", "lang": "ts" } }, { "id": "entity", "distinctFrom": [ { "id": "architecture:value-object", "reason": "An entity is known by an identity that outlives its attributes, while a value object is known only by its attributes." } ], "name": "Entity", "definition": "A design pattern that models a domain object by a stable identity, which stays the same while its attributes change.", "type": "pattern", "scope": [ "domain", "identity", "lifecycle" ], "requires": ["Stable Identity"], "reinforces": [ "Domain Model", "Encapsulation" ], "enables": [ "Identity-Based Equality", "Lifecycle Tracking" ], "conflicts_with": ["Anemic Domain Model"], "tensions_with": ["Value Object"], "violated_by": ["lexicon:attribute-compared-identity"], "detected_by": ["equality by field value where identity is meant"], "measured_by": ["attribute-equality misuse count"], "refactored_by": [], "enforced_by": ["domain model review"], "severity": "contextual", "exemplar": { "before": "type Foo = { id: string; name: string; status: string };\nfoo.status = \"active\";", "after": "class Foo {\n constructor(readonly id: FooId, private name: string, private status: FooStatus) {}\n equals(other: Foo) { return this.id === other.id; }\n activate() { this.status = \"active\"; }\n}", "lang": "ts" } }, { "id": "domain-service", "distinctFrom": [ { "id": "architecture:aggregate", "reason": "A domain service holds a stateless rule spanning entities, while an aggregate holds entities and their invariants under one root." } ], "name": "Domain Service", "definition": "A design pattern that places a domain rule spanning several entities in a stateless service named in the domain's language.", "type": "pattern", "scope": [ "domain", "behavior", "coordination" ], "requires": ["Domain Model"], "reinforces": [ "Single Responsibility Principle (SRP)", "Ubiquitous Language" ], "enables": ["Cross-Entity Domain Logic"], "conflicts_with": [ "Fat Controller", "Transaction Script Sprawl" ], "tensions_with": ["Aggregate"], "violated_by": ["architecture:fat-controller"], "detected_by": ["domain logic in application/transport layers"], "measured_by": ["misplaced domain-rule count"], "refactored_by": [], "enforced_by": ["domain model review"], "severity": "contextual", "exemplar": { "before": "class FooAccount {\n transferTo(other: FooAccount, amount: number) {\n this.balance -= amount;\n other.balance += amount;\n }\n}", "after": "class FooTransferService {\n transfer(from: FooAccount, to: FooAccount, amount: Money) {\n from.withdraw(amount);\n to.deposit(amount);\n }\n}", "lang": "ts" } } ] }