Domain Modeling

Modeling Customer

ReadingPreview

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

Commerce system শুনলেই একটি natural assumption আসে:

We need a Customer entity.

তারপর developer হয়তো immediately design করে:

Customer
├── id
├── name
├── email
├── password
├── phone
├── address
├── verified
└── roles

কিন্তু আমাদের system design-এ আমরা intentionally এই direction নিইনি।

আমাদের Order Management Backend-এর responsibility:

manage Products

manage Inventory

create and manage Orders

process payments

enforce customer ownership

এটি একটি:

identity platform

নয়।

Authentication এবং user identity already একটি external mechanism থেকে আসে।

Therefore আমাদের প্রথম প্রশ্ন হওয়া উচিত নয়:

Customer class-এ কোন fields থাকবে?

বরং:

Order Management Backend-এর business workflows-এর জন্য Customer সম্পর্কে আসলে কী জানা প্রয়োজন?

Current answer:

a stable authenticated customer identifier

এই lesson-এর goal:

Customer identity model করা without inventing a local Customer aggregate or duplicating an external identity system.


Start From the Requirement

Our customer-related requirements are relatively small।

A customer can:

create an Order

view their own Orders

cancel their own eligible Orders

pay their own eligible Orders

This requires the backend to know:

Who is making the request?

Which Orders belong to that customer?

It does not currently require the backend to own:

Customer registration

passwords

password reset

email verification

profile management

account deletion

MFA

OAuth login

identity lifecycle

Those are identity-system responsibilities।


Authentication vs Customer Domain

Authentication answers:

Who is this caller?

Our application then receives something conceptually like:

authenticated subject

The Order Management Backend converts or represents that identity using an application-friendly value:

CustomerId

Then business workflow can operate on:

CustomerId

without knowing:

JWT structure

OAuth claims

identity provider implementation

passwords

sessions

Our Boundary

Conceptually:

External Identity System
        ↓
authenticated identity
        ↓
Security Boundary
        ↓
CustomerId
        ↓
Handler
        ↓
UseCase
        ↓
Order

The domain works with:

CustomerId

not with identity-provider protocol details।


Do We Need a Customer Entity?

Given our current requirements:

No.

At least not a local Customer aggregate solely for ordering।

Why?

Because our application does not currently own meaningful Customer state or Customer lifecycle।

Suppose we create:

public class Customer {

    private CustomerId id;
}

What behaviour does it have?

Almost none।

It would simply wrap:

CustomerId

and add another object without meaningful responsibility।


Avoid Empty Domain Entities

A class should not become an Entity just because a noun exists in the business sentence।

Bad reasoning:

"Customer creates Order"

therefore

Customer.java must exist

Domain modeling is about responsibility and ownership, not converting every noun into a class।

For our v1:

Customer identity

is important।

A local Customer aggregate is not।


CustomerId as a Value Object

A better representation:

public record CustomerId(
        String value
) {
}

Conceptually:

CustomerId

is a Value Object।

Two instances containing the same external identity value represent the same customer reference։

Example:

CustomerId a =
        new CustomerId("customer-123");

CustomerId b =
        new CustomerId("customer-123");

Conceptually:

a and b represent the same customer identity

Why Not Use String Everywhere?

We could write:

public Order createOrder(
        String customerId
) {
}

That works।

But consider another method:

cancelOrder(
        String orderId,
        String customerId
);

Both arguments are String

This is possible:

cancelOrder(
        customerId,
        orderId
);

and the compiler cannot distinguish them।

Typed IDs improve semantic safety।


Typed Identity

Better:

cancelOrder(
        OrderId orderId,
        CustomerId customerId
);

Now the method communicates:

which ID is which

and prevents accidental parameter swapping when types differ।

This is a practical reason to introduce CustomerId


CustomerId Should Represent Our Application Concept

The external identity provider may call the identity:

subject

user ID

account ID

principal ID

Our Order Management Backend can represent the application meaning as:

CustomerId

because the authenticated person is acting in the customer role for these workflows।

This keeps provider terminology from leaking throughout our domain।


But Don't Transform Identity Arbitrarily

Suppose the external system provides:

