Designing the System

Identifying Domain Entities

ReadingPreview

আপনি একটি free preview lesson দেখছেন।

আগের lesson-এ আমরা requirements থেকে technical design-এর দিকে যাওয়া শুরু করেছি।

আমরা established করেছি যে system design শুরু করা উচিত business behaviour থেকে:

Business Behaviour
        ↓
Responsibilities
        ↓
Domain Concepts
        ↓
Technical Design

এখন আমাদের Order Management Backend-এর গুরুত্বপূর্ণ domain concepts identify করতে হবে।

Requirements-এ আমরা বারবার দেখেছি:

Customer
Product
Inventory
Order
Order Item
Payment

কিন্তু noun দেখেই একটি Java class তৈরি করা domain modeling নয়।

আমাদের বুঝতে হবে:

  • কোন concept-এর identity গুরুত্বপূর্ণ
  • কোন concept-এর lifecycle আছে
  • কোন concept অন্য concept-এর অংশ
  • কোন data current state এবং কোন data historical fact
  • কোন business rule কোথায় protect করা উচিত
  • কোন concept independent entity এবং কোনটি অন্য entity-এর ownership-এর মধ্যে থাকবে

এই lesson-এ আমরা implementation বা JPA mapping করব না।

Goal হলো business domain-এর একটি পরিষ্কার model তৈরি করা।


What Is a Domain?

Software engineering-এ domain হলো সেই problem space যার জন্য আমরা software তৈরি করছি।

আমাদের broad domain commerce হলেও আমরা পুরো commerce platform তৈরি করছি না।

Current problem space:

Product catalog
Inventory
Ordering
Order history
Cancellation
Payment

আমাদের current scope-এর বাইরে:

Shipping
Warehouses
Discounts
Coupons
Returns
Refunds
Recommendations
Supplier management
Accounting

এই boundary গুরুত্বপূর্ণ।

বাস্তব commerce platform-এ কোনো concept থাকতে পারে বলেই আমাদের current application-এ সেটি model করার প্রয়োজন নেই।

Domain model should represent the problem we are solving now.


What Is a Domain Model?

Domain model business concepts, relationships, state এবং behaviour represent করে।

For example, Order শুধু data নয়:

id
customerId
status
total

Order-এর behaviour-ও আছে:

Can it be paid?

Can it be cancelled?

Which customer owns it?

Which items belong to it?

Which state transitions are valid?

তাই useful domain model-এর মধ্যে থাকে:

Identity
+
State
+
Relationships
+
Business Rules
+
Behaviour

What Is an Entity?

একটি Entity এমন domain concept যার identity গুরুত্বপূর্ণ।

ধরা যাক:

Product #101
Name: Keyboard
Price: €50

এবং:

Product #102
Name: Keyboard
Price: €50

Properties একই হলেও:

Product #101 != Product #102

কারণ identity আলাদা।

Entity-এর properties সময়ের সঙ্গে পরিবর্তিত হতে পারে, কিন্তু identity একই থাকে।

For example:

Product #101

€50
  ↓
€55

এটি এখনও Product #101।


Entity Is Not the Same as JPA @Entity

Java/Spring development-এ @Entity persistence-এর একটি concept।

Domain modeling-এ Entity business identity এবং lifecycle-এর concept।

যখন আমরা বলি:

Order is a domain entity.

তার অর্থ এই নয় যে আমরা ইতোমধ্যে decide করেছি exact same Java class-এ:

@Entity

ব্যবহার করব।

Persistence model পরে design করা হবে।

Similarly:

Domain Entity
≠
Database Table

যদিও অনেক domain entity শেষ পর্যন্ত table-এ persist হবে।


Value-Like Concepts

সব concept-এর independent identity প্রয়োজন হয় না।

For example:

€20

আরেকটি:

€20

একই monetary context-এ value-wise equivalent।

আমাদের Money #123 এবং Money #456 identity প্রয়োজন নেই।

এ ধরনের concept value দিয়ে meaningful।

Examples:

Money
Quantity
Order Status

তবে প্রতিটি value-এর জন্য custom Java class বানাতেই হবে এমন নয়।

Domain concept এবং implementation abstraction আলাদা decision।


