Summary
Master Domain-Driven Design for technical interviews. Covers bounded contexts, aggregates, entities, value objects, and ubiquitous language. Essential for designing complex business applications with clear domain models and maintainable architecture.
Core Concepts
What is DDD?
- Definition: An approach to software development that focuses on the core domain and domain logic
- Goal: Create software that reflects complex business domains through collaboration between technical and domain experts
- Key Principle: The structure and language of software code should match the business domain
Ubiquitous Language
- Definition: A common language shared by developers and domain experts
- Purpose: Eliminate translation between business and technical terms
- Example: Use "Customer" not "User", "Order" not "Transaction" if that's what the business calls them
Bounded Context
- Definition: A logical boundary within which a domain model is defined and applicable
- Purpose: Isolate models to avoid conflicts and maintain clarity
- Example: "Customer" in Sales context vs "Customer" in Support context may have different attributes
// Sales Context
class Customer {
private CustomerId id;
private CreditLimit creditLimit;
private List<Order> orderHistory;
}
// Support Context
class Customer {
private CustomerId id;
private SupportTier tier;
private List<Ticket> tickets;
}
Building Blocks (Tactical Patterns)
1. Entity
- Definition: Object with unique identity that persists over time
- Key: Identity matters more than attributes
- Example: User, Order, Product
public class Order {
private final OrderId id; // Identity
private OrderStatus status;
private List<OrderItem> items;
@Override
public boolean equals(Object o) {
// Equality based on ID only
return id.equals(((Order) o).id);
}
}
2. Value Object
- Definition: Object without identity, defined by its attributes
- Key: Immutable, replaceable
- Example: Money, Address, Email
public final class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
this.amount = amount;
this.currency = currency;
}
// No setters - immutable
public Money add(Money other) {
return new Money(
amount.add(other.amount),
currency
);
}
}
3. Aggregate
- Definition: Cluster of entities and value objects with defined boundaries
- Key: Consistency boundary, accessed through Aggregate Root
- Example: Order (root) with OrderItems
public class Order { // Aggregate Root
private OrderId id;
private List<OrderItem> items;
public void addItem(Product product, int quantity) {
// Business logic enforced at aggregate level
if (items.size() >= 10) {
throw new OrderLimitExceededException();
}
items.add(new OrderItem(product, quantity));
}
}
4. Repository
- Definition: Interface for accessing aggregates
- Key: Abstracts data persistence
- Example: OrderRepository
public interface OrderRepository {
Order findById(OrderId id);
void save(Order order);
List<Order> findByCustomer(CustomerId customerId);
}
5. Domain Service
- Definition: Business logic that doesn't belong to any entity
- Key: Stateless operations
- Example: PricingService, PaymentService
public class PricingService {
public Money calculateDiscount(
Customer customer,
Order order
) {
// Complex logic involving multiple aggregates
if (customer.isVip() && order.getTotal() > 1000) {
return order.getTotal().multiply(0.2);
}
return Money.ZERO;
}
}
6. Domain Event
- Definition: Something important that happened in the domain
- Key: Immutable, past tense
- Example: OrderPlaced, PaymentProcessed
public class OrderPlacedEvent {
private final OrderId orderId;
private final CustomerId customerId;
private final Instant occurredAt;
// Constructor and getters...
}
7. Factory
- Definition: Encapsulates complex object creation
- Key: Ensures valid object creation
- Example: OrderFactory
public class OrderFactory {
public Order createOrder(
CustomerId customerId,
List<OrderItemData> items
) {
// Complex creation logic
validateCustomer(customerId);
Order order = new Order(generateId(), customerId);
items.forEach(item ->
order.addItem(item.product, item.quantity)
);
return order;
}
}
🗺️ Strategic Patterns
Context Mapping Patterns
- Shared Kernel: Shared subset of domain model between contexts
- Customer-Supplier: Upstream team provides for downstream needs
- Conformist: Downstream conforms to upstream model
- Anti-Corruption Layer (ACL): Translation layer between contexts
- Open Host Service: Well-documented service protocol
- Published Language: Well-documented exchange language
Anti-Corruption Layer Example
// External system model
class ExternalCustomer {
String custId;
String fullName;
}
// ACL Translator
class CustomerTranslator {
Customer translate(ExternalCustomer ext) {
String[] names = ext.fullName.split(" ");
return new Customer(
new CustomerId(ext.custId),
new Name(names[0], names[1])
);
}
}
📋 Implementation Patterns
Layered Architecture with DDD
┌─────────────────────────┐
│ Presentation Layer │ (Controllers, Views)
├─────────────────────────┤
│ Application Layer │ (Use Cases, DTOs)
├─────────────────────────┤
│ Domain Layer │ (Entities, VOs, Services)
├─────────────────────────┤
│ Infrastructure Layer │ (Repositories, External Services)
└─────────────────────────┘
Application Service Example
@Service
public class OrderApplicationService {
private OrderRepository orderRepo;
private CustomerRepository customerRepo;
private EventPublisher eventPublisher;
@Transactional
public OrderId placeOrder(PlaceOrderCommand cmd) {
Customer customer = customerRepo.findById(cmd.customerId);
Order order = new Order(customer.getId());
cmd.items.forEach(item ->
order.addItem(item.productId, item.quantity)
);
orderRepo.save(order);
eventPublisher.publish(
new OrderPlacedEvent(order.getId())
);
return order.getId();
}
}
🎪 Common Patterns & Best Practices
1. Specification Pattern
public interface Specification<T> {
boolean isSatisfiedBy(T t);
default Specification<T> and(Specification<T> other) {
return t -> isSatisfiedBy(t) && other.isSatisfiedBy(t);
}
}
// Usage
Specification<Order> highValue =
order -> order.getTotal().isGreaterThan(1000);
Specification<Order> rush =
order -> order.isRush();
var complexSpec = highValue.and(rush);
2. Domain Validation
public class Email {
private final String value;
public Email(String value) {
if (!isValid(value)) {
throw new InvalidEmailException(value);
}
this.value = value;
}
private boolean isValid(String email) {
return email.matches("^[^@]+@[^@]+\\.[^@]+$");
}
}
3. Aggregate Design Rules
- Keep aggregates small
- Reference other aggregates by ID only
- Update one aggregate per transaction
- Use eventual consistency between aggregates
Interview Tips & Common Questions
Key Questions to Prepare
"What is DDD and when should you use it?"
- Use for complex domains with significant business logic
- Not for CRUD applications
- When domain experts are available
"Entity vs Value Object?"
- Entity: Has identity, mutable
- Value Object: No identity, immutable
"How do you handle transactions across aggregates?"
- Domain Events + Eventual Consistency
- Saga/Process Manager pattern
- Two-phase commit (avoid if possible)
"What makes a good aggregate?"
- Small and focused
- Consistency boundary
- Low coupling with other aggregates
Red Flags to Avoid
- ❌ Anemic Domain Model (logic in services, not entities)
- ❌ Large aggregates loading too much data
- ❌ Direct references between aggregates
- ❌ Domain logic leaking to other layers
Design Smells
- Bidirectional relationships between aggregates
- Services with too many dependencies
- Entities without behavior
- Value objects with identity
🔧 Practical Examples
E-commerce Domain Model
// Aggregate Root
public class ShoppingCart {
private CartId id;
private CustomerId customerId;
private List<CartItem> items;
private Money totalAmount;
public void addProduct(ProductId productId, int quantity) {
// Invariant: Max 50 items
if (items.size() + quantity > 50) {
throw new CartLimitExceededException();
}
items.add(new CartItem(productId, quantity));
recalculateTotal();
}
public Order checkout() {
if (items.isEmpty()) {
throw new EmptyCartException();
}
return new Order(customerId, items);
}
}
// Value Object
public class CartItem {
private final ProductId productId;
private final int quantity;
private final Money unitPrice;
// Constructor, getters, equals/hashCode
}
Event Sourcing with DDD
public abstract class AggregateRoot {
private List<DomainEvent> changes = new ArrayList<>();
protected void raiseEvent(DomainEvent event) {
changes.add(event);
apply(event);
}
public List<DomainEvent> getUncommittedChanges() {
return new ArrayList<>(changes);
}
public void markChangesAsCommitted() {
changes.clear();
}
protected abstract void apply(DomainEvent event);
}
📚 Quick Reference
DDD Building Blocks Comparison
| Pattern | Identity | Mutable | Example |
|---|---|---|---|
| Entity | Yes | Yes | User, Order |
| Value Object | No | No | Money, Address |
| Aggregate | Yes (via root) | Yes | Order + Items |
| Domain Event | No | No | OrderPlaced |
When to Use What
- Entity: When identity matters across time
- Value Object: When only attributes matter
- Domain Service: When logic doesn't fit in entities
- Repository: To abstract persistence
- Factory: For complex object creation
- Domain Event: To communicate between aggregates
Architecture Checklist
- Domain layer has no dependencies
- Repositories return domain objects, not DTOs
- Application services orchestrate, don't contain business logic
- Each bounded context has its own model
- Aggregates enforce invariants
- Value objects are immutable
- Domain events are past tense
Interview Scenario Solutions
Scenario: Design a Banking System
// Bounded Contexts: Account Management, Transactions, Fraud Detection
// Account Aggregate
public class Account {
private AccountId id;
private CustomerId owner;
private Money balance;
private AccountStatus status;
public void withdraw(Money amount) {
if (!status.isActive()) {
throw new InactiveAccountException();
}
if (balance.isLessThan(amount)) {
throw new InsufficientFundsException();
}
balance = balance.subtract(amount);
raiseEvent(new MoneyWithdrawnEvent(id, amount));
}
}
// Transaction Context
public class TransferService {
@Transactional
public void transfer(
AccountId from,
AccountId to,
Money amount
) {
// Orchestrate between aggregates
Account fromAccount = accountRepo.find(from);
Account toAccount = accountRepo.find(to);
fromAccount.withdraw(amount);
toAccount.deposit(amount);
accountRepo.save(fromAccount);
accountRepo.save(toAccount);
eventBus.publish(new TransferCompletedEvent(
from, to, amount
));
}
}
Remember: DDD is about modeling the business domain accurately, not just using patterns!
Found this useful? Pass it on.