๐๏ธ Project Architecture Overview
This project implements a complete e-commerce order management system using Domain-Driven Design (DDD) principles with Spring Boot. The architecture follows the classic DDD layered approach with clear separation of concerns.
๐ Project Structure
src/main/java/com/ecommerce/
โโโ ECommerceApplication.java # Main Spring Boot application
โโโ shared/ # Shared kernel across domains
โ โโโ domain/ # Base domain abstractions
โ โโโ infrastructure/ # Cross-cutting infrastructure
โ โโโ application/ # Base application interfaces
โโโ customer/ # Customer bounded context
โโโ product/ # Product bounded context
โโโ order/ # Order bounded context (main focus)
โโโ web/ # Web/REST layer
๐ฏ Core DDD Concepts Implemented
1. Bounded Contexts
The system is divided into three main bounded contexts:
- Customer Context: User management and authentication concerns
- Product Context: Inventory and catalog management
- Order Context: Order processing and fulfillment (primary context)
2. Aggregate Roots
Each context has its own aggregate root that maintains consistency:
-
Customer
- manages customer data and behavior -
Product
- handles product lifecycle and inventory -
Order
- orchestrates order creation and status management
3. Value Objects
Immutable objects that represent concepts without identity:
-
Email
- with built-in validation -
Money
- with currency support and arithmetic operations -
CustomerId
,ProductId
,OrderId
- strongly-typed identifiers
๐ Deep Dive into Each Layer
Shared Domain Layer
The foundation that provides common abstractions:
Base Classes:
-
Entity<T>
: Base class for domain entities with identity -
ValueObject
: Abstract base for value objects -
AggregateRoot<T>
: Extends Entity and manages domain events -
DomainEvent
: Base for all domain events with timestamp
Key Features:
- Identity Management: Proper equals/hashCode based on ID
- Domain Events: Event sourcing capabilities for cross-aggregate communication
- Event Publishing: Infrastructure abstraction for event handling
Customer Domain
Core Components:
// Strong-typed identifier
CustomerId customerId = CustomerId.generate();
// Validated email value object
Email email = Email.of("john@example.com"); // Throws if invalid
// Domain entity with business logic
Customer customer = Customer.create("John Doe", email);
customer.updateEmail(Email.of("newemail@example.com"));
Business Rules Enforced:
- Email format validation using regex
- Immutable value objects prevent invalid state
- Factory methods ensure proper object creation
Product Domain
Sophisticated Money Handling:
// Type-safe money operations
Money price = Money.usd(new BigDecimal("99.99"));
Money total = price.multiply(5); // $499.95
Money combined = price.add(otherPrice); // Same currency required
Inventory Management:
Product laptop = Product.create("Laptop", "Gaming laptop",
Money.usd(new BigDecimal("1299.99")), 10);
// Business logic encapsulated in domain
if (laptop.isAvailable(3)) {
laptop.reduceStock(3); // Atomic operation with validation
}
Order Domain - The Heart of the System
This is the most complex aggregate, demonstrating advanced DDD patterns:
Order Aggregate Structure:
Order order = Order.create(customerId);
order.addItem(new OrderItem(productId, "Laptop", price, 2));
order.confirm(); // State transition with business rules
Advanced Features:
- State Management: Orders have well-defined states (PENDING โ CONFIRMED โ SHIPPED โ DELIVERED)
- Business Invariants: Cannot modify confirmed orders, cannot confirm empty orders
- Aggregate Consistency: All order items are managed within the order boundary
-
Domain Events: Publishes
OrderCreatedEvent
for downstream processing
Order Item Value Object:
OrderItem item = new OrderItem(productId, "Laptop", unitPrice, quantity);
Money itemTotal = item.getTotalPrice(); // quantity * unitPrice
๐ Application Layer - Use Cases and Orchestration
CreateOrderUseCase - Complex Business Process:
This use case demonstrates sophisticated orchestration:
@Transactional
public Order execute(CreateOrderRequest request) {
// 1. Validate customer exists
Customer customer = customerRepository.findById(customerId)
.orElseThrow(() -> new IllegalArgumentException("Customer not found"));
// 2. Create order aggregate
Order order = Order.create(customerId);
// 3. Process each item with validation
for (OrderItemRequest itemRequest : request.getItems()) {
Product product = productRepository.findById(productId)
.orElseThrow(() -> new IllegalArgumentException("Product not found"));
// 4. Check availability (business rule)
if (!product.isAvailable(quantity)) {
throw new IllegalStateException("Insufficient stock");
}
// 5. Add to order and update inventory atomically
order.addItem(new OrderItem(productId, product.getName(),
product.getPrice(), quantity));
product.reduceStock(quantity);
productRepository.save(product);
}
// 6. Confirm order (triggers state transition)
order.confirm();
Order savedOrder = orderRepository.save(order);
// 7. Publish domain events for downstream processing
savedOrder.getDomainEvents().forEach(eventPublisher::publish);
savedOrder.clearDomainEvents();
return savedOrder;
}
Key Patterns Demonstrated:
- Transaction Boundaries: Entire use case is transactional
- Domain Event Publishing: Decoupled communication
- Error Handling: Domain-specific exceptions
- Aggregate Coordination: Multiple aggregates working together
๐๏ธ Infrastructure Layer - Persistence & Implementation
Repository Pattern Implementation
Clean separation between domain and infrastructure:
Domain Interface (in domain layer):
public interface OrderRepository {
Order save(Order order);
Optional<Order> findById(OrderId id);
List<Order> findByCustomerId(CustomerId customerId);
}
Infrastructure Implementation:
The repository implementation handles the complex object-relational mapping:
@Repository
public class OrderRepositoryImpl implements OrderRepository {
// Converts between domain objects and JPA entities
private Order toDomain(OrderEntity entity) {
List<OrderItem> items = entity.getItems().stream()
.map(itemEntity -> new OrderItem(
ProductId.of(itemEntity.getProductId()),
itemEntity.getProductName(),
Money.of(itemEntity.getUnitPrice(), itemEntity.getCurrency()),
itemEntity.getQuantity()
))
.collect(Collectors.toList());
return Order.restore(OrderId.of(entity.getId()),
CustomerId.of(entity.getCustomerId()),
items, entity.getStatus(), entity.getOrderDate());
}
}
JPA Entity Mapping
Sophisticated mapping that preserves domain model integrity:
- OrderEntity: Main aggregate root entity
- OrderItemEntity: Child entities with proper cascade relationships
- Value Object Flattening: Money mapped to separate amount/currency columns
- Enum Mapping: OrderStatus stored as string for readability
๐ Web Layer - REST API Design
RESTful Design Principles:
-
Resource-based URLs:
/api/orders
,/api/customers
,/api/products
- HTTP Status Codes: 201 for creation, 404 for not found, 400 for bad requests
- Content Negotiation: JSON request/response bodies
- Error Handling: Consistent error responses
Order Controller Example:
@PostMapping
public ResponseEntity<OrderResponse> createOrder(@RequestBody CreateOrderRequest request) {
try {
Order order = createOrderUseCase.execute(request);
return ResponseEntity.status(HttpStatus.CREATED)
.body(OrderResponse.from(order));
} catch (IllegalArgumentException | IllegalStateException e) {
return ResponseEntity.badRequest().build();
}
}
๐งช Testing Strategy
Domain-Driven Testing:
The project includes comprehensive unit tests focusing on business logic:
@Test
void shouldNotConfirmEmptyOrder() {
Order order = Order.create(CustomerId.generate());
assertThrows(IllegalStateException.class, order::confirm);
}
@Test
void shouldCalculateTotalAmount() {
Order order = Order.create(CustomerId.generate());
order.addItem(new OrderItem(productId, "Product", Money.usd(99.99), 2));
Money total = order.getTotalAmount();
assertEquals(new BigDecimal("199.98"), total.getAmount());
}
๐ Running the Application
Development Setup:
- Database: H2 in-memory database for rapid development
-
Sample Data: Pre-loaded customers and products via
data.sql
-
H2 Console: Available at
/h2-console
for database inspection - API Documentation: RESTful endpoints with clear request/response formats
Configuration Highlights:
spring:
datasource:
url: jdbc:h2:mem:testdb
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
h2:
console:
enabled: true
๐ Business Scenarios Supported
Complete Order Flow:
- Customer Registration: Create customer with validated email
- Product Catalog: Browse available products with pricing
- Cart Management: Add multiple items to order
- Inventory Check: Real-time stock validation
- Order Confirmation: Atomic order processing with inventory updates
- Event Publishing: Downstream notifications for fulfillment
Sample API Usage:
# Create customer
POST /api/customers
{
"name": "John Doe",
"email": "john@example.com"
}
# Create order with multiple items
POST /api/orders
{
"customerId": "cust-001",
"items": [
{"productId": "prod-001", "quantity": 1},
{"productId": "prod-002", "quantity": 2}
]
}
๐ฏ DDD Benefits Demonstrated
1. Ubiquitous Language:
- Domain experts can read and understand the code
- Business terminology used throughout (Order, Customer, Product, not generic entities)
2. Business Logic Encapsulation:
- Rules like "cannot modify confirmed orders" are enforced by the domain model
- Calculations like order totals are handled by the appropriate aggregates
3. Bounded Context Isolation:
- Customer logic is separate from order logic
- Each context can evolve independently
4. Domain Events for Integration:
- Loose coupling between contexts
- Eventual consistency through event-driven architecture
5. Rich Domain Model:
- Objects have behavior, not just data
- Business invariants are maintained automatically
๐ฎ Extension Points
This architecture supports easy extension:
- New Bounded Contexts: Payment, Shipping, Inventory
- Additional Aggregates: ShoppingCart, Wishlist, Review
- Event Sourcing: Complete audit trail of domain events
- CQRS: Separate read/write models for complex queries
- Integration Events: External system notifications
๐ Production Considerations
For production deployment, consider:
- Database: Replace H2 with PostgreSQL/MySQL
- Caching: Redis for frequently accessed data
- Message Queues: RabbitMQ/Apache Kafka for event processing
- Security: Spring Security for authentication/authorization
- Monitoring: Actuator endpoints for health checks
- API Documentation: Swagger/OpenAPI specification
This project serves as a solid foundation for a production e-commerce system while demonstrating sophisticated DDD implementation patterns.
Top comments (0)