A production-grade distributed system built with .NET 8, demonstrating lead-level architecture decisions across three microservices.
Clone it. Run one command. See a real distributed system.
git clone https://github.com/padmarajan26/TradeTrac.git
cd TradeTrac
docker-compose up --build┌─────────────────────────────────────────────────────────┐
│ API Gateway (YARP Reverse Proxy) │
│ JWT Auth · Routing │
└──────────────────┬──────────────────────────────────────┘
│
┌───────────▼──────────┐
│ Order Service │
│ .NET 8 Clean Arch │
│ DDD · CQRS · EF Core│
└───────────┬──────────┘
│ publishes events
┌───────────▼──────────┐
│ RabbitMQ │
│ (MassTransit) │
└───────────┬──────────┘
│ consumes events
┌───────────▼──────────┐
│ Notification Service │
│ Hangfire · Idempotent│
│ consumer · Retry │
└──────────────────────┘
PostgreSQL (Orders) PostgreSQL (Notifications)
OpenTelemetry → Jaeger + Prometheus
| Concern | Technology |
|---|---|
| Language | C# / .NET 8 |
| Architecture | Clean Architecture + DDD + CQRS |
| ORM | EF Core 8 + Dapper (read side) |
| Message bus | RabbitMQ via MassTransit |
| Background jobs | Hangfire (PostgreSQL backed) |
| API Gateway | YARP reverse proxy |
| Auth | JWT Bearer |
| Observability | OpenTelemetry → Jaeger + Prometheus |
| Containerisation | Docker + Docker Compose |
| CI/CD | GitHub Actions |
Prerequisites: Docker Desktop
git clone https://github.com/padmarajan26/TradeTrac.git
cd TradeTrac
docker-compose up --build| Service | URL |
|---|---|
| Order Service Swagger | http://localhost:5001/swagger |
| Hangfire Dashboard | http://localhost:5002/hangfire |
| RabbitMQ Management | http://localhost:15672 |
| Jaeger Traces | http://localhost:16686 |
| Prometheus Metrics | http://localhost:9090 |
1. Get a token
POST http://localhost:5001/auth/token
{ "username": "trader1", "password": "Password123!" }2. Place an order
POST http://localhost:5001/api/orders
Authorization: Bearer <token>
{
"symbol": "INFY",
"side": "Buy",
"orderType": "Limit",
"price": 1800.50,
"quantity": 10,
"currency": "INR"
}3. Watch the event flow
- Order saved to PostgreSQL
OrderPlacedIntegrationEventpublished to RabbitMQ (via Outbox pattern)- Notification Service consumes the event (idempotent)
- Hangfire job fires → notification logged
- Full trace visible in Jaeger
Domain events are written to the outbox_messages table in the same transaction as the order. A background worker publishes them to RabbitMQ. This guarantees no event is lost even if the broker is temporarily unavailable.
Commands go through EF Core (full aggregate loading, domain event dispatch). Queries use raw Dapper — no EF overhead, direct SQL, easily optimised with indexes.
Every RabbitMQ message is checked against a processed_events table before processing. Re-delivered messages are safely ignored.
Domain → no dependencies (pure business logic)
Application → depends on Domain (CQRS handlers, MediatR)
Infrastructure → depends on Application (EF Core, RabbitMQ)
API → depends on Application (controllers, middleware)
TradeTrac/
├── src/
│ ├── OrderService/
│ │ ├── Domain/ # Entities, Value Objects, Domain Events
│ │ ├── Application/ # Commands, Queries, MediatR pipeline
│ │ ├── Infrastructure/ # EF Core, Outbox, RabbitMQ
│ │ └── API/ # Controllers, Middleware, Program.cs
│ ├── NotificationService/ # MassTransit consumers + Hangfire
│ └── ApiGateway/ # YARP + JWT validation
├── tests/
│ └── OrderService.Tests/ # Unit + Integration tests (Testcontainers)
├── benchmarks/
│ └── OrderService.Benchmarks/ # BenchmarkDotNet — Dapper vs EF Core
├── infra/
│ ├── docker/ # Dockerfiles
│ ├── helm/ # Kubernetes Helm charts
│ └── otel/ # OpenTelemetry collector config
└── docker-compose.yml
| User | Password | Role |
|---|---|---|
| trader1 | Password123! | Trader |
| trader2 | Password123! | Trader |
| svc-internal | InternalPass! | InternalService |