A comprehensive demonstration of Domain-Driven Design (DDD), Clean Architecture, Hexagonal Architecture, and Onion Architecture patterns using an e-commerce domain, with MCP (Model Context Protocol) server integration for AI assistant interaction.
This project showcases best practices for structuring a Spring Boot application with clean architecture principles. It implements three bounded contexts:
- Product Catalog - Product management with pricing and inventory
- Shopping Cart - Customer shopping cart with checkout functionality
- Portal - Application home page and navigation
- AI-Accessible Product Catalog via MCP server (Spring AI 1.1.0-M3)
- Complete Architecture Testing with ArchUnit (10 test suites)
- 16 Architecture Decision Records documenting design choices
- Shared Kernel pattern for cross-context value objects
- Framework-Independent Domain layer (no Spring/JPA in core)
Strategic Patterns:
- Bounded Contexts: Product Catalog, Shopping Cart
- Shared Kernel: Cross-context value objects (Money, Price, ProductId)
- Context Mapping: Contexts communicate via shared kernel
Tactical Patterns:
- Aggregates: Product, ShoppingCart
- Entities: CartItem
- Value Objects: ProductId, SKU, Price, Money, Quantity, Category, etc.
- Repositories: Interfaces in application layer, implementations in adapters
- Domain Services: PricingService, CartTotalCalculator
- Domain Events: ProductCreated, ProductPriceChanged, CartCheckedOut, CartItemAddedToCart
- Factories: ProductFactory
- Specifications: ProductAvailabilitySpecification
- Use Cases (Input Ports): Explicit use case interfaces with single responsibility
- Input/Output Models: Commands, Queries, and Response objects decouple layers
- Framework Independence: Domain and application layers are framework-agnostic
- Dependency Rule: Dependencies point inward (Infrastructure → Adapters → Application → Domain)
- Use Case Organization: One use case per operation (CreateProductUseCase, AddItemToCartUseCase, etc.)
- Input Ports: Use case interfaces (defined in application layer)
- Output Ports: Repository and service interfaces (defined in sharedkernel.application.port)
- Incoming Adapters (Primary): REST Controllers, MCP Server, Web MVC
- Outgoing Adapters (Secondary): In-memory repository implementations
Layers (from innermost to outermost):
- Domain Model (per bounded context) - Pure business logic, framework-independent
- Application Services (per bounded context) - Use case orchestration
- Adapters (per bounded context) - External interfaces
- Shared Kernel - Cross-context shared concepts
- Infrastructure - Cross-cutting concerns
src/main/java/de/sample/aiarchitecture/
├── sharedkernel/ # Shared Kernel (cross-context)
│ ├── domain/
│ │ ├── marker/ # DDD marker interfaces
│ │ │ ├── AggregateRoot.java
│ │ │ ├── Entity.java
│ │ │ ├── Value.java
│ │ │ ├── Repository.java
│ │ │ ├── DomainService.java
│ │ │ ├── Factory.java
│ │ │ ├── Specification.java
│ │ │ └── DomainEvent.java
│ │ └── common/ # Shared value objects
│ │ ├── ProductId.java # Cross-context ID
│ │ ├── Money.java # Cross-context value
│ │ └── Price.java # Cross-context value
│ ├── common/
│ │ └── annotation/ # Framework-agnostic annotations
│ │ └── AsyncInitialize.java
│ └── application/
│ └── port/ # Outbound ports (cross-context)
│ ├── UseCase.java # Base use case interface
│ ├── Repository.java # Base repository interface
│ └── DomainEventPublisher.java # Event publisher interface
│
├── product/ # Product Catalog bounded context
│ ├── domain/
│ │ ├── model/ # Domain model
│ │ │ ├── Product.java # Aggregate Root
│ │ │ ├── SKU.java # Value Objects
│ │ │ ├── ProductName.java
│ │ │ ├── ProductDescription.java
│ │ │ ├── ProductStock.java
│ │ │ ├── Category.java
│ │ │ ├── ProductFactory.java # Factory
│ │ │ └── ProductAvailabilitySpecification.java
│ │ ├── service/ # Domain services
│ │ │ └── PricingService.java
│ │ └── event/ # Domain events
│ │ ├── ProductCreated.java
│ │ └── ProductPriceChanged.java
│ ├── application/ # Application layer
│ │ ├── createproduct/ # Use case: Create Product
│ │ │ ├── CreateProductInputPort.java # Input port interface
│ │ │ ├── CreateProductUseCase.java # Use case implementation
│ │ │ ├── CreateProductCommand.java
│ │ │ └── CreateProductResponse.java
│ │ ├── getallproducts/ # Use case: Get All Products
│ │ │ ├── GetAllProductsInputPort.java
│ │ │ ├── GetAllProductsUseCase.java
│ │ │ ├── GetAllProductsQuery.java
│ │ │ └── GetAllProductsResponse.java
│ │ ├── getproductbyid/ # Use case: Get Product By ID
│ │ │ ├── GetProductByIdInputPort.java
│ │ │ ├── GetProductByIdUseCase.java
│ │ │ ├── GetProductByIdQuery.java
│ │ │ └── GetProductByIdResponse.java
│ │ ├── updateproductprice/ # Use case: Update Product Price
│ │ │ ├── UpdateProductPriceInputPort.java
│ │ │ ├── UpdateProductPriceUseCase.java
│ │ │ ├── UpdateProductPriceCommand.java
│ │ │ └── UpdateProductPriceResponse.java
│ │ ├── reduceproductstock/ # Use case: Reduce Product Stock
│ │ │ ├── ReduceProductStockInputPort.java
│ │ │ ├── ReduceProductStockUseCase.java
│ │ │ ├── ReduceProductStockCommand.java
│ │ │ └── ReduceProductStockResponse.java
│ │ └── shared/ # Shared output ports
│ │ └── ProductRepository.java # Repository interface
│ └── adapter/ # Adapters
│ ├── incoming/ # Incoming adapters (primary)
│ │ ├── api/
│ │ │ ├── ProductResource.java # REST API
│ │ │ ├── CreateProductRequest.java
│ │ │ └── ProductDto.java
│ │ ├── mcp/
│ │ │ └── ProductCatalogMcpTools.java
│ │ ├── web/
│ │ │ └── ProductPageController.java
│ │ └── event/
│ │ └── ProductEventListener.java
│ └── outgoing/ # Outgoing adapters (secondary)
│ └── persistence/
│ ├── InMemoryProductRepository.java
│ └── SampleDataInitializer.java
│
├── cart/ # Shopping Cart bounded context
│ ├── domain/
│ │ ├── model/ # Domain model
│ │ │ ├── ShoppingCart.java # Aggregate Root
│ │ │ ├── CartItem.java # Entity
│ │ │ ├── CartId.java # Value Objects
│ │ │ ├── CartItemId.java
│ │ │ ├── CustomerId.java
│ │ │ ├── Quantity.java
│ │ │ └── CartStatus.java
│ │ ├── service/ # Domain services
│ │ │ └── CartTotalCalculator.java
│ │ └── event/ # Domain events
│ │ ├── CartCheckedOut.java
│ │ └── CartItemAddedToCart.java
│ ├── application/ # Application layer
│ │ ├── createcart/ # Use case: Create Cart
│ │ │ ├── CreateCartInputPort.java
│ │ │ ├── CreateCartUseCase.java
│ │ │ ├── CreateCartCommand.java
│ │ │ └── CreateCartResponse.java
│ │ ├── additemtocart/ # Use case: Add Item to Cart
│ │ │ ├── AddItemToCartInputPort.java
│ │ │ ├── AddItemToCartUseCase.java
│ │ │ ├── AddItemToCartCommand.java
│ │ │ └── AddItemToCartResponse.java
│ │ ├── checkoutcart/ # Use case: Checkout Cart
│ │ │ ├── CheckoutCartInputPort.java
│ │ │ ├── CheckoutCartUseCase.java
│ │ │ ├── CheckoutCartCommand.java
│ │ │ └── CheckoutCartResponse.java
│ │ ├── getallcarts/ # Use case: Get All Carts
│ │ │ ├── GetAllCartsInputPort.java
│ │ │ ├── GetAllCartsUseCase.java
│ │ │ ├── GetAllCartsQuery.java
│ │ │ └── GetAllCartsResponse.java
│ │ ├── getcartbyid/ # Use case: Get Cart By ID
│ │ │ ├── GetCartByIdInputPort.java
│ │ │ ├── GetCartByIdUseCase.java
│ │ │ ├── GetCartByIdQuery.java
│ │ │ └── GetCartByIdResponse.java
│ │ ├── getorcreateactivecart/ # Use case: Get or Create Active Cart
│ │ │ ├── GetOrCreateActiveCartInputPort.java
│ │ │ ├── GetOrCreateActiveCartUseCase.java
│ │ │ ├── GetOrCreateActiveCartCommand.java
│ │ │ └── GetOrCreateActiveCartResponse.java
│ │ ├── removeitemfromcart/ # Use case: Remove Item from Cart
│ │ │ ├── RemoveItemFromCartInputPort.java
│ │ │ ├── RemoveItemFromCartUseCase.java
│ │ │ ├── RemoveItemFromCartCommand.java
│ │ │ └── RemoveItemFromCartResponse.java
│ │ └── shared/ # Shared output ports
│ │ └── ShoppingCartRepository.java
│ └── adapter/ # Adapters
│ ├── incoming/ # Incoming adapters
│ │ ├── api/
│ │ │ ├── ShoppingCartResource.java
│ │ │ ├── AddToCartRequest.java
│ │ │ ├── ShoppingCartDto.java
│ │ │ └── ShoppingCartDtoConverter.java
│ │ └── event/
│ │ └── CartEventListener.java
│ └── outgoing/ # Outgoing adapters
│ └── persistence/
│ └── InMemoryShoppingCartRepository.java
│
├── portal/ # Portal bounded context
│ └── adapter/ # Adapters
│ └── incoming/ # Incoming adapters
│ └── web/
│ └── HomePageController.java
│
└── infrastructure/ # Infrastructure (cross-cutting)
└── config/ # Spring configuration
├── SecurityConfiguration.java
├── TransactionConfiguration.java
├── SpringDomainEventPublisher.java
├── AsyncConfiguration.java
└── AsyncInitializationProcessor.java
- Java 21
- Gradle 9.1 or higher
# Build the project
./gradlew build
# Run the application
./gradlew bootRunThe application will start on http://localhost:8080
curl http://localhost:8080/actuator/healthcurl http://localhost:8080/api/productscurl http://localhost:8080/api/products/{productId}curl http://localhost:8080/api/products/sku/LAPTOP-001curl -X POST http://localhost:8080/api/products \
-H "Content-Type: application/json" \
-d '{
"sku": "TEST-001",
"name": "Test Product",
"description": "A test product",
"price": 99.99,
"category": "Electronics",
"stock": 10
}'curl -X DELETE http://localhost:8080/api/products/{productId}curl -X POST "http://localhost:8080/api/carts?customerId=customer-123"curl http://localhost:8080/api/carts/customer/customer-123/activecurl http://localhost:8080/api/carts/{cartId}curl -X POST http://localhost:8080/api/carts/{cartId}/items \
-H "Content-Type: application/json" \
-d '{
"productId": "{productId}",
"quantity": 2
}'curl -X DELETE http://localhost:8080/api/carts/{cartId}/items/{itemId}curl -X POST http://localhost:8080/api/carts/{cartId}/checkoutcurl -X DELETE http://localhost:8080/api/carts/{cartId}The product catalog is accessible via MCP server for AI assistants like Claude. The server runs on http://localhost:8080/mcp using streamable HTTP (HTTP + Server-Sent Events).
all-products
- Returns all products in the catalog with complete details
- No parameters required
product-by-sku
- Find product by SKU (e.g., "LAPTOP-001")
- Parameters:
sku(String) - must contain uppercase letters, numbers, hyphens only
product-by-category
- Find all products in a category
- Parameters:
categoryName(String) - Valid categories: Electronics, Clothing, Books, Home & Garden, Sports & Outdoors
product-by-id
- Get product by internal UUID
- Parameters:
id(String) - UUID format
Create .mcp.json in the project root:
{
"mcpServers": {
"product-catalog": {
"type": "http",
"url": "http://localhost:8080/mcp"
}
}
}AI assistants will automatically discover and connect to the MCP server.
See: docs/integrations/mcp-server-integration.md for detailed MCP server documentation.
The application initializes with 11 sample products across different categories:
- Electronics: Laptop, Smartphone, Tablet
- Clothing: T-Shirt, Jeans
- Books: DDD Book, Clean Architecture Book
- Home & Garden: Office Chair, Standing Desk
- Sports: Yoga Mat, Dumbbells
For comprehensive architecture documentation, see:
- docs/architecture/ - Complete architecture documentation
- docs/architecture/architecture-principles.md - Detailed patterns and principles
- CLAUDE.md - Development guidelines and best practices
Domain Layer (per bounded context) - Framework-independent business logic
- No Spring/JPA annotations in domain models
- All business rules in domain objects
- Organized into: domain.model, domain.service, domain.event
- Dependencies point inward toward domain
Application Layer (per bounded context) - Use case orchestration
- Thin coordination layer (no business logic)
- Manages transactions and domain event publishing
- Defines ports: Input ports (use cases) and output ports (repositories)
- One use case class per operation
Adapter Layer (per bounded context) - External interfaces
- Incoming Adapters (Primary):
adapter.incoming.api- REST APIs (@RestController) returning JSONadapter.incoming.web- Web MVC (@Controller) returning HTMLadapter.incoming.mcp- MCP Server for AI assistants
- Outgoing Adapters (Secondary):
adapter.outgoing.persistence- Repository implementations
- DTO conversion happens here
- Adapters don't communicate directly
Shared Kernel - Cross-context shared concepts
sharedkernel.domain.marker- DDD marker interfacessharedkernel.domain.common- Shared value objects (Money, ProductId, Price)sharedkernel.common.annotation- Framework-agnostic custom annotationssharedkernel.application.port- Outbound ports (UseCase, Repository, DomainEventPublisher)
Infrastructure Layer - Cross-cutting concerns
infrastructure.config- Spring configuration and framework integrations
Architecture rules are actively enforced using ArchUnit with 10 comprehensive test suites:
./gradlew test-architectureTest Suites:
- DddTacticalPatternsArchUnitTest - Aggregate, Entity, Value Object, Repository patterns
- DddAdvancedPatternsArchUnitTest - Domain Events, Services, Factories, Specifications
- DddStrategicPatternsArchUnitTest - Bounded Context isolation
- HexagonalArchitectureArchUnitTest - Ports and Adapters rules
- OnionArchitectureArchUnitTest - Dependency flow rules
- LayeredArchitectureArchUnitTest - Layer access rules
- NamingConventionsArchUnitTest - Naming standards
- PackageCyclesArchUnitTest - Circular dependency detection
- UseCasePatternsArchUnitTest - Application service patterns
- BaseArchUnitTest - Common test infrastructure
These tests verify:
- Domain layer has no framework dependencies
- Aggregates only reference other aggregates by ID
- Value objects are immutable records
- Repository interfaces live in domain
- No circular package dependencies
- Naming conventions are followed
- Bounded contexts are properly isolated
./gradlew test- In-Memory Storage: Uses ConcurrentHashMap for simplicity; production would use JPA/database
- Security: Permits all requests for demo purposes
- Price Snapshot: Cart items store price at time of addition (common e-commerce pattern)
- Package-Private Constructors: CartItem can only be created through ShoppingCart
- Immutable Value Objects: All value objects are Java records
- MCP as Primary Adapter: MCP server implemented as incoming adapter, maintaining clean architecture
- Read-Only MCP Tools: AI access limited to queries for safety
- Shared Kernel: Cross-context value objects shared between bounded contexts
Architecture Decision Records: See docs/architecture/adr/README.md for 16 documented architectural decisions.
- Price must be positive
- Stock cannot be negative
- SKU must be unique
- Cannot modify checked-out cart
- Cannot checkout empty cart
- Each product appears once (quantities combined)
- Cart stores price snapshot at time of addition
- Architecture Documentation - Complete architecture guide
- Architecture Principles - Detailed DDD, Hexagonal, and Onion patterns
- CLAUDE.md - Development guidelines and documentation standards
- Domain-Driven Design by Eric Evans
- Implementing Domain-Driven Design by Vaughn Vernon
- Hexagonal Architecture by Alistair Cockburn
- Clean Architecture by Robert C. Martin
- ArchUnit - Architecture testing framework
This is a sample project for educational purposes.