subject = "usr_8f92..."

We should not generate a completely unrelated local customer identifier unless there is a requirement।

A straightforward approach is often:

CustomerId
    wraps
stable external subject identifier

Then Order ownership remains traceable to the authoritative identity system।


Stable Identity Is Critical

Orders live longer than individual requests।

Therefore customer identity stored on an Order must be stable across:

login sessions

token refreshes

application restarts

Bad identity choices include:

access token

session ID

temporary request ID

These are not stable customer identity।


Never Store the Access Token as Customer Identity

Bad:

public class Order {

    private String accessToken;
}

Why wrong?

Tokens may:

expire

rotate

contain sensitive data

change between sessions

Order ownership should use a stable identity claim/reference, not authentication credential।


Authentication Credential vs Identity

Distinguish:

access token
→ proves/authenticates current request

from:

CustomerId
→ identifies the customer in application state

The token is temporary infrastructure/security data।

CustomerId is durable application identity reference।


Order Owns the Customer Reference

An Order belongs to a customer।

Therefore Order can contain:

public class Order {

    private final CustomerId customerId;

}

This gives us durable ownership information։


Why Store CustomerId on Order?

Later when customer requests:

GET own Order

UseCase can compare:

authenticated CustomerId

with:

Order.customerId

Conceptually:

if (!order.customerId()
        .equals(customerId)) {
    // reject access
}

No need to load a complete Customer object just to perform ownership check।


Customer Ownership Is Historical State

Suppose external identity profile later changes:

name changes

email changes

Order still belongs to the same stable identity।

That is why Order should reference:

CustomerId

rather than storing Customer profile object as ownership source।


Do Not Store Role on Order

Another tempting model:

public class Order {

    private CustomerId customerId;
    private String customerRole;
}

We explicitly do not need this।

Order ownership requires:

who owns the Order

not:

what role that person had when Order was created

Authorization roles are runtime security concerns।


Roles Come From the Security Boundary

For example:

CUSTOMER

ADMIN

may come from authenticated security context।

A Handler/security layer can determine whether current caller is permitted to invoke:

CreateProductUseCase

AdjustInventoryUseCase

Order domain does not need to store that role।


CustomerId Should Not Know Spring Security

Avoid:

public record CustomerId(
        Authentication authentication
) {
}

or:

public static CustomerId current() {
    return new CustomerId(
            SecurityContextHolder
                    .getContext()
                    .getAuthentication()
                    .getName()
    );
}

Now a domain Value Object depends on Spring Security।

Wrong direction।


Security Adapter Extracts CustomerId

Conceptually:

Spring Security Authentication
        ↓
security boundary
        ↓
CustomerId

For example, later we might have:

CustomerId customerId =
        currentUser.customerId();

Then pass it into:

createOrderUseCase.execute(
        customerId,
        command
);

The exact Spring Security integration comes later।


Handler Does Not Accept Arbitrary CustomerId

For authenticated customer operations, avoid:

public OrderResponse handle(
        CreateOrderRequest request,
        String customerIdFromRequest
) {
}

where customer chooses the owner։

Instead:

CustomerId

comes from authenticated context।

Request supplies only customer-controlled order intent।


Bad API Shape

For customer order creation:

{
  "customerId": "someone-else",
  "items": [
    {
      "productId": "P-100",
      "quantity": 1
    }
  ]
}

This allows a caller to attempt to create an Order belonging to another customer।


Better API Boundary

Request:

{
  "items": [
    {
      "productId": "P-100",
      "quantity": 1
    }
  ]
}

Ownership:

authenticated identity
    ↓
CustomerId

The client does not submit authoritative ownership information।


Create Order Flow

Conceptually:

HTTP Request
    ↓
Spring Security
    ↓
Authenticated Identity
    ↓
CreateOrderHandler
    ↓
CustomerId
    ↓
CreateOrderUseCase

Then:

CreateOrderUseCase
    ↓
creates Order
    ↓
Order.customerId = authenticated CustomerId

This gives us trustworthy ownership।


Order History Flow

Requirement:

Customer can view only their own Orders.

Conceptually:

authenticated CustomerId
        ↓
