Kafka
Abstract
Mental model Kafka is a distributed event streaming platform. Producers write records to topics; consumers read those records, usually through consumer groups.
Basic Model
flowchart LR Producer["Order Service"] --> Topic["order-events"] Topic --> P0["Partition 0"] Topic --> P1["Partition 1"] Topic --> P2["Partition 2"] P0 --> ConsumerA["Consumer A"] P1 --> ConsumerB["Consumer B"] P2 --> ConsumerC["Consumer C"]
Topic → Partition → Offset
A topic is divided into partitions.
order-events
│
├── partition-0
│ ├── offset 0
│ ├── offset 1
│ └── offset 2
│
├── partition-1
│ ├── offset 0
│ └── offset 1
│
└── partition-2
└── offset 0The offset identifies a record’s position within a partition.
Why Partitions Matter
Partitions provide parallelism.
1 partition
↓
1 consumer can process it
6 partitions
↓
up to 6 consumers in one consumer groupDoes Kafka scale because of replication?
Not primarily.
- Partitioning → scalability / parallelism
- Replication → fault tolerance / durability
This distinction is very important in interviews.
Consumer Groups
flowchart LR Topic["order-events"] Topic --> P0["P0"] Topic --> P1["P1"] Topic --> P2["P2"] P0 --> C1["Consumer 1"] P1 --> C2["Consumer 2"] P2 --> C3["Consumer 3"]
Consumers in the same consumer group divide partitions among themselves.
A different consumer group gets its own view of the topic.
order-events
│
├── inventory-group
│
├── payment-group
│
└── notification-groupThis is exactly what we want for an e-commerce system.
An OrderCreated event can be independently consumed by Inventory,
Payment, and Notification.
Small Java Producer
kafkaTemplate.send(
"order-events",
orderId,
orderCreatedEvent
);Small Java Consumer
@KafkaListener(
topics = "order-events",
groupId = "inventory-service"
)
public void consume(OrderCreated event) {
inventory.reserve(event.orderId());
}Small .NET Producer
await producer.ProduceAsync(
"order-events",
new Message<string, OrderCreated>
{
Key = orderId,
Value = event
});Small .NET Consumer Concept
consumer.Subscribe("order-events");
var result = consumer.Consume(ct);
await inventory.ReserveAsync(result.Message.Value);Ordering
Kafka ordering is guaranteed within a partition, not across an entire topic.
This is why the message key matters.
kafkaTemplate.send(
"order-events",
orderId, // key
event
);Using the same orderId as the key normally routes related events to
the same partition.
Important Kafka does not automatically make your business
operation exactly-once.
You still need to think about:
- duplicate delivery
- retries
- consumer crashes
- idempotency
- database transactions
- offset commits
What Happens If a Consumer Crashes?
Kafka
│
▼
Consumer
│
├── process message
│
└── crashDepending on when the offset was committed, the message may be processed again.
Therefore:
Remember Kafka gives you durable messaging; your application must make processing safe.
Kafka + E-Commerce
flowchart LR Order["Order Service"] --> Kafka["Kafka"] Kafka --> Inventory["Inventory"] Kafka --> Payment["Payment"] Kafka --> Notification["Notification"] Kafka --> Shipping["Shipping"]
This is where event-driven architecture becomes useful rather than theoretical.
Interview Questions
Kafka vs REST?
REST is generally request/response and synchronous. Kafka decouples producers and consumers and allows asynchronous processing.
Why use Kafka instead of calling Payment directly?
Potential benefits include decoupling, buffering, independent scaling, asynchronous processing, and the ability for multiple consumers to react to the same event.
But direct synchronous calls may still be preferable when the caller needs an immediate response.
Does Kafka guarantee exactly-once business processing?
No. Kafka has transactional and delivery semantics, but business-level exactly-once behavior still requires careful application design.
] → understand
when events are actually better than synchronous REST calls.