🛒 RnD Ecommerce — System Design

Modular Monolith · Domain-Driven Design · Vertical Slice Architecture · CQRS · Outbox Pattern
C# / .NET · Minimal API · Entity Framework Core · PostgreSQL · RabbitMQ
📱 Client Applications
🖥 Admin Panel Next.js
Quản lý sản phẩm · Đơn hàng · Kho hàng
Nhân viên · Báo cáo · Cấu hình hệ thống
Phân quyền · Audit log · Refund management
🌐 Storefront Next.js
Trang sản phẩm · Tìm kiếm · Filter
Giỏ hàng · Checkout · Theo dõi đơn hàng
Lịch sử mua · Loyalty point · Profile
📱 Mobile App Flutter
Mua hàng · Theo dõi đơn · Push notification
QR thanh toán · Loyalty wallet · In-app thông báo
HTTPS / REST API · JWT Auth · Rate Limiting · WebSocket (SignalR) · OpenAPI (Scalar)
MediatR (In-Process) · RabbitMQ (Async Integration Events) · Outbox Pattern (at-least-once)
🧩 Modular Monolith — Business Modules (ASP.NET Core · Minimal API · CQRS · DDD · VSA)
CORE BUSINESS MODULES
📦 Catalog
Product · ProductVariant (SKU/Barcode/Price)
Brand · Category (tree, multi-level)
ProductImage · ProductAttribute
ProductAttributeValue (per Variant)
REST API ProductCreated ProductVariantCreated ProductVariantPriceChanged catalog.*
🛒 Sales
Cart · CartItem (merge by variant+options)
Order lifecycle (Draft→Paid→Completed)
OrderItem snapshot · Shipment lifecycle
Refund state · Idempotency · Optimistic lock
OrderCreated OrderPaid OrderCompleted OrderCancelled ShipmentDelivered sales.*
🏭 Inventory
Warehouse · Inventory (per Variant+Warehouse)
OnHand / Reserved / Available Quantity
StockReservation (idempotent by OrderId)
InventoryTransaction (append-only audit)
StockReserved StockDeducted StockReservationFailed StockLowDetected inventory.*
💳 Payment
Payment lifecycle (Pending→Captured→Refunded)
PaymentProvider abstraction (VNPay, Momo, Stripe)
Webhook idempotent (by ProviderEventId)
Refund · RefundTransaction · PCI compliant
PaymentSucceeded PaymentFailed RefundCompleted RefundRejected payment.*
SUPPORTING MODULES
🔑 Identity
User · Role · Permission (module.resource.action)
UserRole · RolePermission · RefreshToken
LoginSession · AuditLog
Token family rotation · Revocation · 2FA
UserCreated UserLocked PasswordResetRequested identity.*
👤 Customer
Customer profile · CustomerAddress (multi, default)
LoyaltyPoint ledger (balance non-negative)
LoyaltyPointTransaction (append-only)
Address snapshot for Sales checkout
CustomerCreated LoyaltyPointsGranted LoyaltyPointsReversed customer.*
👷 Employee
Employee profile · Reporting line (manager_id)
Link Identity.User (reference only)
Status: Active / Inactive / Terminated
Circular reporting line prevention
EmployeeCreated EmployeeLinkedToUser employee.*
🔔 Notification
Email (SendGrid/SES) · SMS (Twilio)
Push FCM · In-App (SignalR realtime)
Template (multi-lang, placeholder)
Preference opt-out · Provider webhook
NotificationSent NotificationFailed notification.*
CROSS-CUTTING
🔧 Shared Kernel
BaseEntity · AggregateRoot · DomainEvent
Result<T> · Pagination · Guard clauses
AuditableEntity (CreatedAt, UpdatedAt)
Outbox Pattern (at-least-once delivery)
ICurrentUser · IPermissionService (internal contract)
🏗 Architecture Patterns
DDD: Aggregate, Entity, Value Object
Domain Events → Outbox → Integration Events
CQRS: Command (EF Core) / Query (Dapper)
VSA: 1 slice = Endpoint + Command/Query + Handler
Repository Pattern (Write) / Direct Query (Read)
Infrastructure Adapters (EF Core · Dapper · RabbitMQ · Redis)
⚙️ Infrastructure Layer
⚡ Redis
Cart session · Output cache · Distributed lock
Rate limit counter · Distributed cache
🐇 RabbitMQ
Async Integration Events via Outbox
Order→Inventory→Payment→Notification
Dead letter queue · Retry policy · Exponential backoff
📨 Outbox Pattern
Transactional Outbox table per module
Background polling publisher (5s interval)
Idempotency key · retry_count · dead_letter
At-least-once delivery guarantee
📋 Observability
Serilog → Seq · OpenTelemetry (Jaeger)
Prometheus + Grafana · Health Checks UI
Structured logging · Distributed tracing
EF Core (Write / Command) · Dapper (Read / Query) · Repository Pattern
🗄️ Data Layer
🐘 PostgreSQL — Primary Write
Schema isolation per module (one shared DB, separate schemas):
catalog · sales · inventory · payment · customer · identity · employee · notification
PostgreSQL native types: uuid · timestamptz · jsonb · text[] · numeric(18,2) · citext · inet · xid
🐘 PostgreSQL — Replica Read
Analytics queries · Report exports
Dashboard read · Tách read/write để giảm tải
📦 Redis
Cart data · Sessions
Cache · Distributed lock
Key Tables per Module
📦 catalog.*
products (id, slug, brand_id, status)
product_variants (sku, barcode, price, size, color)
brands (slug, logo_url)
categories (parent_id, display_order)
product_images (variant_id, is_primary)
product_attributes (is_filterable)
product_attribute_values
🛒 sales.*
sales_orders (status, payment_status, address_snapshot, row_version, idempotency_key)
sales_order_items (snapshot: name, sku, price, qty, line_total)
sales_carts (customer_id, status)
sales_cart_items (variant_id, selected_options)
sales_shipments (tracking_number, carrier_name)
🏭 inventory.*
inventories (warehouse_id, variant_id, on_hand_qty, reserved_qty, available_qty, row_version)
warehouses (name, code, is_active)
stock_reservations (reference_type, reference_id, expires_at, status)
inventory_transactions (type, qty, reason, actor — append-only)
💳 payment.*
payments (order_id, amount, captured_amount, refunded_amount, status, idempotency_key, row_version)
payment_transactions (type: Authorize/Capture/Cancel/Refund)
payment_providers (name, webhook_secret, config)
payment_webhook_events (provider_event_id — idempotent)
refunds (refund_type: Full/Partial)
refund_transactions
🔑 identity.*
users (username, email citext, user_type, password_hash, status, failed_login_count, locked_at)
roles · permissions (module.resource.action)
user_roles · role_permissions
refresh_tokens (token_hash, family_id, revoke_reason)
login_sessions (ip_address inet, user_agent)
audit_logs (actor, action, details jsonb)
👤 customer.*
customers (email citext, user_id, status, is_email_verified)
customer_addresses (is_default_shipping, is_default_billing, address_type)
loyalty_points (balance, lifetime_earned, lifetime_redeemed)
loyalty_point_transactions (type: Earn/Redeem/Expire/Adjust/Reverse, idempotency_key — append-only)
👷 employee.*
employees (first_name, last_name, email citext, phone_number, status, user_id, manager_id FK self-ref)
Status: Active / Inactive / Suspended / Terminated
Circular reporting line prevention
🔔 notification.*
notification_templates (channel, type, language, subject_template, body_template)
notification_preferences (user_id, opt_out_marketing, opt_out_promotion)
notification_records (idempotency_key, channel, status, provider_message_id)
in_app_notifications (is_read, action_url)
🔄 Luồng đặt hàng — Order Flow (Outbox-driven)
1. Client checkout (POST /api/sales/carts/{id}/checkout) 2. Sales: validate CartItems (variant còn bán) 3. Sales: lấy AddressSnapshot từ Customer 4. Sales: tạo OrderItem snapshot (ProductId, SKU, Price, Qty)
5. Order tạo (Pending) + idempotency_key 6. Outbox: StockReservationRequested → Inventory 7. Inventory: reserve stock (idempotent by OrderId) 8. Outbox: StockReserved → Sales (Order → Confirmed)
9. Outbox: PaymentRequested → Payment 10. Payment: khởi tạo với Provider (VNPay/Stripe) 11. Provider webhook → Payment Webhook Handler (idempotent) 12. Outbox: PaymentSucceeded → Sales (Order → Paid)
13. Outbox: StockDeductionRequested → Inventory (Order → Processing) 14. Sales: tạo Shipment (Order → Shipping) 15. Shipment Delivered → Order → Completed 16. Outbox: LoyaltyPointsGrantRequest → Customer
17. Notification: email/push xác nhận đơn hàng, giao hàng
💸 Luồng hoàn tiền — Refund Flow
1. POST /api/sales/orders/{id}/refund (full/partial) 2. Sales: kiểm tra RefundEligibility 3. Outbox: RefundRequested → Payment (OrderId, PaymentId, Amount) 4. Payment: Approve → Process → Provider refund API
5. Provider callback → Outbox: RefundCompleted → Sales 6. Sales: Order → Refunded / PartiallyRefunded 7. Outbox: LoyaltyPointsReverseRequest → Customer (nếu policy yêu cầu)
🗺 Module Integration Map (Event-driven via Outbox)
Catalog → Others
Catalog→ InventoryProductVariantCreated · ProductVariantDeactivated
Catalog→ SalesProductVariantPriceChanged · ProductVariantUpdated
Sales ↔ Others
Sales→ InventoryStockReservationRequested · StockDeductionRequested · StockReleaseRequested
Inventory→ SalesStockReserved · StockReservationFailed · StockDeducted
Sales→ PaymentPaymentRequested · RefundRequested
Payment→ SalesPaymentSucceeded · PaymentFailed · RefundCompleted · RefundRejected
Sales→ CustomerLoyaltyPointsGrantRequest · LoyaltyPointsReverseRequest
Identity & Cross-cutting
Identity→ AllIPermissionService (internal contract) · JWT Policy
Employee→ IdentityEmployeeCreated · EmployeeLinkedToUser
Customer← IdentityUserRegistered (CustomerCreated link)
Payment→ CustomerRefundCompleted (loyalty reverse)
Notification Consumers
Notification← IdentityUserLocked · PasswordResetRequested · RefreshTokenFamilyRevoked
Notification← SalesOrderConfirmed · OrderShipped · OrderCompleted · OrderCancelled
Notification← CustomerLoyaltyPointsGranted · LoyaltyPointsReversed
Notification← InventoryStockLowDetected
🏗 Domain Model Summary — Aggregates & Key Entities
Catalog Module
◆ Product (Aggregate Root)
  ├ ProductVariant (SKU unique, Price, Size, Color)
  ├ ProductImage (Product|Variant level)
  └ ProductAttributeValue