GetOrderHistoryHandler
        ↓
GetOrderHistoryUseCase
        ↓
OrderRepository

Repository may support something like:

findByCustomerId(
        CustomerId customerId,
        ...
);

This makes ownership part of the query itself where appropriate।


Prefer Ownership-Aware Queries

For single Order access, instead of:

load any Order
    ↓
then accidentally forget ownership check

we may choose repository operations such as:

Optional<Order> findByIdAndCustomerId(
        OrderId orderId,
        CustomerId customerId
);

depending on persistence/application design।

This can make unauthorized access harder to introduce accidentally।


But Repository Does Not Decide Authentication

Repository knows:

CustomerId value

It does not know:

JWT

roles

current security context

UseCase/security boundary supplies the trusted identity।


Cancellation Flow

Conceptually:

Authenticated CustomerId
        ↓
CancelOrderUseCase
        ↓
load Order
        ↓
verify ownership
        ↓
order.cancel()
        ↓
restore Inventory

Two separate rules:

Does this customer own the Order?
→ UseCase/application authorization
Can this Order be cancelled?
→ Order domain

Keep them separate।


Payment Flow

Similarly:

Authenticated CustomerId
        ↓
PayOrderUseCase
        ↓
load Order
        ↓
verify ownership
        ↓
verify Order payment eligibility
        ↓
PaymentService

The external Payment Service does not determine Order ownership।

Our application does।


Admin Access Is Different

An Admin may be allowed to:

view all Orders

depending on the endpoint.

This does not mean Order's owner changes।

Order still stores:

CustomerId

Admin access is a runtime authorization rule।


CustomerId Is Not a Role

Do not model:

public record CustomerId(
        String value,
        Role role
) {
}

These concepts have different lifecycles।

Identity:

who is this?

Role:

what are they allowed to do now?

Keep them separate।


Do We Need Customer Name on Order?

Not for current requirements।

Order history requires ownership and order information।

We do not need to copy:

customer name

email

phone

onto every Order।

If future invoices or legal records require customer information snapshotting, that would be a new requirement and a separate design decision।


Don't Query Identity Service for Every Order Read Without Need

Suppose Order history only needs:

Order ID

items

total

status

createdAt

There is no reason to call external identity system merely to reconstruct customer profile information that the API doesn't need।

Avoid unnecessary coupling and network calls।


External Identity Service Is Not a Repository

Our terminology matters։

A repository represents:

application-owned persisted state

An external identity provider represents:

third-party/external Service

If later we need to query it, we would use an external integration boundary such as:

IdentityService

not:

CustomerRepository

unless we actually own local Customer persistence।


Do We Need IdentityService Right Now?

Not necessarily।

If Spring Security already validates tokens and exposes a stable authenticated subject, our Order workflows may only need:

CustomerId

No direct external identity API call may be required।

Do not add an IdentityService simply because identity is external।

Introduce it only if a workflow genuinely needs to call an external identity API।


Avoid Architecture Without a Requirement

Bad speculative setup:

CustomerRepository

CustomerService

CustomerEntity

CustomerProfile

CustomerMapper

when none of these are required by current behaviour।

This would create a fake local Customer subsystem।


Our Customer Model May Be One Type

For v1, the entire domain representation may be:

package io.liveklass.ordermanagement.security;

public record CustomerId(
        String value
) {
}

or another appropriate package depending on final code structure।

That's not under-modeling।

It is matching the actual responsibility।


Where Should CustomerId Live?

There are a few reasonable options।

It could live near:

security/authentication

because identity enters through security।

Or it could be a small application/domain concept shared by Order ownership and security।

What we should avoid is creating:

customer/
├── handler/
├── usecase/
├── domain/
└── repository/

when no Customer capability exists।


Package Ownership Should Follow Real Responsibility

A possible later structure:

security/
├── authentication/
│   └── AuthenticatedUser.java
└── context/
    └── CurrentUser.java

while:

CustomerId

may live in a small shared identity location if several capabilities need it।

But remember our previous rule:

shared/ should appear only when a genuinely shared concept exists.

Customer identity may eventually become such a concept, but we don't need to settle package placement before Security implementation।


