Skip to content

πŸ“¨ Level 5 β€” Messaging & Event-Driven Architecture ​

Decouple your services and handle scale with async messaging.


5.1 Why Messaging Queues? ​

Messaging queues allow services to communicate asynchronously, improving reliability and performance.

Visualizing Asynchronous Decoupling ​


5.2 MQ vs Pub/Sub vs Streaming ​

PatternDescriptionExample Tools
Message QueueOne-to-one; message consumed onceRabbitMQ, SQS
Pub/SubOne-to-many; broadcast to subscribersRedis, SNS
Event StreamingRetained logs; high throughputApache Kafka

πŸ’» JS Example: Simplified Pub/Sub Pattern ​

Using the native Node.js EventEmitter to demonstrate subscriber logic.

javascript
const EventEmitter = require("events");
const orderTracker = new EventEmitter();

// Subscriber 1: Email Notification
orderTracker.on("order_created", (data) => {
  console.log(`Sending email for order ${data.id}...`);
});

// Subscriber 2: Inventory Sync
orderTracker.on("order_created", (data) => {
  console.log(`Syncing inventory for items ${data.items.join(", ")}...`);
});

// Publisher
orderTracker.emit("order_created", { id: 123, items: ["Laptop", "Mouse"] });

5.3 Apache Kafka Deep Dive ​

Kafka is the backbone of high-performance event streaming.

Kafka Architecture: Topics & Partitions ​


5.4 Event-Driven Patterns ​

The Saga Pattern (Choreography) ​

Used to handle distributed transactions across multiple microservices.

πŸ’» JS Example: Conceptual Worker (Producer) ​

javascript
// Generic producer logic (e.g., amqplib / RabbitMQ)
async function publishOrder(orderData) {
  const connection = await amqp.connect("amqp://localhost");
  const channel = await connection.createChannel();
  const queue = "orders";

  await channel.assertQueue(queue);
  channel.sendToQueue(queue, Buffer.from(JSON.stringify(orderData)));

  console.log(" [x] Sent order to queue");
}

5.5 πŸ”₯ Kafka vs RabbitMQ β€” Full System Design Comparison ​

The most common interview & architecture decision: which message broker should you use? This section gives you a complete, honest comparison with real-world guidance.


πŸ›οΈ Architectural Philosophy ​

The two tools were built with fundamentally different goals in mind:

DimensionApache KafkaRabbitMQ
Core ModelDistributed Event Log (append-only)Traditional Message Broker (push / route)
Message RetentionRetained on disk for a configurable periodDeleted after successful acknowledgement
Who Pulls?Consumers pull at their own paceBroker pushes messages to consumers
Primary Use CaseEvent streaming, audit log, data pipelineTask queues, RPC, workflow routing
Born FromLinkedIn (2011) β€” built for high-volume logsPivotal / VMware β€” built for enterprise messaging

πŸ—ΊοΈ Architecture Diagrams ​

Kafka Architecture ​

Key insight: Multiple independent consumer groups can replay the same events at different speeds. Kafka is like a TV broadcast β€” you can rewind.


RabbitMQ Architecture ​

Key insight: RabbitMQ has rich routing via exchanges. Messages are gone once consumed. It's like a postal service β€” once delivered, it's delivered.


βš™οΈ Deep Feature Comparison ​

FeatureApache KafkaRabbitMQ
Message OrderingGuaranteed per partitionGuaranteed per queue (strict FIFO)
ThroughputπŸš€ Millions of msg/sec (sequential disk I/O)⚑ Tens of thousands of msg/sec
LatencyLow (ms range), optimised for batchVery low (sub-ms possible), optimised for single
Delivery SemanticsAt-least-once (default) / exactly-once (Kafka Tx)At-least-once (with ack) / at-most-once
Message TTL / PriorityNo native priority; TTL via retention policyβœ… Native message priority & per-message TTL
Dead Letter HandlingManual (separate topic)βœ… Native Dead Letter Exchange (DLX)
Message Replayβœ… Yes β€” rewind offset, replay entire history❌ No β€” consumed = gone
Consumer ModelPull (consumer controls offset/pace)Push (broker delivers to ready consumer)
Routing FlexibilityTopic-based only (key β†’ partition)βœ… Direct, Topic, Fanout, Headers exchanges
ProtocolCustom binary (Kafka protocol)AMQP 0-9-1, STOMP, MQTT
Horizontal Scalingβœ… Partitions = natural parallelism unitCluster + Shovel plugin required
PersistenceAlways persistent (append log)Optional per-queue (durable queues)
Built-in Stream SQLβœ… Kafka Streams, ksqlDB❌ Not built-in
EcosystemKafka Connect, Schema Registry, Confluent CloudManagement UI, Federation, Shovel
Ops ComplexityHigh (ZooKeeper/KRaft, partitions, replication)Medium (straightforward for small-medium clusters)
Best ForEvent sourcing, CDC, analytics pipelines, audit logsTask queues, RPC, complex routing, job scheduling

