C#

CQRS Learning Project — .NET 10 + PostgreSQL

A complete, runnable learning project that teaches CQRS (Command Query Responsibility Segregation) through a realistic Order Management System built with .NET 10, PostgreSQL, and Docker Compose.

What This Project Teaches

  • CQRS fundamentals — command vs query separation, read vs write models
  • Clean Architecture — Domain, Application, Infrastructure, API layers
  • MediatR pipeline — commands, queries, handlers, pipeline behaviors
  • FluentValidation — input validation as a cross-cutting concern
  • PostgreSQL — write-side schema, read-side projections, optimistic concurrency
  • Docker Compose — containerized development environment
  • Testing — unit tests for domain/handlers/validators, integration tests for API endpoints

CQRS Summary

CQRS separates reads (queries) from writes (commands) into distinct models:

Write Side (Commands)Read Side (Queries)
CreateOrderCommandGetOrderByIdQuery
Loads full aggregatesProjects to DTOs
Enforces business rulesOptimizes for consumers
Uses IWriteDbContextUses IReadDbContext
Returns minimal data (ID)Returns rich DTOs

Key insight: This project uses a single PostgreSQL database for both sides. CQRS separation is logical (different interfaces, different models), not physical.

Architecture

text
┌─────────────────────────────────────────────┐
│                 API Layer                    │
│  Command Endpoints    Query Endpoints        │
│  POST/PUT/DELETE      GET                    │
├─────────────────────────────────────────────┤
│              Application Layer               │
│  ┌──────────┐  ┌──────────┐  ┌───────────┐  │
│  │ Commands  │  │ Queries  │  │ Behaviors │  │
│  │ Handlers  │  │ Handlers │  │ Logging   │  │
│  │ Validators│  │          │  │ Validation│  │
│  └────┬─────┘  └────┬─────┘  └───────────┘  │
├───────┼──────────────┼───────────────────────┤
│       │   Domain     │                       │
│  Order Entity    Value Objects               │
│  OrderItem       Exceptions                  │
├───────┼──────────────┼───────────────────────┤
│       │Infrastructure│                       │
│  IWriteDbContext  IReadDbContext              │
│       └──────┬───────┘                       │
│         AppDbContext                          │
│         PostgreSQL                           │
└─────────────────────────────────────────────┘

Folder Structure

text
DotnetCQRS/
├── src/
│   ├── CqrsLearning.Api/              # HTTP endpoints, middleware, startup
│   │   ├── Endpoints/
│   │   │   ├── OrderCommandEndpoints.cs
│   │   │   └── OrderQueryEndpoints.cs
│   │   ├── Middleware/
│   │   │   └── ExceptionHandlingMiddleware.cs
│   │   └── Program.cs
│   ├── CqrsLearning.Application/      # Use cases: commands, queries, handlers
│   │   ├── Commands/
│   │   │   ├── CreateOrder/
│   │   │   ├── AddOrderItem/
│   │   │   ├── UpdateOrderStatus/
│   │   │   ├── CancelOrder/
│   │   │   └── RemoveOrderItem/
│   │   ├── Queries/
│   │   │   ├── GetOrderById/
│   │   │   ├── SearchOrders/
│   │   │   └── GetOrdersDashboard/
│   │   ├── Behaviors/
│   │   │   ├── LoggingBehavior.cs
│   │   │   └── ValidationBehavior.cs
│   │   └── Interfaces/
│   │       ├── IWriteDbContext.cs
│   │       └── IReadDbContext.cs
│   ├── CqrsLearning.Domain/           # Business entities and rules
│   │   ├── Entities/
│   │   │   ├── Order.cs
│   │   │   └── OrderItem.cs
│   │   ├── ValueObjects/
│   │   ├── Enums/
│   │   └── Exceptions/
│   ├── CqrsLearning.Infrastructure/   # EF Core, PostgreSQL, migrations
│   │   └── Persistence/
│   │       ├── AppDbContext.cs
│   │       ├── Configurations/
│   │       ├── Migrations/
│   │       └── Seed/
│   └── CqrsLearning.Contracts/        # Shared DTOs: requests and responses
│       ├── Commands/
│       ├── Queries/
│       └── Responses/
├── tests/
│   ├── CqrsLearning.UnitTests/
│   └── CqrsLearning.IntegrationTests/
├── docs/                               # Documentation with Mermaid diagrams
├── docker-compose.yml
├── Dockerfile
└── CqrsLearning.sln