Start From Business Language

আমাদের requirements বলছে:

Customers place orders.

Orders contain products.

Products have inventory.

Orders preserve purchase-time prices.

Customers can cancel eligible orders.

Customers can pay for orders.

এগুলো আমাদের domain concepts identify করতে সাহায্য করে।

এখন প্রতিটি concept analyse করি।


Customer

Customer:

  • order create করে
  • নিজের orders দেখে
  • নিজের eligible order cancel করে
  • নিজের order pay করে

Customer identity গুরুত্বপূর্ণ কারণ:

Customer A

এবং:

Customer B

এক নয়।

Critical ownership rule:

Customer A cannot access Customer B's orders.

তাই ordering domain-এর জন্য একটি stable customer identity প্রয়োজন।


Do We Need a Full Customer Entity?

আমাদের engineering context অনুযায়ী authentication একটি existing identity capability provide করে।

আমাদের system build করছে না:

Registration
Password storage
Password reset
Email verification
MFA

তাই Order Management Backend-এর জন্য full identity/account model প্রয়োজন নেই।

Current ordering domain মূলত need করে:

CustomerId

যাতে বলা যায়:

This order belongs to Customer X.

External identity system own করবে user authentication।

Our backend own করবে order ownership।

Conceptually:

Identity System
      ↓
authenticated user identity
      ↓
Order Management Backend

এটি important boundary।


Product

Product clearly একটি domain entity।

Product-এর:

  • stable identity আছে
  • current catalog data আছে
  • current price আছে
  • active/inactive ordering state আছে
  • lifecycle আছে

Example:

Product #101
Price: €20
Status: ACTIVE

Later:

Product #101
Price: €25
Status: ACTIVE

Still same Product।

Later:

Product #101
Status: INACTIVE

Still same Product।

Therefore:

Product is a domain entity.


Product Responsibilities

Current requirements অনুযায়ী Product-এর responsibility roughly:

stable identity

current product information

current price

whether it is available for ordering
from the product-status perspective

এখানে একটি important distinction আছে।

Product active হলেই necessarily inventory available নয়।

For example:

Product status = ACTIVE
Inventory = 0

Product catalog-এর perspective থেকে active, কিন্তু currently order করা যাবে না।

তাই product status এবং inventory আলাদা facts।


Inventory

আমাদের দুইটি possible model হতে পারত।

Option A:

Product
├── name
├── price
├── status
└── quantity

Option B:

Product

Inventory
└── product
    quantity

আমাদের requirements অনুযায়ী Inventory separate concept হিসেবে model করা clearer।

কারণ Product এবং Inventory different reasons-এ change হয়।

Product:

name changes
price changes
product deactivated

Inventory:

stock added
order created
order cancelled
administrator adjustment

এগুলোর lifecycle এবং mutation pattern আলাদা।


Inventory Invariants

Inventory-এর critical rule:

quantity >= 0

Order creation-এর সময়:

requested quantity <= available quantity

Cancellation-এর সময় consumed inventory restore হবে।

এই behaviour Product metadata-এর responsibility নয়।

So:

Inventory is a separate stateful domain concept.

আমাদের current v1-এ multiple warehouse নেই।

Therefore inventory conceptually একটি product-এর current available quantity represent করে।


Is Inventory an Entity?

Inventory-এর state সময়ের সঙ্গে পরিবর্তিত হয়:

10
 ↓
7
 ↓
12
 ↓
9

এবং Product A-এর Inventory এবং Product B-এর Inventory আলাদা।

Current model-এ Inventory একটি product-associated stateful concept।

Implementation-এর সময় dedicated entity হিসেবে model করা sensible হতে পারে।

কিন্তু independent business identity Product বা Order-এর মতো prominent নয়।

Domain perspective থেকে সবচেয়ে important relation:

Inventory belongs to / is associated with a Product.

Product Availability vs Inventory Availability

আমাদের দুটি separate fact থাকবে:

Product is orderable

এবং:

Inventory has enough quantity

An order can proceed only when both relevant conditions satisfy হয়।

Avoid ambiguous model:

boolean available;

কারণ available বলতে unclear:

Product active?

Inventory > 0?

