Domain Modeling
Modeling Customer
আপনি একটি 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 আমাদের প্রথম প্রশ্ন হওয়া উচিত নয়:
Customerclass-এ কোন 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
customerstable.
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
CustomerIdto 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.
CustomerIdis a natural Value Object candidate.- Typed customer identity improves semantics and reduces ID confusion.
- The stable external subject can usually be represented as
CustomerIdwithout 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
customerstable or foreign key is required solely for Order ownership. - Creating a shadow Customer table would introduce synchronization and authority problems.
- A direct
IdentityServiceintegration 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.