From User Stories to Entities - A Practical Map for Software Engineers
Shipping good software is not “just” about writing code. It’s about turning real user needs into clear models and predictable behaviors.
In most modern architectures (DDD, Clean Architecture, hexagonal, etc.), we keep seeing the same words:
user stories, features, use cases, entities, value objects, aggregates, repositories, DTOs, adapters, facades, domain events…
This article connects all of them in one coherent flow, using a simple e-commerce example, so any engineer can understand how the pieces fit.
1. The Big Picture
Here’s the high-level flow we’ll use:
User Story → Feature → Use Case → Domain Model (Entities, Value Objects, Aggregates) → Repository / Adapters / DTOs → UI via Facades → Domain Events
We’ll walk this path step by step.
Imagine a basic e-commerce: Shoply, an online store that sells shoes.
2. User Stories — Start with the Human
What it is
A user story is a short, human-centric description of a desired outcome, expressed from the user’s point of view.
Typical format
As a [type of user], I want [goal], so that [value].
Example (Shoply)
As a shopper, I want to add products to a cart so that I can review everything and check out once.
Why it matters
- Forces us to think about who, what, and why.
- Avoids jumping directly into UI or database design.
- Becomes the starting point for Features and Use Cases.
Key idea:
User stories are not technical. They’re about value.
3. Features — Product Slices You Can Ship
What it is
A feature is a coherent piece of functionality that delivers noticeable value to the user. It often maps to something they see in the product.
Examples (Shoply)
- “Shopping Cart”
- “Product Search”
- “Order History”
- “Wishlist”
For our user story:
“As a shopper, I want to add products to a cart…”
We might define:
- Feature: Shopping Cart
What a Feature includes
- Related UI (pages, components, flows).
- Error states and edge cases.
- Possibly multiple user stories (e.g., add to cart, remove from cart, update quantity).
Role:
Features live mainly in product and UI discussions. They are how product managers and designers think about the system.
4. Use Cases — System Behaviors, Not Screens
What it is
A use case describes a specific behavior the system must perform to support a feature. It’s a clear, technical description of what happens when a user or external system triggers an action.
In DDD/Clean Architecture, Use Cases live in the Application Layer.
Examples (Shoply / Shopping Cart feature)
AddItemToCartUseCaseRemoveItemFromCartUseCaseUpdateCartItemQuantityUseCaseGetActiveCartUseCase
Responsibilities of a Use Case
- Validate input and business rules (at a workflow level).
- Load/modify entities from repositories.
- Coordinate domain logic, but don’t know UI or database details.
- Emit domain events when something meaningful happens.
Pseudo-code example
class AddItemToCartUseCase {
constructor(private readonly cartRepository: CartRepository) {}
async execute(input: {
customerId: string;
productId: string;
quantity: number;
}) {
const cart = await this.cartRepository.getActiveCart(input.customerId);
cart.addItem(input.productId, input.quantity);
await this.cartRepository.save(cart);
return cart.toDto();
}
}
Key idea:
Features are “what the user sees.”
Use Cases are “what the system does.”
5. Entities — The Heart of the Business
What it is
An entity is a domain object with:
- Identity (it’s uniquely identifiable).
- State (fields).
- Behavior (methods that enforce rules).
Entities live in the Domain Layer and represent the real business concepts.
Examples (Shoply)
CustomerCartCartItemProductOrder
Example: Cart Entity
class Cart {
constructor(
private readonly id: CartId,
private readonly customerId: CustomerId,
private items: CartItem[] = []
) {}
addItem(productId: ProductId, quantity: number) {
const existing = this.items.find((i) => i.productId.equals(productId));
if (existing) {
existing.increaseQuantity(quantity);
} else {
this.items.push(new CartItem(productId, quantity));
}
}
// other behaviors: removeItem, updateQuantity, calculateTotal, etc.
}
Key idea:
Entities encapsulate rules and invariants, not just data.
6. Value Objects — Strong Types for Important Concepts
What it is
A value object is:
- Defined by its value, not identity.
- Immutable (a change creates a new instance).
- Used to represent concepts like money, email, quantity, etc.
Examples (Shoply)
Money(amount + currency)EmailAddressProductIdCartIdQuantity
Example
class Money {
constructor(
readonly amount: number,
readonly currency: "USD" | "EUR" | "BRL"
) {
if (amount < 0) throw new Error("Amount cannot be negative");
}
add(other: Money): Money {
if (this.currency !== other.currency) throw new Error("Currency mismatch");
return new Money(this.amount + other.amount, this.currency);
}
}
Relationship to Entities
- Entities are often composed of Value Objects.
- This keeps rules close to the data and reduces bugs.
7. Aggregates — Consistency Boundaries
What it is
An aggregate is a cluster of Entities and Value Objects that:
- Are treated as one consistency boundary.
- Have a single entry point: the Aggregate Root.
Examples (Shoply)
Cartaggregate:- Root:
Cart - Children:
CartItem
- Root:
Orderaggregate:- Root:
Order - Children:
OrderItem,ShippingAddress,PaymentInfo(as value objects)
- Root:
Why it matters
- You only modify the aggregate through its root.
- Aggregates help you reason about transactions and invariants.
Example invariant:
“An order total must equal the sum of its items plus shipping minus discounts.”
→ This invariant is enforced inside theOrderaggregate.
8. Repositories — Persistence Without Leaking the Database
What it is
A repository is an abstraction that provides methods to retrieve and save aggregates. It hides persistence details.
Examples (Shoply)
interface CartRepository {
getActiveCart(customerId: string): Promise<Cart>;
save(cart: Cart): Promise<void>;
}
Implementation might use:
- PostgreSQL
- MongoDB
- DynamoDB
- A microservice API
…but the Use Case doesn’t care.
Relationship
- Use Cases talk to Repositories.
- Repositories load and save Aggregates.
- Repositories live in the Adapters / Infrastructure layer.
9. DTOs — Crossing Boundaries Safely
What it is
A DTO (Data Transfer Object) is a plain data structure used to move data across boundaries:
- From UI → Use Case.
- From Use Case → UI.
- From Use Case → Adapters (e.g., APIs, queues).
DTOs are not domain models and shouldn’t contain business logic.
Example (Shoply)
interface AddItemToCartRequestDto {
customerId: string;
productId: string;
quantity: number;
}
interface CartDto {
id: string;
customerId: string;
items: {
productId: string;
quantity: number;
}[];
totalAmount: number;
currency: string;
}
Why use DTOs
- Decouple internal domain representation from external interfaces.
- Control exactly what leaves and enters each layer.
- Make versioning and backward compatibility easier.
10. Adapters — Talking to the Outside World
What it is
An adapter is an implementation that connects your application to external systems:
- HTTP controllers / REST or GraphQL APIs
- Database clients
- Message brokers (Kafka, RabbitMQ, SNS/SQS)
- External services (payment providers, shipping APIs)
In hexagonal/ports-and-adapters architecture:
- Ports = interfaces defined by your application (e.g.,
CartRepository). - Adapters = concrete implementations (e.g.,
PostgresCartRepository).
Example (Shoply)
class HttpCartController {
constructor(private readonly addItemToCartUseCase: AddItemToCartUseCase) {}
async postAddItem(req, res) {
const dto: AddItemToCartRequestDto = req.body;
const cartDto = await this.addItemToCartUseCase.execute(dto);
res.status(200).json(cartDto);
}
}
Key idea:
Use Cases define what they need via ports (interfaces).
Adapters implement those ports with real technologies.
11. Facades — Making Life Easier for the UI
What it is
A facade is a thin service that:
- Simplifies access to multiple Use Cases.
- Adapts Use Case signatures to the needs of UI frameworks (Angular, React, etc.).
- Improves developer experience in the presentation layer.
Example (Shoply)
class CartFacade {
constructor(
private readonly addItemToCartUseCase: AddItemToCartUseCase,
private readonly getActiveCartUseCase: GetActiveCartUseCase
) {}
addItem(productId: string, quantity: number, customerId: string) {
return this.addItemToCartUseCase.execute({
productId,
quantity,
customerId,
});
}
loadCart(customerId: string) {
return this.getActiveCartUseCase.execute({ customerId });
}
}
In the frontend, instead of wiring multiple use cases, components just call the facade.
12. Domain Events — Letting the System React and Scale
What it is
A domain event says: “Something important happened in the domain.”
Examples:
CartCheckedOutOrderPlacedPaymentDeclinedProductOutOfStock
Example (Shoply)
When an order is placed:
class Order {
// ...
placeOrder() {
if (!this.canPlace()) throw new Error("Order cannot be placed");
this.status = "PLACED";
this.domainEvents.push(
new OrderPlacedEvent(this.id, this.customerId, this.total)
);
}
}
Why Domain Events are powerful
- They decouple the core business logic from side effects.
- You can easily add:
- Email notifications
- Inventory updates
- Analytics tracking
- Integrations with external systems
without modifying the original Use Case or Entity logic excessively.
13. How Everything Fits Together (End-to-End Flow)
Let’s walk through the story:
As a shopper, I want to add products to a cart so that I can review everything and check out once.
-
User Story
Defines the user’s goal and value. -
Feature: Shopping Cart
PM and design create UX flows, errors, success states. -
Use Cases
AddItemToCartUseCaseGetActiveCartUseCase
-
Entities & Aggregates
Cart(aggregate root)CartItem(entity)ProductId,CartId,Money,Quantity(value objects)
-
Repositories
CartRepositoryto load and saveCartaggregates.
-
Adapters
PostgresCartRepositoryimplementsCartRepository.HttpCartControllerexposes/cart/itemsAPI.
-
DTOs
AddItemToCartRequestDtofrom the HTTP body.CartDtoto return cart details to the frontend.
-
Facades (Frontend)
CartFacadeto let UI components calladdItemandloadCart.
-
Domain Events
- Later, when
CartCheckedOutoccurs, we can:- Generate an
Order. - Notify email service.
- Update analytics.
- Generate an
- Later, when
Everything is connected, but each part has a clear responsibility.
14. Common Mistakes to Avoid
-
Jumping straight to database design
- Starting with tables instead of user stories and domain models.
- Result: anemic entities, hard-to-change schemas.
-
Putting business logic in controllers or UI
- Logic should live in Use Cases and Entities, not random React components or HTTP handlers.
-
Using entities as DTOs
- Exposing internal domain objects directly over APIs.
- Leads to tight coupling, versioning pain, and security issues.
-
No clear aggregate boundaries
- Putting too much in one entity (“God object”) or spreading invariants across multiple tables with no clear root.
-
Skipping domain events
- Hard to extend behavior without editing old code everywhere.
15. Final Takeaway
If you remember only one mental model from this article, make it this one:
User Story explains the value →
Feature organizes the experience →
Use Case defines the behavior →
Entities / Aggregates / Value Objects encode the rules →
Repositories / Adapters / DTOs / Facades handle communication & infrastructure →
Domain Events let the system react and evolve.
When you structure your e-commerce (or any product) this way, you get:
- Clear separation of concerns.
- Easier testing and refactoring.
- A system that is closer to the real business and easier to grow.