Communication Questions
Synchronous vs asynchronous communication: when do you use each?
| Synchronous (REST, gRPC, Feign) | Asynchronous (Kafka, RabbitMQ) | |
|---|---|---|
| Caller waits? | yes, needs the answer now | no, fire an event and move on |
| Coupling | temporal: both must be up | decoupled: consumer can be down and catch up |
| Use for | queries, "get price", "validate user" | side effects: send email, update inventory, analytics |
| Failure handling | timeouts, retries, circuit breaker | retries, DLQ, idempotent consumers |
Tip: Rule of thumb: query synchronously, notify asynchronously. Long chains of synchronous calls multiply latency and failure probability.
How do you call another service with OpenFeign? How do you handle errors and timeouts?
@FeignClient(name = "product-service", path = "/api/products")
public interface ProductClient {
@GetMapping("/{id}")
ProductDto getProduct(@PathVariable Long id);
}
@Component
class FeignErrorDecoder implements ErrorDecoder {
public Exception decode(String method, Response r) {
return switch (r.status()) {
case 404 -> new ProductNotFoundException();
case 503 -> new RetryableException(503, "unavailable", r.request().httpMethod(), (Long) null, r.request());
default -> new ErrorDecoder.Default().decode(method, r);
};
}
}spring:
cloud:
openfeign:
client:
config:
product-service:
connect-timeout: 1000
read-timeout: 2000
circuitbreaker:
enabled: truename is the Eureka service id, so Spring Cloud LoadBalancer picks an instance. Always set timeouts; the defaults are too long for user-facing calls.
Feign vs RestClient vs WebClient?
| OpenFeign | RestClient (Spring 6.1+) | WebClient | |
|---|---|---|---|
| Style | declarative interface | fluent, blocking | fluent, reactive (non-blocking) |
| Best for | many endpoints of one service | simple calls in MVC apps | WebFlux apps, high fan-out |
| Load balancing | built in with name | @LoadBalanced RestClient.Builder | @LoadBalanced WebClient.Builder |
Note: Spring Cloud OpenFeign is now feature-complete (maintenance mode). For new code the Spring team suggests the HTTP interface (@HttpExchange) backed by RestClient, which gives the same declarative style.
Explain Kafka basics: topic, partition, consumer group, offset.
- Topic: named stream of events, e.g.
order-placed. - Partition: a topic is split into ordered logs; ordering is guaranteed only within a partition. The message key (e.g.
orderId) decides the partition. - Consumer group: each partition is read by exactly one consumer in a group; add consumers (up to the partition count) to scale.
- Offset: position of a consumer in a partition; committed after processing, so a restart resumes from there.
// producer (order-service)
kafkaTemplate.send("order-placed", order.id().toString(), new OrderPlaced(order.id(), order.total()));
// consumer (inventory-service)
@KafkaListener(topics = "order-placed", groupId = "inventory")
void on(OrderPlaced event) { inventory.reserve(event.orderId()); }Kafka vs RabbitMQ?
| Kafka | RabbitMQ | |
|---|---|---|
| Model | distributed append-only log | message broker with queues and exchanges |
| Retention | keeps messages (days), consumers can replay | deleted after ack |
| Throughput | very high (millions/sec) | high, lower than Kafka |
| Routing | by topic + partition key | rich: direct, topic, fanout, headers |
| Use for | event streaming, event sourcing, analytics, CDC | task queues, RPC-like work, complex routing |
What delivery guarantees does Kafka give and how do you get "exactly once"?
By default at-least-once: if a consumer processes a message and crashes before committing the offset, it processes it again. Producers can enable enable.idempotence=true (default since Kafka 3.0) and transactions for exactly-once within Kafka. For side effects outside Kafka (DB writes, payments), you still need idempotent consumers: store the processed event id and skip duplicates.
What is a dead letter topic and how do you configure retries for a Kafka consumer?
@Bean
DefaultErrorHandler errorHandler(KafkaTemplate<Object, Object> template) {
var recoverer = new DeadLetterPublishingRecoverer(template); // sends to "order-placed.DLT"
var backoff = new ExponentialBackOff(1000, 2.0);
backoff.setMaxElapsedTime(10_000);
var handler = new DefaultErrorHandler(recoverer, backoff);
handler.addNotRetryableExceptions(ValidationException.class); // bad data: no point retrying
return handler;
}Poison messages go to the DLT after retries so the partition is not blocked. Monitor the DLT and replay after a fix. Spring Kafka also offers @RetryableTopic for non-blocking retries via retry topics.