Enough inventory for requested quantity?

Explicit state clearer।


Order

Order আমাদের system-এর central domain entity।

Order-এর:

  • stable identity আছে
  • customer ownership আছে
  • lifecycle আছে
  • items আছে
  • total আছে
  • payment behaviour আছে
  • cancellation rules আছে

So:

Order is a domain entity.


Order Identity

After creation:

Order #5001

আমরা later একই order:

view
pay
cancel

করতে পারি।

State change:

UNPAID
   ↓
PAID

হলেও identity remains:

Order #5001

Order Is More Than Data

Weak model:

class Order {
    Long id;
    Long customerId;
    String status;
    BigDecimal total;
}

এখানে arbitrary mutation easy:

order.setStatus(CANCELLED);

কিন্তু requirement বলছে:

Paid orders cannot be cancelled in v1.

Cancelled orders cannot be paid.

Meaningful behaviour better expresses intent:

order.cancel();
order.markPaid();

Exact code later implement হবে।

Important principle:

Entity should protect meaningful business state where appropriate.


Order Lifecycle

Current v1-এর জন্য আমরা শুধু required lifecycle model করব:

UNPAID
PAID
CANCELLED

Transitions:

UNPAID
  ├── successful payment ──▶ PAID
  └── cancellation ─────────▶ CANCELLED

Invalid:

PAID ──X──▶ CANCELLED
CANCELLED ──X──▶ PAID

আমরা add করছি না:

SHIPPED
DELIVERED
REFUNDED
RETURNED
PROCESSING
PACKED

কারণ current requirements-এ এগুলো নেই।


Why UNPAID Instead of a Vague PENDING

PENDING ambiguous হতে পারে:

Pending payment?

Pending approval?

Pending fulfilment?

Our current state specifically means order exists but payment has not succeeded।

So domain language should express actual meaning clearly।

Exact enum naming implementation-এর সময় final হবে, কিন্তু vague terminology avoid করব।


Order Item

Order multiple products contain করতে পারে।

কিন্তু শুধু Product references enough নয়।

প্রতিটি ordered item-এর জন্য প্রয়োজন:

Product reference

Quantity

Purchase-time unit price

এই concept:

Order Item

Why Purchase-Time Price Belongs to Order Item

Suppose:

Product P1
Current price = €20

Customer creates:

Order O1

P1 × 2 @ €20

Later Product price becomes:

€25

Historical O1 should remain:

P1 × 2 @ €20

New order O2:

P1 × 2 @ €25

Therefore:

Product.price
=
current catalog price

while:

OrderItem.unitPrice
=
price at order creation

These are different facts।

Storing historical price is not meaningless duplication।

It preserves historical business meaning।


Should We Snapshot Product Name Too?

Current requirement specifically establishes historical pricing।

It does not say historical product name must remain unchanged।

Therefore we will not invent a requirement to snapshot every Product field into Order Item।

Current required historical fact:

purchase-time unit price

If future requirements need historical product name or description snapshots, design can evolve।


Does Order Item Need Independent Identity?

Current behaviour does not allow:

independently fetch an Order Item

independently cancel an Order Item

independently pay an Order Item

independently modify an Order Item

Order Item exists as part of Order।

Therefore its lifecycle is tied to Order।

Conceptually:

Order
  ├── Order Item
  ├── Order Item
  └── Order Item

Order Item may have a database row ID later, but that does not make its identity important to the business domain।


Order Owns Its Items

This ownership is important for consistency।

Suppose order total is:

€40

If external code can arbitrarily change:

quantity 2 → 3

without Order knowing, total can become inconsistent।

Therefore:

Order should control the consistency of its items and order-level state.

This makes Order a strong candidate for an Aggregate Root


Aggregate Thinking

We do not need a deep DDD course here, but Aggregate is useful।

An aggregate defines a consistency boundary around related domain objects।

For us:

Order
└── Order Items

is a natural aggregate।

Order is the root।

External behaviour should generally work through Order rather than independently mutating Order Items।


Product Is Not Part of the Order Aggregate

Order Item references Product, কিন্তু Order does not own Product।

Product:

  • exists before the order
  • appears in many orders
  • changes independently

Therefore:

OrderItem
    ↓ references
Product

not:

Order
    ↓ owns
Product

Inventory Is Not Part of the Order Aggregate

Inventory also exists independently।

Many orders and admin actions may modify it।

Therefore Order should not own Inventory।

Order creation workflow coordinates:

Product
+
Inventory
+
Order

but ownership remains separate।


Why This Matters

If we placed Inventory inside Order:

Order
├── Items
└── Inventory

we would incorrectly imply one Order controls inventory lifecycle।

It doesn't।

Similarly, Product should not be copied into Order as a mutable owned entity।

Clear ownership prevents confused models।


Order Total

Order total has no independent identity।

It is based on:

Σ(orderItem.unitPrice × orderItem.quantity)

So total is value-like।

Important invariant:

Order total must remain consistent
with the recorded order items.

Whether we:

calculate total whenever needed

or:

persist the calculated total

is a later persistence decision।

Domain meaning remains the same।


Quantity

Quantity is value-like।

Current rule:

quantity >= 1

And during ordering:

requested quantity <= available inventory

Could implementation use:

int quantity;

Yes।

Could we introduce a Quantity value object?

Possibly।

But we will not introduce one solely to make the model look sophisticated।

Abstraction should earn its complexity।


Price and Money

Money needs careful technical handling।

We should not use binary floating-point values such as:

double

for exact monetary calculations।

In Java, BigDecimal is commonly suitable for decimal monetary values।

But should we create a rich:

Money(amount, currency)

value object?

Current scope explicitly excludes multiple currencies।

Therefore we should not build a generic currency framework without a requirement।

We will choose the simplest representation that preserves monetary correctness for current scope।


Payment

Payment is an important concept, but its exact domain/persistence model is not fully known yet।

Requirements tell us:

Customer can pay an eligible order.

Successful payment makes the order paid.

Failed payment must not make the order paid.

The same successful payment must not be applied twice.

Actual payment execution belongs to the external Payment Provider।

Our backend cares about:

Which order is being paid?

Did the provider confirm success?

What provider reference identifies the payment?

Was this successful result already applied?

Do We Need a Payment Entity?

Maybe.

But we should not create one prematurely।

Possible future model might require:

Payment

or:

PaymentAttempt

if we need to represent multiple attempts such as:

Attempt 1 → timeout
Attempt 2 → success

But exact provider behaviour has not yet been designed।

Therefore our current decision:

Payment is a separate domain/integration concern, but its exact persistent entity model is deferred until payment workflow design.

That is better than inventing a complicated payment system now।


Why paid = true May Be Too Weak

The simplest model could be:

boolean paid;

But external integration requirements may require more context:

provider payment reference

duplicate-success protection

previous attempt state

A boolean may eventually be insufficient।

However, we will solve this when payment design gives us enough information।

Not before।


Order and Payment Are Different Concepts

Order represents the commercial order।

Payment represents payment processing/outcome related to that order।

Successful payment can trigger:

Order
UNPAID → PAID

But Payment and Order should remain conceptually distinct।

This keeps external-integration concerns from leaking into Order domain logic।


Customer, Product, Inventory, Order, Payment: Ownership

Current conceptual ownership:

External Identity Capability
    owns authentication identity
Product capability
    owns current Product state
Inventory capability
    owns available quantity
Order capability
    owns Order lifecycle and Order Items
Payment integration/workflow
    owns interaction with the external payment provider
    and the payment state needed by our application

Clear ownership prevents responsibility leakage।


Current Domain Relationships

Conceptually:

Customer Identity
       │
       │ owns
       ▼
     Order
       │
       │ contains
       ▼
   Order Items
       │
       │ reference
       ▼
    Product
       │
       │ has associated
       ▼
   Inventory

Payment interaction:

Order
  │
  │ payment workflow
  ▼
Payment Boundary
  │
  ▼
External Payment Provider

This is a domain-level view।

It is not a database schema or Java package diagram।


Entity Relationships Are Not Automatically JPA Relationships

Conceptually:

Order contains Order Items.

This does not mean we immediately write:

@OneToMany

Likewise:

OrderItem references Product.

does not automatically answer whether persistence should use:

@ManyToOne

