Claude Cursor GitHub Copilot Skill

domain-driven-design

Model software around the business domain using bounded contexts, aggregates, and ubiquitous language. Use when the user mentions "domain modeling", "bounded context", "aggregate root", "ubiquitous language", "anti-corruption layer", "context mapping", "domain events", "strategic

LLM Mart · 0 points · 9 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download wondelai-skills-domain-driven-design-eade5d1.zip · 37 KB
Part of wondelai/skills — 183 skills

Install

skills CLI npx skills add https://github.com/wondelai/skills/tree/main/domain-driven-design
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install wondelai-skills@llmmart
Git git clone https://github.com/wondelai/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole wondelai/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Domain-Driven Design Framework

Framework for tackling software complexity by modeling code around the business domain. The greatest risk in software is not technical failure -- it is building a model that does not reflect how the business actually works.

Core Principle

The model is the code; the code is the model. Software should embody a deep, shared understanding of the business domain. When domain experts and developers speak the same language and that language is directly expressed in the codebase, complexity becomes manageable and the system evolves gracefully as the business changes.

Scoring

Goal: 10/10. Score a domain model by awarding 1 point per satisfied row of the Quick Diagnostic (7 rows) plus up to 3 points for depth: +1 if the Core Domain has a genuinely rich model (not just CRUD), +1 if invariants live inside aggregates rather than in services, +1 if the ubiquitous language is consistent across conversation, code, and tests. Bands: 9-10 = expert-readable names, explicit context boundaries with ACLs, small aggregates, behavior-rich entities, events for cross-aggregate flow, an identified Core Domain; 5-6 = some domain language but leaky boundaries or anemic objects; <=3 = technical naming, one model for everything, logic scattered in services. Report the score and the specific diagnostic rows failing.

Framework

1. Ubiquitous Language

Core concept: A shared, rigorous language between developers and domain experts, used consistently in conversation, documentation, and code. When the language changes, the code changes -- and awkward naming in code feeds back into refining the language.

Why it works: Ambiguity is the root cause of most modeling failures. When a developer says "order" and an expert means "purchase request," bugs are inevitable; a ubiquitous language forces every name in code to map to a concept the business recognizes and validates.

Key insights:

  • The language emerges from deep collaboration, not a glossary bolted on after the fact
  • If a concept is hard to name, the model is likely wrong -- naming difficulty is a design signal
  • Technical jargon (DataProcessor vs. ClaimAdjudicator) hides domain logic from the experts who could correct it
  • Different bounded contexts may use the same word with different meanings -- and that is fine

Code applications:

Context Pattern Example
Class/method naming Name after domain concepts and verbs LoanApplication, policy.underwrite() -- not RequestHandler, process()
Module structure Organize by domain concept shipping/, billing/ -- not controllers/, services/
Code review Reject technical-only names Flag Manager, Helper, Processor, Utils as naming smells

See: references/ubiquitous-language.md when running modeling sessions or maintaining a glossary -- covers how the language evolves and feeds back into code.

2. Bounded Contexts and Context Mapping

Core concept: A bounded context is an explicit boundary within which a particular domain model applies. The same word ("Customer") can mean different things in different contexts; context maps define the relationships and translation strategies between them.

Why it works: Large systems that try to maintain a single unified model inevitably collapse into inconsistency. Bounded contexts accept that different parts of the business need different models; context maps manage the integration between them.

