Skip to content

Production-ready .NET 8 microservice demonstrating event-driven architecture with RabbitMQ Pub/Sub, Docker orchestration, clean architecture, and distributed messaging patterns

License

Notifications You must be signed in to change notification settings

dariemcarlosdev/OrderProcessing-RabbitMQ-Microservices

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

15 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

OrderFlow - Event-Driven Order Processing with RabbitMQ Pub/Sub & .NET 8

Author: Dariem Carlos Macias


.NET 8 RabbitMQ Docker C#

Architecture Pattern Microservices Clean Architecture

License PRs Welcome GitHub Stars


"The art of programming is the art of organizing complexity." β€” Edsger W. Dijkstra


πŸš€ How to Contribute to This Project

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.

🎯 Quick Contribution Guide

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-examples

Development 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-persistence

4. Create a Stellar Pull Request

PR Title Format:

  • feat: add database persistence with PostgreSQL
  • fix: resolve RabbitMQ connection timeout on startup
  • docs: add comprehensive testing guide
  • refactor: simplify API response mapping logic
  • test: 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 descriptive

5. 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!

πŸ“– Contribution Standards

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 features
  • fix: bug fixes
  • docs: documentation changes
  • refactor: code refactoring
  • test: adding or updating tests
  • chore: maintenance tasks

🀝 Need Help?

  • πŸ’¬ 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

πŸ† Recognition

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! πŸš€


🎯 Project Vision

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.

Why This Project Exists

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.

What I Learned

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.


πŸ“š Table of Contents

  1. Architecture
  2. Prerequisites
  3. Quick Start with Docker Compose
  4. Alternative: Local Development Setup
  5. How the Pub/Sub Pattern Works
  6. API Endpoints
  7. Testing the Pub/Sub Pattern
  8. Routing Keys
  9. Project Structure
  10. Key Features
  11. Monitoring
  12. Troubleshooting
  13. Configuration
  14. Production Deployment
  15. πŸ“– Documentation
  16. Next Steps
  17. Resources
  18. License

OrderFlow.Core - Order Processing with RabbitMQ Pub/Sub

A .NET 8 Web API project demonstrating order processing using the Publish/Subscribe pattern with RabbitMQ as the message broker.

Architecture

This project implements a complete order processing system using the Publish/Subscribe pattern with the following components:

Publisher

  • OrdersController: REST API endpoints that publish order events to RabbitMQ

Subscribers (Background Services)

  1. OrderProcessingSubscriber: Processes new orders (routing key: order.created)
  2. PaymentVerificationSubscriber: Verifies payments (routing key: payment.*)
  3. ShippingSubscriber: Handles shipping (routing key: order.shipped)
  4. NotificationSubscriber: Sends notifications for all order events (routing key: order.*)

Prerequisites

  • .NET 8 SDK
  • Docker Desktop (for containerized deployment)

Quick Start with Docker Compose (Recommended)

The easiest way to run the entire application stack:

1. Build the Application

dotnet publish -c Release -o ./publish

2. Start All Services

docker-compose up -d

This will:

  • Start RabbitMQ with management UI
  • Wait for RabbitMQ to be healthy
  • Start the OrderFlow.Core application
  • Create a bridge network for service communication

3. Access the Application

4. View Logs

# All services
docker-compose logs -f

# Specific service
docker-compose logs -f orderflow-core

5. Stop Services

docker-compose down

πŸ“– For detailed Docker deployment instructions, troubleshooting, and production considerations:

Alternative: Local Development Setup

If you prefer to run the application locally without Docker:

1. Start RabbitMQ Only with 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

2. Wait for RabbitMQ to be Ready

# Check if RabbitMQ is healthy (wait until "Ping succeeded")
docker exec rabbitmq rabbitmq-diagnostics ping

3. Update Configuration

Ensure appsettings.json matches your RabbitMQ settings:

"RabbitMq": {
  "HostName": "localhost",
  "Port": 5672,
  "UserName": "admin",
  "Password": "admin123",
  "ExchangeName": "order_exchange",
  "ExchangeType": "topic"
}

4. Run the Application

dotnet build
dotnet run

The application will be available at:

How the Pub/Sub Pattern Works in This Project

This section explains the complete message flow and the role of each component in the publish/subscribe architecture.

Overview

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.

Key Components

1. Configuration Layer

RabbitMqSettings (Configuration/RabbitMqSettings.cs)

  • Contains all RabbitMQ connection parameters (hostname, port, credentials)
  • Defines exchange name (order_exchange) and type (topic)
  • Loaded from appsettings.json during application startup
  • Injected into all RabbitMQ-related services via dependency injection

2. Domain Models

Order (Models/Order.cs)

  • Core domain entity representing an order
  • Contains customer info, product details, quantity, amount, status, and timestamps
  • Status transitions through the OrderStatus enum: 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.

3. Infrastructure Layer - Connection Management

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 RabbitMqSettings to establish connections
  • Provides comprehensive logging for connection events and errors

4. Publishing Layer

IMessagePublisher (Infrastructure/RabbitMQ/IMessagePublisher.cs)

  • Interface defining the contract for message publishing
  • Single method: PublishAsync(OrderEvent, routingKey)

RabbitMqPublisher (Infrastructure/RabbitMQ/RabbitMqPublisher.cs)

  • Implements IMessagePublisher and handles actual message publishing
  • Initialization: Creates connection, channel, and declares the exchange
  • Publishing Process:
    1. Serializes OrderEvent to JSON
    2. Creates message properties (persistent, content-type, timestamp)
    3. Publishes to order_exchange with specified routing key
    4. Logs all operations for monitoring
  • 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 β†’ Publishes order.created event
    • POST /api/orders/{id}/payment β†’ Publishes payment.verified event
    • POST /api/orders/{id}/ship β†’ Publishes order.shipped event
    • POST /api/orders/{id}/deliver β†’ Publishes order.delivered event
    • DELETE /api/orders/{id} β†’ Publishes order.cancelled event
  • Creates Order and OrderEvent objects from requests
  • Uses IMessagePublisher to publish events to RabbitMQ
  • Returns consistent ApiResponse<T> wrapper for all endpoints

πŸ“– Learn more about the API Response Pattern: API Response Pattern Guide

5. Subscription Layer

RabbitMqSubscriberBase (Infrastructure/RabbitMQ/RabbitMqSubscriberBase.cs)

  • Abstract base class for all subscribers
  • Extends BackgroundService to run continuously in the background
  • Initialization:
    1. Creates connection and channel via IRabbitMqConnectionFactory
    2. Declares the exchange (order_exchange)
    3. Declares queue with durable settings
    4. Binds queue to exchange using routing key pattern
    5. Sets QoS to process one message at a time
  • Message Processing:
    1. Receives messages via EventingBasicConsumer
    2. Deserializes JSON to OrderEvent object
    3. Calls abstract ProcessMessageAsync method (implemented by derived classes)
    4. Acknowledges message on success (BasicAck)
    5. Requeues message on error (BasicNack with requeue=true)
  • Derived classes must define: QueueName, RoutingKey, and ProcessMessageAsync

6. Concrete Subscribers (Background Services)

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)

7. Background Subscriber Service Lifecycle

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.

Message Flow Example

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

Topic Exchange Routing

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.* matches order.created, order.shipped)
  • # (hash) matches zero or more words (not used in this project, but available)

Dependency Injection Setup

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");

Key Advantages of This Pattern

  1. Loose Coupling: Publishers don't know about subscribers, and vice versa
  2. Scalability: Add new subscribers without modifying existing code
  3. Asynchronous Processing: HTTP requests return immediately; heavy processing happens in background
  4. Multiple Consumers: Same event can trigger multiple business processes
  5. Reliability: Persistent messages and acknowledgments ensure no data loss
  6. Flexibility: Topic routing allows sophisticated event filtering
  7. Resilience: Automatic recovery handles connection failures gracefully with retry logic

Error Handling

  • 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

API Endpoints

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

Create Order

POST /api/orders
Content-Type: application/json

{
  "customerName": "John Doe",
  "productName": "Laptop",
  "quantity": 1,
  "totalAmount": 1299.99
}

Verify Payment

POST /api/orders/{orderId}/payment

Ship Order

POST /api/orders/{orderId}/ship

Deliver Order

POST /api/orders/{orderId}/deliver

Cancel Order

DELETE /api/orders/{orderId}

Health Check

GET /health

Returns JSON with application health status and RabbitMQ connectivity.

Testing the Pub/Sub Pattern

Using Docker Compose (Recommended)

  1. Start all services:

    docker-compose up -d
  2. Wait for services to be ready:

    docker-compose ps
  3. Access Swagger UI:

  4. Create a new order:

    • Execute POST /api/orders with sample data
    • Check the logs to see message flow:
    docker-compose logs -f orderflow-core
  5. Observe in the logs:

    • Publisher: Message published to RabbitMQ
    • OrderProcessingSubscriber: Processing the order
    • NotificationSubscriber: Sending notification
  6. 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

Using Local Development

  1. Start RabbitMQ (see Alternative: Local Development Setup)
  2. Start the application with dotnet run
  3. Use Swagger UI at https://localhost:7279/swagger
  4. Check console logs to see message flow

Routing Keys

The project uses topic exchange with the following routing patterns:

  • order.created β†’ OrderProcessingSubscriber, NotificationSubscriber
  • payment.verified β†’ PaymentVerificationSubscriber
  • order.shipped β†’ ShippingSubscriber, NotificationSubscriber
  • order.delivered β†’ NotificationSubscriber
  • order.cancelled β†’ NotificationSubscriber

Project Structure

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

Key Features

  • βœ… 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

Monitoring

Application Logs

# Docker Compose
docker-compose logs -f orderflow-core

# Local Development
Check console output

RabbitMQ Management UI

Visit http://localhost:15672 to monitor:

  • Exchange creation and bindings
  • Queue status and message rates
  • Consumer connections
  • Message flow and routing

Health Endpoint

Check http://localhost:8080/health for:

  • Overall application health status
  • RabbitMQ connection status
  • Response time metrics

Troubleshooting

Common Issues

"Unable to connect to RabbitMQ"

  • 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.json or docker-compose environment variables

"Swagger UI not loading"

"Container won't start"

  • 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

Docker Compose Environment

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

Local Development Environment

Configuration is loaded from appsettings.json:

  • RabbitMQ hostname: localhost
  • Port: 5672
  • Credentials: admin/admin123

Production Deployment

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


πŸ“– Documentation

This project includes comprehensive documentation covering all aspects of the system:

🐳 Containerization & Deployment

  • Docker Deployment Guide

    • 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

🎨 Patterns & Architecture

  • Options Pattern Guide

    • 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
  • API Response Pattern Guide

    • 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

πŸ§ͺ Testing

  • 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

πŸ“š Documentation Index

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

Next Steps

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

Resources

Official Documentation

Project Documentation


πŸŽ“ Learning Path

If you're new to these concepts, here's a suggested learning path:

  1. Start Simple β€” Run the project locally and test the /api/orders endpoint
  2. Observe the Flow β€” Watch the logs and RabbitMQ Management UI
  3. Understand Routing β€” Experiment with different routing keys
  4. Read the Docs β€” Explore the comprehensive documentation for deep dives
  5. Test Thoroughly β€” Use the Testing Guide for scenarios
  6. Deploy β€” Use Docker Compose to experience the full stack
  7. Containerize β€” Learn from Docker Containerization Guide
  8. Optimize β€” Implement dead letter queues and retry policies

🀝 Contributing

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!


πŸ’¬ Feedback & Support

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

πŸ™ Acknowledgments

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

License

This is a demonstration project for learning purposes.


πŸ’™ Built with Love & Passion for Clean Code

"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.


πŸ‘¨β€πŸ’» About the Author

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 β˜•

About

Production-ready .NET 8 microservice demonstrating event-driven architecture with RabbitMQ Pub/Sub, Docker orchestration, clean architecture, and distributed messaging patterns

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published