Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

25 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ›’ StackCart

A modern, full-featured e-commerce platform built with .NET 10 as a Modular Monolith with Clean Architecture, CQRS, and multi-tenant isolation.

.NET PostgreSQL Redis License

Manage tenants, users, products, categories, orders, customers, and shopping carts β€” all from a single, secure, multi-tenant API.


πŸ“– Table of Contents


✨ Features

Multi-Tenancy

  • Tenant isolation via shared-database with TenantId column on every tenant-scoped table.
  • Automatic tenant resolution from X-Tenant-Id HTTP header.
  • Tenant validation middleware β€” verifies JWT tenant_id claim matches the header.

User Management

  • Registration with email uniqueness enforced per tenant.
  • Login with JWT token generation (BCrypt password hashing).
  • Profile retrieval (GET /api/users/me) with Redis caching (15-min TTL).
  • User deactivation for account lifecycle management.

Catalog Management

  • Products β€” Create, read, search (paginated), update, delete. SKU uniqueness per tenant.
  • Categories β€” Create, read by ID/slug, list all, update, delete. Self-referencing hierarchy.
  • Price value object with amount + currency, DB CHECK constraint (price > 0).
  • Stock tracking with DB CHECK constraint (stock >= 0).

Order Management

  • Order lifecycle β€” Create, add items, submit, cancel.
  • Order status tracking via OrderStatus enum.
  • Paginated order listing with tenant isolation.
  • Domain events β€” OrderSubmittedDomainEvent raised on submission.

Customer Management

  • Customer profiles linked to user accounts.
  • Address management via CustomerAddress value object (street, city, state, country, postal code).
  • Self-service β€” customers manage their own profile via /api/customers/me.

Shopping Cart

  • Full cart CRUD β€” Add items, update quantities, remove items, clear cart.
  • Checkout flow β€” Converts cart to an order and clears the cart.
  • Real-time capacity checks β€” Duplicate prevention and stock validation.

Cross-Cutting

  • CQRS β€” Every action is a Command (mutates) or Query (reads).
  • Result Pattern β€” All operations return Result<T> or Result. No exceptions for control flow.
  • Custom Mediator β€” Built on native DI (no MediatR dependency).
  • Soft Deletes β€” Entities are marked IsDeleted, never physically removed.
  • Audit Trail β€” CreatedAtUtc, CreatedBy, ModifiedAtUtc, ModifiedBy auto-set on every entity.
  • Hybrid Caching β€” Redis-backed HybridCache for high-performance lookups.
  • Structured Logging β€” Serilog with console (dev) and MongoDB (prod) sinks.
  • JWT Authentication β€” Bearer token with sub, email, tenant_id, tenant_slug claims.

πŸ›  Tech Stack

Layer Technology
Runtime .NET 10
API Host Minimal APIs (no controllers)
ORM Entity Framework Core 10
Database PostgreSQL 18 (single shared DB)
Caching HybridCache (with Redis 8 backing store)
Logging Serilog with MongoDB 8 sink
Mapping Mapster 10 (planned)
Mediator Custom mediator via native DI
Auth JWT Bearer (Microsoft.AspNetCore.Authentication.JwtBearer)
Password Hashing BCrypt.Net-Next 4.2.0
Proxy/LB Nginx 1.29 (planned)
Containerization Docker Compose (planned)
Testing xUnit + FluentAssertions + Testcontainers (planned)

πŸ“‚ Project Structure

