10 KiB
RabbitMQ Integration Example
This sample project demonstrates Phase 4: Cross-Service Communication using RabbitMQ for event streaming between microservices.
Overview
The sample shows how events emitted by command handlers are automatically published to RabbitMQ, enabling cross-service communication without any RabbitMQ-specific code in your handlers.
Architecture
┌─────────────────────┐
│ AddUserCommand │
│ Handler │
└──────────┬──────────┘
│
│ workflow.Emit(UserAddedEvent)
│
▼
┌─────────────────────┐
│ UserWorkflow │
│ (External Scope) │
└──────────┬──────────┘
│
│ Auto-publish to RabbitMQ
│
▼
┌─────────────────────┐ ┌─────────────────────┐
│ RabbitMQ Exchange │ │ Internal Event │
│ svrnty-sample. │ │ Store (PostgreSQL) │
│ user-events │ │ │
└──────────┬──────────┘ └─────────────────────┘
│ │
│ │
┌──────┴──────┐ ┌────────┴────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐
│RabbitMQ │ │ Email │ │Internal │ │ Analytics │
│Consumer │ │ Service │ │Consumer │ │ Service │
│(Sample) │ │(External)│ │(Sample) │ │ (External) │
└─────────┘ └──────────┘ └──────────┘ └──────────────┘
Key Features Demonstrated
- Zero RabbitMQ Code in Handlers: Command handlers emit events via workflows without any knowledge of RabbitMQ
- Automatic Topology Management: Framework creates exchanges, queues, and bindings automatically
- Dual Delivery: Events are published to both internal store and RabbitMQ (External scope)
- Consumer Groups: Multiple consumers can load-balance message processing
- Type-Safe Event Handling: Events are deserialized with full type information
Configuration
Enable/Disable RabbitMQ
Edit appsettings.json:
{
"EventStreaming": {
"RabbitMQ": {
"Enabled": true, // Set to false to disable RabbitMQ
"ConnectionString": "amqp://guest:guest@localhost:5672/"
}
}
}
Stream Configuration
In Program.cs, streams are configured with StreamScope.External to publish to RabbitMQ:
streaming.AddStream<UserWorkflow>(stream =>
{
stream.Type = StreamType.Ephemeral;
stream.DeliverySemantics = DeliverySemantics.AtLeastOnce;
stream.Scope = StreamScope.External; // Publish to RabbitMQ
stream.ExternalStreamName = "user-events"; // RabbitMQ stream name
});
Running the Sample
Prerequisites
- Docker and Docker Compose installed
- .NET 10 SDK installed
Steps
- Start Infrastructure
# From repository root
docker-compose up -d
# Verify services are running
docker ps
Expected services:
- PostgreSQL (port 5432)
- RabbitMQ (ports 5672, 15672)
- pgAdmin (port 5050) - optional
- Build and Run Sample
cd Svrnty.Sample
dotnet build
dotnet run
- Run Automated Test
cd Svrnty.Sample
chmod +x test-rabbitmq-integration.sh
./test-rabbitmq-integration.sh
This script will:
- Start the application
- Execute commands that emit events
- Verify events are published to RabbitMQ
- Show consumer logs
Manual Testing
1. Execute Command via HTTP API
curl -X POST http://localhost:6001/api/command/addUser \
-H "Content-Type: application/json" \
-d '{
"name": "Alice Johnson",
"email": "alice@example.com",
"age": 30
}'
2. Verify in RabbitMQ Management UI
Open http://localhost:15672 (guest/guest)
Check Exchanges:
- Name:
svrnty-sample.user-events - Type:
topic - Durable:
yes
Check Queues:
- Name:
svrnty-sample.email-service - Consumers:
1 - Messages: Should show activity
Check Bindings:
- Exchange:
svrnty-sample.user-events - Queue:
svrnty-sample.email-service - Routing key:
#(wildcard)
3. View Application Logs
Watch for these log entries:
[Information] Subscribing to 'user-events' stream from RabbitMQ...
[Information] [RABBITMQ] Received external event: UserAddedEvent (EventId: xxx, CorrelationId: xxx)
[Information] [RABBITMQ] Sending welcome email to alice@example.com (UserId: 123)
Event Flow
When you execute AddUserCommand:
-
Handler Emits Event
workflow.EmitAdded(new UserAddedEvent { UserId = userId, Name = command.Name, Email = command.Email }); -
Framework Publishes to RabbitMQ
- Serializes event to JSON
- Adds metadata headers (event-type, event-id, correlation-id, timestamp)
- Publishes to exchange:
svrnty-sample.user-events - Uses routing key based on event type
-
RabbitMQ Routes Message
- Exchange routes message to bound queues
- Queue:
svrnty-sample.email-servicereceives message
-
Consumer Receives Event
[Information] [RABBITMQ] Received external event: UserAddedEvent [Information] [RABBITMQ] Sending welcome email to alice@example.com
RabbitMQ Topology
The framework automatically creates:
Exchanges
- Name:
{ExchangePrefix}.{StreamName}- Example:
svrnty-sample.user-events
- Example:
- Type:
topic(configurable) - Durable:
true - Auto-delete:
false
Queues
- Name:
{ExchangePrefix}.{SubscriptionId}- Example:
svrnty-sample.email-service
- Example:
- Durable:
true - Prefetch:
10(configurable) - Mode: Consumer Group (multiple consumers share queue)
Bindings
- Exchange:
svrnty-sample.user-events - Queue:
svrnty-sample.email-service - Routing Key:
#(receives all events)
Consumer Implementation
See RabbitMQEventConsumerBackgroundService.cs:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await _rabbitMq.SubscribeExternalAsync(
streamName: "user-events",
subscriptionId: "email-service",
consumerId: $"rabbitmq-consumer-{Guid.NewGuid():N}",
eventHandler: ProcessEventAsync,
cancellationToken: stoppingToken);
}
private Task ProcessEventAsync(
ICorrelatedEvent @event,
IDictionary<string, string> metadata,
CancellationToken cancellationToken)
{
switch (@event)
{
case UserAddedEvent userAdded:
_logger.LogInformation(
"[RABBITMQ] Sending welcome email to {Email}",
userAdded.Email);
// Send email logic here
break;
}
return Task.CompletedTask;
}
Comparison: Internal vs External Event Consumption
The sample demonstrates two consumption patterns:
1. Internal Event Consumption (EventConsumerBackgroundService)
- Consumes from internal PostgreSQL event store
- Same process/service
- Uses
IEventSubscriptionClient - Good for: Same-service event handlers, sagas, process managers
2. External Event Consumption (RabbitMQEventConsumerBackgroundService)
- Consumes from RabbitMQ
- Cross-service communication
- Uses
IExternalEventDeliveryProvider - Good for: Microservices, distributed systems, event-driven architecture
Performance Considerations
Publisher
- Throughput: ~10,000 events/second (local RabbitMQ)
- Latency: ~5-10ms per publish
- Reliability: Publisher confirms disabled by default (can be enabled for critical events)
Consumer
- Throughput: Limited by prefetch (default: 10) and handler processing time
- Prefetch 10: ~1,000 events/second (lightweight handlers)
- Prefetch 100: ~10,000 events/second (lightweight handlers)
Configuration for High Throughput
{
"EventStreaming": {
"RabbitMQ": {
"PrefetchCount": 100,
"EnablePublisherConfirms": false,
"PersistentMessages": false
}
}
}
Configuration for Reliability
{
"EventStreaming": {
"RabbitMQ": {
"PrefetchCount": 10,
"EnablePublisherConfirms": true,
"PersistentMessages": true,
"DurableQueues": true,
"DurableExchanges": true
}
}
}
Troubleshooting
Events Not Appearing in RabbitMQ
- Check RabbitMQ is running:
docker ps | grep rabbitmq - Check RabbitMQ logs:
docker logs svrnty-rabbitmq - Verify
Enabled: truein appsettings.json - Check stream scope is
Externalin Program.cs - Look for errors in application logs
Consumer Not Receiving Events
- Check consumer is subscribed: Look for "Subscribing to 'user-events' stream" in logs
- Check queue has consumers: RabbitMQ Management UI → Queues
- Verify routing keys match: Exchange bindings should show
# - Check for exceptions in consumer logs
Connection Failures
- Verify connection string:
amqp://guest:guest@localhost:5672/ - Check RabbitMQ is accessible:
telnet localhost 5672 - Review RabbitMQ credentials (default: guest/guest)
- Check firewall rules
Next Steps
- Add more event types and consumers
- Implement cross-service workflows (Service A → Service B → Service C)
- Add integration tests with TestContainers
- Explore consumer group scaling (multiple instances)
- Implement dead letter queue handling
- Add monitoring and observability
Related Documentation
- RABBITMQ-GUIDE.md - Comprehensive RabbitMQ integration guide
- PHASE4-COMPLETE.md - Phase 4 completion summary
- docker-compose.yml - Infrastructure setup
Support
For questions or issues, see: https://git.openharbor.io/svrnty/dotnet-cqrs