DEV Community

Saras Growth Space
Saras Growth Space

Posted on

LLD Domain Modeling: Designing an Amazon Cart System (Mutable State, Checkout Flow & Order Consistency)

An Amazon-style cart system looks deceptively simple.

At first glance:

Add item
→ apply coupon
→ checkout
→ place order
Enter fullscreen mode Exit fullscreen mode

But internally, this system contains several important Low-Level Design challenges:

  • mutable business state
  • pricing consistency
  • inventory validation
  • checkout transitions
  • order immutability
  • workflow orchestration

And this is exactly why it’s a great domain modeling exercise.


Step 1 — Start With Business Behavior

Strong LLD does not begin with:

class Cart {}
class Order {}
class Product {}
Enter fullscreen mode Exit fullscreen mode

Instead, it starts with understanding:

  • lifecycle
  • ownership
  • state evolution
  • business constraints

So let’s first understand the workflow.


Simplified Cart Flow

User adds items
→ updates quantity
→ applies coupon
→ checks out
→ payment happens
→ order gets created
Enter fullscreen mode Exit fullscreen mode

Now ask carefully:

Which business objects own these responsibilities?

That question shapes the entire design.


Step 2 — Identify Core Entities

We first identify objects with:

  • identity
  • lifecycle
  • business tracking

Product

Tracks:

  • productId
  • stock
  • pricing
  • catalog information

Entity ✅


Cart

Very important Entity.

Because Cart:

  • evolves continuously
  • belongs to a user
  • controls item state
  • changes frequently

Entity ✅


Order

Order has:

  • orderId
  • lifecycle
  • payment relationship
  • shipment state

Entity ✅


Step 3 — Identify Value Objects

Now separate descriptive values.


Money

Money(1200)
Enter fullscreen mode Exit fullscreen mode

We care about:

  • amount
  • currency

not identity.

Value Object ✅


Quantity

Quantity(3)
Enter fullscreen mode Exit fullscreen mode

Again:

  • value matters
  • identity does not

Value Object ✅


Coupon

In many beginner/intermediate systems:

  • coupon behaves more like a rule/value descriptor

Example:

Coupon("SAVE10")
Enter fullscreen mode Exit fullscreen mode

Often modeled as a Value Object initially.


Step 4 — Understand the Most Important Design Problem

The biggest design challenge here is:

Cart is mutable, but Order must become stable.

This distinction is extremely important.


Cart Continuously Changes

Users can:

  • add items
  • remove items
  • update quantity
  • apply discounts

Cart remains a working state.

Example:

Cart
→ evolving
→ temporary
→ editable
Enter fullscreen mode Exit fullscreen mode

Order Must Become Immutable

Once checkout succeeds:

Order
→ frozen snapshot
→ historical truth
→ audit-safe
Enter fullscreen mode Exit fullscreen mode

After order creation:

  • totals should not randomly change
  • discounts should remain preserved
  • purchased item state must stay stable

This transition from mutable → immutable is a key modeling concept.


Step 5 — Identify Invariants

Now ask:

“What business rules must always remain valid?”

Examples:

  • quantity cannot become negative
  • cart total must equal item totals
  • checkout cannot happen with empty cart
  • stock must be available
  • final order total must remain frozen after checkout

These invariants define system correctness.


Step 6 — Define Aggregate Boundaries

Now identify:

which objects must stay consistent together?


Cart Aggregate

Cart Aggregate
 ├── Cart (Root)
 ├── CartItems
 ├── Pricing Rules
 └── Coupon
Enter fullscreen mode Exit fullscreen mode

Cart becomes the Aggregate Root because it controls:

  • item consistency
  • pricing consistency
  • quantity validation
  • checkout eligibility

Why This Boundary Matters

Weak systems allow random modifications:

cartItem.quantity = -5;
Enter fullscreen mode Exit fullscreen mode

or:

cart.total = 100;
Enter fullscreen mode Exit fullscreen mode

Now consistency breaks easily.

Instead:

cart.addItem(product, quantity);
cart.calculateTotal();
Enter fullscreen mode Exit fullscreen mode

The Aggregate Root protects the business rules.


Step 7 — Checkout Is a State Transition

Checkout is not merely:

  • an API call
  • a database insert

It is actually:

a domain state transition.

Example:

ACTIVE_CART
→ CHECKED_OUT_ORDER
Enter fullscreen mode Exit fullscreen mode

This transition changes:

  • ownership
  • mutability
  • lifecycle guarantees

Strong systems model this transition explicitly.


Step 8 — Separate Domain Logic From Workflow Logic

Now consider the complete checkout flow:

  • validate cart
  • reserve inventory
  • process payment
  • create order
  • send notification

This coordination belongs inside Services.


Checkout Service

class CheckoutService {

    void checkout(Cart cart) {

        cart.validateCheckout();

        paymentService.process();

        orderService.createOrder(cart);

        notificationService.send();
    }
}
Enter fullscreen mode Exit fullscreen mode

The Service coordinates workflow.

The Cart protects business correctness.

That separation keeps systems maintainable.


Step 9 — Think Beyond Happy Paths

Strong systems also handle:

  • payment failures
  • inventory shortage
  • duplicate requests
  • cart expiration
  • coupon invalidation

Without proper state modeling:

  • totals become inconsistent
  • inventory becomes incorrect
  • orders become unreliable

Good domain models prepare for these realities.


Final Domain Model

Entities
- Product
- Cart
- Order

Value Objects
- Money
- Quantity
- Coupon

Aggregate
- Cart Aggregate

Invariants
- valid pricing
- stock consistency
- immutable order state

Services
- CheckoutService
- PaymentService
- InventoryService
Enter fullscreen mode Exit fullscreen mode

The Most Important Insight

An Amazon Cart system is not fundamentally about:

  • products
  • UI
  • checkout buttons

At its core, it is actually:

a controlled state transition system.

The difficult part is ensuring:

  • mutable business state evolves safely
  • pricing remains consistent
  • final orders become trustworthy snapshots

And this is exactly what strong domain modeling helps achieve.

Because good Low-Level Design is not just about organizing code.

It is about designing systems that preserve business correctness even as complexity grows.

Top comments (0)