◆ Brand (Aggregate Root)
◆ Category (Tree, Circular ref check)
Sales Module
◆ Cart (Active/Abandoned/Converted)
  └ CartItem (merge by variant+options)
◆ Order (10 states, row_version)
  └ OrderItem (immutable snapshot at checkout)
◆ Shipment (Created→Delivered)
Identity Module
◆ UserAggregate (Root)
  ├ RefreshToken (family_id, token_hash)
  └ LoginSession (ip_address inet, user_agent)
◆ RoleAggregate
  ├ Permission (module.resource.action)
  └ RolePermission
AuditLog (actor, action, details jsonb)
Payment Module
◆ Payment (Pending→Captured→Refunded)
  ├ PaymentTransaction (Authorize/Capture/Refund)
  └ PaymentProvider (abstraction + config)
◆ Refund (Full/Partial, 6 states)
  └ RefundTransaction
◆ PaymentWebhookEvent (idempotent)
Inventory Module
◆ Inventory (per Warehouse+Variant)
  ├ StockReservation (idempotent by OrderId)
  │  Status: Active/Released/Deducted/Expired
  └ AvailableQty = OnHand - Reserved
◆ Warehouse (Name, Code unique)
InventoryTransaction (append-only audit)
Customer Module
◆ Customer (profile, UserId link)
  ├ CustomerAddress (Shipping|Billing|Both)
  │  Only 1 default per type
  ├ LoyaltyPoint (balance ≥ 0, ledger header)
  └ LoyaltyPointTransaction
      Earn/Redeem/Expire/Adjust/Reverse
