domain-entity
Domain Entity
Goal
Define a domain entity as a domain object with a unique identity that persists over time, even as its attributes change.
A domain entity encapsulates business behavior, business rules, and invariants related to its own data. It acts as a core building block in the domain or business layer for concepts such as a customer, order, subscription, account, or shipment.
Treat a domain entity as identity-driven code. The key question is whether the object represents a specific domain concept that remains the same thing throughout its lifecycle while its state evolves.
What Counts as a Domain Entity
Classify code as a domain entity when it does one or more of these things:
- represents a specific domain concept through a stable identity
- carries an identifier that distinguishes one instance from another across time
- preserves continuity as state changes throughout a lifecycle
- exposes behaviors that apply domain rules to its own data
- enforces invariants that must remain true for that specific domain concept
- controls valid state transitions for its own lifecycle
- encapsulates internal state and requires callers to use intention-revealing methods
- protects its data from arbitrary mutation so validity is maintained
Domain entities often appear in code that answers questions such as:
- which specific customer, order, account, or shipment is this
- what makes this object the same domain thing after its attributes change
- which operations may change its state
- which conditions must remain true throughout its lifecycle
- how callers must interact with it to keep it valid
Detection Workflow
-
Read the code for identity first.
- Look for identifiers, natural keys, references, or equality rules that distinguish one instance from another.
- Pay attention to code that tracks the same domain concept over time rather than only its current attributes.
-
Identify lifecycle continuity.
- Determine whether the object remains the same domain concept as its properties change.
- Identify the states, transitions, and milestones that define its lifecycle.
-
Trace behaviors and invariants.
- Identify methods that change the entity's state, enforce rules, or reject invalid transitions.
- Identify the conditions that must always hold true for the entity to remain valid.
-
Check the encapsulation boundary.
- Identify whether callers are expected to change state through explicit methods instead of direct arbitrary mutation.
- Prefer designs where domain operations are expressed as named behaviors with domain meaning.
-
Prefer semantic classification to file or framework conventions.
- Do not assume code is or is not a domain entity only because of its folder, class name, annotation, ORM mapping, or framework role.
- Classify by whether the object models a stable domain identity with owned behavior and invariants.
Writing or Changing Domain Entities
-
Preserve the identity model before refactoring.
- Restate what makes the entity the same domain concept over time.
- Keep identifiers and identity semantics explicit.
-
Use a class to co-locate identity, state, invariants, and behavior.
- Use a class, or the closest class-like construct available in the language, so that identity, state, invariants, and domain behavior live together in a single construct.
- A plain type alias, interface, or record paired with standalone functions is not a class-like construct. Do not model a domain entity that way.
- Follow the project's existing conventions for how classes are modeled.
- Only fall back to a non-class construct when the language has no class support at all, not merely because a functional style is common or preferred by convention.
-
Keep behavior close to the entity.
- Put state-changing domain operations on the entity when they belong to that entity's own rules.
- Use method names that express business intent, such as
changeAddress,approve,cancel, orassignOwner.
-
Protect invariants through explicit operations.
- Verify that every allowed state change still enforces the required rules.
- Avoid exposing write paths that let callers bypass validity checks.
-
Preserve lifecycle integrity.
- Verify that transitions between states remain valid and understandable.
- Verify that the entity keeps the same identity across creation, loading, updates, and later lifecycle stages.
-
Hide internal structure where validity depends on it.
- Expose the minimum data and methods needed for callers to interact correctly.
- Prefer intention-revealing methods over unrestricted setters when rules must be enforced.
Review Questions
When reading or reviewing code, ask:
- What makes this object the same domain thing over time?
- Which identifier distinguishes it from other instances?
- Which behaviors belong to this object as part of its own domain responsibility?
- Which invariants must remain true for it to stay valid?
- Would changing this code alter the identity, lifecycle, or rule enforcement of a specific domain concept?
If the answer is yes, treat the code as a domain entity.
Report the Outcome
When finishing the task:
- state which code was identified or treated as a domain entity
- state whether a class or equivalent construct was used, and why
- state which identities, behaviors, lifecycle transitions, or invariants were implemented or preserved
- state which methods or rules protect the entity's validity
More from code-sherpas/agent-skills
neverthrow-return-types
Require `neverthrow`-based return types in TypeScript and JavaScript code whenever the surrounding technology allows it. Use when creating, refactoring, reviewing, or extending standalone functions, exported module functions, class methods, object methods, service methods, repository methods, and similar APIs that should expose explicit success and failure result types in their signatures. Prefer `Result<T, E>` for synchronous code and `ResultAsync<T, E>` for asynchronous code. Only skip a `neverthrow` return type when a framework, library, runtime interface, or externally imposed contract is incompatible and requires a different return shape.
16neverthrow-wrap-exceptions
Capture exceptions and promise failures with `neverthrow` instead of hand-written `try/catch` in TypeScript and JavaScript code. Use when wrapping synchronous functions that may throw, promise-returning functions that may throw before returning, existing `PromiseLike` values that may reject, or third-party APIs such as parsers, database clients, HTTP clients, file-system helpers, serializers, and SDK calls. Prefer `Result.fromThrowable` for synchronous throwers, `ResultAsync.fromThrowable` for promise-returning functions that may throw or reject, and `ResultAsync.fromPromise` when you already have a `PromiseLike` value in hand. Only keep `try/catch` when the language construct, cleanup requirement, or framework boundary truly requires it.
11atomic-design
Create or update web UI components with a strict reuse-first workflow. Use when building, refactoring, restyling, or extending frontend or template components while minimizing raw DOM or HTML by reusing or generalizing existing components first.
10write-persistence-representations
Create or update persistence-layer data representations in any stack, including ORM entities, schema definitions, table mappings, document models, collection definitions, and similar database-facing code. Use when agents needs to add or change persisted fields, identifiers, relationships, indexes, timestamps, auditing fields, or storage mappings in frameworks, libraries, or ORMs such as Prisma, TypeORM, Sequelize, Drizzle, Mongoose, Hibernate/JPA, Doctrine, Ecto, Active Record, or equivalent persistence technologies.
7business-logic
Identify, interpret, review, or write business logic in code. Use when an agent needs to decide whether code expresses business rules, business algorithms, or business workflows, or when it must implement, preserve, or refactor code that creates, stores, or transforms data according to real business policies.
7immutable-domain-entities
Require the immutable design pattern for domain entities. Use when an agent needs to create, modify, review, or interpret domain entities and should preserve identity while expressing state changes through new immutable instances. Domain entities must be modeled as immutable classes, not as plain type aliases or interfaces paired with standalone functions.
7