πŸ”„ Message Flow Internals ​

How Kafka Stores & Delivers Messages ​

How RabbitMQ Routes & Delivers Messages ​


πŸ’» JS Code Examples: Side by Side ​

RabbitMQ β€” Producer & Consumer (amqplib) ​

javascript
// === PRODUCER ===
const amqp = require("amqplib");

async function sendOrder(order) {
  const conn = await amqp.connect("amqp://localhost");
  const ch = await conn.createChannel();

  // Exchange: topic type β€” supports wildcard routing
  await ch.assertExchange("orders_exchange", "topic", { durable: true });

  const routingKey = `order.${order.status}`; // e.g. "order.paid"
  ch.publish(
    "orders_exchange",
    routingKey,
    Buffer.from(JSON.stringify(order)),
    { persistent: true, priority: order.priority ?? 0 }
  );

  console.log(`[RabbitMQ] Published order ${order.id} β†’ key: ${routingKey}`);
  await ch.close();
  await conn.close();
}

// === CONSUMER ===
async function startWorker() {
  const conn = await amqp.connect("amqp://localhost");
  const ch = await conn.createChannel();

  await ch.assertQueue("payment_queue", {
    durable: true,
    deadLetterExchange: "dlx_exchange", // Failed messages go here
  });
  await ch.bindQueue("payment_queue", "orders_exchange", "order.paid");

  ch.prefetch(1); // Process one message at a time

  ch.consume("payment_queue", async (msg) => {
    if (!msg) return;
    const order = JSON.parse(msg.content.toString());
    try {
      await processPayment(order);
      ch.ack(msg); // πŸ‘ Success: remove from queue
    } catch (err) {
      console.error("Payment failed:", err.message);
      ch.nack(msg, false, false); // πŸ‘Ž Send to Dead Letter Queue
    }
  });
}

Kafka β€” Producer & Consumer (kafkajs) ​

javascript
const { Kafka } = require("kafkajs");

const kafka = new Kafka({
  clientId: "order-service",
  brokers: ["kafka1:9092", "kafka2:9092"],
});

// === PRODUCER ===
async function publishOrderEvent(order) {
  const producer = kafka.producer();
  await producer.connect();

  await producer.send({
    topic: "orders",
    messages: [
      {
        key: String(order.userId), // Same key β†’ same partition (ordering!)
        value: JSON.stringify(order),
        headers: { source: "order-service", version: "v2" },
      },
    ],
  });

  console.log(`[Kafka] Produced event for order ${order.id}`);
  await producer.disconnect();
}

// === CONSUMER (Consumer Group) ===
async function startConsumer() {
  const consumer = kafka.consumer({ groupId: "analytics-group" });
  await consumer.connect();
  await consumer.subscribe({ topic: "orders", fromBeginning: false });

  await consumer.run({
    eachMessage: async ({ topic, partition, message }) => {
      const order = JSON.parse(message.value.toString());
      const offset = message.offset;

      console.log(
        `[Kafka] Partition ${partition} | Offset ${offset} | Order: ${order.id}`
      );

      // Offset is committed automatically after this function resolves
      await recordAnalytics(order);
    },
  });
}

// === REPLAY EXAMPLE: rewind to beginning ===
async function replayAllOrders() {
  const consumer = kafka.consumer({ groupId: "replay-group" });
  await consumer.connect();
  await consumer.subscribe({ topic: "orders", fromBeginning: true }); // πŸ” Replay!

  await consumer.run({
    eachMessage: async ({ message }) => {
      const order = JSON.parse(message.value.toString());
      await rebuildOrderReadModel(order);
    },
  });
}

🧭 When to Choose Which β€” Decision Tree ​


πŸ—οΈ Real-World System Design Scenarios ​

Scenario 1: E-Commerce Order System ​

Pattern: Use Kafka for high-volume event streaming across multiple independent consumers. Use RabbitMQ for targeted task dispatch (e.g., send one email to one worker).


Scenario 2: Real-Time Event Streaming Pipeline ​

