This document provides a high-level overview of the ecommerce-store-api architecture, design decisions, and system flows.
- System Context
- Strategic Domain-Driven Design
- High-Level Architecture
- Component Dependencies
- Checkout Sequence Diagram
- SAGA Compensation Flow
- Key Patterns Implemented
- Online vs COD Checkout Logic
- Payment Event Handling
- Idempotency Logic
- Notification System Architecture
We utilize Strategic DDD to define boundaries and relationships between different parts of the system.
| Subdomain | Type | Description |
|---|---|---|
| Orders | Core Domain | The heart of the business. Handles the complex lifecycle of customer orders, SAGA orchestration, and revenue generation. |
| Carts | Supporting | Manages temporary shopping sessions, item selection, and cart persistence. |
| Inventory | Supporting | Manages stock levels and reservations. Essential but not the primary competitive advantage. |
| Products | Supporting | Manages the product catalog, categories, and search indexing. Supports the core selling process. |
| Customers | Supporting | Manages user profiles, shipping addresses, and customer preferences. Custom-built to support specific business needs. |
| Payments | Generic | Handles transaction processing. Uses standard patterns (Stripe/PayPal) that can be bought/outsourced. |
| Auth | Generic | Identity and Access Management. Standard JWT implementation. |
| Notifications | Generic | Delivery mechanism for real-time and background alerts. |
Each Module acts as a Bounded Context. We use Context Mapping to define how they interact, strictly enforcing boundaries to prevent a "Big Ball of Mud".
graph TD
subgraph "Core Domain"
Orders[Orders Context]
end
subgraph "Supporting Subdomains"
Inventory[Inventory Context]
Products[Products Context]
Carts[Carts Context]
Customers[Customers Context]
end
subgraph "Generic Subdomains"
Payments[Payments Context]
Auth[Auth Context]
Notifications[Notifications Context]
end
%% Relationships
Orders -->|ACL / CustomerGateway| Customers
Orders -->|ACL / CartGateway| Carts
Orders -->|Event / IntegrationEvent| Notifications
Carts -->|ACL / InventoryGateway| Inventory
Carts -->|Shared Data / ProductRepo| Products
Auth -->|Identity| Customers
Payments -->|Verify| Auth
classDef core fill:#ff9999,stroke:#333,stroke-width:2px;
classDef support fill:#99ff99,stroke:#333,stroke-width:1px;
classDef generic fill:#9999ff,stroke:#333,stroke-width:1px;
class Orders core;
class Inventory,Products,Carts,Customers support;
class Payments,Auth,Notifications generic;
Anti-Corruption Layer (ACL): The
Orderscontext does not directly depend on the implementation ofCustomersorCarts. Instead, it defines its own Ports (Gateways), and we implement Adapters that translate external models into the Order domain's language. This protects the Core Domain from changes in upstream modules.
A high-level view of how the E-commerce API fits into the existing landscape.
C4Context
title System Context Diagram for E-commerce Store API
Person(customer, "Customer", "A user of the e-commerce store")
System(ecommerce, "E-commerce API", "Handles orders, payments, inventory, and products")
System_Ext(stripe, "Stripe", "Payment Gateway")
System_Ext(paypal, "PayPal", "Payment Gateway")
System_Ext(redis, "Redis Stack", "Cache, Search, & Queue")
System_Ext(postgres, "PostgreSQL", "Primary Database")
Rel(customer, ecommerce, "Uses", "HTTPS/WSS")
Rel(ecommerce, stripe, "Processes payments", "HTTPS")
Rel(ecommerce, paypal, "Processes payments", "HTTPS")
Rel(ecommerce, redis, "Reads/Writes", "TCP")
Rel(ecommerce, postgres, "Persists data", "TCP")
The system is built as a modular monolith using NestJS, designed for scalability and maintainability. It uses PostgreSQL as the primary data store and Redis Stack for high-performance caching, search, and message brokering.
graph TD
Client["📱 Client App (Web/Mobile)"] -->|HTTP/REST| API["🛡️ NestJS API Gateway"]
Client -->|WebSocket| WS["🔌 WebSocket Gateway"]
subgraph "Application Core (Modular Monolith)"
API --> Auth["🔐 Auth Module"]
API --> Orders["📦 Orders Module"]
API --> Products["🏷️ Products Module"]
API --> Carts["🛒 Carts Module"]
API --> Payments["💳 Payments Module"]
API --> Inventory["🏭 Inventory Module"]
API --> Customers["👥 Customers Module"]
WS --> Notifications["🔔 Notifications Module"]
Orders -->|SAGA Orchestration| Inventory
Orders -->|SAGA Orchestration| Payments
Orders -->|Event| Notifications
end
subgraph "Secondary Adapters (Infrastructure)"
Auth -->|Persist| PG["🐘 PostgreSQL"]
Orders -->|Persist| PG
Products -->|Persist| PG
Carts -->|Cache/Persist| Redis["⚡ Redis Stack"]
Products -->|Search| Redis
Orders -->|Async Jobs| BullMQ["🐂 BullMQ Job Queue"]
Notifications -->|Async Jobs| BullMQ
end
subgraph "External Services"
Payments <-->|Verify| Stripe["💳 Payment Gateway"]
end
Key Strength: This system implements a Hybrid Payment Architecture, orchestrating both synchronous online payments (Stripe/PayPal imitation) and asynchronous manual confirmations (Cash on Delivery) via unified SAGA flows.
The application is structured into Bounded Contexts (Modules). This diagram shows the actual dependencies between modules, highlighting the orchestration role of the Orders module.
graph TD
subgraph "Orchestration Layer"
Orders["📦 Orders Module"]
end
subgraph "Core Domains"
Inventory["🏭 Inventory Module"]
Payments["💳 Payments Module"]
Products["🏷️ Products Module"]
Customers["👥 Customers Module"]
Carts["🛒 Carts Module"]
end
subgraph "Support"
Auth["🔐 Auth Module"]
Notifications["🔔 Notifications Module"]
end
%% Orders Dependencies
Orders -->|ACL| Inventory
Orders -->|ACL| Payments
Orders -->|ACL| Customers
Orders -->|ACL| Carts
Orders -->|Event| Notifications
%% Carts Dependencies
Carts -->|ACL| Inventory
Carts -->|Validates Item| Products
%% Auth Dependencies
Auth -->|Manages| Customers
Payments -->|Verifies| Auth
The standard flow for online payments (Credit Card, PayPal) where strict SAGA coordination is required ensuring payment is only captured if stock is reserved.
sequenceDiagram
participant Client
participant CheckoutUC as CheckoutUseCase
participant OrderRepo as Order Repository
participant BullMQ as Job Queue
participant Worker as Background Worker
participant Inventory as Inventory Module
participant Payment as Payment Module
Client->>CheckoutUC: POST /checkout
CheckoutUC->>CheckoutUC: Validate Cart & User
CheckoutUC->>OrderRepo: Create Order (Status: PENDING_PAYMENT)
CheckoutUC->>BullMQ: Schedule SAGA Process
CheckoutUC-->>Client: 201 Created (Payment Pending)
rect rgba(0, 255, 0, 0.1)
Note over Worker,Inventory: Async Phase 1: Reservations
BullMQ->>Worker: Process Job
Worker->>Inventory: Reserve Stock logic
Inventory-->>Worker: Confirmed
end
rect rgba(0, 0, 255, 0.1)
Note over Worker,Payment: Async Phase 2: Payment
Worker->>Payment: Process Payment logic
Payment-->>Worker: Success
end
Worker->>OrderRepo: Update Order Status (CONFIRMED)
Worker-->>BullMQ: Job Completed
If any step in the checkout process fails (e.g., payment declined), the system triggers a Compensation SAGA to rollback previous changes and ensure consistency.
sequenceDiagram
participant Job as Checkout Job
participant Listener as CheckoutFailureListener
participant Payment as Payment Module
participant Order as Order Module
participant Inventory as Inventory Module
Note over Job: Checkout Job Fails ❌
Job->>Listener: Emits 'failed' event
Listener->>Listener: Analyze Failure Reason
par Compensation Steps
Listener->>Payment: Process Refund (if paid)
Listener->>Order: Cancel Order Entity
Listener->>Inventory: Release Stock Reservation
end
Listener-->>Listener: Log Compensation Success
- Rich Domain Models: Business logic resides in entities, not services.
- Value Objects: Immutable objects for things like
Money,Address. - Repositories: Interfaces defined in Domain, implemented in Infrastructure.
We use a functional Result<T, E> type instead of throwing exceptions for business logic flow. This makes error handling explicit and type-safe.
Critical endpoints (like Checkout) are protected by a custom @Idempotent() decorator backed by Redis, preventing double-charging or duplicate orders during network retries.
Long-running tasks are offloaded to BullMQ to keep the API responsive.
A unified view of how the system handles different payment flows, including Failure & Compensation paths.
flowchart TD
Start((Start)) --> Validate[Validate Cart & User]
Validate --> CreateOrder[Create Order]
CreateOrder --> CheckMethod{Payment Method?}
%% Online Flow
CheckMethod -->|Online| AssignPending[Status: PENDING_PAYMENT]
AssignPending --> ScheduleOnline[Schedule SAGA: Online Flow]
ScheduleOnline --> ReturnOnline[Return 201: Created]
ScheduleOnline -.-> WorkerOnline[Worker Processing]
WorkerOnline --> ValidateCart[Validate Cart]
ValidateCart -->|Fail| FailSAGA[❌ Compensation SAGA]
ValidateCart --> ReserveStock[Reserve Stock]
ReserveStock -->|Fail| FailSAGA
ReserveStock --> ProcessPayment[Process Payment]
ProcessPayment -->|Fail| FailSAGA
ProcessPayment --> PaymentSuccess{Success?}
PaymentSuccess -- Yes --> ConfirmOnline[Status: CONFIRMED]
PaymentSuccess -- No --> FailSAGA
ConfirmOnline --> ConfirmResOnline[Confirm Reservation]
ConfirmResOnline --> ClearCartOnline[Clear Cart]
ClearCartOnline --> FinalizeOnline[Finalize Order]
%% COD Flow
CheckMethod -->|COD| AssignConfirm[Status: PENDING_CONFIRMATION]
AssignConfirm --> ScheduleCOD[Schedule SAGA: COD Flow]
ScheduleCOD --> ReturnCOD[Return 201: Action Required]
ScheduleCOD -.-> WorkerCOD[Worker Processing]
WorkerCOD --> ValidateCartCOD[Validate Cart]
ValidateCartCOD -->|Fail| FailSAGA
ValidateCartCOD --> ReserveStockCOD[Reserve Stock]
ReserveStockCOD -->|Fail| FailSAGA
ReserveStockCOD --> Stop[🛑 Stop & Wait]
Stop --> ManualCall[📞 Manual Confirmation Call]
ManualCall --> AdminConfirm[Admin Clicks 'Confirm']
AdminConfirm --> UpdateStatus[Status: CONFIRMED]
UpdateStatus --> ScheduleFinalizeCOD[Schedule Finalization]
ScheduleFinalizeCOD --> ConfirmResCOD[Confirm Reservation]
ConfirmResCOD --> ClearCartCOD[Clear Cart]
ClearCartCOD --> FinalizeCOD[Finalize Order]
%% Shared Compensation Logic (Handles both Online & COD)
FailSAGA --> Refund["Refund Payment<br/>(If Paid)"]
Refund --> ReleaseStock["Release Stock<br/>(If Reserved)"]
ReleaseStock --> CancelOrder[Status: CANCELLED]
While checkout initiates payment, the final confirmation often happens asynchronously (e.g., via webhooks or delayed processing).
sequenceDiagram
participant Queue as Payment Events Queue
participant Processor as PaymentEventsProcessor
participant Step as PaymentCompletedStep
participant UseCase as HandlePaymentCompletedUC
participant OrderRepo as OrderRepository
Queue->>Processor: Process Job (PAYMENT_COMPLETED)
Processor->>Step: Execute Step
Step->>UseCase: Execute UseCase
UseCase->>OrderRepo: Find Order
UseCase->>OrderRepo: Update Status (PAID)
UseCase-->>Step: Success
Step-->>Processor: Job Completed
We prevent duplicate operations using a custom interceptor backed by Redis.
flowchart TD
Request[Incoming Request] --> Interceptor{Idempotency Interceptor}
Interceptor -->|Key Exists?| CheckRedis[Check Redis Store]
CheckRedis -->|Found Result| ReturnCached[Return Cached Response]
CheckRedis -->|Found 'In Progress'| ThrowConflict[Throw 409 Conflict]
CheckRedis -->|Not Found| Lock[Lock Key in Redis]
Lock --> Controller[Execute Controller Logic]
Controller --> Store[Store Result in Redis]
Store --> Response[Return Response]
The notification system is designed for reliability and real-time delivery, ensuring no notifications are lost even if the user is offline. It uses a Nested BullMQ Flow to guarantee the order of operations: Save -> Send -> Update.
The module follows strict Ports & Adapters layering, separating business rules from technical implementation.
graph TD
subgraph "Primary Adapters (Driving)"
NC[NotificationsController]
NP[NotificationsProcessor]
end
subgraph "Core / Application"
DNS[DeliverNotificationService]
GUC[GetNotificationsUseCase]
MUC[MarkAsReadUseCase]
end
subgraph "Core / Domain"
NE[Notification Entity]
RI[NotificationRepository Interface]
SI[NotificationScheduler Interface]
end
subgraph "Secondary Adapters (Driven)"
PR[PostgresNotificationRepository]
BS[BullMqNotificationScheduler]
WG[WebsocketGateway]
end
%% Interactions
NC --> GUC
NC --> MUC
NP --> DNS
GUC --> RI
MUC --> RI
DNS --> WG
DNS --> NE
BS -- implements --> SI
PR -- implements --> RI
BS --> NE
To prevent "lost notifications" (where a notification is sent but not saved, or vice versa), we use a strictly ordered job flow.
sequenceDiagram
participant System as Trigger (e.g., OrderService)
participant Scheduler as NotificationScheduler
participant Queue as BullMQ Flow
participant Worker as NotificationProcessor
participant DB as PostgreSQL
participant WS as WebSocketGateway
participant Client
System->>Scheduler: Schedule Notification
Scheduler->>Queue: Add Flow (Save -> Send -> Update)
Note over Queue, Worker: Step 1: Persistence (Critical)
Queue->>Worker: Job: SAVE_NOTIFICATION_HISTORY
Worker->>DB: INSERT Notification (Status: PENDING)
DB-->>Worker: Success
Note over Queue, Worker: Step 2: Delivery
Queue->>Worker: Job: SEND_NOTIFICATION
Worker->>WS: Send to User Room
WS-->>Client: Emit 'notification' Event
Worker-->>Queue: Success
Note over Queue, Worker: Step 3: Status Update
Queue->>Worker: Job: UPDATE_NOTIFICATION_STATUS
Worker->>DB: UPDATE Status (SENT)