Author: Dariem Carlos Macias
"The art of programming is the art of organizing complexity." β Edsger W. Dijkstra
Want to help make this project even better? Contributions are not just welcomeβthey're encouraged! Whether you're fixing a bug, adding a feature, improving documentation, or implementing one of the Next Steps, your contribution matters.
1. Explore What Needs Doing
- Review the Next Steps section for features waiting to be implemented
- Check open issues on GitHub for bugs and enhancement requests
- Identify areas for improvement (performance, documentation, testing, etc.)
2. Pick Your Task Choose something that excites you:
- β Implement Database Persistence (SQL Server, PostgreSQL, or MongoDB)
- β Add Dead Letter Queues for failed message handling
- β Build Unit and Integration Tests for subscribers and publishers
- β Enhance API Authentication with JWT or OAuth2
- β Add Circuit Breaker Pattern using Polly library
- β Implement Idempotency for duplicate message handling
- β Create Metrics and Observability with Prometheus/Grafana
- β Improve documentation or add new guides
3. Follow Best Practices for Pull Requests
Before You Start:
# Fork the repository on GitHub
# Clone your fork locally
git clone https://github.com/YOUR_USERNAME/OrderProcessing-RabbitMQ-Microservices.git
cd OrderProcessing-RabbitMQ-Microservices
# Add upstream remote
git remote add upstream https://github.com/dariemcarlosdev/OrderProcessing-RabbitMQ-Microservices.git
# Create a feature branch (use descriptive names)
git checkout -b feature/database-persistence
# or
git checkout -b fix/rabbitmq-connection-timeout
# or
git checkout -b docs/add-testing-examplesDevelopment Workflow:
# Keep your branch up to date
git fetch upstream
git rebase upstream/master
# Make your changes (follow existing code style)
# Test thoroughly (run the application, test all scenarios)
dotnet build
dotnet test # If tests exist
# Commit with clear, descriptive messages
git add .
git commit -m "feat: add PostgreSQL persistence for Order entity
- Implement OrderRepository with CRUD operations
- Add Entity Framework Core configuration
- Update appsettings.json with connection string
- Add database migration scripts
- Update documentation with setup instructions"
# Push to your fork
git push origin feature/database-persistence4. Create a Stellar Pull Request
PR Title Format:
feat: add database persistence with PostgreSQLfix: resolve RabbitMQ connection timeout on startupdocs: add comprehensive testing guiderefactor: simplify API response mapping logictest: add integration tests for subscribers
PR Description Template:
## π Summary
Brief description of what this PR does and why it's needed.
## π― Changes Made
- Added PostgreSQL database persistence for Order entity
- Implemented OrderRepository with async CRUD operations
- Added EF Core migrations and seeding
- Updated Docker Compose with PostgreSQL service
- Added connection string configuration
## π§ͺ Testing Performed
- β
Tested order creation and retrieval from database
- β
Verified database connection in Docker environment
- β
Ran all existing integration tests (all passing)
- β
Tested migration scripts on clean database
## π Documentation Updates
- Updated README.md with database setup instructions
- Added new guide: Docs/Persistence/DATABASE-SETUP.md
- Updated docker-compose.yml comments
## π Related Issues
Closes #42
Related to #38
## πΈ Screenshots (if applicable)
[Include screenshots of new features, UI changes, or test results]
## β
Checklist
- [x] Code follows project style and conventions
- [x] All tests pass
- [x] Documentation updated
- [x] Docker Compose configuration tested
- [x] No breaking changes (or clearly documented)
- [x] Commit messages are clear and descriptive5. Request Review
- Tag relevant maintainers or contributors
- Be responsive to feedback and suggestions
- Make requested changes promptly
- Engage in constructive discussion
6. Celebrate! π Once your PR is merged, you've officially contributed to OrderFlow.Core!
Code Quality:
- Follow existing code style and conventions (Clean Architecture, SOLID principles)
- Write self-documenting code with clear naming
- Add XML documentation comments for public APIs
- Keep methods small and focused (Single Responsibility)
Testing:
- Add unit tests for new business logic
- Add integration tests for new subscribers or publishers
- Ensure all existing tests still pass
- Document test scenarios
Documentation:
- Update README.md if adding new features
- Create new documentation files in
Docs/if needed - Add code comments for complex logic
- Update API documentation in Swagger
Commit Messages: Use Conventional Commits:
feat:new featuresfix:bug fixesdocs:documentation changesrefactor:code refactoringtest:adding or updating testschore:maintenance tasks
- π¬ Open a GitHub issue to discuss your idea before starting
- π§ Reach out if you're stuck or need guidance
- π Star the repository to show support
- π’ Share the project with others who might benefit
All contributors will be recognized in the project. Your name will be added to a Contributors section, and your efforts will be celebrated!
Ready to contribute? Pick a task from Next Steps and let's build something amazing together! π
OrderFlow.Core is a comprehensive demonstration of event-driven architecture and microservices patterns using .NET 8 and RabbitMQ. This project serves as both a learning resource and a production-ready reference implementation for building scalable, resilient, and maintainable distributed systems.
In modern software architecture, the ability to design systems that are:
- β Loosely coupled β components operate independently
- β Highly scalable β horizontal scaling without code changes
- β Resilient β graceful failure handling and automatic recovery
- β Maintainable β clean separation of concerns
...is not just a luxuryβit's a necessity. This project demonstrates these principles through a real-world order processing system using the Publish/Subscribe pattern with RabbitMQ as the message broker.
Building this project from the ground up was an incredible journey into the world of distributed systems and event-driven architecture. Here's what I discovered through hands-on implementation:
πΉ Event-Driven Architecture β I learned that decoupling components through message brokers isn't just theoryβit's a powerful way to build systems that truly scale. Watching orders flow through RabbitMQ and seeing multiple subscribers process them independently was a lightbulb moment. The beauty lies in how publishers don't need to know about subscribers; they just fire events and move on.
πΉ RabbitMQ Topic Exchange β Initially, routing keys seemed like simple strings, but mastering wildcard patterns (order.*, payment.*) revealed their true power. I discovered how to route one event to multiple queues based on patterns, enabling sophisticated message routing without complex conditional logic. The "aha!" moment came when I realized NotificationSubscriber could listen to all order events with a single order.* pattern.
πΉ Clean Architecture β Separating concerns into distinct layers (Controllers, Infrastructure, Domain Models, Contracts) felt tedious at first, but it paid off massively during refactoring. When I needed to change the response format, I only touched DTOsβnot a single domain model or subscriber. The Single Responsibility Principle stopped being a buzzword and became my best friend.
πΉ Docker Orchestration β Writing a multi-container docker-compose.yml taught me that deployment isn't just about running codeβit's about orchestrating ecosystems. Managing service dependencies, health checks, and network isolation showed me why Docker is indispensable for modern development. Watching RabbitMQ wait for health checks before my app started was satisfying in ways I didn't expect.
πΉ Background Services β Implementing BackgroundService in .NET 8 revealed the elegance of long-running processes. My subscribers run continuously in the background, consuming messages without blocking HTTP requests. I learned about graceful shutdown, exception handling in loops, and how to prevent memory leaks in infinite loops.
πΉ API Design Patterns β Creating the generic ApiResponse<T> wrapper taught me that consistency is king. Instead of scattered response formats, every endpoint now returns a predictable structure with success, message, data, and errors. This pattern eliminated so much client-side guesswork and made API consumption a joy.
πΉ Connection Resilience β I learned the hard way that connections failβRabbitMQ restarts, networks hiccup, containers restart. Implementing exponential backoff with retry logic (1s, 2s, 4s, 8s, 16s) transformed my brittle system into a resilient one. Automatic recovery and connection pooling became non-negotiables, not nice-to-haves.
πΉ Health Monitoring β Adding health checks wasn't just about green checkmarksβit was about observability. When I integrated ASP.NET Core health checks for RabbitMQ, I could finally see when things went wrong before users did. The /health endpoint became my first debugging tool, not my last resort.
The Biggest Lesson? Building distributed systems is about embracing failure and designing for resilience. Every component can fail, every network can drop, every message can be duplicated. The systems that survive aren't the ones that never failβthey're the ones that fail gracefully and recover automatically.
This project transformed my understanding from "I know the theory" to "I've lived the battle scars." Every bug taught me something new, every refactor made me appreciate clean architecture, and every successful deployment reinforced that good software isn't writtenβit's rewritten.
This project is designed for software engineers, architects, and students who want to go beyond tutorials and build something real. If you're ready to learn by doing, dive inβthe journey is worth it.
- Architecture
- Prerequisites
- Quick Start with Docker Compose
- Alternative: Local Development Setup
- How the Pub/Sub Pattern Works
- API Endpoints
- Testing the Pub/Sub Pattern
- Routing Keys
- Project Structure
- Key Features
- Monitoring
- Troubleshooting
- Configuration
- Production Deployment
- π Documentation
- Next Steps
- Resources
- License
A .NET 8 Web API project demonstrating order processing using the Publish/Subscribe pattern with RabbitMQ as the message broker.
This project implements a complete order processing system using the Publish/Subscribe pattern with the following components:
- OrdersController: REST API endpoints that publish order events to RabbitMQ
- OrderProcessingSubscriber: Processes new orders (routing key:
order.created) - PaymentVerificationSubscriber: Verifies payments (routing key:
payment.*) - ShippingSubscriber: Handles shipping (routing key:
order.shipped) - NotificationSubscriber: Sends notifications for all order events (routing key:
order.*)
- .NET 8 SDK
- Docker Desktop (for containerized deployment)
The easiest way to run the entire application stack:
dotnet publish -c Release -o ./publishdocker-compose up -dThis will:
- Start RabbitMQ with management UI
- Wait for RabbitMQ to be healthy
- Start the OrderFlow.Core application
- Create a bridge network for service communication
- Swagger UI: http://localhost:8080/swagger
- Health Check: http://localhost:8080/health
- RabbitMQ Management UI: http://localhost:15672
- Username:
admin - Password:
admin123
- Username:
# All services
docker-compose logs -f
# Specific service
docker-compose logs -f orderflow-coredocker-compose downπ For detailed Docker deployment instructions, troubleshooting, and production considerations:
If you prefer to run the application locally without Docker:
docker run -d --name rabbitmq \
-p 5672:5672 \
-p 15672:15672 \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin123 \
rabbitmq:3-management# Check if RabbitMQ is healthy (wait until "Ping succeeded")
docker exec rabbitmq rabbitmq-diagnostics pingEnsure appsettings.json matches your RabbitMQ settings:
"RabbitMq": {
"HostName": "localhost",
"Port": 5672,
"UserName": "admin",
"Password": "admin123",
"ExchangeName": "order_exchange",
"ExchangeType": "topic"
}dotnet build
dotnet runThe application will be available at:
- Swagger UI: https://localhost:7279/swagger (or http://localhost:5246/swagger)
- Health Check: https://localhost:7279/health
This section explains the complete message flow and the role of each component in the publish/subscribe architecture.
The project implements an event-driven architecture using RabbitMQ's Topic Exchange pattern. When an order-related action occurs (create, payment, shipping, etc.), an event is published to RabbitMQ. Multiple subscribers listen for specific event types and process them independently and asynchronously.
RabbitMqSettings (Configuration/RabbitMqSettings.cs)
- Contains all RabbitMQ connection parameters (hostname, port, credentials)
- Defines exchange name (
order_exchange) and type (topic) - Loaded from
appsettings.jsonduring application startup - Injected into all RabbitMQ-related services via dependency injection
Order (Models/Order.cs)
- Core domain entity representing an order
- Contains customer info, product details, quantity, amount, status, and timestamps
- Status transitions through the
OrderStatusenum: Created β Processing β PaymentVerified β Shipped β Delivered (or Cancelled)
OrderEvent (Models/OrderEvent.cs)
- Wrapper class for all order-related events
- Contains OrderId, EventType, OrderData (the full Order object), Timestamp, and Message
- Serialized to JSON for transmission through RabbitMQ
OrderEventTypes (Models/OrderEventTypes.cs)
- Static class defining all event type constants
- Ensures consistency across publishers and subscribers
- Examples:
order.created,payment.verified,order.shipped, etc.
IRabbitMqConnectionFactory (Infrastructure/RabbitMQ/IRabbitMqConnectionFactory.cs)
- Interface for abstracting RabbitMQ connection creation
- Enables testability and loose coupling
RabbitMqConnectionFactory (Infrastructure/RabbitMQ/RabbitMqConnectionFactory.cs)
- Concrete implementation that creates RabbitMQ connections
- Implements retry logic with exponential backoff (5 attempts)
- Configures automatic recovery and network recovery intervals
- Uses
RabbitMqSettingsto establish connections - Provides comprehensive logging for connection events and errors
IMessagePublisher (Infrastructure/RabbitMQ/IMessagePublisher.cs)
- Interface defining the contract for message publishing
- Single method:
PublishAsync(OrderEvent, routingKey)
RabbitMqPublisher (Infrastructure/RabbitMQ/RabbitMqPublisher.cs)
- Implements
IMessagePublisherand handles actual message publishing - Initialization: Creates connection, channel, and declares the exchange
- Publishing Process:
- Serializes
OrderEventto JSON - Creates message properties (persistent, content-type, timestamp)
- Publishes to
order_exchangewith specified routing key - Logs all operations for monitoring
- Serializes
- Features: Auto-recovery on channel closure, persistent message delivery, proper resource cleanup
OrdersController (Controllers/OrdersController.cs)
- REST API controller that acts as the Event Publisher
- Receives HTTP requests and publishes corresponding events
- Endpoints:
POST /api/ordersβ Publishesorder.createdeventPOST /api/orders/{id}/paymentβ Publishespayment.verifiedeventPOST /api/orders/{id}/shipβ Publishesorder.shippedeventPOST /api/orders/{id}/deliverβ Publishesorder.deliveredeventDELETE /api/orders/{id}β Publishesorder.cancelledevent
- Creates
OrderandOrderEventobjects from requests - Uses
IMessagePublisherto publish events to RabbitMQ - Returns consistent
ApiResponse<T>wrapper for all endpoints
π Learn more about the API Response Pattern: API Response Pattern Guide
RabbitMqSubscriberBase (Infrastructure/RabbitMQ/RabbitMqSubscriberBase.cs)
- Abstract base class for all subscribers
- Extends
BackgroundServiceto run continuously in the background - Initialization:
- Creates connection and channel via
IRabbitMqConnectionFactory - Declares the exchange (
order_exchange) - Declares queue with durable settings
- Binds queue to exchange using routing key pattern
- Sets QoS to process one message at a time
- Creates connection and channel via
- Message Processing:
- Receives messages via
EventingBasicConsumer - Deserializes JSON to
OrderEventobject - Calls abstract
ProcessMessageAsyncmethod (implemented by derived classes) - Acknowledges message on success (
BasicAck) - Requeues message on error (
BasicNackwith requeue=true)
- Receives messages via
- Derived classes must define:
QueueName,RoutingKey, andProcessMessageAsync
OrderProcessingSubscriber (Services/Subscribers/OrderProcessingSubscriber.cs)
- Queue:
order_processing_queue - Routing Key:
order.created - Purpose: Processes newly created orders
- Actions: Validates order details, checks inventory, calculates costs
- Triggered by: POST /api/orders
PaymentVerificationSubscriber (Services/Subscribers/PaymentVerificationSubscriber.cs)
- Queue:
payment_verification_queue - Routing Key:
payment.*(wildcard - matches all payment events) - Purpose: Verifies payment transactions
- Actions: Integrates with payment gateways, validates amounts, handles fraud detection
- Triggered by: POST /api/orders/{id}/payment
ShippingSubscriber (Services/Subscribers/ShippingSubscriber.cs)
- Queue:
shipping_queue - Routing Key:
order.shipped - Purpose: Handles shipping logistics
- Actions: Integrates with carriers, generates labels, calculates delivery times
- Triggered by: POST /api/orders/{id}/ship
NotificationSubscriber (Services/Subscribers/NotificationSubscriber.cs)
- Queue:
notification_queue - Routing Key:
order.*(wildcard - matches ALL order events) - Purpose: Sends customer notifications for any order event
- Actions: Sends emails, SMS, push notifications based on event type
- Triggered by: ALL order endpoints (creates notifications for every event)
Understanding how subscribers come to life and start processing messages is crucial to grasping the event-driven architecture. Here's the complete instantiation and execution flow:
Instantiation Flow (Happens Once at Application Startup)
1. β
Constructor Invoked
ββ> Dependency Injection container creates subscriber instance
ββ> Injects: IRabbitMqConnectionFactory, RabbitMqSettings, ILogger
ββ> Base class constructor called immediately
2. β
Configuration Loaded
ββ> Reads subscriber-specific config from appsettings.json
ββ> Example: "Notification" β QueueName, RoutingKey
ββ> Validates configuration (throws exception if missing)
3. β
Registered as Hosted Service
ββ> AddHostedService<T>() in Program.cs registers subscriber
ββ> .NET Host manages lifecycle (start, stop, dispose)
ββ> All subscribers run in parallel as background tasks
Execution Flow (Continuous Background Processing)
4. β
Application Starts
ββ> .NET Host calls ExecuteAsync() automatically
ββ> Subscriber transitions from "Created" β "Running" state
ββ> No HTTP request neededβruns independently
5. β
RabbitMQ Connection Established
ββ> IRabbitMqConnectionFactory.CreateConnection()
ββ> Retry logic with exponential backoff (up to 5 attempts)
ββ> Connection settings: AutomaticRecoveryEnabled = true
6. β
Infrastructure Declared
ββ> Exchange declared (order_exchange, type: topic, durable: true)
ββ> Queue declared (e.g., notification_queue, durable: true)
ββ> QoS configured (prefetchCount: 1 - process one message at a time)
7. β
Queue Binding Configured
ββ> Binds queue to exchange with routing key pattern
ββ> Example: notification_queue β "order.*" pattern
ββ> RabbitMQ now knows where to route matching messages
8. β
Consumer Starts Listening
ββ> EventingBasicConsumer attached to channel
ββ> Subscribes to message delivery events
ββ> Manual acknowledgment mode (autoAck: false)
9. β
Ready State - Infinite Loop
ββ> while (!stoppingToken.IsCancellationRequested)
ββ> Waits for incoming messages (event-driven, non-blocking)
ββ> CPU usage near 0% when idleβno polling overhead
10. β
Message Processing (Per Message)
ββ> OnMessageReceived event fires automatically
ββ> Decodes UTF-8 message body β JSON string
ββ> Deserializes JSON β OrderEvent object
ββ> Calls ProcessMessageAsync() (derived class implementation)
ββ> On Success: BasicAck (remove from queue)
ββ> On Error: BasicNack with requeue=true (retry later)
11. β
Graceful Shutdown (On Application Stop)
ββ> CancellationToken triggered
ββ> Stops consuming new messages
ββ> Completes current message processing
ββ> Closes channel and connection
ββ> Disposes resources (Dispose pattern)
Key Characteristics
- Zero Configuration Per Message: Once started, subscribers process messages automaticallyβno manual intervention required
- Parallel Execution: All subscribers run simultaneously in separate background tasks
- Event-Driven: No polling loopsβRabbitMQ pushes messages when they arrive
- Resilient: Automatic connection recovery handles network failures transparently
- Isolated: Each subscriber has its own queue, channel, and processing logic
- Testable: Base class handles infrastructure; derived classes focus on business logic
- Graceful Degradation: Failed messages requeue automatically for retry
Example: NotificationSubscriber Instantiation
// Program.cs - Registration
builder.Services.AddHostedService<NotificationSubscriber>();
// Runtime Flow:
1. App starts β DI creates NotificationSubscriber instance
2. Constructor loads "Notification" config from appsettings.json
3. .NET Host calls ExecuteAsync() automatically
4. Subscriber connects to RabbitMQ, declares queue, binds routing key "order.*"
5. Starts listening for messages matching "order.*" pattern
6. When POST /api/orders occurs:
- Publisher sends message with routing key "order.created"
- RabbitMQ routes to notification_queue (matches "order.*")
- NotificationSubscriber receives message automatically
- ProcessMessageAsync() executes notification logic
- Message acknowledged, removed from queue
7. Subscriber continues waiting for next message (infinite loop)This architecture ensures subscribers are always ready to process messages the moment they arrive, without requiring manual polling or explicit invocation. The .NET BackgroundService abstraction combined with RabbitMQ's event-driven model creates a robust, scalable, and maintainable message processing pipeline.
Let's trace what happens when you create a new order:
1. Client Request
ββ> POST /api/orders { "customerName": "John", "productName": "Laptop", ... }
2. OrdersController (Publisher)
ββ> Creates Order object (ID, Status=Created, Timestamp)
ββ> Creates OrderEvent object (OrderId, EventType="order.created", OrderData)
ββ> Calls RabbitMqPublisher.PublishAsync(orderEvent, "order.created")
3. RabbitMqPublisher
ββ> Serializes OrderEvent to JSON
ββ> Creates AMQP message with persistent properties
ββ> Publishes to "order_exchange" with routing key "order.created"
4. RabbitMQ Topic Exchange
ββ> Receives message with routing key "order.created"
ββ> Evaluates routing key against queue bindings:
β ββ> Matches "order.created" pattern β Routes to order_processing_queue
β ββ> Matches "order.*" pattern β Routes to notification_queue
ββ> Delivers message to both queues (Pub/Sub pattern!)
5. Subscribers (Parallel Processing)
ββ> OrderProcessingSubscriber
β ββ> Receives from order_processing_queue
β ββ> Deserializes JSON to OrderEvent
β ββ> Calls ProcessMessageAsync()
β ββ> Executes order processing logic
β ββ> Acknowledges message (BasicAck)
β
ββ> NotificationSubscriber
ββ> Receives from notification_queue
ββ> Deserializes JSON to OrderEvent
ββ> Calls ProcessMessageAsync()
ββ> Determines notification type: "Order Confirmation"
ββ> Sends notification to customer
ββ> Acknowledges message (BasicAck)
6. Response
ββ> OrdersController returns HTTP 200 with Order details
The Topic Exchange uses pattern matching for routing:
| Event Published | Routing Key | Matched Subscribers |
|---|---|---|
| Order Created | order.created |
OrderProcessingSubscriber (order.created)NotificationSubscriber ( order.*) |
| Payment Verified | payment.verified |
PaymentVerificationSubscriber (payment.*) |
| Order Shipped | order.shipped |
ShippingSubscriber (order.shipped)NotificationSubscriber ( order.*) |
| Order Delivered | order.delivered |
NotificationSubscriber (order.*) |
| Order Cancelled | order.cancelled |
NotificationSubscriber (order.*) |
Wildcard Patterns:
*(star) matches exactly one word (e.g.,order.*matchesorder.created,order.shipped)#(hash) matches zero or more words (not used in this project, but available)
In Program.cs, all components are registered:
// Configuration
builder.Services.Configure<RabbitMqSettings>(
builder.Configuration.GetSection("RabbitMq"));
// Infrastructure
builder.Services.AddSingleton<IRabbitMqConnectionFactory, RabbitMqConnectionFactory>();
builder.Services.AddScoped<IMessagePublisher, RabbitMqPublisher>();
// Subscribers (Background Services)
builder.Services.AddHostedService<OrderProcessingSubscriber>();
builder.Services.AddHostedService<PaymentVerificationSubscriber>();
builder.Services.AddHostedService<ShippingSubscriber>();
builder.Services.AddHostedService<NotificationSubscriber>();
// Health Checks
builder.Services.AddHealthChecks()
.AddRabbitMQ(rabbitConnectionString, name: "rabbitmq");- Loose Coupling: Publishers don't know about subscribers, and vice versa
- Scalability: Add new subscribers without modifying existing code
- Asynchronous Processing: HTTP requests return immediately; heavy processing happens in background
- Multiple Consumers: Same event can trigger multiple business processes
- Reliability: Persistent messages and acknowledgments ensure no data loss
- Flexibility: Topic routing allows sophisticated event filtering
- Resilience: Automatic recovery handles connection failures gracefully with retry logic
- Publisher: Logs errors and throws exceptions (returns 500 to client)
- Subscribers: Catch exceptions, log errors, and requeue messages for retry
- Connection Failures: Automatic recovery enabled with 10-second intervals
- Connection Retry: Exponential backoff with 5 retry attempts (1s, 2s, 4s, 8s, 16s)
- Message Failures: Messages stay in queue until successfully processed
All endpoints return a consistent ApiResponse<T> wrapper with the following structure:
{
"success": true,
"message": "Order created successfully",
"data": { ... },
"errors": []
}π Learn more about the API Response Pattern: API Response Pattern Guide
POST /api/orders
Content-Type: application/json
{
"customerName": "John Doe",
"productName": "Laptop",
"quantity": 1,
"totalAmount": 1299.99
}POST /api/orders/{orderId}/paymentPOST /api/orders/{orderId}/shipPOST /api/orders/{orderId}/deliverDELETE /api/orders/{orderId}GET /healthReturns JSON with application health status and RabbitMQ connectivity.
-
Start all services:
docker-compose up -d
-
Wait for services to be ready:
docker-compose ps
-
Access Swagger UI:
- Navigate to http://localhost:8080/swagger
- Use the interactive UI to test endpoints
-
Create a new order:
- Execute POST
/api/orderswith sample data - Check the logs to see message flow:
docker-compose logs -f orderflow-core
- Execute POST
-
Observe in the logs:
- Publisher: Message published to RabbitMQ
- OrderProcessingSubscriber: Processing the order
- NotificationSubscriber: Sending notification
-
Monitor in RabbitMQ UI:
- Visit http://localhost:15672 (admin/admin123)
- Check Exchanges β
order_exchange - Check Queues β See all 4 queues and message flow
π For comprehensive testing scenarios and automated test scripts: Testing Guide
- Start RabbitMQ (see Alternative: Local Development Setup)
- Start the application with
dotnet run - Use Swagger UI at https://localhost:7279/swagger
- Check console logs to see message flow
The project uses topic exchange with the following routing patterns:
order.createdβ OrderProcessingSubscriber, NotificationSubscriberpayment.verifiedβ PaymentVerificationSubscriberorder.shippedβ ShippingSubscriber, NotificationSubscriberorder.deliveredβ NotificationSubscriberorder.cancelledβ NotificationSubscriber
OrderFlow.Core/
βββ Configuration/
β βββ RabbitMqSettings.cs
βββ Contracts/
β βββ Requests/
β β βββ CreateOrderRequestDto.cs
β βββ Responses/
β βββ ApiResponse.cs
β βββ CreateOrderResponseDto.cs
β βββ OrderOperationResponseDto.cs
β βββ OrderResponseDto.cs
βββ Controllers/
β βββ OrdersController.cs
βββ Docs/
β βββ Containerization/
β β βββ DOCKER-CONTAINERIZE-README.md
β β βββ DOCKER-DEPLOYMENT.md
β βββ Patterns/
β β βββ API-RESPONSE-PATTERN.md
β β βββ API-RESPONSE-SIMPLIFICATION.md
β β βββ GENERIC-MAPPING-EXTENSIONS.md
β βββ Tests/
β βββ TEST-README.md
βββ Extensions/
β βββ MappingExtensions.cs
βββ Infrastructure/
β βββ RabbitMQ/
β βββ IRabbitMqConnectionFactory.cs
β βββ RabbitMqConnectionFactory.cs
β βββ IMessagePublisher.cs
β βββ RabbitMqPublisher.cs
β βββ RabbitMqSubscriberBase.cs
βββ Models/
β βββ Order.cs
β βββ OrderEvent.cs
β βββ OrderEventTypes.cs
βββ Services/
β βββ Subscribers/
β βββ OrderProcessingSubscriber.cs
β βββ PaymentVerificationSubscriber.cs
β βββ ShippingSubscriber.cs
β βββ NotificationSubscriber.cs
βββ docker-compose.yml
βββ Dockerfile
βββ .dockerignore
βββ Program.cs
βββ appsettings.json
βββ README.md
- β Event-Driven Architecture β Topic-based routing with RabbitMQ
- β Multiple Subscribers β Different subscribers for different order events
- β Reliable Messaging β Automatic message acknowledgment and persistent messages
- β Connection Resilience β Automatic connection recovery with exponential backoff retry logic
- β Clean Architecture β Separation of concerns and SOLID principles
- β Background Services β Continuous message consumption without blocking HTTP requests
- β Docker Compose β Easy deployment with container orchestration
- β Health Monitoring β ASP.NET Core health checks for RabbitMQ connectivity
- β API Documentation β Interactive Swagger UI
- β
Generic Response Pattern β Consistent
ApiResponse<T>wrapper for all endpoints - β Type-Safe Mapping β Generic mapping extensions for domain models to DTOs
- β Comprehensive Documentation β Multiple guides covering all aspects
- β Structured Logging β Detailed logging for debugging and monitoring
# Docker Compose
docker-compose logs -f orderflow-core
# Local Development
Check console outputVisit http://localhost:15672 to monitor:
- Exchange creation and bindings
- Queue status and message rates
- Consumer connections
- Message flow and routing
Check http://localhost:8080/health for:
- Overall application health status
- RabbitMQ connection status
- Response time metrics
- Docker Compose: Ensure RabbitMQ is healthy with
docker-compose ps - Local: Check RabbitMQ is running with
docker ps - Verify port 5672 is accessible
- Check credentials match in
appsettings.jsonor docker-compose environment variables
- Docker: Access http://localhost:8080/swagger (not HTTPS)
- Local: Try both HTTP and HTTPS URLs
- Check application logs for startup errors
- Check logs:
docker-compose logs orderflow-core - Verify publish folder exists:
ls ./publish - Rebuild:
dotnet publish -c Release -o ./publish && docker-compose up -d --build
π For detailed troubleshooting and Docker-specific issues: Docker Deployment Guide
Configuration is managed via environment variables in docker-compose.yml:
- RabbitMQ hostname:
rabbitmq(Docker service name) - Credentials: admin/admin123
π For in-depth docker-compose.yml explanation: Docker Containerization Guide
Configuration is loaded from appsettings.json:
- RabbitMQ hostname:
localhost - Port: 5672
- Credentials: admin/admin123
For production deployment considerations, including:
- Secret management
- HTTPS configuration
- Resource limits
- Monitoring and logging
- Health checks
- Scaling strategies
π See the Production Considerations section in: Docker Deployment Guide
This project includes comprehensive documentation covering all aspects of the system:
-
- Quick start with Docker Compose
- Environment configuration
- Port mappings and networking
- Troubleshooting container issues
- Production deployment strategies
-
Docker Containerization Deep Dive
- Complete docker-compose.yml breakdown
- Service orchestration explained
- Networking architecture
- Volume management and persistence
- Health checks and dependencies
- Container lifecycle management
- Advanced scenarios and best practices
-
- Strongly-typed configuration binding
- IOptions pattern implementation
- Configuration loading from appsettings.json
- Dependency injection integration
- Validation and best practices
-
Pub/Sub RabbitMQ Pattern Guide
- Event-driven architecture overview
- Topic exchange routing patterns
- Publisher and subscriber implementation
- Message flow and routing examples
- Real-world usage scenarios
-
- Consistent response wrapper structure
- Generic
ApiResponse<T>implementation - Error handling strategies
- Success and failure response examples
- Best practices for API design
-
API Response Simplification Guide
- Response structure evolution
- Before/after comparison
- Migration guide for API consumers
- Rollback instructions
-
Generic Mapping Extensions Guide
- Generic
MapTo<TSource, TDestination>method - Collection mapping with
MapToList - Type-safe domain model to DTO mapping
- Real-world usage examples
- Performance considerations
- Generic
- Comprehensive Testing Guide
- Quick start testing with Swagger UI
- Detailed test scenarios for each event type
- RabbitMQ Management UI monitoring
- Automated test scripts
- Troubleshooting test failures
- Performance and load testing
| Topic | Document | Description |
|---|---|---|
| Quick Start | Main README | Getting started, architecture overview |
| Docker Deployment | DOCKER-DEPLOYMENT.md | Step-by-step deployment guide |
| Docker Architecture | DOCKER-CONTAINERIZE-README.md | Deep dive into docker-compose.yml |
| Options Pattern | OPTIONS-PATTERN.md | Configuration binding with IOptions |
| Pub/Sub Pattern | PUB-SUB-RABBITMQ-PATTERN.md | Event-driven architecture with RabbitMQ |
| API Patterns | API-RESPONSE-PATTERN.md | API response design patterns |
| API Simplification | API-RESPONSE-SIMPLIFICATION.md | Response structure evolution guide |
| Generic Mapping | GENERIC-MAPPING-EXTENSIONS.md | Type-safe domain to DTO mapping |
| Testing | TEST-README.md | Comprehensive testing guide |
Consider adding:
- Connection Resilience: β Implemented with retry logic and exponential backoff
- Health Checks: β Implemented with ASP.NET Core health checks
- Docker Support: β Full Docker Compose orchestration
- API Response Pattern: β Consistent response wrapper implemented
- Generic Mapping Extensions: β Type-safe domain model to DTO mapping
- Comprehensive Documentation: β Multiple guides covering all aspects
- Structured Logging: β Detailed logging for debugging and monitoring
- Database Persistence: Store orders in a database (SQL Server, PostgreSQL)
- Dead Letter Queues: Handle permanently failed messages
- Message Retry Policies: Enhanced backoff strategies with dead letter exchange
- Circuit Breaker Pattern: Prevent cascading failures (Polly library)
- Unit and Integration Tests: Test publishers and subscribers
- Metrics and Observability: Prometheus, Grafana, or Application Insights
- API Authentication: Add JWT or OAuth2 authentication
- Rate Limiting: Protect API endpoints from abuse
- Idempotency: Handle duplicate message processing
- Event Sourcing: Full event history and replay capability
- RabbitMQ Documentation
- .NET RabbitMQ Client
- ASP.NET Core Background Services
- Docker Compose Documentation
- Topic Exchange Tutorial
- Docker Deployment Guide
- Docker Containerization Deep Dive
- Options Pattern Guide
- Pub/Sub RabbitMQ Pattern Guide
- API Response Pattern Guide
- API Response Simplification Guide
- Generic Mapping Extensions Guide
- Comprehensive Testing Guide
If you're new to these concepts, here's a suggested learning path:
- Start Simple β Run the project locally and test the
/api/ordersendpoint - Observe the Flow β Watch the logs and RabbitMQ Management UI
- Understand Routing β Experiment with different routing keys
- Read the Docs β Explore the comprehensive documentation for deep dives
- Test Thoroughly β Use the Testing Guide for scenarios
- Deploy β Use Docker Compose to experience the full stack
- Containerize β Learn from Docker Containerization Guide
- Optimize β Implement dead letter queues and retry policies
This project is open for contributions! Whether you want to:
- π Fix bugs or improve documentation
- β¨ Add new features or patterns
- π Share your learnings or use cases
- π‘ Suggest architectural improvements
Feel free to open an issue or submit a pull request. All contributions are welcome!
Have questions or suggestions? Feel free to:
- π§ Open an issue on GitHub
- π Share your thoughts on implementation patterns
- π Star this repository if you find it helpful
This project stands on the shoulders of giants:
- The RabbitMQ team for building an incredible message broker
- The .NET team for the amazing ASP.NET Core framework
- The Docker community for simplifying deployment
- Edsger W. Dijkstra for teaching us to organize complexity
- Every developer who has shared knowledge through open source
This is a demonstration project for learning purposes.
"Code is like humor. When you have to explain it, it's bad." β Cory House
This project was crafted with care to demonstrate best practices, clean architecture, and the joy of building distributed systems. Whether you're a student learning event-driven architecture, an engineer building production systems, or an architect evaluating patternsβI hope this project serves you well.
May your messages always route correctly, your queues never overflow, and your connections always recover gracefully. π
Happy Coding! β¨οΈβ¨
If this project helped you in your journey, consider giving it a β star and sharing it with others who might benefit.
Dariem Carlos Macias
Software Engineer | Distributed Systems Enthusiast
This project represents my journey into the world of event-driven architecture, microservices patterns, and production-ready system design. Every line of code, every architectural decision, and every piece of documentation was crafted with the goal of not just building software, but building understanding.
"The best way to learn is to teach, and the best way to teach is through code that speaks for itself."
β Built with .NET 8, RabbitMQ, Docker, and a lot of β