Most domain models fail not because they’re technically wrong, but because they capture the wrong things. Teams model classes, services or tables and call them “domain concepts”. They’re not. A domain concept is what your domain expert names when they explain what the business actually does. If the expert doesn’t recognize it, it doesn’t belong in the model.
Take a ride-hailing platform. A first attempt at modeling might produce: TripOrchestrationService, DriverLocationRepository, PaymentGatewayAdapter. Three classes, but zero concepts.
A domain expert at Uber would talk about a Ride, a Driver, a Rider. Words that everyone in the company uses, from ops to product to engineering. Everything else is just noise.
The difference between a working domain model and a rotting one comes down to eight questions.
Does it have a name people actually use?
The name is the concept’s handle. If domain experts don’t use it naturally in conversation, it’s the wrong name. Names allow teams to point to a shared idea without re-explaining it each time.
Ride works. TripOrchestrationService doesn’t. Driver works. TransportProviderEntity doesn’t. The test is simple: would a non-technical stakeholder understand what you mean? If you need to explain the name, change it. At Uber, everyone says “ride”, from the rider opening the app to the support agent handling a complaint.
Does it have a reason to exist?
A concept earns its place by serving a need, not by describing a mechanism. The purpose answers why, not how.
Ride exists to transport a rider from pickup to dropoff, not Car dispatch which describes a mechanism.
SurgeZone exists to balance supply and demand in real time, not apply a surge multiplier to the fare.
If you can’t state the purpose in one sentence without mentioning implementation, the concept is likely a technical artifact disguised as a domain concept.
Can you tell one instance from another?
Identity determines how you distinguish one instance from another over time, regardless of how its data changes.
A Driver identified by an immutable ID will remain the same driver after changing their phone number, vehicle, or city.
The rating fluctuates, the verification state evolves, the vehicle gets replaced. But the driver’s identity remains the same.
Not everything needs identity. A FareBreakdown (base fare, per-km charge, surge multiplier, tolls) carries no lifecycle.
If two FareBreakdown instances share identical amounts, they’re interchangeable.
This distinction between entity and value shapes how you store, compare, and reason about every instance.
Does it carry only what it needs?
State is the concept’s memory: the minimum data required to support its behavior.
A Driver needs to remember their verificationState, averageRating, totalRidesCompleted, and current vehicle.
No need to remember the history of routes taken. That data belongs to the geolocation module, not to the driver.
The Driver doesn’t need it to decide availability, validate a verification, or compute a rating.
If an attribute doesn’t participate in any of the concept’s business rules, it belongs to another context or doesn’t belong in the domain model at all.
Does it do something?
Without behavior, there is no concept. You have data with a name.
Behavior means actions that change state. A Ride lives through its lifecycle:
requestinitiates matching and computes a fare estimate,acceptlinks a driver and starts navigation,pickupbegins the trip,completetriggers fare calculation and payment capture,cancelapplies different rules depending on who cancels and when. Each action takes inputs, applies rules, and transitions the concept into a new state.
If you can’t enumerate the actions, reconsider whether you’re looking at a concept or a data structure.
Can you demonstrate it working?
A scenario proves the concept fulfills its purpose. It’s illustrated with a narrative, not a feature list. Focus on a sequence of actions that an expert can verify against their understanding of how the business works.
For Ride:
A rider requests a ride, a nearby driver accepts within 15 seconds, drives to pickup, starts the trip, completes it at dropoff, and the fare is captured automatically.
For Driver:
A driver submits identity documents, gets verified, goes online, receives ride offers, and if their rating drops below 4.2 after 20 completed rides, gets automatically suspended.
If the scenario doesn’t hold, neither will the concept.
What must always be true?
Invariants protect the concept’s integrity. They’re the rules that the system must guarantee, no matter what sequence of actions occurs:
- A driver can only receive ride offers when their verification state is
verifiedand their availability isonline. - A ride offer expires after exactly 15 seconds.
- A single driver cannot be assigned to two rides simultaneously.
- A surge multiplier cannot exceed 5.0x.
These aren’t edge cases to handle later. They’re the contract. Break the contract and the concept loses meaning.
Preconditions guard the entry to an action. Invariants guard the exit. Together, they fence the concept into a space where it always makes sense.
How does it compose with others?
Concepts are strongest when they stand alone. But in a real system, they interact.
When Ride.complete() fires, it triggers Payment.capture() to charge the rider and Driver.creditEarnings() to credit the driver after commission.
One concept’s action triggers others, like a chain reaction. But the coupling stays explicit: Ride doesn’t know how payment works internally.
It only knows that completion triggers a fare capture.
When a Driver’s cancellation count exceeds a certain threshold within 24 hours, it triggers a 30-minute suspension.
The Ride module signals the event, the Driver module enforces the consequence.
Composition ranges from loose (concepts running in parallel) to tightly coordinated (one concept’s lifecycle depending on another’s). The design choice depends on how tightly coupled the domain logic demands them to be.
The checklist
A concept that passes all eight tests is anchored in the domain:
- named
- purposeful
- identifiable
- stateful
- behavioral
- scenario-proven
- rule-bound
- composable
Shared understanding between domain experts and developers is the entire point of domain modeling. Code can drift. Documentation can go stale. But a well-captured concept resists both, because everyone involved can verify it against their own knowledge of the domain.
Most modeling failures aren’t technical. They’re failures of capture. The concept was there all along, in the expert’s vocabulary, in the business rules, in the edge cases that keep breaking production. The model just never asked the right questions.