AuthenticatedUser vs CustomerId

These are also different concepts।

AuthenticatedUser might contain runtime security information such as:

subject

roles

while:

CustomerId

is the stable business identity reference used by Orders।

Conceptually:

AuthenticatedUser
    ↓
customerId()
    ↓
CustomerId

UseCases consume only what they need।


Do Not Pass Full Security Principal Everywhere

Bad:

createOrderUseCase.execute(
        Authentication authentication,
        command
);

Now UseCase knows Spring Security।

Better:

createOrderUseCase.execute(
        customerId,
        command
);

The UseCase receives an application concept।


Minimal CustomerId Validation

If external identity IDs are strings, a minimal type might be:

public record CustomerId(
        String value
) {

    public CustomerId {
        if (
                value == null ||
                value.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Customer ID is required"
            );
        }
    }
}

This says:

Our application cannot represent an unidentified customer ownership reference.

This is a reasonable invariant if the external identity subject is guaranteed non-empty।


Do Not Invent External ID Format Rules

Avoid:

if (!value.startsWith("customer_")) {
    ...
}

unless the external identity contract guarantees that format and our application intentionally depends on it।

Likewise avoid guessing:

UUID only

numeric only

email as ID

Stable identity format should come from the actual authentication system contract।


Never Use Email as Identity by Default

A tempting model:

public record CustomerId(
        String email
) {
}

Emails can change।

They may also have normalization and uniqueness concerns।

Unless the identity provider explicitly defines email as the stable subject identifier, use the provider's stable subject instead।


Display Information Is Not Identity

Customer may have:

name = Sakib

but name is not stable unique identity।

Order ownership must not rely on display fields।

Same for:

email

phone

username

unless a contract explicitly guarantees stable identity semantics।


Identity Provider Owns Identity Lifecycle

If customer account is disabled or deleted in the external identity system, what happens to historical Orders?

Our Order data still needs to preserve ownership reference/history।

That is another reason not to model Order ownership as a live Customer object that must always resolve successfully।

Stored:

CustomerId

remains historical application state।


Historical Orders Must Survive Profile Changes

Suppose:

Customer changes email.

Should Order rows update?

No reason currently।

Suppose:

Customer changes display name.

Order ownership still points to same stable customer identity।

This separation prevents profile changes from mutating order history।


CustomerId in Order

Conceptually:

public class Order {

    private final CustomerId customerId;

    public CustomerId customerId() {
        return customerId;
    }
}

Order knows:

who owns me

It does not know:

how that customer authenticated

This is clean domain ownership।


Should Order Have belongsTo()?

We could add:

public boolean belongsTo(
        CustomerId customerId
) {
    return this.customerId.equals(
            customerId
    );
}

This is reasonable because Order owns the ownership identifier।

Then UseCase can write:

if (!order.belongsTo(customerId)) {
    ...
}

This improves readability without coupling Order to security framework।


Is Authorization Domain Logic?

There is nuance।

Order can answer:

Does this CustomerId match my owner?

But deciding:

Is the current caller allowed to execute this operation?

is broader application/security responsibility।

So:

order.belongsTo(customerId)

can be domain behaviour।

The UseCase decides what to do with that information in the current operation।


Example Cancellation

public void execute(
        OrderId orderId,
        CustomerId customerId
) {
    Order order =
            orderRepository.findById(
                    orderId
            );

    if (!order.belongsTo(customerId)) {
        throw new OrderAccessDeniedException();
    }

    order.cancel();

    // restore inventory
    // persist
}

Here:

Order
→ knows ownership relationship
UseCase
→ enforces authorization for the operation

Good separation।


Admin UseCase Can Use a Different Access Rule

For admin operation:

ViewAllOrdersUseCase

may not require Order ownership match।

Authorization is checked before or around invoking the operation based on admin role।

Order ownership remains unchanged।

This demonstrates why belongsTo() is not the complete authorization system।


No Local Customer Table Required

Our persistence design for Order can simply store:

customer_id

on the Order record।

Conceptually:

orders
├── id
├── customer_id
├── status
└── ...

No foreign key to a local:

customers

table is required if the customer record is externally owned।


