Cart·AI Shopping is a modern e-commerce backend platform built using clean coding practices, microservices design patterns, and robust event-driven architecture.
This project is built using Hexagonal Architecture (Ports & Adapters) to keep business logic completely decoupled from external frameworks, database technologies, and messaging systems.
The project follows a pure Hexagonal Architecture separation, ensuring that the domain model remains free of infrastructure concerns (like Spring annotations or database entities).
graph TD
%% Primary Adapters (Incoming)
subgraph Primary [Primary Adapters]
RestAPI["REST Controllers (Cart, Customer, Order, Product, Auth)"]
KafkaListeners["Kafka Listeners (CustomerAddedEventListener, OrderPlacedEventNotificationListener)"]
end
%% Application Layer
subgraph Application [Application Layer]
UseCases["Use Cases Orchestrators"]
Commands["Command DTOs"]
end
%% Core Domain
subgraph Core [Domain Layer]
DomainEntities["Domain Entities (Order, Cart, Customer, Product, User, Role)"]
ValueObjects["Value Objects (CustomerId, Email, ProductId, OrderId, UserId, RoleId)"]
PortsIn["Incoming Ports (Use Case Interfaces)"]
PortsOut["Outgoing Ports (Repository & Publisher Interfaces)"]
end
%% Secondary Adapters (Outgoing)
subgraph Secondary [Secondary Adapters]
MongoAdapters["MongoDB Adapters (MongoTemplate, Repositories)"]
OutboxAdapters["Outbox Adapters (OutboxTransactionDocument)"]
KafkaPublishers["Kafka Outbound Adapters (OutboxTransactionScheduler -> KafkaTemplate)"]
end
%% External Infrastructure
subgraph Infrastructure [External Services]
MongoDB[("MongoDB Database")]
KafkaCluster["Apache Kafka Broker"]
end
%% Connections
RestAPI --> Commands
Commands --> UseCases
UseCases -.-> PortsIn
PortsIn --> DomainEntities
DomainEntities --> ValueObjects
UseCases --> PortsOut
MongoAdapters -.-> PortsOut
OutboxAdapters -.-> PortsOut
MongoAdapters --> MongoDB
OutboxAdapters --> MongoDB
KafkaListeners --> UseCases
KafkaPublishers --> KafkaCluster
KafkaCluster --> KafkaListeners
%% Outbox Scheduling
KafkaPublishers --> MongoDB
- Backend: Spring Boot 3.5 (Java 21)
- Security: Spring Security & JSON Web Tokens (JWT)
- Database: MongoDB (Catalog, Outbox, Order, Cart, Users, and Roles storage)
- Event Streaming: Apache Kafka (Spring Kafka)
- Build Tool: Gradle
- Boilerplate Reduction: Lombok
- API Documentation: Springdoc OpenAPI / Swagger
This project uses Linear to track all tasks, features, and bugs.
Commit messages are prefixed with the corresponding Linear issue ID (e.g., CAR-7: feat: ...).
- Custom security filter chains configured using Hexagonal architecture adapters.
- Authenticated endpoints using JSON Web Tokens (JWT).
- Password hashing using BCrypt through an outgoing port.
- Sub-domain architecture support for security including roles (e.g.
User,Role,Permission). - Role-Based Access Control (RBAC):
@PreAuthorizeannotations enforce thatCUSTOMERusers can only operate on their own resources, whileADMINhas global access across all domains.
- Product images, user avatars, and customer media are stored and retrieved using MinIO (S3-compatible).
- A Two-Phase Upload flow is implemented for user avatars:
- The client uploads the file to a temporary bucket (no authentication required). The temporary bucket enforces a TTL lifecycle rule (1 day) via S3 Lifecycle Configuration, ensuring orphaned files are automatically cleaned up.
- Upon successful user registration, the avatar is promoted to the permanent bucket by the
CreateUserUseCase.
- Avatar uploads enforce a strict 2MB file size limit and image content-type validation at the controller level.
- Rate limiting for public upload endpoints is delegated to the infrastructure/web server layer (e.g. Nginx, API Gateway).
To guarantee At-Least-Once Delivery and prevent inconsistencies between database updates and event publishing (e.g., publishing a message to Kafka for a database transaction that ultimately failed and rolled back), we implement the Transactional Outbox Pattern:
- Instead of publishing events directly to Kafka in the request thread, events are saved in an
outbox_transactioncollection inside MongoDB within the same database transaction as the business entity. - An asynchronous OutboxTransactionScheduler polls MongoDB, publishes the events, and updates their status.
To secure the Outbox Scheduler in clustered or multi-instance environments:
- Instead of a simple database fetch-and-update (which causes race conditions where multiple replicas dispatch the same
message), we use an atomic
findAndModifyoperation. - This operation retrieves a pending message and updates its status to
PROCESSINGin a single atomic step at the database level. Other instances are prevented from pulling the same message, ensuring zero duplication at the scheduler level.
- Centralized Error Handling: We use a global
CommonErrorHandlerconfigured withDeadLetterPublishingRecovererin KafkaErrorHandlerConfig. This isolates failing payloads to a dedicated<topic-name>.DLTtopic after 3 failed attempts (1 original + 2 retries), avoiding partition blockage. - ErrorHandlingDeserializer: Configured in application.properties to intercept parsing/deserialization errors immediately. This prevents invalid payloads (Poison Pills) from freezing the consumer thread.
- Producer Idempotency: Enabled via
spring.kafka.producer.properties.enable.idempotence=trueto ensure network failures and retries between the application and the Kafka brokers do not write duplicate messages inside the topics. - Consumer Idempotency: Application state validation (e.g., verifying if a shopping cart already exists before creating it) is implemented to handle double-delivery scenarios gracefully.
- To ensure all actions related to a single customer are processed in the sequence they occurred, events are partitioned
using the
userIdas the Kafka message key (configured in the Outbox message entity), routing all customer-scoped events to the same partition.
In addition to standard REST endpoints, CartAI supports event-driven product creation via Apache Kafka.
- A dedicated Python worker (CartAI-ProductWorker) acts as an asynchronous ingestion pipeline.
- It receives unstructured catalog documents (PDFs, images), extracts structured product data using Generative AI (LiteLLM, Instructor, Vision Models), and publishes the result to the
catalog.readyKafka topic. - The Java backend consumes this topic securely, parsing the AI payload and persisting the new products directly into the catalog domain without blocking any HTTP threads.
src/main/java/cart/ai/shopping/
├── application/ <-- Application Layer (Orchestration & Command Pattern)
│ └── usecases/
│ ├── identity/ <-- Identity Use Cases (Login, Register, User, Role management)
│ │ ├── commands/
│ │ ├── role/
│ │ └── user/
│ └── shop/ <-- Shop Use Cases (Cart, Customer, Order, Product)
│ ├── commands/
│ ├── cart/
│ ├── customer/
│ ├── order/
│ └── product/
│
├── domain/ <-- Pure Domain Layer (Framework-free Business Logic)
│ ├── common/ <-- Common Domain Wrappers (result/Result.java)
│ ├── model/
│ │ ├── identity/ <-- Identity Domain Models (User, Role, Permission) & vos/ (Email, UserId, etc.)
│ │ └── shop/ <-- Shop Domain Models (Cart, Customer, Order, Product) & vos/ (OrderId, etc.)
│ └── ports/ <-- Interfaces
│ ├── common/ <-- Common Ports (IncrementIdGeneratorPort)
│ ├── identity/ <-- Identity Ports (Repositories, Events, PasswordEncoderPort)
│ └── shop/ <-- Shop Ports (Repositories, Events)
│
└── infrastructure/ <-- Infrastructure Layer (Frameworks & Adapters)
├── config/ <-- Configurations (Kafka, UseCaseInjectionConfig)
├── in/ <-- Primary / Inbound Adapters
│ ├── kafka/ <-- Kafka Event Listeners (shop events)
│ └── rest/ <-- REST Controllers (shop and identity controllers)
├── out/ <-- Secondary / Outbound Adapters
│ ├── kafka/ <-- Outbox Schedulers & Publishers
│ └── persistence/mongo/ <-- Mongo Adapters, Documents, and Mappers (common, identity, shop)
└── security/ <-- Security Technical Layer (configs, filters, services, adapters)