StackCart/
β”œβ”€β”€ Directory.Packages.props              # Central package version management
β”œβ”€β”€ StackCart.slnx                        # Solution file (new .slnx format)
β”œβ”€β”€ .env.example                          # Environment variable template
β”œβ”€β”€ project-architecture-map.md           # Living architecture documentation
β”‚
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ BuildingBlocks/                   # Shared infrastructure & domain primitives
β”‚   β”‚   β”œβ”€β”€ StackCart.BuildingBlocks.Domain/
β”‚   β”‚   β”‚   β”œβ”€β”€ Abstractions/             # Entity, AggregateRoot, ValueObject, IDomainEvent
β”‚   β”‚   β”‚   β”œβ”€β”€ Shared/                   # Error, Result (Result Pattern)
β”‚   β”‚   β”‚   └── MultiTenancy/             # ITenantScoped, ITenantContext
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ StackCart.BuildingBlocks.Application/
β”‚   β”‚   β”‚   β”œβ”€β”€ Messaging/                # IRequest, IRequestHandler, IMediator, Mediator
β”‚   β”‚   β”‚   β”œβ”€β”€ Caching/                  # ICacheService
β”‚   β”‚   β”‚   β”œβ”€β”€ Security/                 # ICurrentUser, IJwtService, IPasswordHasher
β”‚   β”‚   β”‚   β”œβ”€β”€ Abstractions/             # IApplicationDbContext
β”‚   β”‚   β”‚   └── Extensions/               # ResultExtensions (centralized HTTP mapping)
β”‚   β”‚   β”‚
β”‚   β”‚   └── StackCart.BuildingBlocks.Infrastructure/
β”‚   β”‚       β”œβ”€β”€ Persistence/              # ApplicationDbContext, Interceptors
β”‚   β”‚       β”‚   β”œβ”€β”€ Interceptors/         # AuditInterceptor, SoftDeleteInterceptor, TenantSaveChangesInterceptor
β”‚   β”‚       β”œβ”€β”€ Caching/                  # HybridCacheService
β”‚   β”‚       β”œβ”€β”€ Security/                 # CurrentUserService, JwtService
β”‚   β”‚       β”œβ”€β”€ Tenancy/                  # TenantContext
β”‚   β”‚       β”œβ”€β”€ Messaging/                # DomainEventDispatcher (stub)
β”‚   β”‚       └── Logging/                  # SerilogConfiguration
β”‚   β”‚
β”‚   └── Modules/                          # β˜… Each module is a self-contained vertical slice β˜…
β”‚       β”‚
β”‚       β”œβ”€β”€ Tenancy/                      # Multi-tenant management
β”‚       β”‚   β”œβ”€β”€ StackCart.Tenancy.Domain/         # Tenant entity, TenantSlug VO
β”‚       β”‚   β”œβ”€β”€ StackCart.Tenancy.Application/    # CreateTenant, GetTenantBySlug
β”‚       β”‚   β”œβ”€β”€ StackCart.Tenancy.Infrastructure/ # TenancyDbContext, TenantConfiguration
β”‚       β”‚   β”œβ”€β”€ StackCart.Tenancy.Presentation/   # TenancyEndpoints
β”‚       β”‚   └── StackCart.Tenancy.Contracts/      # TenantSummaryDto, TenantCreatedIntegrationEvent
β”‚       β”‚
β”‚       β”œβ”€β”€ Users/                        # User authentication & profile
β”‚       β”‚   β”œβ”€β”€ StackCart.Users.Domain/           # User entity, UserErrors, UserRegisteredDomainEvent
β”‚       β”‚   β”œβ”€β”€ StackCart.Users.Application/      # RegisterUser, Login, GetCurrentUser, DeactivateUser
β”‚       β”‚   β”œβ”€β”€ StackCart.Users.Infrastructure/   # UsersDbContext, BCryptPasswordHasher
β”‚       β”‚   β”œβ”€β”€ StackCart.Users.Presentation/     # UserEndpoints
β”‚       β”‚   └── StackCart.Users.Contracts/        # UserSummaryDto, LoginResponse
β”‚       β”‚
β”‚       β”œβ”€β”€ Catalog/                      # Product & category management
β”‚       β”‚   β”œβ”€β”€ StackCart.Catalog.Domain/         # Product, Category, Price VO, Sku VO
β”‚       β”‚   β”œβ”€β”€ StackCart.Catalog.Application/    # CRUD + SearchProducts (paginated)
β”‚       β”‚   β”œβ”€β”€ StackCart.Catalog.Infrastructure/ # CatalogDbContext, ProductConfiguration, CategoryConfiguration
β”‚       β”‚   β”œβ”€β”€ StackCart.Catalog.Presentation/   # CatalogEndpoints
β”‚       β”‚   └── StackCart.Catalog.Contracts/      # ProductSummaryDto, CategorySummaryDto, PagedResult<T>
β”‚       β”‚
β”‚       β”œβ”€β”€ Ordering/                     # Order lifecycle management
β”‚       β”‚   β”œβ”€β”€ StackCart.Ordering.Domain/        # Order, OrderItem, OrderStatus enum
β”‚       β”‚   β”œβ”€β”€ StackCart.Ordering.Application/   # CreateOrder, AddOrderItem, SubmitOrder, CancelOrder, GetOrders
β”‚       β”‚   β”œβ”€β”€ StackCart.Ordering.Infrastructure/# OrderingDbContext, OrderConfiguration, OrderItemConfiguration
β”‚       β”‚   β”œβ”€β”€ StackCart.Ordering.Presentation/  # OrderingEndpoints
β”‚       β”‚   └── StackCart.Ordering.Contracts/     # OrderSummaryDto, OrderSubmittedIntegrationEvent
β”‚       β”‚
β”‚       └── Customer/                     # Customer profiles & shopping cart
β”‚           β”œβ”€β”€ StackCart.Customer.Domain/        # Customer, Cart, CartItem, CustomerAddress VO
β”‚           β”œβ”€β”€ StackCart.Customer.Application/   # CreateCustomer, UpdateCustomer, GetCustomer, Cart CRUD, Checkout
β”‚           β”œβ”€β”€ StackCart.Customer.Infrastructure/# CustomerDbContext, CustomerConfiguration, CartConfiguration
β”‚           β”œβ”€β”€ StackCart.Customer.Presentation/  # CustomerEndpoints
β”‚           └── StackCart.Customer.Contracts/     # CustomerSummaryDto, CartDto
β”‚
└── src/StackCart.Api/                    # API Host β€” composition root
    β”œβ”€β”€ Program.cs                        # App startup, middleware pipeline, module registration
    β”œβ”€β”€ Middleware/                        # TenantResolutionMiddleware
    β”œβ”€β”€ appsettings.json                  # Configuration (JWT, connection strings)
    └── appsettings.Development.json      # Dev overrides (Serilog console sink)

