A .NET 9 Web API for managing A/B experiments and tracking user events asynchronously using RabbitMQ for distributed message processing.
┌─────────────────────────────────────────────────────────────────────────────┐
│ Client Requests │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ ExperimentPlatform (ASP.NET Core Web API) │
│ ├── Controllers │
│ │ ├── ExperimentsController → Create experiments, assign variants │
│ │ └── EventsController → Track events (async via RabbitMQ) │
│ └── Program.cs │
└─────────────────────────────────────────────────────────────────────────────┘
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ ExperimentPlatformApplication (Business Logic Layer) │
│ ├── CreateExperimentHandler │
│ ├── AssignVariantHandler │
│ └── TrackEventHandler ──────────────────────► IBackgroundQueue │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ ExperimentPlatformInfrastructure (Data Access & Background Processing) │
│ ├── Persistence (EF Core + PostgreSQL) │
│ │ ├── ExperimentRepository │
│ │ ├── EventRepository │
│ │ └── ExperimentDbContext │
│ └── Background │
│ ├── RabbitMqBackgroundQueue ──────────────────────────────┐ │
│ └── InMemoryBackgroundQueue (fallback) │ │
└────────────────────────────────────────────────────────────────┼───────────┘
│ │
│ ▼
│ ┌────────────────────────┐
│ │ RabbitMQ Broker │
│ │ ├── Exchange │
│ │ ├── Queue (durable) │
│ │ └── Consumer │
│ └────────────────────────┘
▼ │
┌──────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ PostgreSQL Database │
└─────────────────────────────────────────────────────────────────────────────┘
| Project | Purpose |
|---|---|
ExperimentPlatform |
Web API entry point (controllers, middleware) |
ExperimentPlatformApplication |
Use case handlers and abstractions |
ExperimentPlatformDomain |
Entities, interfaces, enums, value objects |
ExperimentPlatformInfrastructure |
EF Core repositories, PostgreSQL, background queue |
ExperimentPlatformWorker |
Background service for async processing |
- Experiments CRUD: Create A/B experiments with weighted variants
- Variant Assignment: Deterministic user-to-variant mapping
- Event Tracking: Asynchronous event ingestion via RabbitMQ
- Message Broker: RabbitMQ integration with durable queues and persistent messages
- Distributed Processing: Decoupled event publishing and consumption
- Reliable Delivery: Automatic acknowledgment with retry on failure (requeue)
- Background Processing: RabbitMQ consumer processes queued events in order
- PostgreSQL: Persistent storage with Entity Framework Core
- Docker Ready: Multi-container setup with
docker-compose(API, PostgreSQL, RabbitMQ)
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/experiments |
Create a new experiment |
POST |
/api/experiments/{id}/assign |
Assign a variant to a user |
POST |
/api/experiments/{id}/events |
Track an event |
GET |
/health |
Health check |
# Start PostgreSQL and RabbitMQ
docker compose up db rabbitmq -d
# Run the API
cd ExperimentPlatform && dotnet runThe API will be available at https://localhost:8081 (Swagger at /swagger in Development).
RabbitMQ Management UI will be available at http://localhost:15672 (guest/guest).
# Set the database connection string
$env:ConnectionStrings__db = "Host=localhost;Port=5434;Database=experiments;Username=postgres;Password=BaraoLaika"
docker compose up --buildConnection strings and queue settings are managed via environment variables and appsettings.json:
| Variable | Default | Description |
|---|---|---|
ConnectionStrings__db |
Host=localhost;Port=5434;... |
PostgreSQL connection string |
Configure RabbitMQ settings in appsettings.Development.json:
{
"QueueSettings": {
"Type": "RabbitMQ"
},
"RabbitMQ": {
"HostName": "rabbitmq",
"Port": 5672,
"UserName": "guest",
"Password": "guest",
"QueueName": "experiment-events",
"ExchangeName": "experiment-exchange",
"RoutingKey": "event.tracked"
}
}The system supports two queue implementations:
- RabbitMQ (default): Distributed, persistent message queue with automatic retry
- InMemory: Simple in-memory queue for development/testing (fallback)
Set QueueSettings:Type to switch between implementations.
- Event Publishing: When an event is tracked via
/api/experiments/{id}/events, theTrackEventHandlerserializes the event and publishes it to RabbitMQ - Exchange & Routing: Messages are published to a durable exchange (
experiment-exchange) with routing key (event.tracked) - Queue: The exchange routes messages to a durable queue (
experiment-events) - Consumer:
RabbitMqBackgroundQueueconsumes messages asynchronously - Processing: Each message is deserialized and persisted to PostgreSQL via
IEventRepository - Acknowledgment: Successfully processed messages are acknowledged; failed messages are requeued for retry
- Durability: Messages persist across restarts
- Reliability: Automatic requeue on processing failures
- Scalability: Multiple consumers can process messages in parallel
- Decoupling: API responds immediately without waiting for database writes
- Observability: RabbitMQ Management UI for monitoring queue depth and throughput
| Setting | Description | Default |
|---|---|---|
HostName |
RabbitMQ broker hostname | rabbitmq |
Port |
AMQP protocol port | 5672 |
UserName |
Authentication username | guest |
Password |
Authentication password | guest |
QueueName |
Durable queue for events | experiment-events |
ExchangeName |
Direct exchange for routing | experiment-exchange |
RoutingKey |
Routing key for event messages | event.tracked |
- Dead Letter Queues: Route permanently failed messages to DLQ for manual inspection
- Retry Policies: Exponential backoff with maximum retry attempts
- Metrics: Prometheus metrics for queue depth, processing latency, and error rates
- Event Sourcing: Store events as an immutable log for analytics and replay
- Multiple Consumers: Scale horizontally with multiple worker instances
- Circuit Breaker: Prevent cascading failures when database is unavailable
- Framework: ASP.NET Core 9
- ORM: Entity Framework Core 9
- Database: PostgreSQL 16
- Message Broker: RabbitMQ 3 with Management Plugin
- Client Library: RabbitMQ.Client
- Serialization:
System.Text.Json - Background Processing: RabbitMQ Consumer with
AsyncEventingBasicConsumer