External References Are Normal

Applications frequently store identifiers referencing externally owned concepts।

For example:

payment provider reference

identity subject

external shipment ID

Not every reference needs a local foreign-key target।

The application must simply understand which system owns the referenced lifecycle।


Database Integrity Trade-Off

Without a local Customer table, PostgreSQL cannot enforce:

FOREIGN KEY customer_id → customers.id

because that table does not exist।

That's acceptable because customer existence/authentication is established at the security boundary, not through local relational integrity।

Different consistency boundaries have different enforcement mechanisms।


Don't Create a Shadow Customer Table Just for a Foreign Key

Bad reasoning:

We need a foreign key, so let's copy every authenticated customer into a local customers table.

That creates new problems:

Who synchronizes it?

What happens when identity changes?

Is it authoritative?

Can it become stale?

What if first request fails before synchronization?

Only create local Customer persistence if the application needs local Customer-owned state।


When Would a Local Customer Model Become Justified?

Future requirements might include application-owned customer data such as:

shipping preferences

application-specific membership state

customer credit

internal account status

commerce-specific profile data

Then a local Customer aggregate/table could become legitimate।

But that would be a new architectural decision։


Don't Future-Proof by Duplicating Identity

Current design should remain:

External Identity
    ↓
stable CustomerId
    ↓
Orders

not:

External Identity
    ↓
Local Customer copy
    ↓
Local Account
    ↓
Order

without a business need।


Customer and Product Have Different Modeling Needs

Product:

application owns lifecycle

Therefore:

Product Entity
ProductRepository

make sense।

Customer identity:

external system owns lifecycle

Therefore:

CustomerId

may be sufficient।

This difference demonstrates why nouns alone do not determine architecture।


Customer and Inventory Also Differ

Inventory:

application owns quantity

so local state/entity/repository is required।

Customer:

application only references authenticated identity

so local aggregate is unnecessary।

Ownership drives modeling decisions।


Authentication Failure Happens Before Business Workflow

If request has:

invalid token

it should usually be rejected by the security layer before:

CreateOrderUseCase

executes।

The UseCase should not manually validate JWTs।

Conceptually:

HTTP Request
    ↓
Authentication
    ↓
valid identity?
    ├── no → reject
    └── yes
          ↓
      Handler
          ↓
      UseCase

Authorization May Span Security and UseCase

Role-based endpoint authorization:

ADMIN required

can be enforced at the security/Handler boundary।

Resource ownership:

customer can access only own Order

often requires loading application state and therefore belongs in/around UseCase।

This distinction will be explored deeply in the Security module।


Avoid Passing Roles Into Domain

Bad:

order.cancel(
        customerId,
        role
);

Order cancellation rule is:

UNPAID → CANCELLED

Whether caller is allowed to request cancellation is an application authorization concern।

Keep role mechanics outside Order lifecycle behaviour।


CustomerId Should Be Immutable

As a Value Object, CustomerId should not support:

customerId.setValue(...);

If identity changes, that represents another identity value։

Immutability fits the concept naturally।

Java record is a good fit।


Equality of CustomerId

Java record gives value-based:

equals()
hashCode()

which is exactly what we need।

Two:

new CustomerId("usr-1")

values compare equal because they represent the same identifier value।


CustomerId Is Safe to Pass Through UseCases

Unlike an access token, a stable identifier is application data।

It can be passed:

Handler
    ↓
UseCase

and stored in Order。

However, whether IDs are sensitive enough to avoid certain logging depends on the actual identity system and privacy requirements।

Don't casually log all identity details without need।


Avoid Logging Tokens and Full Principals

Definitely avoid:

Authorization header

JWT raw value

refresh token

in application logs।

A stable customer identifier may be useful in controlled logs, but operational/privacy policy should guide that later।


CustomerId and Correlation ID Are Different

Do not confuse:

CustomerId

with:

RequestId / CorrelationId

CustomerId answers:

who?

Correlation ID answers:

which request/operation?

Different lifecycles and responsibilities।


CustomerId and OrderId Are Different

Typed IDs make this explicit:

public Order execute(
        OrderId orderId,
        CustomerId customerId
) {
}

