Event-driven architecture (EDA) is a powerful approach where services communicate through events rather than direct calls, enabling scalability, decoupling, and resilience. In C# and .NET systems, three popular message brokers used to implement EDA are Apache Kafka, RabbitMQ, and Azure Service Bus.

This document compares these technologies, explaining their use cases, pros and cons, and offering guidance on when to choose each for your system.

Overview of Each Technology

Apache Kafka

Apache Kafka is a distributed streaming platform designed for high-throughput event streams and real-time data pipelines. It stores events durably in ordered logs and allows multiple consumers to process the same stream independently.

RabbitMQ

RabbitMQ is a traditional message broker that implements Advanced Message Queuing Protocol (AMQP). It supports a variety of messaging patterns, including publish/subscribe, work queues, and request/reply.

Azure Service Bus

Azure Service Bus is a fully managed cloud messaging service provided by Microsoft, designed for enterprise-level applications. It supports queues, topics, and subscriptions, offering rich integration with Azure services.

Comparisons: Kafka vs. RabbitMQ vs. Azure Service Bus

Feature Apache Kafka RabbitMQ Azure Service Bus
Delivery Guarantee At least once, exactly once (with Kafka Streams) At least once, exactly once (with plugins) At least once, exactly once (with sessions)
Ordering Per partition Per queue Per session or partition
Throughput Very high (millions/sec) Medium to high Medium to high
Persistence Durable log (configurable retention) Persistent or transient queues Persistent queues
Management Self-managed, requires cluster ops Easier to set up, requires broker mgmt Fully managed by Azure
Protocols Kafka protocol, REST, Kafka Streams AMQP, MQTT, STOMP, HTTP AMQP, HTTP, REST
Ecosystem/Integrations Wide (Kafka Connect, ksqlDB, Streams) Good (plugins, adapters) Tight Azure ecosystem integration
Cloud Offering Confluent Cloud, AWS MSK, Azure Event Hubs Cloud AMQP brokers, CloudAMQP Native Azure service
Best for Event streams, analytics, big data pipelines Work queues, routing, lightweight pub/sub Enterprise workflows, cloud-to-cloud, hybrid apps


Pros and Cons

Apache Kafka

Pros

Cons

RabbitMQ

Pros

Cons

Azure Service Bus

Pros

Cons

When to Choose Which?

Each message broker has its strengths and trade-offs. The right choice depends on your architecture, scale, operational needs, and cloud strategy. By understanding these differences, you can confidently design event-driven architectures in C# that are scalable, resilient, and fit-for-purpose.

Sample C# Producer and Consumer Code for Kafka, RabbitMQ, and Azure Service Bus

This section provides basic C# producer and consumer code examples for Apache Kafka, RabbitMQ, and Azure Service Bus to help you get started quickly.

Apache Kafka

Kafka Producer (C#)

using Confluent.Kafka;

var config = new ProducerConfig { BootstrapServers = "localhost:9092" };

using var producer = new ProducerBuilder<Null, string>(config).Build();
await producer.ProduceAsync("my-topic", new Message<Null, string> { Value = "Hello Kafka" });

Kafka Consumer (C#)

using Confluent.Kafka;
var config = new ConsumerConfig
{
    BootstrapServers = "localhost:9092",
    GroupId = "my-group",
    AutoOffsetReset = AutoOffsetReset.Earliest
};
using var consumer = new ConsumerBuilder<Ignore, string>(config).Build();
consumer.Subscribe("my-topic");
while (true)
{
    var consumeResult = consumer.Consume();
    Console.WriteLine($"Received: {consumeResult.Message.Value}");
}

RabbitMQ

RabbitMQ Producer (C#)

using RabbitMQ.Client;
using System.Text;

var factory = new ConnectionFactory() { HostName = "localhost" };

using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();

channel.QueueDeclare(
    queue: "hello", 
    durable: false, 
    exclusive: false, 
    autoDelete: false
);

var body = Encoding.UTF8.GetBytes("Hello RabbitMQ");

channel.BasicPublish(
    exchange: "", 
    routingKey: "hello", 
    basicProperties: null, 
    body: body
);

RabbitMQ Consumer (C#)

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;

var factory = new ConnectionFactory() { HostName = "localhost" };

using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();

channel.QueueDeclare(
    queue: "hello", 
    durable: false, 
    exclusive: false, 
    autoDelete: false
);

var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
    var body = ea.Body.ToArray();
    var message = Encoding.UTF8.GetString(body);
    Console.WriteLine($"Received: {message}");
};

channel.BasicConsume(
    queue: "hello", 
    autoAck: true, 
    consumer: consumer
);

Azure Service Bus

Service Bus Producer (C#)

using Azure.Messaging.ServiceBus;

string connectionString = "<your-service-bus-connection-string>";
string queueName = "myqueue";

await using var client = new ServiceBusClient(connectionString);
ServiceBusSender sender = client.CreateSender(queueName);

ServiceBusMessage message = new ServiceBusMessage("Hello Azure Service Bus");
await sender.SendMessageAsync(message);

Service Bus Consumer (C#)

using Azure.Messaging.ServiceBus;

string connectionString = "<your-service-bus-connection-string>";
string queueName = "myqueue";
await using var client = new ServiceBusClient(connectionString);
ServiceBusProcessor processor = client.CreateProcessor(queueName);
processor.ProcessMessageAsync += async args =>
{
    string body = args.Message.Body.ToString();
    Console.WriteLine($"Received: {body}");
    await args.CompleteMessageAsync(args.Message);
};
processor.ProcessErrorAsync += args =>
{
    Console.WriteLine(args.Exception.ToString());
    return Task.CompletedTask;
};
await processor.StartProcessingAsync();
Console.ReadKey();
await processor.StopProcessingAsync();