πŸš€ Getting Started

Prerequisites

1. Clone the Repository

git clone https://github.com/realAhmedAnwer/stack-cart.git
cd stack-cart

2. Configure Environment Variables

cp .env.example .env

Open .env and adjust values as needed. The defaults are configured for local development:

CONNECTIONSTRINGS__DEFAULT=Host=localhost;Port=5432;Database=stackcart;Username=postgres;Password=your_password_here
CONNECTIONSTRINGS__MONGOLOGCONNECTION=mongodb://localhost:27017/stackcart_logs
REDIS_CONNECTION=localhost:6379
JWT__SECRET=your-super-secret-key-at-least-32-characters-long
JWT__ISSUER=StackCart
JWT__AUDIENCE=StackCart
JWT__EXPIRYMINUTES=60

3. Start Dependencies (Docker)

docker run -d --name stackcart-postgres -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=your_password_here -e POSTGRES_DB=stackcart -p 5432:5432 postgres:18
docker run -d --name stackcart-redis -p 6379:6379 redis:8-alpine
docker run -d --name stackcart-mongo -p 27017:27017 mongo:8

4. Apply Database Migrations

# From the solution root, run migrations for each module:
dotnet ef database update --project src/Modules/Tenancy/StackCart.Tenancy.Infrastructure --startup-project src/StackCart.Api
dotnet ef database update --project src/Modules/Users/StackCart.Users.Infrastructure --startup-project src/StackCart.Api
dotnet ef database update --project src/Modules/Catalog/StackCart.Catalog.Infrastructure --startup-project src/StackCart.Api
dotnet ef database update --project src/Modules/Ordering/StackCart.Ordering.Infrastructure --startup-project src/StackCart.Api
dotnet ef database update --project src/Modules/Customer/StackCart.Customer.Infrastructure --startup-project src/StackCart.Api

5. Run the Application

dotnet run --project src/StackCart.Api

The API will be available at https://localhost:5001 (or http://localhost:5000).

6. Verify

curl -k https://localhost:5001/health

Open your browser and navigate to https://localhost:5001/openapi/v1.json (dev mode) to view the OpenAPI specification.


πŸ— Architecture & Design Patterns

This project combines Clean Architecture (horizontal layer separation) with Vertical Slice Architecture (feature-based organization within the Application layer), creating a hybrid approach that maximizes both structural clarity and feature cohesion.

Clean Architecture (Layered Dependencies)

The solution enforces a strict inward dependency flow:

BuildingBlocks.Domain  (no dependencies)
        ↑
BuildingBlocks.Application
        ↑
BuildingBlocks.Infrastructure
        ↑
{Module}.Domain  β†’  BuildingBlocks.Domain
        ↑
{Module}.Application  β†’  {Module}.Domain, {Module}.Contracts, BuildingBlocks.Application
        ↑
{Module}.Infrastructure  β†’  {Module}.Application, {Module}.Domain, BuildingBlocks.Infrastructure
        ↑
{Module}.Presentation  β†’  {Module}.Application, {Module}.Contracts, BuildingBlocks.Application
        ↑
