So far in this series, we’ve learned:
- Entities
- Value Objects
- Aggregates
- Invariants
- State transitions
- Service vs Entity separation
- Bounded Contexts
Now it’s time to apply everything together in a real Low-Level Design problem.
Let’s design a simplified Ride Sharing system like Uber/Ola.
Not from a “class diagram” perspective.
But from a domain modeling perspective.
Step 1 — Start With Business Flows, Not Classes
A common beginner mistake is:
Ride.java
Driver.java
User.java
Payment.java
before understanding system behavior.
Strong LLD starts differently.
First identify:
- workflows
- lifecycle
- business rules
- consistency needs
Core Ride Flow
Our simplified flow:
User requests ride
→ Driver gets assigned
→ Ride starts
→ Ride completes
→ Payment happens
Now we can begin modeling.
Step 2 — Identify Entities
Ask:
“Which business objects have identity and lifecycle?”
Ride
Ride has:
- rideId
- lifecycle
- status transitions
- business tracking
So Ride is clearly an Entity.
Driver
Driver has:
- driverId
- availability state
- active ride tracking
Also an Entity.
User
User identity matters across rides and payments.
Also an Entity.
Step 3 — Identify Value Objects
Now ask:
“Which objects are defined only by value?”
Location
Location(28.61, 77.20)
We care about:
- coordinates not
- identity
So Location becomes a Value Object.
Fare
Fare(₹250)
Again:
- no independent lifecycle
- no business identity
Fare is also a Value Object.
Step 4 — Identify Invariants
Now we discover business rules.
Examples:
- Ride cannot complete before starting
- Driver cannot accept multiple active rides
- Fare cannot become negative
- Payment should happen only once
These are invariants.
The system must always protect them.
Step 5 — Identify Aggregate Boundaries
Now ask:
“Which objects must stay consistent together?”
This is where many beginner designs become weak.
Ride Aggregate
Ride Aggregate
├── Ride (Root)
├── Fare
├── PickupLocation
├── DropLocation
└── DriverReference
Ride becomes the Aggregate Root because:
- it controls lifecycle
- it validates transitions
- it protects consistency
External systems should modify ride state only through Ride.
Step 6 — Protect State Transitions
Weak systems expose state directly:
ride.status = COMPLETED;
Dangerous.
Now invalid transitions become easy.
Instead:
class Ride {
void completeRide() {
if(status != IN_PROGRESS) {
throw new InvalidRideStateException();
}
status = COMPLETED;
}
}
Now the Entity protects lifecycle correctness itself.
Step 7 — Separate Entity Logic From Workflow Logic
Another common beginner mistake:
putting all logic inside services.
Example:
class RideService {
void completeRide(Ride ride) {
if(ride.status != IN_PROGRESS) {
throw error;
}
ride.status = COMPLETED;
}
}
Over time:
- services become massive
- logic gets duplicated
- rules scatter everywhere
Better Separation
Ride Entity
Responsible for:
- protecting state
- validating transitions
- enforcing invariants
RideService
Responsible for:
- orchestration
- payment coordination
- notifications
- driver updates
Example:
class RideService {
void completeRide(Ride ride) {
ride.completeRide();
paymentService.process(ride);
notificationService.notify(ride);
driverService.markAvailable(ride.getDriver());
}
}
This creates cleaner boundaries.
Step 8 — Think About Future Scaling
As systems grow:
- pricing becomes complex
- matching evolves separately
- payments become independent
- driver systems scale differently
This is where bounded thinking becomes useful.
Example contexts:
Ride Matching Context
Payments Context
Driver Management Context
Each context evolves independently.
Final Domain Model
Entities
- Ride
- Driver
- User
Value Objects
- Location
- Fare
Aggregate
- Ride Aggregate
Invariants
- valid ride lifecycle
- driver consistency
Services
- RideService
- PaymentService
- NotificationService
Now the design is not just “object-oriented”.
It is:
- behavior-oriented
- consistency-aware
- lifecycle-aware
- business-driven
The Most Important Insight
Most beginners think system design means:
“creating classes and APIs.”
But real LLD is actually about:
- modeling business behavior
- protecting consistency
- controlling state evolution
- assigning responsibilities correctly
That is why strong domain models feel stable even as systems grow.
Because good design is not created by connecting objects randomly.
It is created by carefully modeling how the business actually behaves.
Top comments (0)