This is clearer than:

public Order execute(
        String id1,
        String id2
) {
}

Domain vocabulary reduces mistakes।


A Minimal Customer Identity Model

For current scope, conceptually:

package io.liveklass.ordermanagement.order.domain;

public record CustomerId(
        String value
) {

    public CustomerId {
        if (
                value == null ||
                value.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Customer ID is required"
            );
        }
    }
}

Exact package can change once Security implementation establishes better ownership।

The important model is the type itself।


Should CustomerId Be in order.domain?

Maybe initially, because Order is the primary domain concept using it।

But if later:

payment

other customer-owned resources

also depend on the same identity, it may deserve a shared application identity location।

Don't create a global package prematurely।

Refactor when reuse becomes real।


An Alternative Naming: UserId

Why not:

UserId

?

Our application distinguishes customer operations from admin operations।

CustomerId makes Order ownership semantics explicit।

However, if the external identity model uses one stable UserId for both roles and the domain needs that general concept, UserId might be appropriate।

The naming should follow actual security contract when implemented।

For now our accepted business language uses:

Customer

for Order ownership, so CustomerId is a useful conceptual name।


We Are Not Modeling Customer Profile

Do not add:

public class Customer {

    private CustomerId id;
    private String name;
    private String email;
}

unless the Order Management Backend actually owns or needs that state।

A profile displayed in another system is not automatically our domain responsibility।


We Are Not Modeling Passwords

Never put:

password

passwordHash

into the Order Management domain simply because customers authenticate।

Identity provider owns those credentials।

Security boundary validates authentication result।


We Are Not Modeling Customer Registration

No:

RegisterCustomerUseCase

No:

CreateCustomerHandler

unless product requirements explicitly introduce customer registration owned by this backend।

This is why the earlier generic milestone idea of POST /customers should not override the accepted architecture.

Our reviewed design takes precedence:

no local Customer aggregate solely for ordering

We Are Not Adding /customers Just Because It Sounds RESTful

A resource should exist because the application owns meaningful state/behaviour।

If this backend does not own Customer account lifecycle, then:

POST /customers

would be misleading।

The REST API will instead focus on actual owned capabilities।


This Is an Important Architecture Lesson

A professional engineer must be able to say:

This concept exists in the business, but our system does not own it.

That statement prevents unnecessary services, tables, synchronization, and coupling।

Not every business concept belongs inside every system।


Bounded Responsibility

Our backend's responsibility around Customer is:

receive authenticated stable identity

associate new Orders with that identity

enforce ownership where required

That's enough।

The external identity system's responsibility includes:

authenticate users

manage credentials

manage identity lifecycle

Separation is deliberate।


Testing Customer Ownership

We should be able to test ownership with simple domain/application values।

Example:

@Test
void orderBelongsToItsCustomer() {
    CustomerId customerId =
            new CustomerId("customer-1");

    Order order =
            orderFor(customerId);

    assertTrue(
            order.belongsTo(customerId)
    );
}

No Spring Security required।


Testing UseCase Authorization

Example conceptually:

@Test
void customerCannotCancelAnotherCustomersOrder() {
    CustomerId owner =
            new CustomerId("customer-1");

    CustomerId anotherCustomer =
            new CustomerId("customer-2");

    Order order =
            unpaidOrder(owner);

    orderRepository.save(order);

    assertThrows(
            OrderAccessDeniedException.class,
            () -> useCase.execute(
                    order.id(),
                    anotherCustomer
            )
    );
}

Again:

authentication framework

is unnecessary for this UseCase test।

We test application authorization semantics directly।


Security Integration Tests Come Later

When Spring Security is implemented, separate tests will verify:

token authentication

role mapping

current identity extraction

protected endpoints

This keeps domain/application tests fast and focused।


Customer Identity Review Checklist

When working with customer identity, ask:

Is this value a stable identity?

Did it come from authenticated context?

Are we accepting ownership from request body?

Are we accidentally storing a token instead of an ID?

Does this backend really own Customer state?

Do we need a local Customer table?

Are we duplicating identity-provider data?

Does domain code know Spring Security?