StackCart.Api  β†’  All module Infrastructure + Presentation, BuildingBlocks.Infrastructure
  • Domain has zero external dependencies β€” pure entities, value objects, and enums.
  • Application defines abstractions (ICustomerDbContext, ICacheService, IJwtService) and orchestrates use cases via CQRS handlers.
  • Infrastructure implements abstractions and handles persistence, caching, security, and external services.
  • Presentation owns Minimal API endpoints β€” each module registers its own routes.
  • API Host composes everything at startup.

Vertical Slice Architecture (Feature-Based Organization)

Within each module's Application layer, every business feature is a self-contained vertical slice:

StackCart.Customer.Application/Features/
β”œβ”€β”€ Customers/
β”‚   β”œβ”€β”€ CreateCustomer/
β”‚   β”‚   β”œβ”€β”€ CreateCustomerCommand.cs     # Input record
β”‚   β”‚   └── CreateCustomerHandler.cs     # Handler logic
β”‚   β”œβ”€β”€ GetCustomer/
β”‚   β”‚   └── GetCustomerQuery.cs          # Query + handler
β”‚   └── UpdateCustomer/
β”‚       └── UpdateCustomerCommand.cs     # Command + handler
└── Cart/
    β”œβ”€β”€ AddItemToCart/
    β”œβ”€β”€ RemoveItemFromCart/
    β”œβ”€β”€ UpdateCartItemQuantity/
    β”œβ”€β”€ GetCart/
    β”œβ”€β”€ ClearCart/
    └── Checkout/

Key benefits:

  • Feature isolation β€” Adding a new feature means adding a new folder with minimal changes elsewhere.
  • Layered enforceability β€” Clean Architecture dependency rules prevent logic leakage.
  • Scalability β€” Slices can be developed, tested, and maintained independently.

CQRS (Command Query Responsibility Segregation)

Concern Convention
Commands Mutate state, call SaveChangesAsync, wrapped in implicit transactions
Queries Read-only projections with AsNoTracking(), return DTOs
Result Pattern All handlers return Result<T> or Result β€” never throw for control flow

Custom Mediator

Instead of MediatR, the project uses a custom mediator built on native DI:

  • IRequest<TResponse> β€” marker interface for all requests (covariant).
  • IRequestHandler<TRequest, TResponse> β€” handler contract.
  • IMediator / Mediator β€” resolves handlers dynamically via IServiceProvider and reflection.
  • AddMediator(params Assembly[]) β€” scans assemblies and registers handlers as scoped services.

Other Patterns

Pattern Usage
Result Pattern All operations return Result<T> or Result. Centralized HTTP mapping via ResultExtensions.
Soft Delete All entities implement ISoftDeletableEntity. Global query filters exclude deleted rows.
Audit Trail CreatedAtUtc, CreatedBy, ModifiedAtUtc, ModifiedBy auto-set via AuditInterceptor.
Multi-Tenancy TenantId on all tenant-scoped tables. Global query filters + TenantSaveChangesInterceptor.
Repository via DbContext No explicit repository interfaces. Data access through module-specific DbContexts.
Hybrid Caching Redis-backed HybridCache with tenant-isolated cache keys.
Global Query Filters EF Core filters for IsDeleted == false and TenantId == current.

🌐 API Endpoints

Tenancy (/api/tenants)

Method Route Description Auth
POST /api/tenants Create a new tenant No
GET /api/tenants/by-slug/{slug} Get tenant by slug No

Users (/api/users)

Method Route Description Auth
POST /api/users/register Register a new user No
POST /api/users/login Login and receive JWT token No
GET /api/users/me Get current user profile Yes
POST /api/users/{id}/deactivate Deactivate a user Yes

Catalog (/api/catalog)

Products

Method Route Description Auth
POST /api/catalog/products Create a product Yes
GET /api/catalog/products/{id} Get product by ID Yes
GET /api/catalog/products/by-sku/{sku} Get product by SKU Yes
GET /api/catalog/products?searchTerm&categoryId&page&pageSize Search products (paginated) Yes
PUT /api/catalog/products/{id} Update a product Yes
DELETE /api/catalog/products/{id} Delete a product Yes

Categories