Pattern: Kafka is the backbone here β€” sensors produce millions of events, and multiple downstream systems consume at their own pace without coupling.


Scenario 3: Microservice Task Queue with RabbitMQ ​

Pattern: RabbitMQ's exchange model lets you route the same producer output to completely different queues based on routing keys β€” ideal for tasks with varying priority and retry behavior.


πŸ“Š Performance Benchmarks (Approximate) ​

MetricKafkaRabbitMQ
Max Throughput1–2 million msg/sec / broker20k–100k msg/sec / node
Typical Latency (p99)5–15 ms1–5 ms
Horizontal ScalabilityNear-linear (add brokers)Moderate (clustering helps)
Message Size Sweet SpotSmall to medium (< 1 MB)Small (< 128 KB optimal)
StorageHigh (logs retained on disk)Low (ephemeral by default)

πŸ”’ Reliability & Fault Tolerance ​

Kafka Guarantees ​

  • Replication Factor (RF): Each partition is replicated across N brokers. If a leader fails, a follower is automatically elected.
  • acks=all: Producer waits for ALL in-sync replicas to confirm before returning success β€” zero data loss.
  • Idempotent Producer: Prevents duplicate messages even on retries (enable.idempotence=true).
  • Exactly-once semantics (EOS): Available with Kafka Transactions for end-to-end guarantees.

RabbitMQ Guarantees ​

  • Durable queues + persistent messages: Survive broker restarts.
  • Publisher confirms: Producer gets an ack from the broker when the message is safely persisted.
  • Consumer acknowledgments (ack/nack): Message stays in queue until explicitly acknowledged. On crash, redelivered automatically.
  • Dead Letter Exchange (DLX): Failed/expired messages are automatically routed to a DLQ for inspection and retry.
  • Quorum Queues (v3.8+): Raft-based consensus for high-availability queues β€” replaces classic mirrored queues.

πŸ› οΈ Operational Considerations ​

ConcernKafkaRabbitMQ
Setup ComplexityHigh β€” ZooKeeper (or KRaft), partition tuningLow–Medium β€” single binary, great management UI
MonitoringKafka UI, Confluent Control Center, Prometheus/GrafanaBuilt-in Management Plugin, Prometheus exporter
Schema ManagementConfluent Schema Registry (Avro/Protobuf)Manual / no built-in schema registry
Managed CloudConfluent Cloud, AWS MSK, Azure Event HubsAWS AmazonMQ, CloudAMQP, Azure Service Bus
Learning CurveSteep β€” offsets, consumer groups, partitionsModerate β€” AMQP concepts are well-documented

πŸ† Summary: Quick Reference Card ​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Choose KAFKA when you need:                               β”‚
β”‚  βœ… Massive throughput (>100k msg/s)                       β”‚
β”‚  βœ… Message replay / event history / audit log             β”‚
β”‚  βœ… Multiple independent consumer groups                   β”‚
β”‚  βœ… Event sourcing / Change Data Capture (CDC)             β”‚
β”‚  βœ… Real-time stream processing (Kafka Streams / ksqlDB)   β”‚
β”‚  βœ… Data pipeline between microservices / data warehouse   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Choose RABBITMQ when you need:                            β”‚
β”‚  βœ… Simple, reliable task queues                           β”‚
β”‚  βœ… Complex routing (topic, direct, fanout, headers)       β”‚
β”‚  βœ… Per-message TTL or priority                            β”‚
β”‚  βœ… Native Dead Letter Queue support                       β”‚
β”‚  βœ… RPC-style request/reply pattern                        β”‚
β”‚  βœ… Low-latency delivery for individual tasks              β”‚
β”‚  βœ… Simpler ops with a familiar UI                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Use BOTH together when:                                   β”‚
β”‚  πŸ”€ Kafka handles event streams between services           β”‚
β”‚  πŸ”€ RabbitMQ handles targeted task dispatch within a svc   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

5.6 βœ… Checklist Before Moving On ​

  • [ ] I can explain why async messaging is better than synchronous calls
  • [ ] I understand Kafka partitions and consumer groups
  • [ ] I know the Saga pattern for distributed transactions
  • [ ] I can choose between queue types for different use cases
  • [ ] I can articulate the key architectural difference between Kafka and RabbitMQ
  • [ ] I can draw the internal message flow for both Kafka and RabbitMQ
  • [ ] I know when to use Kafka vs RabbitMQ in a real system design interview
  • [ ] I understand delivery semantics: at-most-once, at-least-once, exactly-once

➑️ Next: Level 6 β€” Microservices

Released under the ISC License.