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 eight bounded contexts:
- Product Catalog - Product management with enriched views (pricing + stock from other contexts)
- Shopping Cart - Customer shopping cart management with article price resolution
- Checkout - Multi-step checkout flow with session management
- Account - User registration and authentication
- Portal - Application home page and navigation
- Inventory - Stock level management (Open Host Service)
- Pricing - Product pricing management (Open Host Service)
- Backoffice - Event publication log viewer and administrative tools
- AI-Accessible Product Catalog via MCP server (Spring AI 2.0.0-M2)
- Spring Modulith for framework-enforced module boundaries and event-driven cross-module communication
- Complete Architecture Testing with ArchUnit (10 test suites) and Spring Modulith verification
- 24 Architecture Decision Records documenting design choices
- Shared Kernel pattern for cross-context value objects
- Framework-Independent Domain layer (no Spring/JPA in core)
- Multi-step Checkout Flow with 5 steps and session management
- Specification Pattern with Visitor for database-agnostic cart filtering
- Multiple Persistence Strategies (InMemory, JPA, JDBC) for shopping cart
Strategic Patterns:
- Bounded Contexts: Product Catalog, Shopping Cart, Checkout, Account, Portal, Inventory, Pricing, Backoffice
- Shared Kernel: Cross-context value objects (Money, Price, ProductId, UserId)
- Context Mapping: Contexts communicate via shared kernel, domain events, and integration events
- Open Host Service: ProductCatalogService, InventoryService, PricingService, and CartService provide cross-context APIs
Tactical Patterns:
- Aggregates: Product, ShoppingCart, CheckoutSession, Account, StockLevel, ProductPrice
- Entities: CartItem, CheckoutLineItem
- Value Objects: ProductId, SKU, Price, Money, Quantity, Category, BuyerInfo, DeliveryAddress, Email, HashedPassword, etc.
- Repositories: Interfaces in application layer, implementations in adapters
- Domain Services: PricingService, CartTotalCalculator, CheckoutStepValidator
- Domain Events: ProductCreated, CartCheckedOut, CartItemAddedToCart, CartItemQuantityChanged, ProductRemovedFromCart, CartCleared, CheckoutSessionStarted, CheckoutConfirmed, AccountRegistered, PriceChanged, StockChanged, etc.
- Factories: ProductFactory, EnrichedCartFactory, CheckoutCartFactory
- Specifications: ProductAvailabilitySpecification, CartSpecification (with Visitor pattern: ActiveCart, HasMinTotal, HasAnyAvailableItem, LastUpdatedBefore, CustomerAllowsMarketing)
- Use Cases (Input Ports): Explicit use case interfaces with single responsibility
- Input/Output Models: Commands, Queries, and Result objects decouple layers
- Framework Independence: Domain layer is framework-agnostic; application layer uses minimal framework annotations pragmatically
- Dependency Rule: Dependencies point inward (Infrastructure → Adapters → Application → Domain)
- Use Case Organization: One use case per operation (CreateProductUseCase, AddItemToCartUseCase, StartCheckoutUseCase, etc.)
- Input Ports: Use case interfaces (defined in application layer)
- Output Ports: Repository and service interfaces (defined in application/shared)
- Incoming Adapters (Primary): REST Controllers, MCP Server, Web MVC, Event Consumers, Open Host Services
- Outgoing Adapters (Secondary): In-memory, JPA, and JDBC 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)
│ ├── marker/ # Architectural markers
│ │ ├── tactical/ # DDD tactical patterns
│ │ │ ├── Id.java # Identity marker
│ │ │ ├── Entity.java
│ │ │ ├── Value.java
│ │ │ ├── AggregateRoot.java
│ │ │ ├── BaseAggregateRoot.java
│ │ │ ├── DomainEvent.java
│ │ │ ├── IntegrationEvent.java
│ │ │ ├── DomainService.java
│ │ │ ├── Factory.java
│ │ │ └── Specification.java
│ │ ├── strategic/ # DDD strategic patterns
│ │ │ ├── BoundedContext.java
│ │ │ ├── SharedKernel.java
│ │ │ └── OpenHostService.java
│ │ ├── port/ # Hexagonal Architecture ports
│ │ │ ├── in/ # Input ports (driving)
│ │ │ │ ├── InputPort.java
│ │ │ │ └── UseCase.java
│ │ │ └── out/ # Output ports (driven)
│ │ │ ├── OutputPort.java
│ │ │ ├── Repository.java
│ │ │ ├── DomainEventPublisher.java
│ │ │ └── IdentityProvider.java
│ │ └── infrastructure/ # Framework integration markers
│ │ └── AsyncInitialize.java
│ ├── domain/
│ │ ├── model/ # Shared value objects
│ │ │ ├── ProductId.java # Cross-context ID
│ │ │ ├── UserId.java # Cross-context ID
│ │ │ ├── Money.java # Cross-context value
│ │ │ ├── Price.java # Cross-context value
│ │ │ ├── PageResult.java # Pagination result
│ │ │ └── PagingRequest.java # Pagination request
│ │ └── specification/ # Composable specification pattern
│ │ ├── CompositeSpecification.java
│ │ ├── AndSpecification.java
│ │ ├── OrSpecification.java
│ │ ├── NotSpecification.java
│ │ └── SpecificationVisitor.java
│ └── adapter/
│ └── outgoing/
│ └── event/
│ └── SpringDomainEventPublisher.java # Domain event publishing
│
├── product/ # Product Catalog bounded context
│ ├── api/ # Spring Modulith @NamedInterface API
│ │ └── ProductCatalogService.java # Open Host Service
│ ├── events/ # Spring Modulith integration events
│ │ └── ProductCreatedEvent.java
│ ├── domain/
│ │ ├── model/ # Domain model
│ │ │ ├── Product.java # Aggregate Root
│ │ │ ├── SKU.java # Value Objects
│ │ │ ├── ProductName.java
│ │ │ ├── ProductDescription.java
│ │ │ ├── ProductArticle.java
│ │ │ ├── ProductStock.java
│ │ │ ├── EnrichedProduct.java # Enriched read model
│ │ │ ├── Category.java
│ │ │ ├── ImageUrl.java
│ │ │ └── ProductFactory.java # Factory
│ │ ├── specification/ # Specifications
│ │ │ └── ProductAvailabilitySpecification.java
│ │ ├── service/ # Domain services
│ │ │ └── PricingService.java
│ │ └── event/ # Domain events
│ │ ├── ProductCreated.java
│ │ ├── ProductNameChanged.java
│ │ ├── ProductDescriptionChanged.java
│ │ └── ProductCategoryChanged.java
│ ├── application/ # Application layer
│ │ ├── createproduct/ # Use case: Create Product
│ │ │ ├── CreateProductInputPort.java
│ │ │ ├── CreateProductUseCase.java
│ │ │ ├── CreateProductCommand.java
│ │ │ └── CreateProductResult.java
│ │ ├── getallproducts/ # Use case: Get All Products
│ │ │ ├── GetAllProductsInputPort.java
│ │ │ ├── GetAllProductsUseCase.java
│ │ │ ├── GetAllProductsQuery.java
│ │ │ └── GetAllProductsResult.java
│ │ ├── getproductbyid/ # Use case: Get Product By ID
│ │ │ ├── GetProductByIdInputPort.java
│ │ │ ├── GetProductByIdUseCase.java
│ │ │ ├── GetProductByIdQuery.java
│ │ │ └── GetProductByIdResult.java
│ │ ├── reduceproductstock/ # Use case: Reduce Product Stock
│ │ │ ├── ReduceProductStockInputPort.java
│ │ │ ├── ReduceProductStockUseCase.java
│ │ │ ├── ReduceProductStockCommand.java
│ │ │ └── ReduceProductStockResult.java
│ │ └── shared/ # Shared output ports
│ │ ├── ProductRepository.java
│ │ ├── PricingDataPort.java # Port for pricing data from Pricing context
│ │ └── ProductStockDataPort.java # Port for stock data from Inventory context
│ ├── infrastructure/ # Per-context infrastructure
│ │ └── ProductDomainConfiguration.java
│ └── adapter/ # Adapters
│ ├── incoming/ # Incoming adapters (primary)
│ │ ├── api/
│ │ │ ├── ProductResource.java
│ │ │ ├── CreateProductRequest.java
│ │ │ ├── ProductDto.java
│ │ │ └── ProductDtoConverter.java
│ │ ├── mcp/
│ │ │ └── ProductCatalogMcpToolProvider.java
│ │ ├── web/
│ │ │ ├── ProductPageController.java
│ │ │ ├── ProductCatalogPageViewModel.java
│ │ │ └── ProductDetailPageViewModel.java
│ │ └── event/
│ │ ├── ProductEventConsumer.java
│ │ └── acl/
│ │ └── CheckoutEventTranslator.java # Anti-corruption layer
│ └── outgoing/ # Outgoing adapters (secondary)
│ ├── event/
│ │ └── ProductCreatedEventPublisher.java
│ ├── persistence/
│ │ └── InMemoryProductRepository.java
│ ├── pricing/
│ │ └── PricingDataAdapter.java # Adapter to Pricing context
│ └── inventory/
│ └── InventoryStockDataAdapter.java # Adapter to Inventory context
│
├── cart/ # Shopping Cart bounded context
│ ├── api/ # Spring Modulith @NamedInterface API
│ │ └── CartService.java # Open Host Service
│ ├── events/ # Spring Modulith integration events
│ │ ├── CartCheckedOutEvent.java
│ │ ├── CartCompletionTrigger.java
│ │ └── CartContentsChangedEvent.java
│ ├── domain/
│ │ ├── model/ # Domain model
│ │ │ ├── ShoppingCart.java # Aggregate Root
│ │ │ ├── CartItem.java # Entity
│ │ │ ├── CartId.java # Value Objects
│ │ │ ├── CartItemId.java
│ │ │ ├── CustomerId.java
│ │ │ ├── Quantity.java
│ │ │ ├── CartStatus.java
│ │ │ ├── ArticlePrice.java # Price from Pricing context
│ │ │ ├── ArticlePriceResolver.java # Resolver for fresh prices
│ │ │ ├── CartArticle.java # Article data for cart items
│ │ │ ├── EnrichedCart.java # Enriched read model
│ │ │ ├── EnrichedCartFactory.java # Factory for enriched carts
│ │ │ ├── EnrichedCartItem.java # Enriched cart item with prices
│ │ │ └── CartValidationResult.java # Validation result
│ │ ├── specification/ # Cart specifications (Visitor pattern)
│ │ │ ├── CartSpecification.java # Base specification interface
│ │ │ ├── CartSpecificationVisitor.java # Visitor for database-agnostic filtering
│ │ │ ├── ComposedCartSpecification.java
│ │ │ ├── ActiveCart.java
│ │ │ ├── HasMinTotal.java
│ │ │ ├── HasAnyAvailableItem.java
│ │ │ ├── LastUpdatedBefore.java
│ │ │ └── CustomerAllowsMarketing.java
│ │ ├── service/ # Domain services
│ │ │ └── CartTotalCalculator.java
│ │ └── event/ # Domain events
│ │ ├── CartCheckedOut.java
│ │ ├── CartItemAddedToCart.java
│ │ ├── CartItemQuantityChanged.java
│ │ ├── ProductRemovedFromCart.java
│ │ ├── CartCleared.java
│ │ ├── CartCompleted.java
│ │ └── CartAbandoned.java
│ ├── application/ # Application layer
│ │ ├── createcart/ # Use case: Create Cart
│ │ │ ├── CreateCartInputPort.java
│ │ │ ├── CreateCartUseCase.java
│ │ │ ├── CreateCartCommand.java
│ │ │ └── CreateCartResult.java
│ │ ├── additemtocart/ # Use case: Add Item to Cart
│ │ │ ├── AddItemToCartInputPort.java
│ │ │ ├── AddItemToCartUseCase.java
│ │ │ ├── AddItemToCartCommand.java
│ │ │ └── AddItemToCartResult.java
│ │ ├── checkoutcart/ # Use case: Checkout Cart
│ │ │ ├── CheckoutCartInputPort.java
│ │ │ ├── CheckoutCartUseCase.java
│ │ │ ├── CheckoutCartCommand.java
│ │ │ └── CheckoutCartResult.java
│ │ ├── getallcarts/ # Use case: Get All Carts
│ │ │ ├── GetAllCartsInputPort.java
│ │ │ ├── GetAllCartsUseCase.java
│ │ │ ├── GetAllCartsQuery.java
│ │ │ └── GetAllCartsResult.java
│ │ ├── getcartbyid/ # Use case: Get Cart By ID
│ │ │ ├── GetCartByIdInputPort.java
│ │ │ ├── GetCartByIdUseCase.java
│ │ │ ├── GetCartByIdQuery.java
│ │ │ └── GetCartByIdResult.java
│ │ ├── getorcreateactivecart/ # Use case: Get or Create Active Cart
│ │ │ ├── GetOrCreateActiveCartInputPort.java
│ │ │ ├── GetOrCreateActiveCartUseCase.java
│ │ │ ├── GetOrCreateActiveCartCommand.java
│ │ │ └── GetOrCreateActiveCartResult.java
│ │ ├── removeitemfromcart/ # Use case: Remove Item from Cart
│ │ │ ├── RemoveItemFromCartInputPort.java
│ │ │ ├── RemoveItemFromCartUseCase.java
│ │ │ ├── RemoveItemFromCartCommand.java
│ │ │ └── RemoveItemFromCartResult.java
│ │ ├── mergecarts/ # Use case: Merge Carts
│ │ │ ├── MergeCartsInputPort.java
│ │ │ ├── MergeCartsUseCase.java
│ │ │ ├── MergeCartsCommand.java
│ │ │ ├── MergeCartsResult.java
│ │ │ └── CartMergeStrategy.java
│ │ ├── getcartmergeoptions/ # Use case: Get Cart Merge Options
│ │ │ ├── GetCartMergeOptionsInputPort.java
│ │ │ ├── GetCartMergeOptionsUseCase.java
│ │ │ ├── GetCartMergeOptionsQuery.java
│ │ │ └── GetCartMergeOptionsResult.java
│ │ ├── completecart/ # Use case: Complete Cart (after checkout)
│ │ │ ├── CompleteCartInputPort.java
│ │ │ ├── CompleteCartUseCase.java
│ │ │ ├── CompleteCartCommand.java
│ │ │ └── CompleteCartResult.java
│ │ ├── recovercart/ # Use case: Recover Cart on Login
│ │ │ ├── RecoverCartOnLoginInputPort.java
│ │ │ ├── RecoverCartOnLoginUseCase.java
│ │ │ ├── RecoverCartOnLoginCommand.java
│ │ │ └── RecoverCartOnLoginResult.java
│ │ └── shared/ # Shared output ports
│ │ ├── ShoppingCartRepository.java
│ │ ├── ArticleDataPort.java # Port for article data (prices + stock)
│ │ └── ProductDataPort.java # Port for product data from other context
│ ├── infrastructure/ # Per-context infrastructure
│ │ └── CartDomainConfiguration.java
│ └── adapter/ # Adapters
│ ├── incoming/ # Incoming adapters
│ │ ├── api/
│ │ │ ├── ShoppingCartResource.java
│ │ │ ├── AddToCartRequest.java
│ │ │ ├── ShoppingCartDto.java
│ │ │ ├── ShoppingCartListDto.java
│ │ │ ├── CartItemDto.java
│ │ │ └── ShoppingCartDtoConverter.java
│ │ ├── web/
│ │ │ ├── CartPageController.java
│ │ │ ├── CartPageViewModel.java
│ │ │ ├── CartMergePageController.java
│ │ │ ├── CartMergePageViewModel.java
│ │ │ ├── MiniBasketControllerAdvice.java
│ │ │ └── MiniBasketItemViewModel.java
│ │ └── event/
│ │ ├── CartEventConsumer.java
│ │ └── CartCompletionEventConsumer.java
│ └── outgoing/ # Outgoing adapters
│ ├── event/
│ │ ├── CartCheckedOutEventPublisher.java
│ │ └── CartContentsChangedEventPublisher.java
│ ├── product/
│ │ └── CompositeArticleDataAdapter.java # Composite adapter for article data
│ └── persistence/
│ ├── InMemoryShoppingCartRepository.java
│ ├── JdbcShoppingCartRepository.java
│ ├── jpa/ # JPA persistence alternative
│ │ ├── JpaShoppingCartRepository.java
│ │ ├── CartJpaRepository.java
│ │ ├── CartEntity.java
│ │ ├── CartItemEntity.java
│ │ └── CartSpecToJpa.java # Specification visitor for JPA
│ └── jdbc/
│ └── CartSpecToJdbc.java # Specification visitor for JDBC
│
├── checkout/ # Checkout bounded context
│ ├── events/ # Spring Modulith integration events
│ │ └── CheckoutConfirmedEvent.java
│ ├── domain/
│ │ ├── model/ # Domain model
│ │ │ ├── CheckoutSession.java # Aggregate Root
│ │ │ ├── CheckoutLineItem.java # Entity
│ │ │ ├── CheckoutSessionId.java # Value Objects
│ │ │ ├── CheckoutLineItemId.java
│ │ │ ├── CheckoutStep.java # Enum: BUYER_INFO, DELIVERY, PAYMENT, REVIEW, CONFIRMATION
│ │ │ ├── CheckoutSessionStatus.java
│ │ │ ├── CheckoutTotals.java
│ │ │ ├── CheckoutValidationResult.java
│ │ │ ├── BuyerInfo.java
│ │ │ ├── DeliveryAddress.java
│ │ │ ├── ShippingOption.java
│ │ │ ├── PaymentSelection.java
│ │ │ ├── PaymentProviderId.java
│ │ │ ├── CustomerId.java
│ │ │ ├── CartId.java
│ │ │ ├── CheckoutArticle.java # Article data for checkout
│ │ │ ├── CheckoutArticlePriceResolver.java # Price resolver
│ │ │ ├── CheckoutCart.java # Cart snapshot for checkout
│ │ │ ├── CheckoutCartFactory.java # Factory for checkout cart
│ │ │ └── EnrichedCheckoutLineItem.java # Enriched line item with prices
│ │ ├── readmodel/ # Read Models
│ │ │ ├── CheckoutCartSnapshot.java # Cart snapshot for display
│ │ │ └── LineItemSnapshot.java # Line item snapshot for display
│ │ ├── service/ # Domain services
│ │ │ └── CheckoutStepValidator.java
│ │ └── event/ # Domain events
│ │ ├── CheckoutSessionStarted.java
│ │ ├── BuyerInfoSubmitted.java
│ │ ├── DeliverySubmitted.java
│ │ ├── PaymentSubmitted.java
│ │ ├── CheckoutConfirmed.java
│ │ ├── CheckoutCompleted.java
│ │ ├── CheckoutAbandoned.java
│ │ └── CheckoutExpired.java
│ ├── application/ # Application layer
│ │ ├── startcheckout/ # Use case: Start Checkout
│ │ │ ├── StartCheckoutInputPort.java
│ │ │ ├── StartCheckoutUseCase.java
│ │ │ ├── StartCheckoutCommand.java
│ │ │ └── StartCheckoutResult.java
│ │ ├── submitbuyerinfo/ # Use case: Submit Buyer Info
│ │ │ ├── SubmitBuyerInfoInputPort.java
│ │ │ ├── SubmitBuyerInfoUseCase.java
│ │ │ ├── SubmitBuyerInfoCommand.java
│ │ │ └── SubmitBuyerInfoResult.java
│ │ ├── submitdelivery/ # Use case: Submit Delivery
│ │ │ ├── SubmitDeliveryInputPort.java
│ │ │ ├── SubmitDeliveryUseCase.java
│ │ │ ├── SubmitDeliveryCommand.java
│ │ │ └── SubmitDeliveryResult.java
│ │ ├── submitpayment/ # Use case: Submit Payment
│ │ │ ├── SubmitPaymentInputPort.java
│ │ │ ├── SubmitPaymentUseCase.java
│ │ │ ├── SubmitPaymentCommand.java
│ │ │ └── SubmitPaymentResult.java
│ │ ├── confirmcheckout/ # Use case: Confirm Checkout
│ │ │ ├── ConfirmCheckoutInputPort.java
│ │ │ ├── ConfirmCheckoutUseCase.java
│ │ │ ├── ConfirmCheckoutCommand.java
│ │ │ └── ConfirmCheckoutResult.java
│ │ ├── getcheckoutsession/ # Use case: Get Checkout Session
│ │ │ ├── GetCheckoutSessionInputPort.java
│ │ │ ├── GetCheckoutSessionUseCase.java
│ │ │ ├── GetCheckoutSessionQuery.java
│ │ │ └── GetCheckoutSessionResult.java
│ │ ├── getactivecheckoutsession/ # Use case: Get Active Checkout Session
│ │ │ ├── GetActiveCheckoutSessionInputPort.java
│ │ │ ├── GetActiveCheckoutSessionUseCase.java
│ │ │ ├── GetActiveCheckoutSessionQuery.java
│ │ │ └── GetActiveCheckoutSessionResult.java
│ │ ├── getconfirmedcheckoutsession/ # Use case: Get Confirmed Checkout Session
│ │ │ ├── GetConfirmedCheckoutSessionInputPort.java
│ │ │ ├── GetConfirmedCheckoutSessionUseCase.java
│ │ │ ├── GetConfirmedCheckoutSessionQuery.java
│ │ │ └── GetConfirmedCheckoutSessionResult.java
│ │ ├── getshippingoptions/ # Use case: Get Shipping Options
│ │ │ ├── GetShippingOptionsInputPort.java
│ │ │ ├── GetShippingOptionsUseCase.java
│ │ │ ├── GetShippingOptionsQuery.java
│ │ │ └── GetShippingOptionsResult.java
│ │ ├── getpaymentproviders/ # Use case: Get Payment Providers
│ │ │ ├── GetPaymentProvidersInputPort.java
│ │ │ ├── GetPaymentProvidersUseCase.java
│ │ │ ├── GetPaymentProvidersQuery.java
│ │ │ └── GetPaymentProvidersResult.java
│ │ ├── synccheckoutwithcart/ # Use case: Sync Checkout with Cart
│ │ │ ├── SyncCheckoutWithCartInputPort.java
│ │ │ ├── SyncCheckoutWithCartUseCase.java
│ │ │ ├── SyncCheckoutWithCartCommand.java
│ │ │ └── SyncCheckoutWithCartResult.java
│ │ └── shared/ # Shared output ports
│ │ ├── CheckoutSessionRepository.java
│ │ ├── CartDataPort.java
│ │ ├── CartData.java
│ │ ├── CheckoutArticleDataPort.java # Port for article data (prices + stock)
│ │ ├── ProductInfoPort.java
│ │ ├── PaymentProvider.java
│ │ └── PaymentProviderRegistry.java
│ └── adapter/ # Adapters
│ ├── incoming/ # Incoming adapters
│ │ ├── web/
│ │ │ ├── StartCheckoutPageController.java
│ │ │ ├── BuyerInfoPageController.java
│ │ │ ├── BuyerInfoPageViewModel.java
│ │ │ ├── DeliveryPageController.java
│ │ │ ├── DeliveryPageViewModel.java
│ │ │ ├── PaymentPageController.java
│ │ │ ├── PaymentPageViewModel.java
│ │ │ ├── ReviewPageController.java
│ │ │ ├── ReviewPageViewModel.java
│ │ │ ├── ConfirmationPageController.java
│ │ │ └── ConfirmationPageViewModel.java
│ │ └── event/
│ │ └── CartChangeEventConsumer.java
│ └── outgoing/ # Outgoing adapters
│ ├── event/
│ │ └── CheckoutConfirmedEventPublisher.java
│ ├── persistence/
│ │ └── InMemoryCheckoutSessionRepository.java
│ ├── cart/
│ │ └── CartDataAdapter.java
│ ├── product/
│ │ ├── CompositeCheckoutArticleDataAdapter.java # Composite adapter
│ │ └── ProductInfoAdapter.java
│ └── payment/
│ ├── MockPaymentProvider.java
│ └── InMemoryPaymentProviderRegistry.java
│
├── account/ # Account bounded context
│ ├── domain/
│ │ ├── model/ # Domain model
│ │ │ ├── Account.java # Aggregate Root
│ │ │ ├── AccountId.java # Value Objects
│ │ │ ├── Email.java
│ │ │ ├── HashedPassword.java
│ │ │ └── AccountStatus.java
│ │ ├── service/ # Domain services
│ │ │ └── PasswordHasher.java # Interface for password hashing
│ │ └── event/ # Domain events
│ │ ├── AccountRegistered.java
│ │ ├── AccountLinkedToIdentity.java
│ │ ├── AccountLoggedIn.java
│ │ ├── AccountPasswordChanged.java
│ │ ├── AccountSuspended.java
│ │ ├── AccountReactivated.java
│ │ └── AccountClosed.java
│ ├── application/ # Application layer
│ │ ├── registeraccount/ # Use case: Register Account
│ │ │ ├── RegisterAccountInputPort.java
│ │ │ ├── RegisterAccountUseCase.java
│ │ │ ├── RegisterAccountCommand.java
│ │ │ └── RegisterAccountResult.java
│ │ ├── authenticateaccount/ # Use case: Authenticate Account
│ │ │ ├── AuthenticateAccountInputPort.java
│ │ │ ├── AuthenticateAccountUseCase.java
│ │ │ ├── AuthenticateAccountCommand.java
│ │ │ └── AuthenticateAccountResult.java
│ │ └── shared/ # Shared output ports
│ │ ├── AccountRepository.java
│ │ ├── RegisteredUserValidator.java
│ │ ├── TokenService.java
│ │ └── IdentitySession.java
│ ├── infrastructure/ # Per-context infrastructure
│ │ └── SecurityConfiguration.java
│ └── adapter/ # Adapters
│ ├── incoming/ # Incoming adapters
│ │ ├── api/
│ │ │ ├── AuthResource.java
│ │ │ ├── LoginRequest.java
│ │ │ ├── LoginResponse.java
│ │ │ ├── RegisterRequest.java
│ │ │ └── RegisterResponse.java
│ │ └── web/
│ │ ├── LoginPageController.java
│ │ └── RegisterPageController.java
│ └── outgoing/ # Outgoing adapters
│ ├── persistence/
│ │ └── InMemoryAccountRepository.java
│ └── security/
│ ├── SpringSecurityPasswordHasher.java
│ ├── AccountBasedRegisteredUserValidator.java
│ ├── SpringSecurityIdentityProvider.java
│ ├── JwtTokenService.java
│ ├── JwtIdentitySession.java
│ ├── JwtIdentity.java
│ ├── JwtIdentityType.java
│ ├── JwtProperties.java
│ └── JwtAuthenticationFilter.java
│
├── inventory/ # Inventory bounded context
│ ├── api/ # Spring Modulith @NamedInterface API
│ │ └── InventoryService.java # Open Host Service
│ ├── events/ # Spring Modulith integration events
│ │ └── StockReductionTrigger.java
│ ├── domain/
│ │ ├── model/ # Domain model
│ │ │ ├── StockLevel.java # Aggregate Root
│ │ │ ├── StockLevelId.java # Value Objects
│ │ │ └── StockQuantity.java
│ │ └── event/ # Domain events
│ │ ├── StockLevelCreated.java
│ │ ├── StockChanged.java
│ │ ├── StockIncreased.java
│ │ ├── StockDecreased.java
│ │ ├── StockReserved.java
│ │ └── StockReleased.java
│ ├── application/ # Application layer
│ │ ├── setstocklevel/ # Use case: Set Stock Level
│ │ │ ├── SetStockLevelInputPort.java
│ │ │ ├── SetStockLevelUseCase.java
│ │ │ ├── SetStockLevelCommand.java
│ │ │ └── SetStockLevelResult.java
│ │ ├── reducestock/ # Use case: Reduce Stock
│ │ │ ├── ReduceStockInputPort.java
│ │ │ ├── ReduceStockUseCase.java
│ │ │ ├── ReduceStockCommand.java
│ │ │ └── ReduceStockResult.java
│ │ ├── getstockforproducts/ # Use case: Get Stock for Products
│ │ │ ├── GetStockForProductsInputPort.java
│ │ │ ├── GetStockForProductsUseCase.java
│ │ │ ├── GetStockForProductsQuery.java
│ │ │ └── GetStockForProductsResult.java
│ │ └── shared/ # Shared output ports
│ │ └── StockLevelRepository.java
│ └── adapter/ # Adapters
│ ├── incoming/ # Incoming adapters
│ │ └── event/
│ │ └── StockReductionEventConsumer.java
│ └── outgoing/ # Outgoing adapters
│ └── persistence/
│ └── InMemoryStockLevelRepository.java
│
├── pricing/ # Pricing bounded context
│ ├── api/ # Spring Modulith @NamedInterface API
│ │ └── PricingService.java # Open Host Service
│ ├── domain/
│ │ ├── model/ # Domain model
│ │ │ ├── ProductPrice.java # Aggregate Root
│ │ │ └── PriceId.java # Value Objects
│ │ └── event/ # Domain events
│ │ ├── PriceCreated.java
│ │ └── PriceChanged.java
│ ├── application/ # Application layer
│ │ ├── setproductprice/ # Use case: Set Product Price
│ │ │ ├── SetProductPriceInputPort.java
│ │ │ ├── SetProductPriceUseCase.java
│ │ │ ├── SetProductPriceCommand.java
│ │ │ └── SetProductPriceResult.java
│ │ ├── getpricesforproducts/ # Use case: Get Prices for Products
│ │ │ ├── GetPricesForProductsInputPort.java
│ │ │ ├── GetPricesForProductsUseCase.java
│ │ │ ├── GetPricesForProductsQuery.java
│ │ │ └── GetPricesForProductsResult.java
│ │ └── shared/ # Shared output ports
│ │ └── ProductPriceRepository.java
│ └── adapter/ # Adapters
│ └── outgoing/ # Outgoing adapters
│ └── persistence/
│ └── InMemoryProductPriceRepository.java
│
├── portal/ # Portal bounded context
│ └── adapter/ # Adapters
│ └── incoming/ # Incoming adapters
│ └── web/
│ └── HomePageController.java
│
├── backoffice/ # Backoffice bounded context
│ ├── application/
│ │ ├── geteventpublications/ # Use case: Get Event Publications
│ │ │ ├── GetEventPublicationsInputPort.java
│ │ │ ├── GetEventPublicationsUseCase.java
│ │ │ ├── GetEventPublicationsQuery.java
│ │ │ └── GetEventPublicationsResult.java
│ │ └── shared/ # Shared output ports
│ │ └── EventPublicationLogRepository.java
│ ├── infrastructure/ # Per-context infrastructure
│ │ ├── BackofficeSecurityConfiguration.java
│ │ └── BackofficeSecurityProperties.java
│ └── adapter/ # Adapters
│ ├── incoming/ # Incoming adapters
│ │ └── web/
│ │ ├── EventPublicationPageController.java
│ │ └── EventPublicationPageViewModel.java
│ └── outgoing/ # Outgoing adapters
│ └── persistence/
│ └── JdbcEventPublicationLogRepository.java
│
└── infrastructure/ # Infrastructure (cross-cutting)
├── AiArchitectureApplication.java # Spring Boot main class
├── config/ # Spring @Configuration classes
│ ├── TransactionConfiguration.java
│ ├── AsyncConfiguration.java
│ └── Pug4jConfiguration.java
├── init/ # Initialization
│ └── SampleDataInitializer.java # Sample data seeding
└── support/ # Framework support components
└── AsyncInitializationProcessor.java
- Java 25
- Gradle 9.3.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, domain.specification
- 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 JSON via DTOsadapter.incoming.web- Web MVC (@Controller) returning HTML via page-specific ViewModelsadapter.incoming.mcp- MCP Server for AI assistantsadapter.incoming.event- Domain event consumersadapter.incoming.event.acl- Anti-corruption layer event translators
- Outgoing Adapters (Secondary):
adapter.outgoing.persistence- Repository implementations (InMemory, JPA, JDBC)adapter.outgoing.event- Integration event publishersadapter.outgoing.cart,adapter.outgoing.product- Cross-context data adaptersadapter.outgoing.payment- Payment provider adaptersadapter.outgoing.security- Security-related adapters
- DTO/ViewModel conversion happens in adapters (not application layer)
- ViewModels use primitives only, created from domain read models
- Adapters don't communicate directly
Open Host Services (Spring Modulith @NamedInterface API packages)
{context}.api- Cross-context APIs exposed as Spring Modulith named interfaces- ProductCatalogService, CartService, InventoryService, PricingService
Integration Events (Spring Modulith event packages)
{context}.events- Integration events published via Spring Modulith for cross-module communication- Separate from internal domain events in
domain.event
Shared Kernel - Cross-context shared concepts
sharedkernel.marker.tactical- DDD tactical patterns (Entity, Value, AggregateRoot, DomainEvent, etc.)sharedkernel.marker.strategic- DDD strategic patterns (BoundedContext, SharedKernel, OpenHostService)sharedkernel.marker.port.in- Input ports (UseCase, InputPort)sharedkernel.marker.port.out- Output ports (Repository, DomainEventPublisher, IdentityProvider)sharedkernel.marker.infrastructure- Framework integration markers (AsyncInitialize)sharedkernel.domain.model- Shared value objects (Money, Price, ProductId, UserId, PageResult, PagingRequest)sharedkernel.domain.specification- Composable specification patternsharedkernel.adapter.outgoing.event- Shared outgoing adapters (SpringDomainEventPublisher)
Infrastructure Layer - Cross-cutting concerns
infrastructure.config- Spring @Configuration classesinfrastructure.support- Framework support components (processors, listeners)
Architecture rules are actively enforced using ArchUnit (10 test suites) and Spring Modulith (module boundary verification):
./gradlew test-architectureArchUnit Test 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
Spring Modulith Verification:
- SpringModulithVerificationTest - Module boundary and cycle verification
Shared Infrastructure:
- BaseArchUnitTest - Common test constants and helpers
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 application/shared
- No circular package dependencies
- Naming conventions are followed
- Bounded contexts are properly isolated
- Spring Modulith module boundaries are respected
./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
- Multi-step Checkout Flow: 5 steps (Buyer Info → Delivery → Payment → Review → Confirmation) with session management
- Specification Pattern with Visitor: Database-agnostic filtering via CartSpecification and CartSpecificationVisitor
- Multiple Persistence Strategies: InMemory (default), JPA, and JDBC implementations for shopping cart
- OpenHost Service Pattern: Cross-context APIs via Spring Modulith
@NamedInterfaceAPI packages - UserId Continuity on Registration: When a user registers, their anonymous UserId is preserved
- Spring Modulith Integration Events: Cross-module communication via dedicated
eventspackages, separate from internal domain events - Interface Inversion for Event Listeners: Event consumers listen on integration events published by source context (ADR-024)
Architecture Decision Records: See docs/architecture/adr/README.md for 24 documented architectural decisions.
- Price must be positive
- Stock cannot be negative
- SKU must be unique
- Cannot modify checked-out or completed cart
- Cannot checkout empty cart
- Each product appears once (quantities combined)
- Cart stores price snapshot at time of addition
- Cannot skip steps - must complete in order (Buyer Info → Delivery → Payment → Review → Confirmation)
- Can go back to modify previous steps (before confirmation)
- Cannot modify confirmed, completed, or expired sessions
- Must have at least one line item to start checkout
- Email must be unique across all accounts
- Password is hashed before storage (never stored in plain text)
- Account status transitions: PENDING → ACTIVE → SUSPENDED/DELETED
- 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
- Spring Modulith - Module boundary enforcement
This is a sample project for educational purposes.