Loading learning content...
Throughout this module, we've explored entities as identifiable things, attributes as their descriptive properties, and behaviors as their capabilities. But there's a deeper skill that separates adequate models from exceptional ones: knowing what truly matters.
Every entity has properties that are essential—fundamental to its identity and purpose—and properties that are incidental—present in specific contexts but not definitional. The ability to distinguish between these shapes everything from how you design classes to how you evolve systems over time.
By the end of this page, you will understand the philosophical and practical distinction between essential and incidental properties, develop frameworks for making this distinction in real-world domains, and learn how to apply this thinking to create domain models that are robust, extensible, and true to the problem they represent.
This distinction has roots stretching back to Aristotle, who differentiated between a thing's essence (what makes it what it is) and its accidents (properties it happens to have but could be otherwise).
In software terms:
Essential properties are those without which the entity would not be that type of entity. They define the entity's identity and fundamental nature.
Incidental properties are those the entity happens to have in particular contexts but could differ without changing the entity's fundamental nature.
Let's make this concrete with examples:
| Entity | Essential Properties | Incidental Properties |
|---|---|---|
| User | Identity (ID), authentication credentials, account status | Profile picture, bio, preferred language, theme preference |
| Order | Identity, customer, items (at least one), total, status | Gift message, delivery instructions, marketing attribution |
| Book | Title, author(s), ISBN | Cover image URL, page count, publisher, reviews |
| Payment | Amount, currency, payer, payee, status | Memo, payment method details, IP address of request |
| Course | Title, syllabus, instructor(s) | Featured image, promotional video, enrollment count |
The test of essential properties:
Ask yourself: If this property were missing or different, would this still be the same type of thing?
Essential properties define the minimum viable entity—the core that must exist for the entity to be coherent.
What's essential depends on context. For a library catalog system, a book's ISBN and publisher are essential—they identify the canonical edition. For a personal reading list app, only title might be essential; users might add books before knowing publisher details. Model for YOUR domain, not some abstract ideal.
Getting this distinction wrong has cascading consequences throughout your system:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
// Classification directly shapes your API contract // Essential properties = required in creationinterface CreateOrderRequest { customerId: string; // Essential: whose order? items: OrderItemInput[]; // Essential: what are they buying? shippingAddressId: string; // Essential: where does it go? billingAddressId?: string; // Incidental: defaults to shipping if not specified giftMessage?: string; // Incidental: entirely optional couponCode?: string; // Incidental: optional discount} // The Order entity reflects thisclass Order { // Essential — provided at construction, never null private readonly id: OrderId; private readonly customerId: CustomerId; private readonly shippingAddress: Address; private readonly createdAt: Date; private items: OrderItem[]; // May change but never empty private status: OrderStatus; // Always has a state // Incidental — may be null, set separately, have defaults private billingAddress?: Address; // Defaults to shipping private giftMessage?: string; // Completely optional private couponId?: CouponId; // Applied or not private notes?: string; // Added by support later constructor(data: CreateOrderData) { // Essential properties: validated and required if (!data.customerId) throw new ValidationError("Customer required"); if (!data.items?.length) throw new ValidationError("At least one item required"); if (!data.shippingAddress) throw new ValidationError("Shipping address required"); this.id = OrderId.generate(); this.customerId = data.customerId; this.items = data.items; this.shippingAddress = data.shippingAddress; this.status = OrderStatus.Draft; this.createdAt = new Date(); // Incidental properties: use defaults or leave undefined this.billingAddress = data.billingAddress; // undefined is OK this.giftMessage = data.giftMessage; // undefined is OK }}Your constructor signature IS your statement of essential properties. If it's in the constructor with no default, it's essential. If it can be set afterward or has a default, it's incidental. Make this intentional, not accidental.
Deciding essential vs. incidental isn't always obvious. Here are decision frameworks to guide your classification:
Framework 1: The Existence Test
Can this entity exist without this property?
This is the most fundamental test: if the entity is incoherent or meaningless without the property, it's essential.
Framework 2: The Core Use Case Test
Is this property required for the entity to fulfill its primary purpose?
Essential properties are what the entity needs to DO ITS JOB.
Framework 3: The Universality Test
Does every instance of this entity have this property?
If something is only present sometimes, it's likely incidental.
Framework 4: The Stability Test
Is this property stable across the entity's lifecycle?
Essential properties tend to be stable or always-present; incidental properties come and go.
Real-world domains aren't black and white. Some properties are essential in certain contexts and incidental in others. Recognizing this nuance prevents both over-rigid and over-loose designs.
Example: User.email in Different Systems
The 'same' entity can have different essential properties based on the bounded context it lives in. This is a core insight of Domain-Driven Design: models are context-specific, not universal.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
// Same "User" concept, different bounded contexts // ==========================================// Authentication Context — email is essential// ==========================================class AuthUser { private readonly id: UserId; private readonly email: EmailAddress; // ESSENTIAL: login credential private passwordHash: PasswordHash; // ESSENTIAL: authentication private mfaEnabled: boolean; constructor(id: UserId, email: EmailAddress, passwordHash: PasswordHash) { // All essential properties required this.id = id; this.email = email; this.passwordHash = passwordHash; this.mfaEnabled = false; } authenticate(password: string): boolean { return this.passwordHash.matches(password); }} // ==========================================// Profile Context — email is incidental// ==========================================class UserProfile { private readonly userId: UserId; // ESSENTIAL: links to auth private displayName: string; // ESSENTIAL for display private avatarUrl?: string; // Incidental private bio?: string; // Incidental private contactEmail?: EmailAddress; // INCIDENTAL: optional contact info private socialLinks: SocialLink[]; // Incidental constructor(userId: UserId, displayName: string) { this.userId = userId; this.displayName = displayName; this.socialLinks = []; } updateContactEmail(email?: EmailAddress): void { this.contactEmail = email; // Can be set to null }} // ==========================================// E-Commerce Context — email for receipts// ==========================================class Customer { private readonly id: CustomerId; private readonly accountId?: UserId; // Incidental: guest checkout allowed private email: EmailAddress; // ESSENTIAL: receipts, tracking private shippingAddresses: Address[]; constructor(id: CustomerId, email: EmailAddress, accountId?: UserId) { this.id = id; this.email = email; this.accountId = accountId; // undefined for guest checkout this.shippingAddresses = []; }} // The lesson: what's essential depends on the bounded context.// Don't try to create one universal User class that handles all cases.Trying to create one model that works for all contexts forces you to make everything optional (to handle contexts where it's incidental) or required (breaking contexts where it's incidental). Instead, accept that different bounded contexts may have different entity definitions even for 'the same' concept.
Let's walk through a realistic design process that applies essential/incidental classification:
Scenario: Designing a Job Posting Entity
Requirements: "Companies post jobs. Jobs have titles, descriptions, locations, salary ranges, required skills, and application deadlines. Some jobs are remote, some require relocation. Jobs can be featured or urgent. Companies can track which jobs get the most views."
Step 1: Extract all potential properties
Title, description, location, salary range, required skills, application deadline, company, is remote, requires relocation, is featured, is urgent, view count, created date...
Step 2: Apply the frameworks
| Property | Existence? | Core Purpose? | Universal? | Classification |
|---|---|---|---|---|
| Title | Cannot post without title | Identifies job | All jobs have one | Essential |
| Company | Must know who's hiring | Core to job | All jobs have one | Essential |
| Description | Could have minimal jobs | Secondary (title suffices for basic) | Most have one | Essential (borderline) |
| Location | Remote jobs may not need | Filtering essential | Most have one | Essential (with 'Remote' option) |
| Salary range | Many jobs don't list | Filtering helpful, not core | Maybe 50% have it | Incidental |
| Required skills | Could be in description | Filtering helpful | Not all specify | Incidental |
| Deadline | Not all jobs expire | Time-sensitive use case | Many don't have | Incidental |
| Is Remote | Needed for remote jobs | Filtering essential | All have explicit or implied value | Essential (boolean flag) |
| Is Featured | Display enhancement | Marketing, not core | Few are featured | Incidental |
| View Count | Analytics | Not job function | Runtime computed | Incidental (or derived) |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
class JobPosting { // === ESSENTIAL PROPERTIES === private readonly id: JobPostingId; private readonly companyId: CompanyId; private title: string; private description: string; private readonly createdAt: Date; private status: JobStatus; private location: JobLocation; // Includes remote option // === INCIDENTAL PROPERTIES === private salaryRange?: SalaryRange; private requiredSkills: Skill[]; // Empty array if none specified private applicationDeadline?: Date; private isFeatured: boolean; // Default false private isUrgent: boolean; // Default false private externalApplyUrl?: string; private viewCount: number; // Analytics, starts at 0 constructor( id: JobPostingId, companyId: CompanyId, title: string, description: string, location: JobLocation ) { // Validate essential properties if (!title?.trim()) { throw new ValidationError("Job title is required"); } if (!description?.trim()) { throw new ValidationError("Job description is required"); } this.id = id; this.companyId = companyId; this.title = title.trim(); this.description = description.trim(); this.location = location; this.createdAt = new Date(); this.status = JobStatus.Draft; // Incidental properties get sensible defaults this.requiredSkills = []; this.isFeatured = false; this.isUrgent = false; this.viewCount = 0; } // Essential properties may have restricted updates updateTitle(title: string): void { if (!title?.trim()) { throw new ValidationError("Job title cannot be empty"); } this.ensureDraft("Cannot update title of published job"); this.title = title.trim(); } // Incidental properties have more flexible updates setSalaryRange(range: SalaryRange | undefined): void { this.salaryRange = range; // Can set or clear } addRequiredSkill(skill: Skill): void { if (!this.requiredSkills.some(s => s.equals(skill))) { this.requiredSkills.push(skill); } } setFeatured(featured: boolean): void { this.isFeatured = featured; } private ensureDraft(message: string): void { if (this.status !== JobStatus.Draft) { throw new InvalidOperationError(message); } }} // Location handles the remote/physical distinctionclass JobLocation { private constructor( private readonly type: "remote" | "onsite" | "hybrid", private readonly city?: string, private readonly country?: string ) {} static remote(): JobLocation { return new JobLocation("remote"); } static onsite(city: string, country: string): JobLocation { return new JobLocation("onsite", city, country); } static hybrid(city: string, country: string): JobLocation { return new JobLocation("hybrid", city, country); } get isRemote(): boolean { return this.type === "remote" || this.type === "hybrid"; }}Essential and incidental aren't fixed forever. As your domain evolves, properties may shift classification. Being prepared for this change is key to maintainable systems.
Pattern 1: Incidental Becomes Essential
Your e-commerce platform initially allowed guest checkout, making userId incidental on Order. But new requirements demand order history, loyalty points, and personalization—now userId is essential.
Strategy: Migrate existing orders to link to auto-created accounts or a 'guest' placeholder. Update constraints and validation. This is usually the easier direction.
Pattern 2: Essential Becomes Incidental
Your system required phone number for all users (essential). But users hate providing it, and SMS verification is no longer your primary flow—now phone is incidental.
Strategy: Relax constraints. Make field nullable in database. Update constructor to accept undefined. Harder than the reverse because existing code may assume presence.
Pattern 3: New Property Added
A new feature requires tracking which marketing campaign brought each order. Is campaignId essential or incidental?
Strategy: For new properties, start incidental unless clearly essential. It's easier to tighten later than to loosen. Default to optional and see if every instance naturally has a value.
Database migration difficulty is a reason to be conservative about marking things essential. Making a nullable column NOT NULL requires backfilling data. Making a NOT NULL column nullable is trivial. When uncertain, start incidental.
Essential and incidental classification doesn't just apply to attributes—it extends to behaviors as well. Some behaviors are fundamental to what the entity IS; others are situational or optional.
Why distinguish behavior essentiality?
Just like attributes, behaviors can shift between essential and incidental. Email notification was once essential for Order.confirm(); now it's a side effect handled by event subscribers. As systems mature, behaviors often migrate out of entities into services, making entities cleaner and more focused.
This module has given you the complete toolkit for modeling entities:
Together, these concepts enable you to create domain models that are:
What's Next:
With entities, attributes, and behaviors mastered, you're ready to explore how entities relate to each other. The next module covers Relationships Between Objects—association, dependency, aggregation, and composition—the connections that turn individual entities into a coherent domain model.
You now have a comprehensive understanding of entities, attributes, behaviors, and the essential/incidental distinction. You can model domain concepts as rich, cohesive entities that accurately represent business reality. Next, we'll explore how these entities connect through relationships.