How to Run

bash
docker compose up --build

Local Development

bash
# Start PostgreSQL
docker compose up postgres -d

# Run the API
cd src/CqrsLearning.Api
dotnet run

API Endpoints

Commands (Write Side)

bash
# Create order
POST /api/orders
{
  "customerName": "Jane Smith",
  "customerEmail": "jane@example.com",
  "shippingStreet": "100 Broadway",
  "shippingCity": "New York",
  "shippingState": "NY",
  "shippingZipCode": "10001",
  "shippingCountry": "US",
  "notes": null,
  "items": [
    { "productName": "Keyboard", "sku": "KB-01", "quantity": 1, "unitPrice": 149.99 }
  ]
}

# Add item to order
POST /api/orders/{id}/items

# Update status (Confirmed, Processing, Shipped, Delivered)
PUT /api/orders/{id}/status
{ "status": "Confirmed" }

# Cancel order
POST /api/orders/{id}/cancel
{ "reason": "Customer requested cancellation" }

# Remove item
DELETE /api/orders/{id}/items/{itemId}

Queries (Read Side)

bash
# Get order with full details
GET /api/orders/{id}

# Search with filters and pagination
GET /api/orders?customerName=Jane&status=Pending&page=1&pageSize=10

# Dashboard aggregations
GET /api/orders/dashboard

Database Setup

PostgreSQL runs in Docker. Migrations are applied automatically on startup. The database is seeded with sample orders.

To reset: docker compose down -v && docker compose up --build

Running Tests

bash
# All tests
dotnet test

# Unit tests only
dotnet test tests/CqrsLearning.UnitTests

# Integration tests only
dotnet test tests/CqrsLearning.IntegrationTests

Learning Roadmap

  1. Start here: Read docs/cqrs-fundamentals.md
  2. Understand the split: Read docs/commands-vs-queries.md
  3. Study the models: Read docs/read-model-vs-write-model.md
  4. Explore the code: Start with Order.cs (domain) → CreateOrderCommandHandler.cs (write) → GetOrderByIdQueryHandler.cs (read)
  5. Understand the pipeline: Read docs/validation-and-pipelines.md
  6. See the database: Read docs/postgresql-design.md
  7. Run the tests: dotnet test and study each test file
  8. Be critical: Read docs/tradeoffs-and-when-not-to-use-cqrs.md
  9. Experiment: Add a new command or query yourself

Documentation

Detailed docs with Mermaid diagrams are in the /docs folder:

DocumentTopic
ArchitectureLayer diagram, dependencies
CQRS FundamentalsWhat, why, when, misconceptions
Commands vs QueriesSequence diagrams, flow comparison
Read vs Write ModelEntity vs DTO, multiple read models
PostgreSQL DesignSchema, indexes, concurrency
Validation & PipelinesFluentValidation, behaviors
Testing StrategyUnit, integration, test pyramid
Running the ProjectDocker, local dev, curl examples
TradeoffsWhen NOT to use CQRS

Troubleshooting

ProblemSolution
Port 5432 in useStop local PostgreSQL or change port in docker-compose.yml
Port 8080 in useChange ports in docker-compose.yml
Migration errorsdocker compose down -v && docker compose up --build
Connection refusedEnsure PostgreSQL is running: docker compose up postgres -d
Tests failEnsure .NET 10 SDK is installed: dotnet --version