Method Route Description Auth
POST /api/catalog/categories Create a category Yes
GET /api/catalog/categories/{id} Get category by ID Yes
GET /api/catalog/categories/by-slug/{slug} Get category by slug Yes
GET /api/catalog/categories List all categories Yes
PUT /api/catalog/categories/{id} Update a category Yes
DELETE /api/catalog/categories/{id} Delete a category Yes

Ordering (/api/ordering/orders)

Method Route Description Auth
POST /api/ordering/orders Create an order Yes
POST /api/ordering/orders/{id}/items Add item to order Yes
POST /api/ordering/orders/{id}/submit Submit an order Yes
GET /api/ordering/orders?page&pageSize List orders (paginated) Yes
GET /api/ordering/orders/{id} Get order by ID Yes
DELETE /api/ordering/orders/{id} Cancel an order Yes

Customers (/api/customers)

Method Route Description Auth
POST /api/customers Create a customer profile Yes
GET /api/customers/me Get current customer profile Yes
PUT /api/customers/me Update current customer profile Yes

Cart (/api/cart)

Method Route Description Auth
GET /api/cart Get current customer's cart Yes
POST /api/cart/items Add item to cart Yes
PUT /api/cart/items/{productId}?quantity Update cart item quantity Yes
DELETE /api/cart/items/{productId} Remove item from cart Yes
POST /api/cart/checkout Checkout (convert cart to order) Yes
DELETE /api/cart Clear entire cart Yes

πŸ—„ Database Design

Multi-Tenant Strategy

  • Single PostgreSQL database shared across all modules.
  • Every tenant-owned table has TenantId column (Guid, FK to tenants.id).
  • EF Core global query filters enforce tenant isolation at query level.
  • TenantSaveChangesInterceptor auto-populates TenantId on new records.
  • Every unique business key includes TenantId (composite unique constraint).

Table Layout

Table Tenant-Scoped Key Unique Constraint Notes
tenants No slug UNIQUE No tenant filter applied
users Yes (tenant_id, email) BCrypt password hash stored
categories Yes (tenant_id, slug) Self-referencing parent_category_id
products Yes (tenant_id, sku) category_id FK nullable, CHECK price > 0, qty >= 0
orders Yes β€” References user_id, status as int
order_items Yes β€” FK to orders, products. CHECK qty > 0
customers Yes (tenant_id, user_id) Links to users table
carts Yes (tenant_id, customer_id) One cart per customer
cart_items Yes β€” FK to carts, products

Indexing Strategy

All tenant-scoped indexes start with tenant_id:

  • ix_users_tenant_email
  • ix_products_tenant_sku
  • ix_categories_tenant_slug
  • ix_orders_tenant_user
  • ix_customers_tenant_user
  • ix_carts_tenant_customer

πŸ” Security & Middleware

Middleware Pipeline Order

app.UseSerilogRequestLogging();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMiddleware<TenantResolutionMiddleware>();
app.UseAuthorization();

JWT Configuration

  • Secret Key β€” Symmetric signing key (Jwt:SecretKey)
  • Issuer β€” Token issuer (Jwt:Issuer)
  • Audience β€” Token audience (Jwt:Audience)
  • Expiration β€” Configurable lifetime (Jwt:ExpirationInMinutes)
  • Claims β€” sub (user ID), email, tenant_id, tenant_slug

Tenant Resolution

  1. TenantContext reads X-Tenant-Id HTTP header from IHttpContextAccessor.
  2. TenantResolutionMiddleware validates that if JWT tenant_id claim is present, it matches the X-Tenant-Id header. Returns 403 on mismatch.

Password Hashing

  • Abstraction: IPasswordHasher in StackCart.BuildingBlocks.Application.Security
  • Implementation: BCryptPasswordHasher in StackCart.Users.Infrastructure.Security using BCrypt.Net-Next

Global Query Filters

Applied in ApplicationDbContext.OnModelCreating:

  • ISoftDeletableEntity β†’ e.IsDeleted == false
  • ITenantScoped β†’ e.TenantId == _tenantContext.TenantId

πŸ“„ License

This project is licensed under the MIT License.


Built with ❀️ by Ahmed Anwer

About

A modern, multi-tenant e-commerce platform built with .NET 10 as a Modular Monolith. Features Clean Architecture, CQRS, Vertical Slice Architecture, custom Mediator, Result Pattern, JWT auth, and PostgreSQL with Redis-backed HybridCache. Includes modules for Tenancy, Users, Catalog, Ordering, Customer profiles, and Shopping Cart.

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages