Sync vs async communication (REST and Kafka)
Synchronous calls (REST or gRPC) make the caller wait for the response. They're simple but couple availability: if payment-service is down, checkout fails. Use them for queries that need an immediate answer.
Asynchronous messaging (Kafka, RabbitMQ): a service publishes an event such as OrderPlaced and moves on, and interested services consume it in their own time. You get loose coupling, natural buffering, and new consumers can be added without touching the producer.
Spring options:
- HTTP:
RestClient, declarative HTTP interface clients (@HttpExchange), or OpenFeign. - gRPC: Spring gRPC, auto-configured since Boot 4.1.
- Messaging:
KafkaTemplatewith@KafkaListener, or Spring Cloud Stream.
Kafka basics: topics are split into partitions; messages with the same key go to the same partition and stay in order; consumer groups share partitions between instances for scaling.
Example
// Declarative HTTP client (Spring 6+)
@HttpExchange("/api/v1/users")
public interface UserClient {
@GetExchange("/{id}")
UserDto get(@PathVariable String id);
}
// Publish an event after saving the order
@Service
public class OrderService {
private final KafkaTemplate<String, OrderPlaced> kafka;
OrderService(KafkaTemplate<String, OrderPlaced> kafka) { this.kafka = kafka; }
public void place(Order order) {
// ... save the order in this service's own database
kafka.send("orders.placed", order.id().toString(),
new OrderPlaced(order.id(), order.userId(), order.totalPaise()));
}
}
// Another service reacts independently
@Component
class EnrollmentListener {
@KafkaListener(topics = "orders.placed", groupId = "enrollment-service")
void on(OrderPlaced event) {
enrollmentService.grantAccess(event.userId(), event.orderId());
}
}Common mistake
Long chains of synchronous calls (A calls B calls C calls D). Latencies add up, and one slow service drags everything down. Prefer events, or keep a local copy of the data you need.
Under the hood
Saving to the database and then publishing to Kafka is a dual write: a crash between the two loses the event. The transactional outbox pattern writes the event to an outbox table in the same database transaction, and a relay (Debezium change data capture, or a poller) publishes it. Kafka delivers at least once, so consumers must be idempotent, for example by de-duplicating on the event id.
Check yourself
What makes a Kafka consumer safe against duplicate delivery?
How this connects
Know these first
Part of Microservices and production.
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.