🍕 Vertical Slice Architecture — Feature Slice Structure
Example: Checkout Feature (Sales Module)
Modules/Sales/Features/
└── Checkout/
    ├── Endpoint.cs   (Minimal API, route: POST /api/sales/carts/{id}/checkout)
    ├── Command.cs   (CartId, AddressId, LoyaltyPointsToUse, IdempotencyKey)
    └── Handler.cs   (MediatR IRequestHandler, orchestrates domain)
Handler responsibilities:
→ Validate cart state (Active) và CartItems (variant còn bán)
→ Gọi IProductVariantSnapshot (internal contract → Catalog)
→ Gọi ICustomerAddressSnapshot (internal contract → Customer)
→ Cart.Checkout() → Order.Create() với OrderItem snapshots
→ Ghi Outbox: StockReservationRequested
→ SaveChanges() (atomic: Order + Outbox trong 1 transaction)
Module folder structure (per module)
Modules/{ModuleName}/
├── Domain/
│   ├── Aggregates/      (Aggregate Roots)
│   ├── Entities/        (child entities)
│   ├── ValueObjects/   (immutable, equality by value)
│   ├── Events/         (Domain Events → Outbox)
│   └── Services/       (Domain Services)
├── Infrastructure/
│   ├── Persistence/   (DbContext, EF Configurations)
│   └── Outbox/        (Integration event handlers)
└── Features/         (1 folder per feature slice)
🔒 Concurrency & Idempotency Strategy
Concurrency
InventoryOptimistic lock (row_version/xid) + CHECK available ≥ 0
OrderOptimistic lock (row_version/xid) cho state transition
PaymentOptimistic lock (row_version) cho webhook callback đồng thời
LoyaltyPointPessimistic lock (SELECT FOR UPDATE) khi update balance
Reservation ExpiryPessimistic lock tránh duplicate expiry giữa các workers
Idempotency
Checkoutidempotency_key (unique constraint) → trả Order cũ nếu trùng
Paymentidempotency_key (unique) · ProviderEventId unique per provider
Refundidempotency_key (unique) per refund request
StockReservationUnique (inventory_id, reference_type, reference_id)
LoyaltyPointidempotency_key (unique) cho mọi thay đổi điểm
Outbox Consumerevent_id dedup table · at-least-once delivery
LEGEND REST API endpoint Domain / Integration Event DB Schema / Table Command (Write) Query (Read) ASP.NET Core Next.js Flutter PostgreSQL Module giao tiếp: MediatR (in-process sync) · RabbitMQ via Outbox (async events) · Internal Contract interfaces (cross-module read)