Definition
- REST – a synchronous request-response protocol over HTTP where the caller blocks until the target service replies, best for direct queries, commands aimed at one service, and external-facing APIs.
- Kafka – a durable asynchronous event log where the producer publishes to a topic and returns immediately, best for events with many unknown consumers, buffering under load, and replay.
- Decision rule – if the caller needs the result right now to proceed, use REST; if the producer is announcing a fact and does not need to wait, use Kafka.
- Coupling – REST temporally couples the caller to the callee's availability; Kafka decouples them so a down consumer never fails the producer.
- Hybrid – most banking systems use both: REST on the critical path and Kafka for side effects.
At National Bank of India, REST is like Rajesh walking up to the accounts counter and asking "what is my balance?" He waits, gets the number, and leaves. Kafka is like the Payment department pinning a note on the announcement board saying "transfer T-123 completed for account SAV2001." Any department that cares, notifications, audit, fraud, reads it whenever it is ready. The counter is for questions you need answered now; the announcement board is for broadcasting that something happened.
Understanding the Concepts
When should you choose REST?
Choose REST when the caller needs the answer to proceed. A balance query, a funds check before a payment, or fetching data to render a screen all require the result immediately. REST is also the right choice at the public edge because browsers, mobile apps, and third parties speak HTTP and JSON.
package com.bank.service;
import org.springframework.web.client.RestClient;
import java.math.BigDecimal;
public class BalanceQueryService {
private final RestClient restClient;
public BalanceQueryService(RestClient.Builder builder) {
this.restClient = builder.baseUrl("http://account-service").build();
}
public BigDecimal getBalance(String accountId) {
// REST is correct here: caller needs the balance NOW
return restClient.get()
.uri("/api/v1/accounts/{id}/balance", accountId)
.retrieve()
.body(BigDecimal.class);
}
}
// GET /api/v1/accounts/SAV2001/balance → 75000.00 // Caller blocks ~5ms, gets the answer, proceeds with the transfer
When should you choose Kafka?
Choose Kafka when the producer should not wait and the interaction is an event, meaning a statement that something happened rather than a command to one service. Publishing to a topic returns immediately, and any number of consumer groups read independently.
package com.bank.service;
import com.bank.event.TransferCompletedEvent;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class TransferEventPublisher {
private final KafkaTemplate<String, TransferCompletedEvent> kafkaTemplate;
public TransferEventPublisher(
KafkaTemplate<String, TransferCompletedEvent> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
public void publishTransferCompleted(String accountId, BigDecimal amount) {
TransferCompletedEvent event = new TransferCompletedEvent(
accountId, amount, "INR");
// Kafka is correct here: fire and forget, many consumers react
kafkaTemplate.send("transfer-events", accountId, event);
}
}
// Producer returns in ~2ms
// Consumer group 1 (Notification): sends SMS to Priya
// Consumer group 2 (Statement): appends to monthly statement
// Consumer group 3 (Fraud): scores the transaction
// Consumer group 4 (Analytics): updates the dashboard
What are the costs of REST?
The cost of REST is temporal coupling. The caller blocks and depends on the callee being up and fast. A chain of REST calls couples the availability of every link, so you must add timeouts, retries, and circuit breakers. REST also does not naturally fan out: if five services care about a payment, the caller would have to call all five, coupling it to all of them.
// WRONG: REST fan-out couples the transfer to five services
public void completeTransfer(String accountId, BigDecimal amount) {
notificationClient.sendSms(accountId, amount); // blocks
statementClient.appendEntry(accountId, amount); // blocks
fraudClient.scoreTransaction(accountId, amount); // blocks
analyticsClient.recordTransfer(accountId, amount); // blocks
auditClient.logTransfer(accountId, amount); // blocks
// If ANY of these is down, the transfer fails or hangs
}
// Total time: sum of all five latencies (~50ms) // If Fraud Service is down → entire transfer fails // This is wrong because none of these need an immediate answer
What are the costs of Kafka?
Kafka is eventually consistent and there is no immediate answer. Consumers must be idempotent because delivery is at-least-once, meaning a message may be delivered more than once on retries or rebalances. Tracing a flow across topics is also harder than following a single REST call.
package com.bank.consumer;
import com.bank.event.TransferCompletedEvent;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
@Component
public class NotificationConsumer {
private final NotificationRepository notificationRepo;
public NotificationConsumer(NotificationRepository notificationRepo) {
this.notificationRepo = notificationRepo;
}
@KafkaListener(topics = "transfer-events", groupId = "notification-svc")
public void onTransferCompleted(TransferCompletedEvent event) {
// Idempotent: check if we already sent this notification
if (notificationRepo.existsByEventId(event.eventId())) {
return; // Already processed, skip duplicate
}
sendSms(event.accountId(), event.amount());
notificationRepo.markProcessed(event.eventId());
}
private void sendSms(String accountId, java.math.BigDecimal amount) {
// Send SMS to account holder
}
}
// First delivery: eventId=EVT-001 → SMS sent, marked processed // Duplicate delivery: eventId=EVT-001 → already exists, skipped // Idempotency prevents sending the same SMS twice
How is REST different from Kafka? (comparison table)
The decision comes down to whether the interaction is a direct question or a broadcast announcement.
| Aspect | REST | Kafka |
|---|---|---|
| Interaction | Request-response to one service | Publish-subscribe to many consumers |
| Caller behavior | Blocks until response | Returns immediately |
| Coupling | Temporal: depends on callee uptime | Decoupled: producer does not know consumers |
| Consistency | Immediate | Eventually consistent |
| Failure handling | Caller fails if callee is down | Messages queue, consumed when ready |
| Fan-out | Caller must call each service | One publish, many consumer groups |
| Replay | No built-in replay | Durable log, consumers can replay |
| Throughput | Limited by service capacity | Buffered by partitions, scales horizontally |
| Best for | Queries, commands, external APIs | Events, notifications, analytics, sagas |
What is the hybrid pattern for banking transfers?
The strongest pattern uses both REST and Kafka in a single flow. The critical path uses REST because correctness depends on it, and the side effects use Kafka because they are independent.
package com.bank.service;
import com.bank.client.AccountClient;
import com.bank.event.TransferCompletedEvent;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class TransferService {
private final AccountClient accountClient;
private final KafkaTemplate<String, TransferCompletedEvent> kafkaTemplate;
public TransferService(AccountClient accountClient,
KafkaTemplate<String, TransferCompletedEvent> kafka) {
this.accountClient = accountClient;
this.kafkaTemplate = kafka;
}
public String transfer(String from, String to, BigDecimal amount) {
// REST: synchronous, caller needs the answer
BigDecimal balance = accountClient.getBalance(from);
if (balance.compareTo(amount) < 0) {
throw new InsufficientFundsException("Balance: " + balance);
}
accountClient.debit(from, amount);
accountClient.credit(to, amount);
// Kafka: asynchronous, side effects happen independently
String txnId = "TXN-" + System.currentTimeMillis();
kafkaTemplate.send("transfer-events", from,
new TransferCompletedEvent(txnId, from, to, amount));
return txnId;
}
}
// Critical path (REST): check balance + debit + credit = ~20ms // Side effects (Kafka): publish event = ~2ms // Notification, audit, fraud react asynchronously // If Fraud Service is down → transfer still succeeds, event waits
What are common mistakes? (wrong vs correct)
Two common mistakes are forcing request-response onto Kafka and building long synchronous chains for fire-and-forget work.
Wrong: using Kafka as a request-response channel.
// WRONG: publishing to Kafka and then polling for a reply topic
// This is forcing synchronous semantics onto an async system
kafkaTemplate.send("balance-request", accountId, request);
// Now poll "balance-response" topic waiting for the reply...
// This is slow, fragile, and misuses Kafka
Correct: use REST when you need a synchronous answer.
// CORRECT: REST for immediate answers
BigDecimal balance = restClient.get()
.uri("/api/v1/accounts/{id}/balance", accountId)
.retrieve()
.body(BigDecimal.class);
// Rule: model commands and queries as REST, model facts as Kafka events // "What is the balance?" → REST (query, needs answer) // "Transfer completed" → Kafka (fact, many consumers)
What should a senior engineer know?
A senior engineer models commands and queries as REST and facts as events. They resist two anti-patterns: forcing Kafka into request-response mode when a synchronous answer is needed, and building long synchronous REST chains for things that should be fire-and-forget events. They also know that Kafka provides ordering only within a partition, so they key messages by the aggregate ID (such as account ID) to ensure all events for one account are ordered correctly.
// Key by accountId so all events for SAV2001 land on the same partition
kafkaTemplate.send("transfer-events", "SAV2001", event);
// This guarantees: debit event for SAV2001 is consumed before credit event
// Without a key, events could land on different partitions and arrive out of order
Visual Overview
Decision Flow: REST or Kafka?
─────────────────────────────
Does the caller need the answer NOW to proceed?
│
├── YES ──▶ Is the caller a browser or external client?
│ │
│ ├── YES ──▶ REST (HTTP/JSON)
│ └── NO ───▶ Is latency critical between internal services?
│ │
│ ├── YES ──▶ gRPC (HTTP/2, protobuf)
│ └── NO ───▶ REST (simpler, good enough)
│
└── NO ───▶ Should many services react independently?
│
├── YES ──▶ Kafka (fan-out, consumer groups)
└── NO ───▶ Does order or replay matter?
│
├── YES ──▶ Kafka (durable log)
└── NO ───▶ RabbitMQ (task queue)
Hybrid Transfer Flow
────────────────────
Rajesh Payment Svc Account Svc Kafka Notification
│ │ │ │ │
│── transfer ──▶│ │ │ │
│ │── balance? ──▶│ (REST) │ │
│ │◀── 75000 ────│ │ │
│ │── debit ────▶│ (REST) │ │
│ │◀── ok ──────│ │ │
│◀── TXN-123 ──│ │ │ │
│ │── event ────────────────────▶│ │
│ │ │ │── consume ──▶│
│ │ │ │ (Kafka) │
Key Takeaway
The choice between REST and Kafka comes down to one question: does the caller need an immediate answer, or is it announcing a fact for others to react to? REST is the right choice when the caller needs the result right now to continue, such as checking the balance of account SAV2001 before approving Rajesh's transfer, or when the client is a browser or mobile app that speaks HTTP and JSON. Kafka is the right choice when the producer should not wait and the event has many independent consumers, such as notifying Priya, updating the statement, and scoring the transaction for fraud after the transfer succeeds.
At National Bank of India, Pankaj designed the transfer flow to use both. The funds check and debit go over REST because the transfer's correctness depends on those answers immediately. After the transfer succeeds, a TransferCompleted event is published to Kafka, and the notification, audit, and fraud services react independently. When the Fraud Service was briefly down during a deployment, the transfer still completed because the event waited in the topic, whereas a synchronous REST call would have failed the whole transfer.
The principle to state in interviews is to model commands and queries as REST and facts as Kafka events. Resist two common mistakes: forcing request-response semantics onto Kafka when you need a synchronous answer, and building long synchronous REST chains for things that should be fire-and-forget events. Used together, REST keeps the critical path correct and immediate, and Kafka keeps everything else decoupled, scalable, and resilient.
Remember one sentence: use REST when the caller needs an answer now, use Kafka when the producer is announcing a fact and should not wait, and in practice use both in the same flow.
---