A comprehensive Sales Management API built with .NET 8 implementing Clean Architecture, CQRS pattern, and Domain-Driven Design (DDD) principles. This project features a complete sales system with automatic discount calculations, product catalog management, shopping carts, and role-based access control.
- Overview
- Tech Stack
- Architecture
- Getting Started
- Database Setup
- Seed Data
- API Documentation
- Business Rules
- Testing
- Security
This project serves as a senior developer evaluation, demonstrating proficiency in:
- Clean Architecture with proper layer separation
- CQRS (Command Query Responsibility Segregation) via MediatR
- Domain-Driven Design with rich domain entities and business rules
- RESTful API design with comprehensive CRUD operations
- Multi-database architecture (PostgreSQL + MongoDB + Redis)
- JWT authentication with role-based authorization
- Automated testing across unit, integration, and functional layers
The API manages a Sales System for a product catalog, handling:
- Sales Management: Complete CRUD operations with automatic discount calculation
- Product Catalog: Beverages, snacks, and energy drinks inventory
- Shopping Carts: Customer cart management with real-time price calculation
- Branch Management: Multi-branch sales tracking
- User Management: Role-based access control (Admin, Manager, Customer)
- Discount Engine: Automatic discounts based on quantity tiers (4–9 items → 10%, 10–20 items → 20%)
- .NET 8.0 — Web framework
- ASP.NET Core — Web API
- C# 12 — Language
- MediatR 12 — CQRS and mediator pattern
- AutoMapper — Object mapping
- FluentValidation — Input validation
- Entity Framework Core 8 — ORM for PostgreSQL
- PostgreSQL 13 — Primary write database (port
5433) - MongoDB 8.0 — Document store / read models (port
27017) - Redis 7.4 — In-memory cache (port
6380)
- Serilog — Structured logging
- Swagger/OpenAPI — Interactive API docs (Swashbuckle 6.8)
- Health Checks —
/healthendpoint for all dependencies
- xUnit — Test framework
- NSubstitute — Mocking
- Bogus — Realistic test data generation
┌─────────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ WebApi / Controllers / Middleware │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ Commands / Queries / Handlers / Validators │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Domain Layer │
│ Entities / Business Rules / Domain Events │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Infrastructure Layer │
│ Repositories / EF Core / MongoDB / Redis / IoC │
└─────────────────────────────────────────────────────────────┘
developer-evaluation/
├── src/
│ ├── DeveloperEvaluation.WebApi/ # ASP.NET Core API, controllers, Swagger
│ ├── DeveloperEvaluation.Application/ # CQRS commands, queries, handlers
│ ├── DeveloperEvaluation.Domain/ # Entities, value objects, domain events
│ ├── DeveloperEvaluation.ORM/ # EF Core DbContext, migrations, seeding
│ ├── DeveloperEvaluation.IoC/ # Dependency injection registration
│ └── DeveloperEvaluation.Common/ # Shared utilities, JWT, health checks
├── tests/
│ ├── DeveloperEvaluation.Unit/ # Unit tests
│ ├── DeveloperEvaluation.Integration/ # Integration tests
│ └── DeveloperEvaluation.Functional/ # End-to-end API tests
├── docker-compose.yml # Full stack (API + databases)
├── docker-compose.infra.yml # Infrastructure only (databases)
├── DeveloperEvaluation.sln
└── coverage-report.sh
-
Clone the repository
git clone https://github.com/LucasGeek/developer-evaluation.git cd developer-evaluation -
Start the database containers
docker-compose -f docker-compose.infra.yml up -d
-
Restore dependencies
dotnet restore
-
Configure the application
Create
src/DeveloperEvaluation.WebApi/appsettings.Development.json:{ "ConnectionStrings": { "DefaultConnection": "Host=localhost;Port=5433;Database=developer_evaluation;Username=developer;Password=ev@luAt10n" }, "MongoDB": { "ConnectionString": "mongodb://developer:ev@luAt10n@localhost:27017", "DatabaseName": "developer_evaluation" }, "Redis": { "ConnectionString": "localhost:6380,password=ev@luAt10n" }, "Jwt": { "SecretKey": "your-secret-key-min-32-chars", "Issuer": "DeveloperEvaluation", "Audience": "DeveloperEvaluation" } } -
Apply database migrations
dotnet ef database update -p src/DeveloperEvaluation.ORM -s src/DeveloperEvaluation.WebApi
-
Seed initial data (optional but recommended)
dotnet run --project src/DeveloperEvaluation.WebApi -- --seed
-
Start the application
dotnet run --project src/DeveloperEvaluation.WebApi
-
Access the API
- Swagger UI: http://localhost:5119/swagger
- API Base: http://localhost:5119/api
- Health Check: http://localhost:5119/health
- HTTPS: https://localhost:7181/swagger
Two compose files are provided:
| File | Purpose |
|---|---|
docker-compose.infra.yml |
Infrastructure only (PostgreSQL, MongoDB, Redis) |
docker-compose.yml |
Full stack including the WebAPI container |
# Start infrastructure only (recommended for local development)
docker-compose -f docker-compose.infra.yml up -d
# Start full stack with Docker
docker-compose up -d
# View service status
docker-compose -f docker-compose.infra.yml ps
# Stop all services
docker-compose -f docker-compose.infra.yml down| Service | Container Port | Host Port |
|---|---|---|
| PostgreSQL | 5432 | 5433 |
| MongoDB | 27017 | 27017 |
| Redis | 6379 | 6380 |
# Create a new migration
dotnet ef migrations add <MigrationName> -p src/DeveloperEvaluation.ORM -s src/DeveloperEvaluation.WebApi
# Apply migrations
dotnet ef database update -p src/DeveloperEvaluation.ORM -s src/DeveloperEvaluation.WebApi
# Drop database (WARNING: destroys all data)
dotnet ef database drop -p src/DeveloperEvaluation.ORM -s src/DeveloperEvaluation.WebApiThe application ships with a DataSeeder that populates the database with realistic test data. Seeding runs as a separate one-off command to keep normal startup fast.
# Apply migrations + seed in one go (seeder calls MigrateAsync internally)
dotnet run --project src/DeveloperEvaluation.WebApi -- --seed
# Force full re-seed (drop → migrate → seed)
dotnet ef database drop -f -p src/DeveloperEvaluation.ORM -s src/DeveloperEvaluation.WebApi
dotnet run --project src/DeveloperEvaluation.WebApi -- --seedNote: Seeding is idempotent — it checks whether data already exists before inserting. Running it twice is safe.
| Role | Password | |
|---|---|---|
| Admin | admin@developereval.com |
Admin123! |
| Manager | manager@developereval.com |
Manager123! |
| Customer | customer1@developereval.com |
Customer123! |
| Customer | customer2@developereval.com |
Customer123! |
| Customer | customer3@developereval.com |
Customer123! |
| Category | Products |
|---|---|
| Beverages | Budweiser, Stella Artois, Corona, Brahma, Antarctica Pilsen |
| Premium Beverages | Beck's, Leffe, Hoegaarden |
| Snacks | Doritos, Lay's, Pringles, Cheetos |
| Soft Drinks | Guaraná Antarctica, Pepsi, H2OH |
| Energy Drinks | Red Bull, Monster Energy |
Each product has realistic pricing ($2.99–$15.99), random ratings (3.5–5.0 stars), and review counts (100–2,500).
- 3 carts — one per customer, each with 2–5 random products
- 15 sales — spread across 5 Brazilian branch locations, with discounts applied and ~15% randomly cancelled
Interactive documentation is available at http://localhost:5119/swagger (Development mode only).
Features: request/response examples, JWT authentication, schema browser, request duration display.
# 1. Get a JWT token
curl -X POST http://localhost:5119/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@developereval.com","password":"Admin123!"}'
# 2. Use the token
curl http://localhost:5119/api/products \
-H "Authorization: Bearer <token>"In Swagger UI: click Authorize, then enter Bearer <token>.
POST /api/auth/login # Authenticate and get JWT token
GET /api/users # List users (Admin/Manager)
POST /api/users # Create user (Admin)
GET /api/users/{id} # Get user by ID
PUT /api/users/{id} # Update user
DELETE /api/users/{id} # Delete user (Admin)
GET /api/products # List with pagination and filters
POST /api/products # Create product (Manager/Admin)
GET /api/products/{id} # Get product by ID
PUT /api/products/{id} # Update product (Manager/Admin)
DELETE /api/products/{id} # Delete product (Manager/Admin)
GET /api/products/category/{category} # Filter by category
GET /api/carts # List carts (Manager/Admin)
POST /api/carts # Create cart
GET /api/carts/{id} # Get cart by ID
PUT /api/carts/{id} # Update cart
DELETE /api/carts/{id} # Delete cart
GET /api/branches # List branches
POST /api/branches # Create branch (Admin)
GET /api/branches/{id} # Get branch by ID
PUT /api/branches/{id} # Update branch (Admin)
DELETE /api/branches/{id} # Delete branch (Admin)
GET /api/sales # List sales
POST /api/sales # Create sale
GET /api/sales/{id} # Get sale by ID
PUT /api/sales/{id} # Update sale
DELETE /api/sales/{id} # Cancel sale
GET /health # Health check (all dependencies)
All list endpoints support:
- Pagination:
?page=1&size=20 - Sorting:
?sort=price desc,title asc - Filtering: e.g.
?category=beverages&minPrice=5&maxPrice=20
POST /api/sales
{
"saleNumber": "SALE-2024-001",
"saleDate": "2024-01-15T10:30:00Z",
"customerId": "<guid>",
"customerDescription": "John Doe",
"branchId": "<guid>",
"branchDescription": "São Paulo - Centro",
"items": [
{
"productId": "<guid>",
"productDescription": "Budweiser Lager Beer",
"quantity": 12,
"unitPrice": 8.99
}
]
}{
"success": true,
"data": {
"id": "<guid>",
"saleNumber": "SALE-2024-001",
"date": "2024-01-15T10:30:00Z",
"customerDescription": "John Doe",
"branchDescription": "São Paulo - Centro",
"totalAmount": 86.30,
"cancelled": false,
"items": [
{
"productDescription": "Budweiser Lager Beer",
"quantity": 12,
"unitPrice": 8.99,
"discount": 21.58,
"total": 86.30
}
]
}
}Discounts are applied automatically per item when the sale is created:
| Quantity | Discount |
|---|---|
| 1–3 items | 0% |
| 4–9 items | 10% |
| 10–20 items | 20% |
| 21+ items | Not allowed |
// Example: 12 units of Budweiser at $8.99
var item = new SaleItem(saleId, productId, "Budweiser", 12, 8.99m);
item.ApplyDiscount();
// Discount: 20% → $21.58
// Total: $86.30- Maximum 20 items per product per sale line
- Positive quantities required (minimum 1)
- Unique email addresses per user
- Strong passwords: 8+ chars, uppercase, lowercase, digit, special character
tests/
├── DeveloperEvaluation.Unit/ # Domain logic, validators, CQRS handlers
├── DeveloperEvaluation.Integration/ # Database and repository tests
└── DeveloperEvaluation.Functional/ # End-to-end API tests
# Run all tests
dotnet test DeveloperEvaluation.sln
# Run a specific project
dotnet test tests/DeveloperEvaluation.Unit/
# Run with verbose output
dotnet test --verbosity normal
# Run with code coverage (requires coverlet)
dotnet test --collect:"XPlat Code Coverage"A convenience script generates an HTML coverage report:
chmod +x coverage-report.sh
./coverage-report.sh
# Opens: TestResults/CoverageReport/index.html- JWT Bearer tokens — configurable expiration via
appsettings - Role-based authorization — Admin, Manager, Customer roles enforced per endpoint
- Password hashing — secure one-way hashing (no plaintext storage)
- FluentValidation — all inputs validated before handler execution
- EF Core parameterization — SQL injection protection by default
appsettings.Development.jsonexcluded from git — never commit credentials
- Author: Lucas Albuquerque
- Email: lucas.albuquerque.gk@gmail.com
- Repository: https://github.com/LucasGeek/developer-evaluation
- Issues: https://github.com/LucasGeek/developer-evaluation/issues
Demonstrating Clean Architecture, CQRS, and DDD principles in .NET 8