or simply store a product identifier.

Those decisions depend on:

loading behaviour

coupling

query patterns

historical requirements

JPA behaviour

We will decide them during persistence design।


Avoid JPA-Driven Domain Design

Bad sequence:

Which `@OneToMany` do I need?
        ↓
Build domain model

Better:

What business relationship exists?
        ↓
Who owns the lifecycle?
        ↓
What consistency is required?
        ↓
How should it be persisted?
        ↓
Choose JPA mapping

Persistence should support the domain model, not define it prematurely।


Current Domain Invariants

Let's collect the important rules.

Product

Product has stable identity.

Product has a current price.

Product has an ordering state.

Inactive products cannot be used for new orders.

Inventory

Inventory belongs to a product context.

Inventory quantity cannot be negative.

Successful order creation cannot consume
more inventory than is available.

Order

Order has stable identity.

Order belongs to one customer.

Order contains at least one item.

Order has a valid lifecycle state.

Paid orders cannot be cancelled in v1.

Cancelled orders cannot become paid.

Order Item

Order Item references a product.

Quantity is positive.

Purchase-time unit price is preserved.

At order construction/request level:

The same product may appear at most once
in one order request.

Order Total

Order total must remain consistent
with item prices and quantities.

Customer Ownership

A customer can perform customer-scoped
operations only on their own orders.

Payment

Only an eligible unpaid order can be paid.

Failed payment does not make the order paid.

A successful payment result must not
be applied twice.

Exact implementation remains later work।


Some Invariants Cross Boundaries

Not every rule can belong entirely to one Entity।

Example:

requested order quantity
<=
available inventory

This needs both requested order information and Inventory state।

Another:

Order cancellation
→ restore inventory

touches Order and Inventory।

Therefore we need application-level workflows that coordinate independent domain concepts।


Domain Behaviour vs Workflow Coordination

A useful distinction:

Domain Entity

Knows rules about its own state।

For example:

Order.cancel()

can protect:

PAID cannot become CANCELLED.

CANCELLED cannot be cancelled again.

Inventory behaviour can protect:

quantity cannot drop below zero.

Application Workflow

Coordinates multiple concepts and dependencies।

For example cancellation:

Load order
    ↓
Verify ownership
    ↓
Ask Order to cancel
    ↓
Restore inventory
    ↓
Persist changes

The Order itself should not know:

Repository

PostgreSQL

HTTP request

Spring Security

Payment provider

Those belong outside the domain entity।


Avoid Repository Calls Inside Entities

Bad direction:

class Order {

    private OrderRepository orderRepository;
    private InventoryRepository inventoryRepository;

    void cancel() {
        // ...
        orderRepository.save(this);
        inventoryRepository.restore(...);
    }
}

Now Order depends on infrastructure and orchestration।

Better conceptually:

Order
    ↓
protects order rules

while:

CancelOrder workflow
    ↓
coordinates Order + Inventory + persistence

This gives clearer responsibilities and easier testing।


Order Creation Workflow

Our domain model suggests an application workflow roughly like:

Identify customer
      ↓
Load products
      ↓
Verify product orderability
      ↓
Load/check inventory
      ↓
Determine current prices
      ↓
Construct Order + Order Items
      ↓
Decrease inventory
      ↓
Persist resulting state

Entities protect their local rules।

Workflow coordinates them।

Transaction design later ensures multi-state mutation is atomic।


Cancellation Workflow

Similarly:

Load Order
      ↓
Verify customer ownership
      ↓
Validate cancellation through Order
      ↓
Restore Inventory
      ↓
Persist changes atomically

The important design lesson:

Order owns order-state rules. The workflow owns cross-concept coordination.


Payment Workflow

Conceptually:

Load Order
      ↓
Verify ownership
      ↓
Verify payment eligibility
      ↓
Call Payment Provider
      ↓
Process confirmed result
      ↓
Transition Order to PAID when appropriate

But the provider is external।

Therefore local database transaction alone cannot solve every payment failure scenario।

Payment integration will receive dedicated design later।


Domain Model Should Make Invalid State Harder to Represent

A useful design goal:

Make invalid state difficult to create.

Instead of:

new Order(emptyItems);