Are roles being confused with identity?

Will historical Orders remain meaningful if
customer profile information changes?

Common Mistake 1 — Building a Customer Entity Automatically

A business noun does not automatically require a local aggregate।


Common Mistake 2 — Copying Identity Provider Data Locally

This introduces synchronization and ownership problems without current value।


Common Mistake 3 — CustomerId From Request Body

Ownership must come from authenticated identity for customer operations।


Common Mistake 4 — Store Access Token on Order

Tokens are credentials, not stable business identity।


Common Mistake 5 — Use Email as Stable ID Without Contract

Display/contact information may change and should not automatically define identity।


Common Mistake 6 — Order Stores Role

Ownership and authorization role are different concepts।


Common Mistake 7 — Domain Reads SecurityContextHolder

This couples domain/application behaviour to Spring Security infrastructure।


Common Mistake 8 — Add CustomerRepository Without Local Customer State

Repositories exist for application-owned persistence, not because a noun appears in the product requirement।


Common Mistake 9 — Add IdentityService Without a Call Requirement

If authentication already provides everything required, no external API client is necessary।


Common Mistake 10 — Shadow Customer Table for Foreign Keys

Relational convenience is not enough reason to duplicate an externally owned identity domain।


How Customer Identity Fits Our Architecture

Incoming request:

HTTP
    ↓
Security
    ↓
Authenticated Identity
    ↓
CustomerId
    ↓
Handler
    ↓
UseCase

Order creation:

UseCase
    ↓
Order(
    customerId,
    ...
)

Order access:

Authenticated CustomerId
        ↓
UseCase
        ↓
Order.customerId
        ↓
ownership decision

No local Customer aggregate required।


Current Domain Relationship

Our model now looks like:

External Identity System
        ↓
CustomerId
        ↓
Order
        ├── OrderItem
        └── OrderItem

And independently:

Product
    ↓
Inventory

Order Items reference Product identity and preserve purchase-time price।

This keeps system ownership clear।


Engineering Principle

The core principle:

Model the Customer information our system actually owns and needs—not the entire person or account simply because the word “customer” appears in the business domain.

Another:

Authentication infrastructure determines who the caller is; the application uses a stable CustomerId to model Order ownership.

And:

Store durable identity references, not temporary credentials or duplicated identity-provider state.


Summary

In this lesson, we learned that:

  • The Order Management Backend does not currently own Customer account lifecycle.
  • Authentication and identity lifecycle belong to an external identity mechanism.
  • Our application primarily needs a stable authenticated customer identifier.
  • A local Customer aggregate is unnecessary for v1 solely to support Orders.
  • CustomerId is a natural Value Object candidate.
  • Typed customer identity improves semantics and reduces ID confusion.
  • The stable external subject can usually be represented as CustomerId without creating a second local identity.
  • Access tokens, session IDs, and other credentials must not be used as durable Order ownership identifiers.
  • Order should store a stable CustomerId.
  • Customer ownership and authorization roles are different concepts.
  • Order should not store the user's runtime role.
  • Authenticated Customer identity should come from the security boundary, not the request body.
  • UseCases should receive application-friendly identity rather than Spring Security Authentication.
  • Domain objects should not read SecurityContextHolder.
  • Order can reasonably expose ownership behaviour such as belongsTo(customerId).
  • UseCases enforce operation-level ownership using authenticated identity and loaded application state.
  • Admin access does not change Order ownership.
  • No local customers table or foreign key is required solely for Order ownership.
  • Creating a shadow Customer table would introduce synchronization and authority problems.
  • A direct IdentityService integration is unnecessary unless a workflow actually needs to call the external identity system.
  • Customer profile information should not be copied into the Order model without a specific historical/business requirement.
  • The backend does not need RegisterCustomerUseCase, Customer passwords, profile management, or customer-account APIs in the current scope.
  • A business concept can exist without being owned by our system.
  • System boundaries should follow ownership, not nouns.

Next lesson:

Modeling Orders and Order Items

There we will build the central domain model of the application: Order and OrderItem, including customer ownership, immutable purchase-time prices, item composition, derived totals, duplicate Product protection, and valid construction of a new Order.