366 lines
10 KiB
Markdown
366 lines
10 KiB
Markdown
# 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
|
|
|
|
1. **Zero RabbitMQ Code in Handlers**: Command handlers emit events via workflows without any knowledge of RabbitMQ
|
|
2. **Automatic Topology Management**: Framework creates exchanges, queues, and bindings automatically
|
|
3. **Dual Delivery**: Events are published to both internal store and RabbitMQ (External scope)
|
|
4. **Consumer Groups**: Multiple consumers can load-balance message processing
|
|
5. **Type-Safe Event Handling**: Events are deserialized with full type information
|
|
|
|
## Configuration
|
|
|
|
### Enable/Disable RabbitMQ
|
|
|
|
Edit `appsettings.json`:
|
|
|
|
```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:
|
|
|
|
```csharp
|
|
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
|
|
|
|
1. Docker and Docker Compose installed
|
|
2. .NET 10 SDK installed
|
|
|
|
### Steps
|
|
|
|
1. **Start Infrastructure**
|
|
|
|
```bash
|
|
# 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
|
|
|
|
2. **Build and Run Sample**
|
|
|
|
```bash
|
|
cd Svrnty.Sample
|
|
dotnet build
|
|
dotnet run
|
|
```
|
|
|
|
3. **Run Automated Test**
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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`:
|
|
|
|
1. **Handler Emits Event**
|
|
```csharp
|
|
workflow.EmitAdded(new UserAddedEvent
|
|
{
|
|
UserId = userId,
|
|
Name = command.Name,
|
|
Email = command.Email
|
|
});
|
|
```
|
|
|
|
2. **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
|
|
|
|
3. **RabbitMQ Routes Message**
|
|
- Exchange routes message to bound queues
|
|
- Queue: `svrnty-sample.email-service` receives message
|
|
|
|
4. **Consumer Receives Event**
|
|
```csharp
|
|
[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`
|
|
- **Type**: `topic` (configurable)
|
|
- **Durable**: `true`
|
|
- **Auto-delete**: `false`
|
|
|
|
### Queues
|
|
|
|
- **Name**: `{ExchangePrefix}.{SubscriptionId}`
|
|
- Example: `svrnty-sample.email-service`
|
|
- **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`:
|
|
|
|
```csharp
|
|
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
|
|
|
|
```json
|
|
{
|
|
"EventStreaming": {
|
|
"RabbitMQ": {
|
|
"PrefetchCount": 100,
|
|
"EnablePublisherConfirms": false,
|
|
"PersistentMessages": false
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Configuration for Reliability
|
|
|
|
```json
|
|
{
|
|
"EventStreaming": {
|
|
"RabbitMQ": {
|
|
"PrefetchCount": 10,
|
|
"EnablePublisherConfirms": true,
|
|
"PersistentMessages": true,
|
|
"DurableQueues": true,
|
|
"DurableExchanges": true
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Events Not Appearing in RabbitMQ
|
|
|
|
1. Check RabbitMQ is running: `docker ps | grep rabbitmq`
|
|
2. Check RabbitMQ logs: `docker logs svrnty-rabbitmq`
|
|
3. Verify `Enabled: true` in appsettings.json
|
|
4. Check stream scope is `External` in Program.cs
|
|
5. Look for errors in application logs
|
|
|
|
### Consumer Not Receiving Events
|
|
|
|
1. Check consumer is subscribed: Look for "Subscribing to 'user-events' stream" in logs
|
|
2. Check queue has consumers: RabbitMQ Management UI → Queues
|
|
3. Verify routing keys match: Exchange bindings should show `#`
|
|
4. Check for exceptions in consumer logs
|
|
|
|
### Connection Failures
|
|
|
|
1. Verify connection string: `amqp://guest:guest@localhost:5672/`
|
|
2. Check RabbitMQ is accessible: `telnet localhost 5672`
|
|
3. Review RabbitMQ credentials (default: guest/guest)
|
|
4. 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](../RABBITMQ-GUIDE.md) - Comprehensive RabbitMQ integration guide
|
|
- [PHASE4-COMPLETE.md](../PHASE4-COMPLETE.md) - Phase 4 completion summary
|
|
- [docker-compose.yml](../docker-compose.yml) - Infrastructure setup
|
|
|
|
## Support
|
|
|
|
For questions or issues, see: https://git.openharbor.io/svrnty/dotnet-cqrs
|