and hoping another layer checks it, Order construction can reject empty items।

Instead of:

inventory.setQuantity(-5);

Inventory behaviour can prevent invalid quantity।

Instead of arbitrary:

order.setStatus(...);

we expose meaningful state transitions।

This is encapsulation applied to real business behaviour।


API Validation Still Matters

Domain validation does not replace boundary validation।

Suppose client sends:

{
  "items": null
}

API layer can reject malformed data early।

Then domain still protects its own invariants।

Conceptually:

HTTP/API Validation
        ↓
Structurally valid request
        ↓
Domain/Application Rules
        ↓
Valid business state

Different boundaries protect different concerns।


Avoid Setter-Driven Domain Models

A common weak model:

order.setStatus(...);
product.setActive(...);
inventory.setQuantity(...);

allows callers to manipulate state without expressing business intent।

More meaningful operations:

order.cancel()

order.markPaid()

product.deactivate()

inventory.decrease(quantity)

inventory.increase(quantity)

communicate why state changes।

Exact API may differ later, but this is the direction we want।


Avoid Unnecessary Inheritance

Our entities all have IDs.

That does not mean:

Product extends BaseCommerceEntity

Order extends BaseCommerceEntity

Inventory extends BaseCommerceEntity

expresses useful domain meaning।

Shared technical fields are not necessarily an is-a relationship।

We should prefer explicit domain concepts and composition।


Order and Order Items Are Composition

This relationship naturally fits composition:

Order
contains
Order Items

Order Items participate in Order's total and historical record।

They do not need an independent service/API lifecycle।

We should not create:

OrderItemController

OrderItemService

just because a database table may exist later।


Orders Are Not Shopping Carts

Our requirement says:

Customer places an order.

It does not include:

Cart
Add item later
Remove item later
Change quantity after order creation

Therefore Order Items should not be modeled as freely editable cart lines।

If a future requirement introduces Cart, that will be a separate domain concept।

Current system starts at order placement।


Product Deactivation vs Deletion

Our agreed product behaviour uses deactivation rather than physical deletion।

Why?

Because historical orders may reference products।

Desired behaviour:

Product ACTIVE
    ↓
Product INACTIVE

Future orders:
cannot use product

Historical orders:
remain intact

We do not expose product deletion because current requirements do not need it।


Historical Meaning Belongs to Order

Once an Order is created, it represents what happened at that time।

Therefore later Product changes must not rewrite the historical transaction।

Current requirement explicitly protects purchase-time price।

This implies a broader mindset:

Order is a historical business record, not a live view of current catalog state.

We still only snapshot the data required by current requirements।


Inventory Consumption Terminology

Earlier discussion sometimes used the term "reserve inventory."

For v1, we should be precise.

Our agreed behaviour is:

Order creation
→ decreases available inventory
Eligible unpaid cancellation
→ restores inventory

We are not introducing a separate:

InventoryReservation

entity or reservation subsystem।

Because that would imply additional behaviour such as:

reservation expiry
timeout
background cleanup

which is not in current scope।


Payment Failure Does Not Automatically Cancel an Order

Current rule:

Failed payment
→ order does not become PAID

We did not establish:

Failed payment
→ automatically CANCELLED

Therefore we will not invent that behaviour।

If business later wants automatic cancellation, it becomes a new requirement।


Current Domain Decisions

We can now summarize the model.

Product

Domain entity

Owns:
- current product data
- current price
- product ordering state

Inventory

Separate stateful domain concept

Owns:
- current available quantity
- quantity invariant

Customer Identity

Externally owned identity

Used by our application for:
- order ownership
- customer authorization

We do not build identity/account management।


Order

Domain entity
Strong aggregate-root candidate

Owns:
- order lifecycle
- customer ownership reference
- order items
- historical order meaning
- order-level invariants

Order Item

Owned by Order

Represents:
- product reference
- quantity
- purchase-time unit price

No independent lifecycle in v1।


Payment

Separate domain/integration concern

Known responsibility:
- interact with external provider
- provide outcome needed by order payment workflow

Exact persistent entity shape:
deferred until payment design

Domain Model Diagram

Our current conceptual model:

