RabbitMQ in modern manufacturing systems, reliability matters more than raw speed.
Machines generate events continuously:
- production cycles
- alarms
- quality results
If these events are lost, the consequences are real:
- incorrect production counts
- broken traceability
- ERP inconsistencies
This is where RabbitMQ becomes critical.
It introduces event-driven architecture, allowing industrial systems to communicate asynchronously.
Industrial Architecture with RabbitMQ
7
Typical production stack:
PLC → OPC UA → .NET Gateway → RabbitMQ → MES → ERP → Cloud
RabbitMQ decouples:
- machines from MES
- MES from ERP
- real-time systems from reporting
Event Flow (Before vs After)
7
Without RabbitMQ
SCADA → MES → ERP
With RabbitMQ
SCADA → RabbitMQ → MES
PLC → RabbitMQ → Quality
MES → RabbitMQ → ERP
RabbitMQ Concepts
8
Core components:
- Producer
- Exchange
- Queue
- Consumer
Docker Setup
docker run -d --hostname rabbit-local --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-managementUI:
http://localhost:15672Step 1: Install Package
dotnet add package RabbitMQ.ClientStep 2: Strongly-Typed Event Model
public sealed class MachineCycleCompleted
{
public Guid EventId { get; init; } = Guid.NewGuid();
public string MachineId { get; init; } = "";
public string ProductionOrder { get; init; } = "";
public DateTime CompletedAtUtc { get; init; } = DateTime.UtcNow;
public int CycleTimeMs { get; init; }
public string Result { get; init; } = "";
}Step 3: Advanced Publisher
using System.Text;
using System.Text.Json;
using RabbitMQ.Client;public sealed class RabbitPublisher : IAsyncDisposable
{
private readonly IConnection _connection;
private readonly IChannel _channel; public RabbitPublisher()
{
var factory = new ConnectionFactory { HostName = "localhost" }; _connection = factory.CreateConnectionAsync().GetAwaiter().GetResult();
_channel = _connection.CreateChannelAsync().GetAwaiter().GetResult();
} public async Task PublishAsync<T>(T message, string routingKey)
{
await _channel.ExchangeDeclareAsync(
"manufacturing.events",
ExchangeType.Topic,
durable: true); var json = JsonSerializer.Serialize(message);
var body = Encoding.UTF8.GetBytes(json); var props = new BasicProperties
{
Persistent = true,
ContentType = "application/json",
MessageId = Guid.NewGuid().ToString()
}; await _channel.BasicPublishAsync(
exchange: "manufacturing.events",
routingKey: routingKey,
basicProperties: props,
body: body);
} public async ValueTask DisposeAsync()
{
await _channel.DisposeAsync();
await _connection.DisposeAsync();
}
}Step 4: Producer Example
var publisher = new RabbitPublisher();await publisher.PublishAsync(
new MachineCycleCompleted
{
MachineId = "LINE-01",
ProductionOrder = "PO-001",
CycleTimeMs = 5000,
Result = "OK"
},
routingKey: "machine.cycle.completed");Step 5: Advanced Consumer
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
using System.Text.Json;var factory = new ConnectionFactory { HostName = "localhost" };var connection = await factory.CreateConnectionAsync();
var channel = await connection.CreateChannelAsync();await channel.QueueDeclareAsync(
"mes-service",
durable: true,
exclusive: false,
autoDelete: false,
arguments: new Dictionary<string, object?>
{
["x-dead-letter-exchange"] = "dead-letter"
});await channel.QueueBindAsync(
"mes-service",
"manufacturing.events",
"machine.cycle.*");await channel.BasicQosAsync(0, 10, false);var consumer = new AsyncEventingBasicConsumer(channel);consumer.ReceivedAsync += async (_, args) =>
{
try
{
var json = Encoding.UTF8.GetString(args.Body.ToArray()); var evt = JsonSerializer.Deserialize<MachineCycleCompleted>(json); Console.WriteLine($"Processed: {evt?.MachineId}"); await channel.BasicAckAsync(args.DeliveryTag, false);
}
catch
{
await channel.BasicNackAsync(args.DeliveryTag, false, true);
}
};
await channel.BasicConsumeAsync("mes-service", false, consumer);Topic Routing Example
5
Routing keys:
machine.cycle.completed
machine.alarm.raised
quality.inspection.completed
erp.order.startedReliability Layer
7
Key mechanisms:
await channel.BasicAckAsync(tag, false);
await channel.BasicNackAsync(tag, false, true);Outbox Pattern (C#)
7
public class OutboxMessage
{
public Guid Id { get; set; }
public string Payload { get; set; } = "";
public DateTime Created { get; set; }
public DateTime? Processed { get; set; }
}Publisher worker:
while(true)
{
var messages = db.Outbox.Where(x => x.Processed == null).ToList(); foreach(var m in messages)
{
await publisher.PublishAsync(m.Payload, "machine.cycle.completed");
m.Processed = DateTime.UtcNow;
} await db.SaveChangesAsync();
await Task.Delay(2000);
}Idempotent Consumer
if(db.Processed.Any(x => x.Id == evt.EventId))
{
await channel.BasicAckAsync(tag, false);
return;
}Best Practices
- Always use durable queues
- Always use persistent messages
- Always use manual ACK
- Use topic exchanges
- Implement retry + DLQ
- Use outbox pattern
Conclusion
RabbitMQ is not just infrastructure.
In manufacturing, it is:
- a reliability layer
- a scalability enabler
- a system decoupler
Combined with .NET, it creates a robust industrial integration platform.
References
https://www.rabbitmq.com/
https://opcfoundation.org/
https://support.industry.siemens.com/cs/document/42014088/programming-an-opc-ua-.net-client-with-c-for-the-simatic-net-opc-ua-server?lc=en-ru
More Info
We implement this in real production systems.
If you need help → contact us