π Introduction
In a microservices architecture, services often need to communicate with each other. While synchronous communication using REST APIs is common, asynchronous communication using Kafka is preferred for event-driven and loosely coupled systems.
In this blog, weβll explore how to implement service-to-service communication using Apache Kafka and Spring Cloud Stream in Spring Boot applications.
π§© Tech Stack
- Java 17+
- Spring Boot 3.x
- Spring Cloud Stream
- Apache Kafka
- Spring Cloud Function (Optional)
π¦ Project Overview
We'll build two microservices:
- Order Service β Publishes events to Kafka when a new order is created.
- Inventory Service β Listens to those events and updates the stock.
π Dependencies (Add to both services)
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Add the Spring Cloud BOM in pom.xml:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
π§Ύ 1. Order Service (Producer)
β application.yml
spring:
application:
name: order-service
cloud:
stream:
bindings:
order-out:
destination: order-topic
content-type: application/json
producer:
required-groups: inventory-group
kafka:
binder:
brokers: localhost:9092
β Order Model
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private String orderId;
private String productId;
private int quantity;
}
β Order Publisher
@Service
@RequiredArgsConstructor
public class OrderService {
private final StreamBridge streamBridge;
public void placeOrder(Order order) {
streamBridge.send("order-out", order);
System.out.println("π€ Sent Order to Kafka: " + order);
}
}
β REST Controller
@RestController
@RequestMapping("/orders")
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@PostMapping
public ResponseEntity<String> createOrder(@RequestBody Order order) {
orderService.placeOrder(order);
return ResponseEntity.ok("Order placed successfully!");
}
}
π¦ 2. Inventory Service (Consumer)
β application.yml
spring:
application:
name: inventory-service
cloud:
stream:
bindings:
order-in:
destination: order-topic
group: inventory-group
content-type: application/json
kafka:
binder:
brokers: localhost:9092
β Inventory Listener
@Component
public class InventoryConsumer {
@StreamListener("order-in")
public void handleOrder(Order order) {
System.out.println("π₯ Received Order in Inventory: " + order);
// Simulate inventory update
System.out.println("β
Inventory updated for product: " + order.getProductId());
}
}
β You can also use
@Beanwith functional style instead of@StreamListener:
@Bean
public Consumer<Order> orderConsumer() {
return order -> {
System.out.println("π₯ [Functional] Received Order: " + order);
};
}
π οΈ Kafka Setup (Local)
- Start Zookeeper & Kafka:
# Start Zookeeper
bin/zookeeper-server-start.sh config/zookeeper.properties
# Start Kafka Server
bin/kafka-server-start.sh config/server.properties
- Create Topic:
bin/kafka-topics.sh --create --topic order-topic --bootstrap-server localhost:9092 --partitions 1 --replication-factor 1
π§ͺ Testing
- Run both services.
- Make a POST call to
localhost:8080/orders:
POST /orders
Content-Type: application/json
{
"orderId": "O101",
"productId": "P5001",
"quantity": 3
}
- Output:
- Order Service:
π€ Sent Order to Kafka - Inventory Service:
π₯ Received Order in Inventory
π§ Why Use Spring Cloud Stream?
| Feature | Benefit |
|---|---|
| Abstraction over Kafka | Write less boilerplate code. |
| Flexible Binding | Easily switch between Kafka, RabbitMQ, etc. |
| Declarative Configuration | Use YAML to configure consumers/producers. |
| Built-in Serialization | JSON, Avro, etc. with minimal setup. |
βοΈ Kafka vs REST for Microservices
| REST (Synchronous) | Kafka (Asynchronous) |
|---|---|
| Simple & direct | Decoupled services |
| Tight coupling | Loose coupling |
| Fails if target down | Can retry later |
| Real-time request/response | Event-driven patterns |
β Best Practices
- β Always define a consumer group.
- β
Handle serialization errors using
ErrorHandlingDeserializer. - β Use dead-letter queues for failed messages.
- β Monitor with Kafka Manager or Confluent Control Center.
- β Use Avro/Schema Registry for contract enforcement.
π Conclusion
Using Spring Cloud Stream with Kafka is a powerful way to enable asynchronous service-to-service communication. It abstracts low-level Kafka configurations and allows you to focus on business logic.
π Whether you're building event-driven microservices or improving scalability, this architecture ensures decoupling, flexibility, and resilience.
\
Top comments (0)