┌────────────────────┐
│ Customer Identity  │
└─────────┬──────────┘
          │ owns
          ▼
┌────────────────────┐
│ Order              │
│                    │
│ lifecycle          │
│ historical record  │
└─────────┬──────────┘
          │ contains
          ▼
┌────────────────────┐
│ Order Item         │
│                    │
│ product reference  │
│ quantity           │
│ purchase-time price│
└─────────┬──────────┘
          │ references
          ▼
┌────────────────────┐
│ Product            │
│                    │
│ current price      │
│ ordering state     │
└─────────┬──────────┘
          │ associated with
          ▼
┌────────────────────┐
│ Inventory          │
│                    │
│ available quantity │
└────────────────────┘

Payment remains an external-facing workflow:

Order
  │
  │ payment workflow
  ▼
Payment Boundary
  │
  ▼
External Payment Provider

Review Against Requirements

Let's verify the model against our actual requirements.

Browse Products

Need:

Product
Inventory

Covered.


Create Order

Need:

Customer Identity
Product
Inventory
Order
Order Item

Covered.


Historical Pricing

Need:

Order Item purchase-time price

Covered.


Order History

Need:

Customer ownership
Order
Order Items
historical price
order creation time/order sequence

Covered conceptually.


Cancellation

Need:

Order lifecycle
Customer ownership
Inventory restoration

Covered.


Payment

Need:

Order lifecycle
Customer ownership
Payment integration boundary

Covered at the level currently justified.


Product Administration

Need:

Product lifecycle

Covered.


Inventory Administration

Need:

Inventory

Covered.


What We Deliberately Did Not Model

We did not add:

Cart

Shipment

Warehouse

Discount

Coupon

Refund

Return

Supplier

Category

Recommendation

Invoice

Inventory Reservation

Event Bus

because none of these are required by current v1।

This is good domain modeling।

The best model is not the one with the largest number of concepts।

It is the smallest model that accurately represents the current business problem।


Open Design Questions

Some questions intentionally remain for later lessons:

How exactly will Customer identity be represented locally?

How will Product and Inventory be persisted?

Should Order total be stored or derived?

What exact Java representation should Money use?

What exact Payment records are required?

How will repositories expose aggregates?

How will Order and Inventory participate
in a database transaction?

How will concurrent inventory updates be protected?

These are not missing because we forgot them।

They belong to later architecture, persistence, transaction, and payment design।


Engineering Principle

The core principle from this lesson:

A domain entity exists because its identity, lifecycle, and behaviour matter to the business—not because we need another Java class or database table.

A second principle:

Ownership must be explicit. Order owns its Order Items, but it does not own Product, Inventory, Customer identity, or the external Payment Provider.

And finally:

Model only the concepts required by the current problem.


Summary

In this lesson, we established that:

  • Domain model represents business identity, state, relationships, rules, and behaviour.
  • Domain Entity and JPA @Entity are different concepts.
  • Product is a domain entity with current price and ordering state.
  • Inventory is a separate stateful concept because its lifecycle and rules differ from Product.
  • Customer identity is required for ownership, but authentication remains outside our system.
  • Order is the central domain entity and a strong aggregate-root candidate.
  • Order owns Order Items.
  • Order Item stores product reference, quantity, and purchase-time price.
  • Current Product price and historical Order Item price represent different facts.
  • Order lifecycle for v1 remains intentionally small: unpaid, paid, cancelled.
  • We do not model future fulfilment/refund states without requirements.
  • Product and Inventory are not owned by Order.
  • Cross-concept operations such as order creation and cancellation require application workflow coordination.
  • Domain entities should not directly depend on repositories, HTTP, PostgreSQL, or payment-provider details.
  • Payment remains a separate domain/integration concern; its exact entity shape is deferred until we know the integration requirements.
  • We do not introduce Cart, Warehouse, Refund, Reservation, Kafka, or other unrelated concepts.
  • The goal is not to create the most elaborate domain model; it is to represent current business behaviour accurately.

Next lesson:

Defining System Responsibilities

There we will decide which responsibilities belong to Product, Inventory, Order, Payment, Customer Context, application workflows, persistence, and HTTP boundaries, so business logic does not collapse into controllers or giant services.