Key insights:

  • A bounded context is not a microservice -- it is a linguistic and model boundary that may contain multiple services
  • Context boundaries often align with team boundaries (Conway's Law)
  • The nine context mapping patterns describe political and technical relationships between teams
  • Anti-Corruption Layer is the most important defensive pattern -- never let a foreign model leak into your core domain
  • Shared Kernel couples two teams; keep it small and explicitly governed
  • Start by mapping what exists (Big Ball of Mud), then define target boundaries

Code applications:

Context Pattern Example
Service integration Anti-Corruption Layer Translate external API responses into your domain objects at the boundary
Legacy migration Conformist / ACL Wrap the legacy system behind an adapter that speaks your domain language
API design Open Host Service + Published Language Expose a well-documented REST API with a canonical schema

See: references/bounded-contexts.md for the nine mapping patterns and integration strategies.

3. Entities, Value Objects, and Aggregates

Core concept: Entities have identity that persists across state changes. Value Objects are defined entirely by their attributes and are immutable. Aggregates are clusters of entities and value objects with a single root that enforces consistency boundaries.

Why it works: Without these distinctions, everything becomes a mutable, identity-bearing object -- tangled state, inconsistent updates, fragile concurrency. Aggregates draw the line: everything inside is guaranteed consistent; everything outside is eventually consistent.

Key insights:

  • Entity test: "Am I the same thing even if all my attributes change?" (a person changes name and address -- still the same person)
  • Value Object test: "Am I defined only by my attributes?" (any $10 bill is interchangeable with another)
  • Most things should be Value Objects, not Entities -- prefer immutability
  • Keep aggregates small (one root plus a minimal cluster); reference other aggregates by ID, not object reference
  • Immediate consistency only within an aggregate; design for eventual consistency between aggregates

Code applications:

Context Pattern Example
Identity tracking Entity with ID Order identified by orderId, survives state changes
Immutable attributes Value Object Address(street, city, zip) -- replace, never mutate
Consistency boundary Aggregate Root Order is root; OrderLine items exist only through it
Concurrency control Optimistic locking on root Version field on Order; conflict if two edits race

See: references/building-blocks.md for aggregate design rules and consistency boundaries.

4. Domain Events

Core concept: A domain event captures something that happened in the domain that experts care about, named in past tense (OrderPlaced, PaymentReceived) -- a fact that has already occurred.

Why it works: Domain events decouple cause from effect. When OrderPlaced is published, shipping, billing, and notifications each react independently without the ordering context knowing about them -- less coupling, eventual consistency, a natural audit trail.

Key insights:

  • Events are immutable facts -- once published, they cannot be changed or retracted
  • Domain events are internal to a bounded context; integration events cross boundaries
  • Events enable temporal decoupling: the producer does not wait for the consumer
  • Event sourcing stores the full event history as the source of truth, deriving current state by replay
  • Not every state change deserves an event -- only publish what the domain cares about

Code applications:

Context Pattern Example
State transitions Raise event on domain action order.place() raises OrderPlaced
Cross-context integration Publish integration event OrderPlaced triggers ShippingLabelRequested in shipping context
Eventual consistency Async event handlers Inventory handler updates stock asynchronously after OrderPlaced

See: references/domain-events.md for event naming, event sourcing, and integration events.

5. Repositories and Factories

Core concept: Repositories provide the illusion of an in-memory collection of domain objects, hiding persistence. Factories encapsulate complex creation logic so aggregates are always born in a valid state.

Why it works: When persistence and assembly details leak into domain code, every storage change ripples through business rules and aggregates can be constructed in half-valid states. Repositories confine SQL/ORM concerns to infrastructure so the domain stays testable in memory; factories make the only path to an aggregate one that enforces its invariants, so an invalid instance is unrepresentable.

Key insights:

  • The Repository interface belongs in the domain layer; its implementation belongs in infrastructure
  • Repository methods speak the ubiquitous language: findPendingOrders(), not getByStatusCode(3)
  • Collection-oriented repositories mimic add/remove; persistence-oriented ones use save
  • Factories are warranted for complex rules or multi-part assembly; a two-field Value Object just needs a constructor
  • The Specification pattern encapsulates query criteria as domain objects: OverdueInvoiceSpecification

Code applications:

Context Pattern Example
Data access abstraction Repository interface OrderRepository.findByCustomer(customerId) in domain; PostgresOrderRepository in infrastructure
Complex creation Factory method Order.createFromQuote(quote) validates and assembles from a Quote aggregate
Query encapsulation Specification spec = OverdueBy(days=30); repo.findMatching(spec)

See: references/repositories-factories.md for Repository, Factory, and Specification patterns.

6. Strategic Design and Distillation

Core concept: Not all parts of a system are equally important. Strategic design identifies the Core Domain -- where competitive advantage lives -- and distinguishes it from Supporting Subdomains (necessary, not differentiating) and Generic Subdomains (commodity).

Why it works: Applying the same rigor everywhere spreads your best talent thin and over-engineers commodity functionality. Identifying the Core Domain concentrates the best developers and deepest modeling where they matter most.

Key insights:

  • Core Domain: invest your best people and deepest modeling; Supporting: build, but don't over-engineer; Generic (auth, email, payments): buy or use open-source
  • Distillation extracts and highlights the Core Domain from surrounding complexity
  • A Domain Vision Statement is a one-page description of the Core Domain's value proposition
  • Revisit what is "core" as the business evolves -- today's differentiator may become tomorrow's commodity

Code applications:

Context Pattern Example
Build vs. buy Classify subdomain type Build custom pricing engine (core); use Stripe for payments (generic)
Team allocation Best developers on Core Domain Seniors model underwriting rules; juniors integrate the email service
Code organization Separate core from generic domain/pricing/ (deep model) vs. infrastructure/email/ (thin adapter)

See: references/strategic-design.md when deciding where to invest engineering effort -- subdomain classification and distillation techniques.

Common Mistakes

Mistake Why It Fails Fix
Technical names instead of domain language Logic hidden behind DataManager; experts can't validate the model Rename to domain terms (ClaimAdjudicator); if no domain term exists, the concept may be wrong
One model to rule them all A single Customer class for billing, shipping, and marketing becomes bloated and contradictory Bounded contexts: each gets its own Customer with only the attributes it needs
Giant aggregates Concurrency conflicts, slow loads, transactional bottlenecks Keep aggregates small; reference by ID; eventual consistency between them
Anemic domain model Objects are data bags; rules scatter across services and duplicate Move behavior into entities and value objects; services orchestrate only
No Anti-Corruption Layer Foreign models leak in; code couples to external schemas Wrap every external system behind a translation layer
Bounded context = microservice Premature extraction; distributed complexity without benefit A context is a model boundary, not a deployment unit; start with modules in a monolith
Skipping domain experts Developers invent a model that doesn't match reality; expensive rework Regular modeling sessions until experts say "yes, that is how it works"

Quick Diagnostic

Question If No Action
Can a domain expert read your class names and understand them? Technical jargon hides the model Rename classes, methods, events to ubiquitous language
Are bounded context boundaries explicitly defined? Models bleed; same term means different things Draw a context map; define boundaries and translations
Are aggregates small (one root + minimal cluster)? Slow loads, concurrency issues Split aggregates; reference by ID; accept eventual consistency
Do domain objects contain behavior, not just data? Anemic model; logic scattered in services Move business rules into entities and value objects
Are domain events used for cross-aggregate communication? Tight coupling, synchronous chains Introduce events; let aggregates react asynchronously
Is there an Anti-Corruption Layer at every external integration? Foreign models pollute your domain Add a translation layer at each boundary
Have you identified which subdomain is core? Best talent spread thin Classify subdomains; focus deep modeling on the Core Domain

Further Reading

For the complete methodology, patterns, and deeper insights:

About the Author

Eric Evans is a software design consultant and the originator of Domain-Driven Design, developed through work on large-scale systems in finance, insurance, and logistics. His 2003 book Domain-Driven Design: Tackling Complexity in the Heart of Software is one of the most influential software architecture books ever written, and he continues to evolve DDD through his consultancy, Domain Language.

Files (skills)
  • references
    • bounded-contexts.md 13.1 KB
      # Bounded Contexts and Context Mapping
      
      A bounded context is the most important strategic pattern in Domain-Driven Design. It defines an explicit boundary within which a particular domain model is defined, consistent, and applicable. Context mapping describes the relationships between bounded contexts and the strategies for translating between them.
      
      ## What Is a Bounded Context?
      
      A bounded context is not a module, a microservice, or a deployment unit -- though it may coincide with any of these. It is a linguistic boundary: within this boundary, every term has a single, precise meaning, and the model is internally consistent.
      
      ### The Problem It Solves
      
      In any system of sufficient size, the same word means different things to different people:
      
      | Term | In Sales Context | In Shipping Context | In Billing Context |
      |------|-----------------|--------------------|--------------------|
      | Customer | A prospect or account with contact info and purchase history | A delivery address with receiving instructions | A billing entity with payment methods and credit terms |
      | Product | A catalog item with descriptions, images, and pricing tiers | A physical item with weight, dimensions, and handling requirements | A line item with a price, tax category, and discount rules |
      | Order | A quote or deal being negotiated | A shipment to be picked, packed, and dispatched | An invoice to be generated and collected |
      
      Trying to create a single `Customer` class that serves all three contexts produces a bloated, contradictory monstrosity with dozens of fields, most of which are irrelevant in any given use case. The bounded context pattern says: stop fighting this. Let each context have its own `Customer` model, optimized for its own needs.
      
      ### Identifying Bounded Context Boundaries
      
      Boundaries emerge from several signals:
      
      **Linguistic signals:**
      - When the same term means different things to different groups, those groups are in different contexts
      - When conversations between groups require "translation" ("when you say X, do you mean Y?"), there is a boundary
      - When a concept exists in one group but has no analog in another, the boundary is clear
      
      **Organizational signals:**
      - Different teams owning different parts of the system
      - Different departments with different processes and vocabulary
      - Different regulatory requirements (e.g., PCI compliance for payments vs. HIPAA for patient data)
      
      **Technical signals:**
      - Different data storage needs (relational vs. document vs. event store)
      - Different consistency requirements (strong consistency for payments vs. eventual consistency for recommendations)
      - Different rate of change (billing rules change quarterly; product catalog changes daily)
      
      ### Context Size
      
      There is no formula for the right size of a bounded context. However, guidelines help:
      
      - **Too large:** If a single context contains concepts that do not cohesively relate, it is too large. A context that contains both "insurance underwriting" and "marketing campaign management" is probably two contexts.
      - **Too small:** If you find yourself creating translation layers between closely related concepts that change together and are owned by the same team, you may have split too aggressively.
      - **Rule of thumb:** A bounded context should be ownable by a single team (5-9 people). If a context requires multiple teams to modify, it is either too large or the team boundaries are wrong.
      
      ## Context Mapping Patterns
      
      Context mapping describes how bounded contexts relate to each other. Eric Evans and the DDD community have identified nine primary patterns. Each represents a different political and technical relationship.
      
      ### 1. Shared Kernel
      
      **What it is:** Two bounded contexts share a small subset of the model, typically a library of common types. Both teams co-own this shared code and must coordinate changes.
      
      **When to use:** When two closely collaborating teams need the same domain concept (e.g., a `Money` value object or a `DateRange` type) and the overhead of translation is not justified.
      
      **Risks:** Coupling. Any change to the shared kernel requires coordination between both teams. If not carefully managed, the shared kernel grows uncontrollably.
      
      **Rules:**
      - Keep the shared kernel as small as possible -- value objects and basic types only
      - Require explicit agreement (both teams) for any change
      - Automated tests in both contexts must pass before any shared kernel change is merged
      - Never put entities or aggregates in the shared kernel
      
      **Example:** Two contexts (billing and shipping) share a `Money(amount, currency)` value object and an `Address(street, city, state, zip, country)` value object.
      
      ### 2. Customer-Supplier
      
      **What it is:** An upstream context (supplier) provides data or services that a downstream context (customer) depends on. The upstream team plans with the downstream team's needs in mind but has its own priorities.
      
      **When to use:** When one team produces something another team consumes, and there is a reasonable working relationship. The downstream team can influence the upstream team's roadmap but does not control it.
      
      **Example:** The Product Catalog team (upstream) provides product data to the Pricing team (downstream). The Pricing team requests new attributes when needed, and the Catalog team accommodates these requests in their planning.
      
      ### 3. Conformist
      
      **What it is:** The downstream context conforms to the upstream context's model without translation. The downstream team accepts the upstream model as-is, even if it is not ideal.
      
      **When to use:** When the upstream team has no incentive or ability to accommodate the downstream team (e.g., a large external service, a legacy system, or a dominant upstream team), and the cost of building a translation layer exceeds the cost of conforming.
      
      **Risks:** Your model is constrained by someone else's design decisions. If the upstream model changes, your context must change too.
      
      **Example:** A startup integrating with a major ERP system may conform to the ERP's data model rather than building translation layers, accepting the ERP's concept of "customer" and "order" even if they do not perfectly fit.
      
      ### 4. Anti-Corruption Layer (ACL)
      
      **What it is:** A translation layer that sits between the downstream context and the upstream context, converting the upstream model into the downstream context's own domain language. The ACL protects the downstream model from being polluted by foreign concepts.
      
      **When to use:** When you need to integrate with a system whose model does not fit yours, and you cannot afford to let that foreign model leak into your domain. This is the most important defensive pattern in DDD.
      
      **Structure:**
      ```
      Your Domain Layer  <-->  ACL (Adapters + Translators)  <-->  External System
      ```
      
      The ACL contains:
      - **Adapters** that handle the technical protocol (HTTP, gRPC, message queue)
      - **Translators** that convert external model objects into your domain objects
      - **Facades** that present a clean interface to your domain layer
      
      **Example:** Integrating with a legacy mainframe that represents customers as `CUST_REC` with fields like `CUST_NM`, `CUST_ADDR1`. The ACL translates this into your domain's `Customer(name: PersonName, address: Address)` value objects.
      
      ### 5. Open Host Service (OHS)
      
      **What it is:** An upstream context exposes a well-defined, documented protocol (API, message format) that downstream contexts can integrate with. The upstream team provides a stable, versioned interface designed for general consumption.
      
      **When to use:** When an upstream context serves multiple downstream consumers and cannot tailor its interface to each one. The OHS provides a standard integration point.
      
      **Characteristics:**
      - Versioned API with backward compatibility guarantees
      - Documentation and contracts (OpenAPI, protobuf schemas, JSON Schema)
      - The interface is deliberately designed for external consumption, not a direct exposure of the internal model
      
      **Example:** An Identity Provider exposes an OAuth 2.0 / OpenID Connect API. Any downstream context can integrate using the standard protocol without knowing the internal user model.
      
      ### 6. Published Language
      
      **What it is:** A well-documented, shared language (schema, format, protocol) used for communication between contexts. Often paired with Open Host Service.
      
      **When to use:** When multiple contexts need to exchange data and a standard format prevents each integration from inventing its own.
      
      **Examples:**
      - Industry-standard formats: HL7 for healthcare, SWIFT for banking, EDI for supply chain
      - Internal schemas: a company-wide JSON schema for events published on a shared message bus
      - Protocol Buffers or Avro schemas for service-to-service communication
      
      ### 7. Separate Ways
      
      **What it is:** Two contexts decide not to integrate at all. Each builds its own solution independently, even if there is some overlap.
      
      **When to use:** When the cost of integration (coordination, translation, coupling) exceeds the cost of duplication. Sometimes it is cheaper and simpler for two teams to build their own `Address` validation than to share one.
      
      **Signals that Separate Ways is appropriate:**
      - The integration would be trivial functionality on both sides
      - The teams are in different organizations with different release cycles
      - The shared functionality is not core to either context
      
      ### 8. Big Ball of Mud
      
      **What it is:** A system with no clear boundaries, where models are entangled and concepts leak everywhere. This is not a recommended pattern -- it is a recognition of reality. Many existing systems are Big Balls of Mud.
      
      **When to use (as a label):** When mapping an existing landscape, some systems simply are Big Balls of Mud. Acknowledging this is the first step toward improvement. Drawing a boundary around the mud and treating it as a single (messy) context allows you to build clean contexts alongside it, protected by an ACL.
      
      **Migration strategy:**
      1. Draw a boundary around the entire mudball
      2. Build new functionality in a clean bounded context
      3. Protect the new context with an ACL against the mudball
      4. Gradually extract functionality from the mud into clean contexts
      
      ### 9. Partnership
      
      **What it is:** Two contexts evolve together with mutual coordination. Both teams jointly plan features and synchronize releases. Neither dominates.
      
      **When to use:** When two contexts are tightly coupled in the domain and both teams are committed to evolving together. More intimate than Customer-Supplier; both teams have equal say.
      
      **Risks:** Requires strong coordination discipline. If one team slips, both are affected.
      
      **Example:** A checkout context and a payment processing context that must evolve in lockstep when payment methods change.
      
      ## Team Relationships and Context Boundaries
      
      ### Conway's Law in Practice
      
      Conway's Law states that systems mirror the communication structures of the organizations that build them. In DDD, this is not a warning -- it is a design tool:
      
      - **Align context boundaries with team boundaries.** If one team owns billing and another owns shipping, these should be separate bounded contexts.
      - **If two teams must share a context, expect friction.** Either split the context or merge the teams.
      - **Cross-team integration should happen at context boundaries,** using well-defined mapping patterns, not through shared code or databases.
      
      ### Choosing the Right Pattern
      
      | Situation | Recommended Pattern |
      |-----------|-------------------|
      | Two teams with a good relationship and shared concepts | Shared Kernel (kept small) or Customer-Supplier |
      | Integrating with a system you do not control | Anti-Corruption Layer |
      | Exposing your context to many consumers | Open Host Service + Published Language |
      | Integrating with a hostile or unresponsive upstream | Conformist (if cost is low) or Separate Ways |
      | Legacy system with no clear model | Big Ball of Mud (label it) + ACL around it |
      | Two teams that must evolve in lockstep | Partnership |
      | Integration cost exceeds duplication cost | Separate Ways |
      
      ### Drawing a Context Map
      
      A context map is a visual representation of all bounded contexts and their relationships. It should include:
      
      1. **Every bounded context** drawn as a labeled box
      2. **The relationships between them** using the patterns above (arrows show upstream/downstream)
      3. **The translation mechanism** (ACL, OHS, Shared Kernel)
      4. **Team ownership** for each context
      5. **The Big Balls of Mud** explicitly labeled
      
      The context map is a communication tool. It should be understandable by both developers and non-technical stakeholders. Keep it on a whiteboard or in a shared diagram that the team references and updates regularly.
      
      ### When Boundaries Change
      
      Context boundaries are not permanent. They evolve as the business and team structure change:
      
      - **Splitting:** A context grows too large for one team; split it along natural seams and introduce a mapping pattern between the new contexts.
      - **Merging:** Two small contexts owned by the same team with heavy inter-communication; merge them and eliminate the translation overhead.
      - **Reclassifying:** A context that was Generic Subdomain becomes Core Domain as business strategy shifts; increase investment and modeling rigor.
      
      The key is to make boundaries explicit and intentional, so that when they need to change, the change is a deliberate design decision rather than an accidental drift.
      
    • building-blocks.md 13.1 KB
      # Building Blocks: Entities, Value Objects, and Aggregates
      
      The tactical building blocks of Domain-Driven Design provide a vocabulary for structuring domain models. Entities, Value Objects, and Aggregates are the three most critical patterns. Getting them right determines whether a domain model is expressive and maintainable or bloated and fragile.
      
      ## Entities
      
      An Entity is a domain object defined by its identity rather than its attributes. An entity persists across time and state changes -- it is the "same thing" even when everything about it changes.
      
      ### The Identity Test
      
      Ask: "If all the attributes change, is it still the same thing?"
      
      - A **person** changes name, address, phone number, job, and appearance -- still the same person. **Entity.**
      - A **bank account** changes its balance daily -- still the same account. **Entity.**
      - A **$10 bill** is interchangeable with any other $10 bill. **Not an entity -- Value Object.**
      
      ### Identity Strategies
      
      | Strategy | How It Works | When to Use |
      |----------|-------------|-------------|
      | Natural key | Use a real-world identifier (SSN, ISBN, VIN) | When a stable, unique external identifier exists |
      | Surrogate key | Generate a synthetic ID (UUID, auto-increment) | When no natural key exists or the natural key can change |
      | Composite key | Combine multiple attributes | When identity is defined by a relationship (e.g., student + course = enrollment) |
      
      **Prefer UUIDs over auto-increment** for distributed systems. UUIDs can be generated anywhere without coordination; auto-increment requires a central authority.
      
      ### Entity Design Rules
      
      1. **Identity is immutable.** Once assigned, an entity's identity never changes. If the "identity" can change, it is not really the identity.
      2. **Entities are mutable.** Unlike Value Objects, entities change state over time. An `Order` moves from `Pending` to `Confirmed` to `Shipped`.
      3. **Equality is based on identity.** Two `Order` objects with the same `orderId` are the same order, regardless of other attribute differences.
      4. **Entities have a lifecycle.** They are created, go through state transitions, and may eventually be archived or deleted.
      5. **Put behavior on entities.** An entity is not a data container. `order.addItem(product, quantity)` belongs on the `Order` entity, not in an `OrderService`.
      
      ### Common Entity Pitfalls
      
      - **Over-identification.** Making everything an entity when most things should be Value Objects. Ask the identity test for every class.
      - **Anemic entities.** Entities with only getters and setters. If all behavior is in services, the entity is a data bag.
      - **Identity leakage.** Exposing database primary keys as domain identity. Use domain-meaningful identifiers (`orderNumber`) rather than technical ones (`id: 42`).
      
      ## Value Objects
      
      A Value Object is a domain object defined entirely by its attributes. It has no identity -- two Value Objects with the same attributes are interchangeable. Value Objects are immutable: you do not change a Value Object; you replace it.
      
      ### The Attribute Test
      
      Ask: "Is it defined by what it is, not which one it is?"
      
      - A **mailing address** (123 Main St, Springfield, IL 62704) -- defined by its attributes. Two objects with the same street, city, state, zip are the same address. **Value Object.**
      - A **money amount** ($49.99 USD) -- defined by amount and currency. **Value Object.**
      - A **date range** (Jan 1 - Dec 31) -- defined by start and end. **Value Object.**
      - A **color** (#FF5733) -- defined by its hex value. **Value Object.**
      
      ### Why Value Objects Matter
      
      Value Objects are the unsung heroes of domain models. Most developers default to entities for everything, but **the majority of concepts in a well-designed domain model should be Value Objects.**
      
      **Benefits of Value Objects:**
      
      | Benefit | Explanation |
      |---------|-------------|
      | Immutability | No shared mutable state; safe to pass around, cache, and use in concurrent code |
      | Side-effect-free behavior | Methods return new Value Objects rather than mutating state; easy to reason about |
      | Self-validation | A Value Object validates itself on creation; an invalid Value Object can never exist |
      | Equality by value | `Money(100, "USD") == Money(100, "USD")` regardless of object reference |
      | Expressiveness | `Money` instead of `BigDecimal`; `EmailAddress` instead of `String`; domain meaning is encoded in the type |
      
      ### Value Object Design Rules
      
      1. **Immutable.** All fields are set at construction and never change. No setters.
      2. **Self-validating.** A `Money` object with a negative amount or null currency should throw on construction. If it exists, it is valid.
      3. **Equality by attributes.** Override `equals()` and `hashCode()` to compare all attributes.
      4. **Side-effect-free methods.** `money.add(other)` returns a new `Money`; it does not mutate the original.
      5. **Replace, don't modify.** To change an address, create a new `Address` and assign it. `customer.changeAddress(newAddress)`.
      
      ### Common Value Objects
      
      | Value Object | Replaces | Why It Is Better |
      |-------------|---------|------------------|
      | `Money(amount, currency)` | `BigDecimal` | Prevents currency mismatch errors; encapsulates rounding rules |
      | `EmailAddress(value)` | `String` | Validates format on construction; impossible to have invalid email in the system |
      | `DateRange(start, end)` | Two `Date` fields | Enforces `start <= end`; contains overlap/contains logic |
      | `Address(street, city, state, zip)` | Multiple String fields | Groups related data; validates as a unit |
      | `Quantity(value, unit)` | `int` or `double` | Prevents unit mismatch (adding kilograms to liters) |
      | `PhoneNumber(countryCode, number)` | `String` | Validates format; normalizes representation |
      
      ### When to Use Value Objects vs. Entities
      
      | Signal | Entity | Value Object |
      |--------|--------|-------------|
      | Needs to be tracked over time | Yes | No |
      | Has a lifecycle (created, modified, archived) | Yes | No -- replaced, not modified |
      | Two instances with same attributes are different things | Yes | No -- they are the same thing |
      | Immutability is natural | No | Yes |
      | Appears in the model as a measurement, description, or attribute | No | Yes |
      
      **Rule of thumb:** If in doubt, make it a Value Object. You can always promote it to an Entity later if identity becomes important. Going the other direction (demoting an Entity to a Value Object) is much harder.
      
      ## Aggregates
      
      An Aggregate is a cluster of domain objects (entities and value objects) treated as a single unit for data changes. Every aggregate has a single root entity -- the Aggregate Root -- through which all external access occurs.
      
      ### Why Aggregates Exist
      
      Without aggregates, any object in the system can hold a reference to any other object and modify it directly. This creates an impossibly tangled web of dependencies where enforcing business invariants (rules that must always be true) becomes a nightmare.
      
      Aggregates solve this by drawing a boundary:
      - **Inside the boundary:** Strong consistency. All invariants are enforced within a single transaction.
      - **Outside the boundary:** Eventual consistency. Changes propagate via domain events or polling.
      
      ### Aggregate Design Rules
      
      Eric Evans and Vaughn Vernon established these rules, refined by the DDD community:
      
      #### Rule 1: Protect Business Invariants Inside the Aggregate
      
      An invariant is a rule that must always be true. Example: "An order's total must equal the sum of its line items." This invariant involves `Order` and `OrderLineItem`. Both belong in the same aggregate because the invariant spans both.
      
      | If the invariant spans... | Then... |
      |--------------------------|---------|
      | A single entity | That entity is its own aggregate |
      | An entity and its closely related objects | They form one aggregate |
      | Two independently identifiable things | They are separate aggregates; enforce the rule via eventual consistency or a domain event |
      
      #### Rule 2: Small Aggregates
      
      Large aggregates cause:
      - **Concurrency conflicts.** Two users editing different parts of the same large aggregate will conflict.
      - **Performance problems.** Loading a large aggregate means loading everything it contains.
      - **Transaction scope bloat.** Larger transaction scope means longer locks and more contention.
      
      **Ideal aggregate size:** One root entity, a small set of value objects, and occasionally a small collection of child entities (e.g., `Order` with `OrderLineItems`).
      
      **Anti-pattern:** An `Organization` aggregate that contains `Departments` which contain `Employees` which contain `Assignments`. This is too large. `Employee` should be its own aggregate, referencing `Organization` and `Department` by ID.
      
      #### Rule 3: Reference Other Aggregates by ID Only
      
      Do not hold direct object references to other aggregates. Instead, store only the identifier:
      
      **Wrong:**
      ```
      class Order {
        Customer customer;  // Direct reference to another aggregate
      }
      ```
      
      **Right:**
      ```
      class Order {
        CustomerId customerId;  // Reference by ID only
      }
      ```
      
      **Why:** Direct references create tight coupling, prevent independent scaling, and make it impossible to enforce aggregate boundaries. With ID references, each aggregate can be loaded, stored, and cached independently.
      
      #### Rule 4: Use Eventual Consistency Across Aggregate Boundaries
      
      When one aggregate's action should trigger a change in another aggregate, do not try to update both in the same transaction. Instead:
      
      1. The first aggregate performs its action and publishes a domain event
      2. An event handler picks up the event and modifies the second aggregate in a separate transaction
      
      **Example:**
      - `Order.place()` publishes `OrderPlaced` event
      - `InventoryHandler` receives `OrderPlaced` and calls `inventory.reserve(items)`
      - These are two separate transactions
      
      ### Choosing Aggregate Boundaries
      
      #### Start with the Invariant
      
      Identify every business invariant. Group objects that participate in the same invariant into the same aggregate.
      
      **Example invariants:**
      | Invariant | Objects Involved | Aggregate |
      |-----------|-----------------|-----------|
      | "An order total must equal the sum of its lines" | Order, OrderLineItem | Order aggregate |
      | "A product must have at least one category" | Product, Category | Product aggregate (Category is a value object or ID reference) |
      | "An account balance must never go below the overdraft limit" | Account | Account aggregate (single entity) |
      | "A reservation cannot overlap with another for the same room" | Reservation | Reservation aggregate (overlap check is a domain service or repository query, not a cross-aggregate invariant) |
      
      #### The Transaction Boundary Test
      
      Ask: "Must these two changes happen atomically, or can they happen with a small delay?"
      
      - If **atomically**: same aggregate
      - If **small delay is acceptable**: separate aggregates with eventual consistency
      
      Most of the time, a small delay is acceptable. Humans rarely need true atomicity outside of financial transactions.
      
      ### Aggregate Root Pattern
      
      The Aggregate Root is the single entity through which all external interaction with the aggregate occurs:
      
      **Rules for the root:**
      1. External objects may only hold references to the root, never to internal entities
      2. All changes to the aggregate go through the root's methods
      3. The root enforces all aggregate invariants
      4. The root controls the lifecycle of all internal objects
      5. Delete the root and everything inside the aggregate is deleted
      
      **Example:**
      ```
      // External code interacts only with Order (the root)
      order.addLineItem(product, quantity, price)
      order.removeLineItem(lineItemId)
      order.calculateTotal()
      
      // LineItems are never accessed directly from outside
      // WRONG: lineItem.changeQuantity(5)
      // RIGHT: order.changeLineItemQuantity(lineItemId, 5)
      ```
      
      ### Common Aggregate Mistakes
      
      | Mistake | Consequence | Fix |
      |---------|------------|-----|
      | Making the entire object graph one aggregate | Concurrency nightmares, slow loading | Split into multiple aggregates; reference by ID |
      | Holding direct references to other aggregates | Tight coupling; cannot enforce boundaries | Replace with ID references |
      | Updating multiple aggregates in one transaction | Distributed lock contention; scaling bottleneck | Use domain events for cross-aggregate consistency |
      | Putting all logic in services instead of the aggregate root | Anemic aggregate; invariants not enforced | Move invariant-enforcing logic into the aggregate root |
      | Creating aggregates based on database tables | Data model drives domain model (backward) | Design aggregates from domain invariants, then map to persistence |
      
      ## Putting It All Together
      
      A well-designed domain model has this structure:
      
      1. **Value Objects** form the majority of types -- measurements, descriptions, identifiers, small composites
      2. **Entities** represent things with identity and lifecycle -- fewer than you think
      3. **Aggregates** cluster related entities and value objects behind a root -- enforcing consistency boundaries
      4. **References between aggregates** are by ID only -- enabling independent evolution
      5. **Cross-aggregate consistency** is achieved through domain events -- eventual consistency is the default
      
      The result is a model that is expressive (reads like the business), consistent (invariants are enforced), and scalable (aggregates are independent units of consistency, persistence, and caching).
      
    • domain-events.md 11.8 KB
      # Domain Events
      
      A domain event represents something that happened in the domain that domain experts care about. Events are named in past tense, are immutable facts, and serve as the primary mechanism for decoupling bounded contexts and achieving eventual consistency across aggregate boundaries.
      
      ## What Domain Events Are
      
      A domain event captures a meaningful occurrence in the business domain. "Meaningful" means that a domain expert would recognize it as significant -- not just a technical state change.
      
      ### Domain Events vs. Technical Events
      
      | Domain Event | Technical Event | Why It Matters |
      |-------------|----------------|----------------|
      | `OrderPlaced` | `RowInserted` | The domain expert cares about orders being placed; they do not care about database rows |
      | `PaymentReceived` | `WebhookProcessed` | The business reacts to payments; the webhook is an implementation detail |
      | `ClaimDenied` | `StatusUpdated` | Denial triggers business processes (appeals, notifications); a status update triggers nothing meaningful |
      | `InventoryDepleted` | `CountReachedZero` | The business has specific procedures for depleted inventory; zero is just a number |
      
      ### The Litmus Test
      
      Ask a domain expert: "Would you care if this happened?" If yes, it is a domain event. If they shrug, it is a technical event that belongs in infrastructure, not in the domain model.
      
      ## Naming Domain Events
      
      ### The Past-Tense Rule
      
      Domain events are always named in past tense because they represent facts that have already occurred. By the time anyone processes the event, the thing has already happened.
      
      **Correct naming:**
      - `OrderPlaced` -- an order was placed
      - `PaymentReceived` -- a payment was received
      - `ShipmentDispatched` -- a shipment was dispatched
      - `AccountSuspended` -- an account was suspended
      - `PolicyRenewed` -- a policy was renewed
      
      **Incorrect naming:**
      - `PlaceOrder` -- this is a command, not an event
      - `OrderPlacing` -- this implies the action is in progress
      - `OrderEvent` -- too generic; what happened?
      - `OrderUpdate` -- "update" is not a domain concept; what specifically changed?
      
      ### Naming Specificity
      
      Be specific about what happened. Vague event names create the same problems as vague method names -- consumers cannot understand what occurred without reading the payload.
      
      | Vague | Specific | Why Specific Is Better |
      |-------|----------|----------------------|
      | `OrderChanged` | `OrderItemAdded`, `OrderItemRemoved`, `OrderAddressChanged` | Different changes trigger different business reactions |
      | `UserUpdated` | `UserEmailVerified`, `UserPasswordChanged`, `UserProfileCompleted` | A password change requires a security audit; a profile completion triggers onboarding flow |
      | `PaymentProcessed` | `PaymentAuthorized`, `PaymentCaptured`, `PaymentRefunded` | Authorization and capture are distinct business steps with different downstream effects |
      
      ### Event Naming Conventions
      
      Adopt a consistent naming pattern across the codebase:
      
      ```
      {AggregateType}{DomainAction}
      ```
      
      Examples:
      - `OrderPlaced`, `OrderCancelled`, `OrderFulfilled`
      - `InvoiceSent`, `InvoicePaid`, `InvoiceOverdue`
      - `MemberRegistered`, `MemberSuspended`, `MemberReinstated`
      
      ## Event Structure
      
      A well-designed domain event contains:
      
      | Field | Purpose | Example |
      |-------|---------|---------|
      | `eventId` | Unique identifier for this specific event occurrence | `uuid("a1b2c3d4...")` |
      | `eventType` | The name of the event | `"OrderPlaced"` |
      | `occurredAt` | When the event happened | `"2024-03-15T14:30:00Z"` |
      | `aggregateId` | The ID of the aggregate that produced the event | `orderId: "ORD-12345"` |
      | `aggregateType` | The type of aggregate | `"Order"` |
      | `payload` | The domain-relevant data | `{ customerId, items, total, shippingAddress }` |
      | `metadata` | Technical metadata (correlation ID, causation ID, user ID) | `{ correlationId, userId }` |
      
      ### What Goes in the Payload
      
      Include enough data for consumers to react without calling back to the producer:
      
      **Too little:**
      ```json
      { "orderId": "ORD-12345" }
      ```
      Every consumer must call back to the Order service to get details. This creates coupling and latency.
      
      **Too much:**
      ```json
      { "order": { /* entire order aggregate serialized */ } }
      ```
      This bloats messages, exposes internal model details, and creates tight coupling to the aggregate structure.
      
      **Just right:**
      ```json
      {
        "orderId": "ORD-12345",
        "customerId": "CUST-789",
        "items": [
          { "productId": "PROD-1", "quantity": 2, "unitPrice": 29.99 }
        ],
        "totalAmount": 59.98,
        "currency": "USD",
        "shippingAddress": { "city": "Springfield", "state": "IL" }
      }
      ```
      Enough for most consumers to react; detailed enough to avoid callbacks for common cases.
      
      ## Publishing Domain Events
      
      ### Where Events Are Raised
      
      Domain events are raised within the aggregate, as part of the domain operation that caused them:
      
      ```
      class Order:
          def place(self):
              self._validate_can_be_placed()
              self.status = OrderStatus.PLACED
              self._raise_event(OrderPlaced(
                  order_id=self.id,
                  customer_id=self.customer_id,
                  items=self.items,
                  total=self.total
              ))
      ```
      
      The aggregate records the event internally. An infrastructure mechanism (event dispatcher, outbox pattern) publishes it after the aggregate is persisted.
      
      ### The Outbox Pattern
      
      The most reliable way to publish domain events is the transactional outbox:
      
      1. Within the same database transaction that persists the aggregate, insert the event into an `outbox` table
      2. A separate process (poller or CDC -- Change Data Capture) reads from the outbox and publishes to the message broker
      3. After successful publication, mark the outbox entry as published
      
      **Why this matters:** If you publish the event and then save the aggregate, the save might fail -- you published a lie. If you save the aggregate and then publish, the publish might fail -- the event is lost. The outbox pattern ties both operations to the same database transaction.
      
      ### Delivery Guarantees
      
      | Guarantee | Meaning | Implementation |
      |-----------|---------|----------------|
      | At-most-once | Events may be lost but never duplicated | Fire-and-forget; no outbox; acceptable for non-critical events |
      | At-least-once | Events are never lost but may be duplicated | Outbox pattern with retry; consumers must be idempotent |
      | Exactly-once | Events are delivered exactly once | Practically impossible in distributed systems; achieve via at-least-once + idempotent consumers |
      
      **At-least-once with idempotent consumers** is the standard approach. Design every event handler to be safe to run multiple times with the same event.
      
      ## Domain Events for Cross-Context Integration
      
      ### Internal vs. Integration Events
      
      | Aspect | Domain Event (Internal) | Integration Event (External) |
      |--------|------------------------|------------------------------|
      | Scope | Within a bounded context | Across bounded contexts |
      | Audience | Event handlers in the same context | Other teams' services |
      | Schema | Can change freely with the model | Must be versioned and backward-compatible |
      | Naming | Uses internal ubiquitous language | Uses published language (shared schema) |
      | Transport | In-process event bus or same database | Message broker (Kafka, RabbitMQ, SNS) |
      
      ### Translation at the Boundary
      
      When a domain event crosses a bounded context boundary, it should be translated into an integration event that uses the published language:
      
      1. **Order context** raises `OrderPlaced` (domain event, internal language)
      2. **Anti-corruption layer** translates to `PurchaseCompleted` (integration event, published language)
      3. **Shipping context** receives `PurchaseCompleted` and translates to `ShipmentRequested` (domain event, shipping language)
      
      This translation prevents internal model changes from breaking external consumers.
      
      ### Event-Driven Architecture Patterns
      
      #### Event Notification
      
      The event carries minimal data ("something happened") and consumers call back for details. Simplest pattern but creates temporal coupling.
      
      #### Event-Carried State Transfer
      
      The event carries all the data consumers need. Consumers maintain their own local copy of relevant data, reducing coupling but increasing event size and requiring consumers to maintain projections.
      
      #### Event Sourcing
      
      Events are the source of truth. Current state is derived by replaying events. This is the most powerful and most complex pattern.
      
      ## Event Sourcing
      
      Event sourcing stores the complete history of state changes as an ordered sequence of events. Instead of storing only the current state ("account balance is $1,000"), the system stores every event that led to that state ("deposited $500, deposited $800, withdrew $300").
      
      ### When to Use Event Sourcing
      
      | Good Fit | Poor Fit |
      |----------|----------|
      | Audit requirements (financial, medical, legal) | Simple CRUD with no audit needs |
      | Complex domain with many state transitions | Domains with few state changes |
      | Need to answer "how did we get here?" | Only need current state |
      | Need to rebuild state at any point in time | No temporal query requirements |
      | High-value domain events that are worth preserving | High-volume, low-value data (telemetry) |
      
      ### Event Sourcing Mechanics
      
      **Storing events:**
      ```
      Stream: Order-12345
        1: OrderCreated { customerId, items }
        2: PaymentAuthorized { paymentId, amount }
        3: OrderConfirmed { confirmedAt }
        4: ItemShipped { trackingNumber, items }
        5: OrderDelivered { deliveredAt, signedBy }
      ```
      
      **Rebuilding state:**
      ```
      currentState = OrderCreated.apply(emptyOrder)
      currentState = PaymentAuthorized.apply(currentState)
      currentState = OrderConfirmed.apply(currentState)
      currentState = ItemShipped.apply(currentState)
      currentState = OrderDelivered.apply(currentState)
      ```
      
      **Snapshots** optimize performance: periodically save the current state so you do not need to replay from the beginning every time.
      
      ### Event Sourcing Challenges
      
      | Challenge | Solution |
      |-----------|----------|
      | Event schema evolution | Use upcasters to transform old events into the current schema; never delete old events |
      | Performance with long event streams | Snapshots at regular intervals; read models for queries |
      | Complexity | Only use event sourcing for aggregates where it provides clear value; not everything needs to be event-sourced |
      | Debugging | Event logs provide excellent debugging and auditing; invest in tooling to browse and replay events |
      
      ## Patterns for Event Handling
      
      ### Idempotent Handlers
      
      Every event handler must be safe to execute multiple times with the same event. Strategies:
      
      - **Idempotency key:** Store processed event IDs; skip duplicates
      - **Idempotent operations:** Design the operation itself to be naturally idempotent (e.g., "set balance to X" instead of "add Y to balance")
      - **Conditional writes:** Use optimistic concurrency (version checks) to prevent double-application
      
      ### Ordering Guarantees
      
      Events from the same aggregate should be processed in order. Events from different aggregates have no ordering guarantees. Design consumers accordingly:
      
      - Partition by aggregate ID in the message broker (Kafka partition key = aggregate ID)
      - Handle out-of-order events gracefully (check event version, buffer and reorder if needed)
      
      ### Dead Letter Handling
      
      Events that repeatedly fail to process should be routed to a dead letter queue for investigation. Never silently drop failed events. Monitor dead letter queues actively.
      
      ### Sagas and Process Managers
      
      Long-running business processes that span multiple aggregates or bounded contexts can be coordinated using sagas:
      
      1. `OrderPlaced` triggers the saga
      2. Saga sends `ReserveInventory` command
      3. `InventoryReserved` event continues the saga
      4. Saga sends `AuthorizePayment` command
      5. `PaymentAuthorized` event continues the saga
      6. If any step fails, the saga sends compensating commands (`ReleaseInventory`, `RefundPayment`)
      
      Sagas maintain their own state and react to events. They do not hold locks -- they coordinate through events and compensating actions.
      
    • repositories-factories.md 15 KB
      # Repositories and Factories
      
      Repositories and Factories are infrastructure-facing patterns in Domain-Driven Design that separate domain logic from persistence and object creation concerns. The Repository provides the illusion of an in-memory collection of aggregates. The Factory encapsulates complex creation logic. Together, they keep the domain model clean and focused on business rules.
      
      
      ## Table of Contents
      1. [The Repository Pattern](#the-repository-pattern)
      2. [The Factory Pattern](#the-factory-pattern)
      3. [The Specification Pattern](#the-specification-pattern)
      4. [Ports and Adapters Relationship](#ports-and-adapters-relationship)
      
      ---
      
      ## The Repository Pattern
      
      A Repository mediates between the domain and data mapping layers, acting like an in-memory collection of domain objects. Domain code uses the repository to obtain aggregates without knowing how they are stored, queried, or reconstructed.
      
      ### Why Repositories Exist
      
      Without repositories, domain logic becomes tangled with data access:
      
      ```
      // Without repository -- domain logic polluted with SQL
      def approve_claim(claim_id):
          row = db.execute("SELECT * FROM claims WHERE id = ?", claim_id)
          claim = Claim(row['id'], row['status'], row['amount'])
          claim.approve()
          db.execute("UPDATE claims SET status = ? WHERE id = ?", claim.status, claim.id)
      ```
      
      ```
      // With repository -- domain logic is clean
      def approve_claim(claim_id):
          claim = claim_repository.find_by_id(claim_id)
          claim.approve()
          claim_repository.save(claim)
      ```
      
      The second version is readable by a domain expert. The first is not.
      
      ### Repository Interface Design
      
      The repository interface belongs in the domain layer. It speaks the ubiquitous language:
      
      **Good repository methods:**
      - `find_by_id(order_id)` -- straightforward identity lookup
      - `find_pending_orders()` -- uses domain language ("pending")
      - `find_by_customer(customer_id)` -- domain-meaningful query
      - `find_overdue_invoices(as_of_date)` -- business concept in the method name
      
      **Bad repository methods:**
      - `get_by_status_code(3)` -- magic number; what is status 3?
      - `query(sql_string)` -- leaks persistence technology into the domain
      - `find_all_with_joins()` -- technical concern, not domain language
      - `get_by_column("status", "PENDING")` -- generic data access, not domain query
      
      ### Collection-Oriented vs. Persistence-Oriented Repositories
      
      Eric Evans described two flavors of repository, each modeling a different metaphor:
      
      #### Collection-Oriented Repository
      
      Models the repository as an in-memory collection. You add objects to it and remove objects from it. Changes to retrieved objects are automatically tracked and persisted (like JPA/Hibernate managed entities).
      
      ```
      interface OrderRepository:
          add(order)           # Like collection.add()
          remove(order)        # Like collection.remove()
          find_by_id(id)       # Like collection.find()
          # No explicit save() -- changes to retrieved objects are auto-tracked
      ```
      
      **Best with:** ORMs that support change tracking (JPA/Hibernate, Entity Framework).
      
      **Advantages:** Clean domain model; changes feel natural; no explicit save calls.
      
      **Disadvantages:** "Magic" change tracking can surprise developers; harder to reason about when persistence happens.
      
      #### Persistence-Oriented Repository
      
      Models the repository as a storage mechanism. You explicitly save objects and the repository does not track changes automatically.
      
      ```
      interface OrderRepository:
          save(order)          # Explicit persist/update
          delete(order_id)     # Explicit remove
          find_by_id(id)       # Retrieve
          # Must call save() explicitly after making changes
      ```
      
      **Best with:** Frameworks without change tracking (most non-ORM approaches, event sourcing, document stores).
      
      **Advantages:** Explicit control over when persistence happens; no surprises; easier to test.
      
      **Disadvantages:** Must remember to call save(); risk of losing changes if save is forgotten.
      
      **Which to choose:** If your persistence technology offers change tracking and your team is comfortable with it, use collection-oriented. Otherwise, use persistence-oriented. The persistence-oriented style is more common in modern applications because it is more explicit.
      
      ### Repository Implementation
      
      The repository interface lives in the domain layer. The implementation lives in the infrastructure layer. This is the Dependency Inversion Principle in action:
      
      ```
      domain/
          model/
              Order.py              # Aggregate root
              OrderLineItem.py      # Entity within aggregate
          repository/
              OrderRepository.py    # Interface (abstract class / protocol)
      
      infrastructure/
          persistence/
              PostgresOrderRepository.py    # Implementation
              InMemoryOrderRepository.py    # Implementation for tests
      ```
      
      The domain layer defines what it needs (the interface). The infrastructure layer provides it (the implementation). The domain never imports from infrastructure.
      
      ### What a Repository Returns
      
      A repository always returns fully constituted aggregates -- not partial objects, not DTOs, not database rows. The aggregate returned from a repository must be in a valid state with all its invariants satisfied.
      
      **Correct:** `order_repository.find_by_id(id)` returns an `Order` with all its `OrderLineItems` loaded, ready to have business operations performed on it.
      
      **Incorrect:** `order_repository.find_by_id(id)` returns an `OrderDTO` with some fields populated and others lazily loaded. The caller must check which fields are available.
      
      ### Repository Anti-Patterns
      
      | Anti-Pattern | Problem | Fix |
      |-------------|---------|-----|
      | Generic repository (`Repository<T>`) | All aggregates look the same; domain-specific queries do not fit the generic interface | Create specific repository interfaces per aggregate type |
      | Repository returns DTOs | DTOs are not domain objects; behavior cannot be called on them | Return full aggregates; use separate read models (CQRS) for queries |
      | Repository per entity (not per aggregate) | Bypasses aggregate root; allows direct modification of internal entities | One repository per aggregate root only |
      | Repository with business logic | Repository starts containing validation or transformation logic | Keep repositories as pure storage; domain logic belongs in the aggregate |
      | Repository depends on domain services | Circular dependency between domain services and repositories | Repositories depend only on the domain model (aggregates, value objects) |
      
      ## The Factory Pattern
      
      A Factory encapsulates the logic of creating a domain object, ensuring that the object is fully formed and valid from the moment it exists. In DDD, factories are used when object creation is complex enough to warrant its own abstraction.
      
      ### When to Use a Factory
      
      | Situation | Factory Needed? | Why |
      |-----------|----------------|-----|
      | Creating a Value Object with 2-3 fields | No | A constructor suffices: `Money(100, "USD")` |
      | Creating an aggregate with multiple parts and validation rules | Yes | The assembly logic is complex; a constructor would be enormous |
      | Creating an object from an external representation (API response, file) | Yes | Translation from external format to domain object is a separate concern |
      | Creating an object with conditional logic (different subtypes) | Yes | The decision of which subtype to create should not be in client code |
      | Reconstituting an object from persistence | Maybe | If the repository handles it, a separate factory may not be needed |
      
      ### Factory Patterns in DDD
      
      #### Factory Method on the Aggregate
      
      The most common pattern: a static or class method on the aggregate itself:
      
      ```
      class Order:
          @staticmethod
          def create_from_cart(cart, customer_id):
              # Validates cart is not empty
              # Converts cart items to OrderLineItems
              # Calculates initial total
              # Returns a fully valid Order
              order = Order(
                  id=OrderId.generate(),
                  customer_id=customer_id,
                  status=OrderStatus.PENDING,
                  items=[OrderLineItem.from_cart_item(item) for item in cart.items],
                  total=cart.calculate_total()
              )
              order._raise_event(OrderCreated(...))
              return order
      ```
      
      **Advantages:** Creation logic lives close to the aggregate. The aggregate controls its own birth.
      
      #### Factory Method on Another Aggregate
      
      When one aggregate creates another:
      
      ```
      class Quote:
          def convert_to_order(self):
              # Quote knows how to create an Order from itself
              order = Order(
                  id=OrderId.generate(),
                  customer_id=self.customer_id,
                  items=[OrderLineItem(item.product_id, item.quantity, item.quoted_price)
                         for item in self.line_items],
                  source_quote_id=self.id
              )
              return order
      ```
      
      #### Standalone Factory
      
      When creation logic does not naturally belong to any existing aggregate:
      
      ```
      class LoanApplicationFactory:
          def create_from_submission(self, submission, credit_report):
              # Complex assembly involving multiple inputs
              # Conditional logic based on loan type
              # Validation against business rules
              applicant = Applicant(submission.name, submission.ssn)
              risk_score = RiskScore.calculate(credit_report)
      
              if submission.loan_type == "mortgage":
                  return MortgageApplication(applicant, risk_score, submission.property)
              elif submission.loan_type == "auto":
                  return AutoLoanApplication(applicant, risk_score, submission.vehicle)
      ```
      
      ### Factory Invariants
      
      The most critical rule of factories: **a factory must never produce an invalid object.** If the inputs are insufficient or violate business rules, the factory must fail (throw an exception), not produce a partially valid object.
      
      ```
      # Good -- factory enforces invariants
      class Order:
          @staticmethod
          def create(customer_id, items):
              if not items:
                  raise EmptyOrderError("Cannot create an order with no items")
              if not customer_id:
                  raise InvalidCustomerError("Order requires a customer")
              return Order(OrderId.generate(), customer_id, items)
      
      # Bad -- factory produces potentially invalid objects
      class Order:
          @staticmethod
          def create(customer_id=None, items=None):
              return Order(OrderId.generate(), customer_id, items or [])
              # caller can now have an order with no customer and no items
      ```
      
      ### Reconstitution vs. Creation
      
      There is an important distinction between creating a new aggregate and reconstituting one from persistence:
      
      | Aspect | Creation | Reconstitution |
      |--------|---------|----------------|
      | When | A new domain object comes into existence | An existing object is loaded from storage |
      | Validation | Full business rule validation | No validation needed; data was validated on creation |
      | Domain events | May raise creation events (`OrderCreated`) | Should NOT raise events; nothing new happened |
      | Identity | Generate a new ID | Use the stored ID |
      | Invariants | Enforce all invariants | Assume invariants hold (data was valid when stored) |
      
      Reconstitution typically happens inside the repository implementation:
      
      ```
      class PostgresOrderRepository:
          def find_by_id(self, order_id):
              row = self.db.query("SELECT * FROM orders WHERE id = ?", order_id)
              items = self.db.query("SELECT * FROM order_items WHERE order_id = ?", order_id)
              # Reconstitute -- no validation, no events
              return Order._reconstitute(
                  id=row['id'],
                  customer_id=row['customer_id'],
                  status=row['status'],
                  items=[OrderLineItem._reconstitute(i) for i in items]
              )
      ```
      
      ## The Specification Pattern
      
      The Specification pattern encapsulates query criteria as first-class domain objects. Instead of building queries in service code, you express criteria as composable specification objects.
      
      ### Why Specifications
      
      Without specifications, query logic scatters across the codebase:
      
      ```
      # Query logic in a service -- not reusable, not composable
      def find_risky_orders(self):
          return db.query("SELECT * FROM orders WHERE total > 10000 AND customer_risk > 7")
      
      # Same logic duplicated elsewhere with slight variations
      def find_very_risky_orders(self):
          return db.query("SELECT * FROM orders WHERE total > 50000 AND customer_risk > 9")
      ```
      
      With specifications:
      
      ```
      high_value = OrderValueExceeds(10000)
      high_risk = CustomerRiskAbove(7)
      risky_orders = order_repository.find_matching(high_value.and_(high_risk))
      
      very_risky = OrderValueExceeds(50000).and_(CustomerRiskAbove(9))
      very_risky_orders = order_repository.find_matching(very_risky)
      ```
      
      ### Specification Composition
      
      Specifications compose using logical operators:
      
      | Operator | Meaning | Example |
      |----------|---------|---------|
      | `and_` | Both must be true | `HighValue.and_(HighRisk)` |
      | `or_` | Either must be true | `HighValue.or_(HighRisk)` |
      | `not_` | Must not be true | `not_(Cancelled)` |
      
      ### Specifications in the Domain Layer
      
      The specification interface lives in the domain layer. Implementations can be in the domain (for in-memory filtering) or infrastructure (for database queries):
      
      ```
      # Domain layer -- specification interface
      class Specification:
          def is_satisfied_by(self, candidate) -> bool:
              pass
      
      class OverdueInvoice(Specification):
          def __init__(self, as_of_date):
              self.as_of_date = as_of_date
      
          def is_satisfied_by(self, invoice):
              return invoice.due_date < self.as_of_date and not invoice.is_paid
      ```
      
      ## Ports and Adapters Relationship
      
      Repositories and Factories fit naturally into the Ports and Adapters (Hexagonal) architecture:
      
      ```
                          Domain Layer
                         ┌─────────────────────────┐
                         │  Aggregates             │
                         │  Value Objects           │
                         │  Domain Events           │
                         │  Repository Interfaces ──┼── Port (interface)
                         │  Factory Interfaces   ──┼── Port (interface)
                         └─────────────────────────┘
                                    │
                                    │ implements
                                    ▼
                         Infrastructure Layer
                         ┌─────────────────────────┐
                         │  PostgresOrderRepo    ──┼── Adapter (implementation)
                         │  InMemoryOrderRepo    ──┼── Adapter (for tests)
                         │  S3DocumentFactory    ──┼── Adapter (implementation)
                         └─────────────────────────┘
      ```
      
      **The key principle:** The domain defines what it needs (ports). Infrastructure provides it (adapters). Dependencies point inward -- infrastructure depends on domain, never the reverse.
      
      This means:
      - The domain layer has zero imports from infrastructure packages
      - Repository interfaces use domain types (`Order`, `OrderId`), not infrastructure types (`Row`, `Document`)
      - The application can swap persistence technologies by providing a new adapter without touching domain code
      - Tests use in-memory adapters to test domain logic without databases
      
    • strategic-design.md 14.2 KB
      # Strategic Design and Distillation
      
      Strategic design is the practice of identifying which parts of a system matter most and allocating design effort accordingly. Not all code is created equal. Some code is the reason the business exists; other code is necessary plumbing. Domain distillation is the process of separating the essential from the incidental, so that the core of the domain model receives the deepest thought and the best talent.
      
      ## The Three Types of Subdomains
      
      Eric Evans classifies every part of a system into one of three subdomain types. This classification drives every major design and investment decision.
      
      ### Core Domain
      
      The Core Domain is the part of the system that provides competitive advantage. It is the reason the business exists and what differentiates it from competitors. Without it, the business has no unique value proposition.
      
      **Characteristics:**
      - Contains the most complex and nuanced business rules
      - Is the source of competitive advantage
      - Changes frequently as the business evolves its strategy
      - Cannot be outsourced without losing differentiation
      - Requires the deepest domain expertise
      
      **Examples:**
      
      | Business | Core Domain | Why It Is Core |
      |----------|-------------|----------------|
      | Amazon | Recommendation engine, marketplace matching, logistics optimization | These are the algorithms that make Amazon uniquely effective |
      | Stripe | Payment processing, fraud detection, developer experience | These are what make Stripe better than alternatives |
      | Netflix | Content recommendation, streaming optimization | These keep subscribers engaged and differentiators from competitors |
      | Insurance company | Risk assessment, claims adjudication, actuarial modeling | These determine profitability and pricing accuracy |
      | Trading firm | Signal generation, execution algorithms, risk management | These are the source of alpha |
      
      **Investment rule:** Put your best developers here. Apply the deepest modeling techniques. This is where DDD patterns earn their complexity cost.
      
      ### Supporting Subdomain
      
      A Supporting Subdomain is necessary for the business to function but does not provide competitive advantage. It supports the Core Domain. You build it because off-the-shelf solutions do not fit your specific needs, but you do not need to over-engineer it.
      
      **Characteristics:**
      - Custom-built because available solutions do not quite fit
      - Important but not differentiating
      - Moderately complex; business-specific but not competitively critical
      - Can be built by competent developers without deep domain modeling
      
      **Examples:**
      
      | Business | Supporting Subdomain | Why It Is Supporting |
      |----------|---------------------|---------------------|
      | E-commerce platform | Order management, inventory tracking | Necessary for operations but not what makes this e-commerce site unique |
      | Insurance company | Policy administration, document generation | Must work correctly but is not a competitive differentiator |
      | Trading firm | Position reporting, compliance reporting | Regulatory requirement, not a source of trading advantage |
      | SaaS product | Tenant management, billing integration | Needed but not what customers buy the product for |
      
      **Investment rule:** Build it, but keep it simple. Use straightforward designs. Do not apply deep DDD modeling patterns unless the complexity warrants it.
      
      ### Generic Subdomain
      
      A Generic Subdomain is functionality that is common across many businesses and has no business specificity. It is commodity software that you should buy, use open-source, or outsource.
      
      **Characteristics:**
      - Not specific to your business; every company needs it
      - Well-solved problems with mature solutions available
      - Building it yourself is a waste of your best developers' time
      - Off-the-shelf solutions are often better than what you would build
      
      **Examples:**
      
      | Generic Subdomain | Buy/Use Instead |
      |-------------------|----------------|
      | Authentication and authorization | Auth0, Okta, Keycloak, Clerk |
      | Email sending | SendGrid, Amazon SES, Postmark |
      | Payment processing (if not your core) | Stripe, Braintree, Adyen |
      | File storage | Amazon S3, Google Cloud Storage |
      | Search indexing | Elasticsearch, Algolia, Typesense |
      | Monitoring and alerting | Datadog, Grafana, PagerDuty |
      | CMS / Content management | WordPress, Contentful, Sanity |
      
      **Investment rule:** Do not build this. Buy it, use open-source, or outsource it. Every hour your best developer spends building a custom email sender is an hour stolen from the Core Domain.
      
      ## Identifying Your Core Domain
      
      ### The Differentiation Test
      
      For each part of the system, ask: "If a competitor had exactly the same implementation of this, would we lose our competitive advantage?"
      
      - **Yes, we would lose advantage:** Core Domain
      - **No, but we would be inconvenienced:** Supporting Subdomain
      - **No, and we could swap it easily:** Generic Subdomain
      
      ### The Outsourcing Test
      
      Ask: "Could we outsource this to a competent contractor or replace it with a SaaS product without damaging our competitive position?"
      
      - **No, absolutely not -- this is our secret sauce:** Core Domain
      - **Maybe, but it would need customization:** Supporting Subdomain
      - **Yes, easily:** Generic Subdomain
      
      ### The Talent Test
      
      Ask: "Does working on this require deep expertise in our specific business domain?"
      
      - **Yes -- only people who deeply understand our industry can get this right:** Core Domain
      - **Somewhat -- general software skills with some domain knowledge:** Supporting Subdomain
      - **No -- any competent developer could implement this:** Generic Subdomain
      
      ### Common Misclassification Errors
      
      | Error | Reality | Consequence |
      |-------|---------|-------------|
      | "Everything is core" | Most things are supporting or generic | Best talent spread thin; nothing gets deep modeling |
      | "Our custom CRM is core" | CRM is generic; your customer relationships are core | Team spent years building what Salesforce does better |
      | "Authentication is core" | Authentication is generic (unless you are Auth0) | Security expertise wasted on commodity functionality |
      | "Infrastructure is core" | Infrastructure is generic (unless you are AWS) | Platform team grows while product team starves |
      | "Our billing system is core" | Billing is usually supporting or generic | Over-engineered billing while the actual product suffered |
      
      ## Domain Distillation
      
      Distillation is the process of extracting and clarifying the Core Domain from the rest of the system. It makes the most important parts of the model explicit, visible, and well-understood.
      
      ### The Domain Vision Statement
      
      A Domain Vision Statement is a short document (one page or less) that describes the Core Domain's value proposition and its most important aspects. It serves as a north star for the team.
      
      **What it contains:**
      - What makes this domain unique and valuable
      - What distinguishes the Core Domain from everything else in the system
      - What the domain model must capture to deliver competitive advantage
      - What the team should focus on and what they should explicitly not focus on
      
      **Example for an insurance company:**
      
      > Our competitive advantage is our ability to accurately assess risk in real-time for commercial property insurance. Our core domain model must capture the nuanced relationships between property characteristics, geographic risk factors, historical claims data, and market conditions. The model must support rapid repricing as conditions change. Everything else -- policy administration, document generation, payment processing -- is supporting infrastructure that must work correctly but does not differentiate us.
      
      ### The Highlighted Core
      
      The Highlighted Core is a technique for making the Core Domain visually obvious in the codebase and in documentation.
      
      **In documentation:**
      - Create a document that marks which modules, classes, and interactions constitute the Core Domain
      - Use diagrams that distinguish core from supporting from generic
      - Keep this document updated as the model evolves
      
      **In code:**
      - Organize the codebase so that Core Domain modules are clearly separated: `domain/core/`, `domain/supporting/`, `infrastructure/`
      - Use naming conventions that signal importance: a module called `pricing-engine` conveys more importance than `util-helpers`
      - Code review standards can be higher for core domain code (require domain expert sign-off)
      
      ### Distillation Techniques
      
      #### Segregated Core
      
      Physically separate the Core Domain from the rest of the codebase. The core should have no dependencies on supporting or generic subdomains -- only the other way around.
      
      ```
      src/
          core/                           # Core Domain -- deepest modeling
              risk-assessment/
                  RiskModel.py
                  RiskFactor.py
                  UnderwritingRules.py
              pricing/
                  PricingEngine.py
                  RateTable.py
          supporting/                     # Supporting -- necessary, simpler design
              policy-admin/
                  PolicyRepository.py
                  PolicyDocument.py
              notifications/
                  NotificationService.py
          generic/                        # Generic -- thin wrappers around external services
              email/
                  EmailGateway.py
              storage/
                  FileStorage.py
              auth/
                  AuthenticationAdapter.py
      ```
      
      #### Abstract Core
      
      Create a distilled model that captures the essential abstractions of the Core Domain without the implementation details. This abstract model serves as a communication tool and a guide for detailed implementation.
      
      The Abstract Core is like an executive summary of the domain model: it captures the key concepts, their relationships, and the most important business rules, without the full detail of every attribute and method.
      
      ## Build vs. Buy vs. Outsource
      
      ### Decision Framework
      
      | Question | Core Domain | Supporting Subdomain | Generic Subdomain |
      |----------|-------------|---------------------|-------------------|
      | Should we build it in-house? | **Yes, always** | Yes, if no good fit exists | **No** |
      | Should we buy/use SaaS? | **No** -- too important to delegate | Only if it fits well | **Yes, always** |
      | Should we outsource development? | **No** -- requires deep domain expertise | Possible with good specs | **Yes** -- or better yet, buy |
      | Should we use open-source? | Only as a foundation to build on | Yes, if it fits | **Yes** |
      | What quality standard? | Highest -- deep modeling, extensive testing, expert review | Good -- solid engineering, adequate testing | Adequate -- it just needs to work |
      
      ### The Opportunity Cost Lens
      
      Every hour spent on non-core work is an hour not spent on the Core Domain. Frame build-vs-buy decisions as opportunity costs:
      
      - "We could build our own email service in 3 months." That is 3 months your best developers are not improving the Core Domain. Use SendGrid.
      - "We could build a custom monitoring dashboard in 6 weeks." That is 6 weeks not spent on the pricing engine. Use Grafana.
      - "We could build our own authentication system in 2 months." That is 2 months of security engineering not applied to fraud detection. Use Auth0.
      
      ### When "Buy" Becomes "Core"
      
      Sometimes a generic subdomain becomes core as the business evolves:
      
      - Stripe started as a payment processor (generic for most businesses) but made payments their Core Domain
      - Amazon started as a bookstore; logistics (supporting for most retailers) became a Core Domain that turned into AWS
      - Netflix treated content recommendation as core from the beginning, while most video platforms treated it as supporting
      
      **Revisit classifications regularly.** What is generic today may become core tomorrow if the business strategy shifts. Annual or quarterly reviews of subdomain classifications prevent stale assumptions.
      
      ## Applying Strategic Design to Team Structure
      
      ### Team Allocation by Subdomain Type
      
      | Subdomain Type | Team Characteristics | Practices |
      |---------------|---------------------|-----------|
      | Core Domain | Senior engineers, domain experts embedded in team, smallest and most skilled team | Deep modeling, event storming, pair programming with domain experts, extensive testing |
      | Supporting Subdomain | Mid-level engineers, domain knowledge acquired through documentation | Standard engineering practices, adequate testing, clear interfaces |
      | Generic Subdomain | Junior engineers or no team at all (use external service) | Integration work, adapter implementation, vendor management |
      
      ### Conway's Law Application
      
      Design team boundaries to match desired bounded context boundaries:
      
      - One team per bounded context (or small number of contexts)
      - Core Domain teams should be co-located or closely collaborating
      - Supporting Subdomain teams can be more independent
      - Generic Subdomain work can be distributed or handled by a platform team
      
      ### Investment Over Time
      
      As the system matures, investment should shift:
      
      | Phase | Core Domain Investment | Supporting Investment | Generic Investment |
      |-------|----------------------|----------------------|-------------------|
      | Early (MVP) | 70% | 20% | 10% (buy everything) |
      | Growth | 60% | 25% | 15% (integrate more) |
      | Mature | 50% | 30% | 20% (optimize and replace) |
      
      The Core Domain always receives the plurality of investment. If it drops below 50%, the team is likely over-engineering supporting functionality or building generic functionality that should be bought.
      
      ## Strategic Design Anti-Patterns
      
      | Anti-Pattern | Signal | Fix |
      |-------------|--------|-----|
      | "Golden hammer" -- applying Core Domain rigor to everything | Every module has aggregates, repositories, domain events, factories | Classify subdomains; simplify supporting and generic code |
      | "Platform first" -- building infrastructure before product | Months spent on logging, monitoring, and deployment before a single domain feature | Use off-the-shelf infrastructure; build Core Domain features first |
      | "Resume-driven development" -- choosing technology for novelty | Core Domain uses experimental framework because it is interesting | Choose boring technology for production; innovate in modeling, not infrastructure |
      | "Outsourced core" -- contracting out the competitive advantage | Core Domain built by offshore team with no domain expertise | Bring core development in-house; invest in domain expert access |
      | "Everyone is equal" -- same standards and investment everywhere | No distinction between a pricing algorithm and a CRUD admin panel | Apply deep modeling only where it pays off; keep the rest simple |
      
    • ubiquitous-language.md 12.4 KB
      # Ubiquitous Language
      
      The single most important practice in Domain-Driven Design. A ubiquitous language is the shared vocabulary between developers and domain experts that is used everywhere -- in conversation, documentation, code, tests, and diagrams. It is not a glossary appended to a wiki. It is the living, evolving language that shapes how the system is built and how the team thinks about the domain.
      
      ## Why Language Matters More Than Code
      
      Software development is fundamentally a communication problem. The hardest bugs are not off-by-one errors or null pointer exceptions -- they are misunderstandings between people. When a developer hears "account" and thinks "user login record" while the domain expert means "financial ledger position," the resulting code will be structurally wrong in ways that no amount of testing can catch.
      
      A ubiquitous language eliminates this class of failure by establishing a single, precise vocabulary that both sides use without translation. When the language is embedded in code, every class name, method name, and variable name becomes a checkpoint: if a domain expert cannot read the code and recognize the concepts, the model is wrong.
      
      ### The Cost of Translation
      
      Without a ubiquitous language, every conversation requires mental translation:
      
      | Developer Says | Domain Expert Hears | Actual Meaning | Risk |
      |---------------|--------------------|-|------|
      | `UserEntity` | "User? We don't have users, we have policyholders" | The system models a concept the business does not recognize | Code does not reflect reality; edge cases are missed |
      | `processData()` | "Process what data? Which business operation?" | A generic method hiding domain-specific logic | Business rules buried in implementation; impossible to validate |
      | `status = 3` | "What does 3 mean?" | An opaque encoding of a domain concept | Magic numbers replace meaningful domain states |
      | `ServiceManager` | "Manager of what?" | A catch-all class with no domain analog | God class accumulates unrelated responsibilities |
      
      ### The Payoff of Alignment
      
      When the language is shared:
      
      - **Domain experts can read tests.** A test that says `policy.underwrite(application)` is immediately meaningful. A test that says `service.process(dto)` is opaque.
      - **Developers catch domain errors.** When a developer writes `order.cancel()` and the domain expert says "we don't cancel orders, we void them," the naming mismatch reveals a modeling error.
      - **New team members onboard faster.** The codebase teaches the domain because the names are the domain.
      - **Refactoring is safer.** Renaming `process()` to `adjudicateClaim()` is not just cosmetic -- it encodes domain knowledge that prevents future misuse.
      
      ## Building the Language
      
      ### Start with Domain Expert Conversations
      
      The language does not come from developers reading documentation. It comes from intensive, iterative conversations between developers and domain experts. These conversations follow a pattern:
      
      1. **Listen for the nouns and verbs the expert uses naturally.** "When a claim comes in, the adjuster reviews it, and then we either approve or deny the claim." The nouns are `Claim`, `Adjuster`. The verbs are `review`, `approve`, `deny`.
      
      2. **Challenge ambiguity.** "You said 'review' -- what exactly happens during a review? Is it the same as 'assess'?" Often the expert will distinguish between terms that outsiders conflate.
      
      3. **Propose the model.** "So a `Claim` goes through an `Adjudication` process where an `Adjuster` either `approves` or `denies` it?" The expert corrects: "Not exactly -- the adjuster makes a recommendation, but only a senior adjuster can approve claims over $10,000."
      
      4. **Refine until the model is precise.** The conversation reveals business rules that no requirements document captured: approval authority limits, recommendation vs. decision, escalation paths.
      
      ### Model Exploration Whirlpool
      
      Eric Evans describes a "whirlpool" process for modeling:
      
      1. **Scenario walkthrough.** Walk through concrete business scenarios with domain experts. "A customer calls to report damage to their vehicle. What happens next?"
      
      2. **Concept extraction.** Identify the key domain concepts that emerge: `Claim`, `Incident`, `CoverageVerification`, `DamageAssessment`.
      
      3. **Name negotiation.** Debate and agree on names. "Should we call it a 'damage report' or a 'claim'? When does a report become a claim?"
      
      4. **Code spike.** Quickly implement the emerging model in code to test whether it holds up under real logic.
      
      5. **Feedback loop.** Show the code (or at least the class and method names) back to the domain expert. "Does this look right to you?"
      
      This cycle repeats continuously throughout the project, not just at the beginning.
      
      ## Language in Code
      
      ### Naming Classes After Domain Concepts
      
      Every class in the domain layer should be named after a concept the business recognizes:
      
      **Good names (domain language):**
      - `LoanApplication` -- the business knows what this is
      - `CreditDecision` -- an explicit outcome of an underwriting process
      - `PaymentSchedule` -- a concrete domain concept
      - `PolicyRenewalNotice` -- named exactly as the business document is named
      
      **Bad names (technical language):**
      - `ApplicationDTO` -- DTO is a technical pattern, not a domain concept
      - `PaymentService` -- "service" is a technical role, not a domain concept; what does this service do?
      - `DataProcessor` -- meaningless in domain terms
      - `BaseEntityAbstractFactory` -- pure technical jargon
      
      ### Naming Methods After Domain Operations
      
      Methods should read like sentences a domain expert would say:
      
      **Good:**
      ```
      policy.renew(effectiveDate)
      claim.submitForAdjudication()
      account.applyInterest(rate, period)
      order.fulfillWith(shipment)
      ```
      
      **Bad:**
      ```
      policy.update(data)
      claim.process()
      account.calculate()
      order.setStatus(STATUS_SHIPPED)
      ```
      
      The difference is not cosmetic. `claim.submitForAdjudication()` tells you what the business operation is. `claim.process()` tells you nothing -- you must read the implementation to understand what it does, which means the domain knowledge is hidden.
      
      ### Naming Events After Domain Facts
      
      Domain events should be named as past-tense facts that a domain expert would recognize as significant business occurrences:
      
      **Good:** `PolicyIssued`, `ClaimDenied`, `PaymentOverdue`, `MembershipExpired`
      
      **Bad:** `PolicyUpdated`, `ClaimProcessed`, `DataChanged`, `RecordModified`
      
      "Updated," "processed," and "changed" are technical descriptions of what happened to data. "Issued," "denied," "overdue," and "expired" are domain descriptions of what happened in the business.
      
      ## Glossary Maintenance
      
      ### The Living Glossary
      
      Maintain a glossary document that evolves with the model. This is not a static artifact created at the start of the project and forgotten. It is a living document updated every time the language changes.
      
      **What goes in the glossary:**
      
      | Term | Definition | Context | Example |
      |------|-----------|---------|---------|
      | Claim | A formal request for payment under an insurance policy following a covered event | Claims processing | "The policyholder filed a claim for water damage" |
      | Adjudication | The process of evaluating a claim to determine whether it is covered and how much to pay | Claims processing | "The claim is in adjudication pending the damage assessment" |
      | Coverage | The set of perils and limits defined in a policy | Underwriting | "This policy provides coverage for fire but not flood" |
      | Premium | The amount the policyholder pays for coverage | Billing | "The annual premium is $1,200, payable monthly" |
      
      **What does NOT go in the glossary:**
      - Technical terms (`Repository`, `Service`, `Controller`)
      - Implementation details (`PostgreSQL table name`, `API endpoint`)
      - Generic programming concepts (`interface`, `abstract class`)
      
      ### When Terms Conflict
      
      The same word often means different things in different parts of the business. This is expected and healthy:
      
      - **"Account"** in billing means a financial ledger position. In authentication, it means a user login. In sales, it means a company relationship.
      - **"Product"** in the catalog means something you can browse. In inventory, it means a physical item with a location. In marketing, it means a brand offering.
      
      The solution is not to force one definition. The solution is bounded contexts: each context has its own definition, and translations happen at the boundary.
      
      ## Language Evolution
      
      ### When to Change the Language
      
      The ubiquitous language is not fixed. It evolves as the team's understanding of the domain deepens. Signals that the language needs to change:
      
      - **Awkward conversations.** "Well, it's kind of like a customer but not exactly -- it's more of an applicant who might become a customer." This means `Customer` is the wrong term; `Applicant` is a distinct concept.
      - **Workarounds in code.** A `type` field that switches behavior (e.g., `if customer.type == 'prospect'`) often means a single class is trying to represent two different domain concepts.
      - **Expert correction.** "We don't really call it a 'request' -- we call it a 'submission.'" Change the code immediately.
      - **New domain insight.** "Actually, a cancellation and a voiding are different things. Cancellation is prospective; voiding is retroactive." Split the concept.
      
      ### The Refactoring Trigger
      
      When the language changes, the code must change. This is not optional. If the team agrees that "submission" is the correct term instead of "request," then:
      
      1. Rename the class: `Request` becomes `Submission`
      2. Rename the repository: `RequestRepository` becomes `SubmissionRepository`
      3. Rename the events: `RequestCreated` becomes `SubmissionFiled`
      4. Update the database: table `requests` becomes `submissions` (with a migration)
      5. Update the API: `/api/requests` becomes `/api/submissions` (with versioning)
      
      This may seem expensive, but the cost of maintaining a divergence between language and code is far higher. Every time a developer reads `Request` and has to mentally translate to "submission," they lose context and risk introducing errors.
      
      ## How Naming Shapes Design
      
      Names are not labels applied after the fact. They are design decisions that constrain and guide the system's evolution.
      
      ### Names Reveal Missing Concepts
      
      When you struggle to name something, the model is telling you something is wrong:
      
      - **"OrderProcessorHelper"** -- If you need a helper, the class it is helping probably has the wrong boundaries. The behavior likely belongs inside `Order` or in a separate, well-named domain concept.
      - **"MiscService"** -- If you cannot name it, you do not understand it. Break it apart until each piece has a clear domain name.
      - **"DataValidator"** -- Validation of what? By what rules? `CreditApplicationValidator` or `AddressVerifier` tells you exactly what domain rules are being enforced.
      
      ### Names Prevent Misuse
      
      A method named `account.debit(amount)` tells future developers exactly what this does and constrains its usage. A method named `account.update(amount)` invites misuse because "update" could mean anything.
      
      ### Names Create Boundaries
      
      When two concepts share a name but have different behaviors, splitting the name splits the model:
      
      - `Customer` in sales vs. `Customer` in billing? These are different bounded contexts. The split in naming reveals the split in models.
      - `Order` before payment vs. `Order` after payment? Perhaps these are `PendingOrder` and `ConfirmedOrder` -- two distinct states that deserve distinct types, not a `status` flag.
      
      ## Anti-Patterns to Avoid
      
      ### The Jargon Trap
      
      Developers invent internal jargon that has no domain analog: "We call it a 'widget' internally." If domain experts do not use the word "widget," it does not belong in the domain model.
      
      ### The Abbreviation Trap
      
      `CustAcctMgr` is not a ubiquitous language term. Write `CustomerAccountManager` -- or better yet, ask what the business actually calls this role. Maybe it is a `RelationshipOfficer` or an `AccountExecutive`.
      
      ### The Thesaurus Trap
      
      Using synonyms interchangeably (`customer` / `client` / `user` / `patron`) destroys precision. Pick one term per concept and enforce it everywhere. If the business uses "client" in legal contexts and "customer" in sales contexts, those are different bounded contexts with different terms -- and both are correct within their context.
      
      ### The Persistence Trap
      
      Naming domain concepts after their storage mechanism: `CustomerRecord`, `OrderRow`, `PaymentTable`. Domain objects are not records or rows. They are domain concepts that happen to be persisted. Name them for what they are in the domain: `Customer`, `Order`, `Payment`.
      
  • SKILL.md 15 KB
    ---
    name: domain-driven-design
    description: 'Model software around the business domain using bounded contexts, aggregates, and ubiquitous language. Use when the user mentions "domain modeling", "bounded context", "aggregate root", "ubiquitous language", "anti-corruption layer", "context mapping", "domain events", "strategic design", "the code doesnt match the business", or "how do we split this big system". Also trigger when breaking a monolith into services, defining service boundaries, or aligning code structure with business processes. Covers entities vs value objects, domain events, and context mapping strategies. For architecture layers, see clean-architecture. For complexity, see software-design-philosophy.'
    license: MIT
    metadata:
      author: wondelai
      version: "1.4.0"
    ---
    
    # Domain-Driven Design Framework
    
    Framework for tackling software complexity by modeling code around the business domain. The greatest risk in software is not technical failure -- it is building a model that does not reflect how the business actually works.
    
    ## Core Principle
    
    **The model is the code; the code is the model.** Software should embody a deep, shared understanding of the business domain. When domain experts and developers speak the same language and that language is directly expressed in the codebase, complexity becomes manageable and the system evolves gracefully as the business changes.
    
    ## Scoring
    
    **Goal: 10/10.** Score a domain model by awarding **1 point per satisfied row of the Quick Diagnostic** (7 rows) plus up to 3 points for depth: +1 if the Core Domain has a genuinely rich model (not just CRUD), +1 if invariants live inside aggregates rather than in services, +1 if the ubiquitous language is consistent across conversation, code, and tests. Bands: **9-10** = expert-readable names, explicit context boundaries with ACLs, small aggregates, behavior-rich entities, events for cross-aggregate flow, an identified Core Domain; **5-6** = some domain language but leaky boundaries or anemic objects; **<=3** = technical naming, one model for everything, logic scattered in services. Report the score and the specific diagnostic rows failing.
    
    ## Framework
    
    ### 1. Ubiquitous Language
    
    **Core concept:** A shared, rigorous language between developers and domain experts, used consistently in conversation, documentation, and code. When the language changes, the code changes -- and awkward naming in code feeds back into refining the language.
    
    **Why it works:** Ambiguity is the root cause of most modeling failures. When a developer says "order" and an expert means "purchase request," bugs are inevitable; a ubiquitous language forces every name in code to map to a concept the business recognizes and validates.
    
    **Key insights:**
    - The language emerges from deep collaboration, not a glossary bolted on after the fact
    - If a concept is hard to name, the model is likely wrong -- naming difficulty is a design signal
    - Technical jargon (`DataProcessor` vs. `ClaimAdjudicator`) hides domain logic from the experts who could correct it
    - Different bounded contexts may use the same word with different meanings -- and that is fine
    
    **Code applications:**
    
    | Context | Pattern | Example |
    |---------|---------|---------|
    | Class/method naming | Name after domain concepts and verbs | `LoanApplication`, `policy.underwrite()` -- not `RequestHandler`, `process()` |
    | Module structure | Organize by domain concept | `shipping/`, `billing/` -- not `controllers/`, `services/` |
    | Code review | Reject technical-only names | Flag `Manager`, `Helper`, `Processor`, `Utils` as naming smells |
    
    See: [references/ubiquitous-language.md](references/ubiquitous-language.md) when running modeling sessions or maintaining a glossary -- covers how the language evolves and feeds back into code.
    
    ### 2. Bounded Contexts and Context Mapping
    
    **Core concept:** A bounded context is an explicit boundary within which a particular domain model applies. The same word ("Customer") can mean different things in different contexts; context maps define the relationships and translation strategies between them.
    
    **Why it works:** Large systems that try to maintain a single unified model inevitably collapse into inconsistency. Bounded contexts accept that different parts of the business need different models; context maps manage the integration between them.
    
    **Key insights:**
    - A bounded context is not a microservice -- it is a linguistic and model boundary that may contain multiple services
    - Context boundaries often align with team boundaries (Conway's Law)
    - The nine context mapping patterns describe political and technical relationships between teams
    - Anti-Corruption Layer is the most important defensive pattern -- never let a foreign model leak into your core domain
    - Shared Kernel couples two teams; keep it small and explicitly governed
    - Start by mapping what exists (Big Ball of Mud), then define target boundaries
    
    **Code applications:**
    
    | Context | Pattern | Example |
    |---------|---------|---------|
    | Service integration | Anti-Corruption Layer | Translate external API responses into your domain objects at the boundary |
    | Legacy migration | Conformist / ACL | Wrap the legacy system behind an adapter that speaks your domain language |
    | API design | Open Host Service + Published Language | Expose a well-documented REST API with a canonical schema |
    
    See: [references/bounded-contexts.md](references/bounded-contexts.md) for the nine mapping patterns and integration strategies.
    
    ### 3. Entities, Value Objects, and Aggregates
    
    **Core concept:** Entities have identity that persists across state changes. Value Objects are defined entirely by their attributes and are immutable. Aggregates are clusters of entities and value objects with a single root that enforces consistency boundaries.
    
    **Why it works:** Without these distinctions, everything becomes a mutable, identity-bearing object -- tangled state, inconsistent updates, fragile concurrency. Aggregates draw the line: everything inside is guaranteed consistent; everything outside is eventually consistent.
    
    **Key insights:**
    - Entity test: "Am I the same thing even if all my attributes change?" (a person changes name and address -- still the same person)
    - Value Object test: "Am I defined only by my attributes?" (any $10 bill is interchangeable with another)
    - Most things should be Value Objects, not Entities -- prefer immutability
    - Keep aggregates small (one root plus a minimal cluster); reference other aggregates by ID, not object reference
    - Immediate consistency only within an aggregate; design for eventual consistency between aggregates
    
    **Code applications:**
    
    | Context | Pattern | Example |
    |---------|---------|---------|
    | Identity tracking | Entity with ID | `Order` identified by `orderId`, survives state changes |
    | Immutable attributes | Value Object | `Address(street, city, zip)` -- replace, never mutate |
    | Consistency boundary | Aggregate Root | `Order` is root; `OrderLine` items exist only through it |
    | Concurrency control | Optimistic locking on root | Version field on `Order`; conflict if two edits race |
    
    See: [references/building-blocks.md](references/building-blocks.md) for aggregate design rules and consistency boundaries.
    
    ### 4. Domain Events
    
    **Core concept:** A domain event captures something that happened in the domain that experts care about, named in past tense (`OrderPlaced`, `PaymentReceived`) -- a fact that has already occurred.
    
    **Why it works:** Domain events decouple cause from effect. When `OrderPlaced` is published, shipping, billing, and notifications each react independently without the ordering context knowing about them -- less coupling, eventual consistency, a natural audit trail.
    
    **Key insights:**
    - Events are immutable facts -- once published, they cannot be changed or retracted
    - Domain events are internal to a bounded context; integration events cross boundaries
    - Events enable temporal decoupling: the producer does not wait for the consumer
    - Event sourcing stores the full event history as the source of truth, deriving current state by replay
    - Not every state change deserves an event -- only publish what the domain cares about
    
    **Code applications:**
    
    | Context | Pattern | Example |
    |---------|---------|---------|
    | State transitions | Raise event on domain action | `order.place()` raises `OrderPlaced` |
    | Cross-context integration | Publish integration event | `OrderPlaced` triggers `ShippingLabelRequested` in shipping context |
    | Eventual consistency | Async event handlers | Inventory handler updates stock asynchronously after `OrderPlaced` |
    
    See: [references/domain-events.md](references/domain-events.md) for event naming, event sourcing, and integration events.
    
    ### 5. Repositories and Factories
    
    **Core concept:** Repositories provide the illusion of an in-memory collection of domain objects, hiding persistence. Factories encapsulate complex creation logic so aggregates are always born in a valid state.
    
    **Why it works:** When persistence and assembly details leak into domain code, every storage change ripples through business rules and aggregates can be constructed in half-valid states. Repositories confine SQL/ORM concerns to infrastructure so the domain stays testable in memory; factories make the only path to an aggregate one that enforces its invariants, so an invalid instance is unrepresentable.
    
    **Key insights:**
    - The Repository interface belongs in the domain layer; its implementation belongs in infrastructure
    - Repository methods speak the ubiquitous language: `findPendingOrders()`, not `getByStatusCode(3)`
    - Collection-oriented repositories mimic `add`/`remove`; persistence-oriented ones use `save`
    - Factories are warranted for complex rules or multi-part assembly; a two-field Value Object just needs a constructor
    - The Specification pattern encapsulates query criteria as domain objects: `OverdueInvoiceSpecification`
    
    **Code applications:**
    
    | Context | Pattern | Example |
    |---------|---------|---------|
    | Data access abstraction | Repository interface | `OrderRepository.findByCustomer(customerId)` in domain; `PostgresOrderRepository` in infrastructure |
    | Complex creation | Factory method | `Order.createFromQuote(quote)` validates and assembles from a `Quote` aggregate |
    | Query encapsulation | Specification | `spec = OverdueBy(days=30); repo.findMatching(spec)` |
    
    See: [references/repositories-factories.md](references/repositories-factories.md) for Repository, Factory, and Specification patterns.
    
    ### 6. Strategic Design and Distillation
    
    **Core concept:** Not all parts of a system are equally important. Strategic design identifies the Core Domain -- where competitive advantage lives -- and distinguishes it from Supporting Subdomains (necessary, not differentiating) and Generic Subdomains (commodity).
    
    **Why it works:** Applying the same rigor everywhere spreads your best talent thin and over-engineers commodity functionality. Identifying the Core Domain concentrates the best developers and deepest modeling where they matter most.
    
    **Key insights:**
    - Core Domain: invest your best people and deepest modeling; Supporting: build, but don't over-engineer; Generic (auth, email, payments): buy or use open-source
    - Distillation extracts and highlights the Core Domain from surrounding complexity
    - A Domain Vision Statement is a one-page description of the Core Domain's value proposition
    - Revisit what is "core" as the business evolves -- today's differentiator may become tomorrow's commodity
    
    **Code applications:**
    
    | Context | Pattern | Example |
    |---------|---------|---------|
    | Build vs. buy | Classify subdomain type | Build custom pricing engine (core); use Stripe for payments (generic) |
    | Team allocation | Best developers on Core Domain | Seniors model underwriting rules; juniors integrate the email service |
    | Code organization | Separate core from generic | `domain/pricing/` (deep model) vs. `infrastructure/email/` (thin adapter) |
    
    See: [references/strategic-design.md](references/strategic-design.md) when deciding where to invest engineering effort -- subdomain classification and distillation techniques.
    
    ## Common Mistakes
    
    | Mistake | Why It Fails | Fix |
    |---------|-------------|-----|
    | Technical names instead of domain language | Logic hidden behind `DataManager`; experts can't validate the model | Rename to domain terms (`ClaimAdjudicator`); if no domain term exists, the concept may be wrong |
    | One model to rule them all | A single `Customer` class for billing, shipping, and marketing becomes bloated and contradictory | Bounded contexts: each gets its own `Customer` with only the attributes it needs |
    | Giant aggregates | Concurrency conflicts, slow loads, transactional bottlenecks | Keep aggregates small; reference by ID; eventual consistency between them |
    | Anemic domain model | Objects are data bags; rules scatter across services and duplicate | Move behavior into entities and value objects; services orchestrate only |
    | No Anti-Corruption Layer | Foreign models leak in; code couples to external schemas | Wrap every external system behind a translation layer |
    | Bounded context = microservice | Premature extraction; distributed complexity without benefit | A context is a model boundary, not a deployment unit; start with modules in a monolith |
    | Skipping domain experts | Developers invent a model that doesn't match reality; expensive rework | Regular modeling sessions until experts say "yes, that is how it works" |
    
    ## Quick Diagnostic
    
    | Question | If No | Action |
    |----------|-------|--------|
    | Can a domain expert read your class names and understand them? | Technical jargon hides the model | Rename classes, methods, events to ubiquitous language |
    | Are bounded context boundaries explicitly defined? | Models bleed; same term means different things | Draw a context map; define boundaries and translations |
    | Are aggregates small (one root + minimal cluster)? | Slow loads, concurrency issues | Split aggregates; reference by ID; accept eventual consistency |
    | Do domain objects contain behavior, not just data? | Anemic model; logic scattered in services | Move business rules into entities and value objects |
    | Are domain events used for cross-aggregate communication? | Tight coupling, synchronous chains | Introduce events; let aggregates react asynchronously |
    | Is there an Anti-Corruption Layer at every external integration? | Foreign models pollute your domain | Add a translation layer at each boundary |
    | Have you identified which subdomain is core? | Best talent spread thin | Classify subdomains; focus deep modeling on the Core Domain |
    
    ## Further Reading
    
    For the complete methodology, patterns, and deeper insights:
    
    - [*"Domain-Driven Design: Tackling Complexity in the Heart of Software"*](https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215?tag=wondelai00-20) by Eric Evans
    
    ## About the Author
    
    **Eric Evans** is a software design consultant and the originator of Domain-Driven Design, developed through work on large-scale systems in finance, insurance, and logistics. His 2003 book *Domain-Driven Design: Tackling Complexity in the Heart of Software* is one of the most influential software architecture books ever written, and he continues to evolve DDD through his consultancy, Domain Language.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related