Enterprise Architecture · Enterprise · Topic 3 of 5
CQRS with Kafka and Read Models
Separate the write (command) path from the read (query) path. Commands produce Kafka events; consumers build read models in stores shaped for queries (a search index, a cache, a relational table). This is an architecture pattern, not a Kafka feature: Kafka carries the events.
@Service
@Slf4j
public class OrderReadModelUpdater {
private final OrderSearchRepository elasticsearchRepo;
private final OrderCacheRepository redisRepo;
// Update the search read model
@KafkaListener(topics = {
"orders.order.created",
"orders.order.updated",
"orders.order.cancelled"
}, groupId = "order-search-updater")
public void updateSearchIndex(ConsumerRecord<String, Object> record) {
String eventType = getEventType(record);
String orderId = record.key();
switch (eventType) {
case "OrderCreated", "OrderUpdated" -> {
OrderSearchDoc doc = buildSearchDoc(record.value());
elasticsearchRepo.save(doc); // upsert by orderId
}
case "OrderCancelled" -> {
OrderSearchDoc doc = elasticsearchRepo.findById(orderId).orElseThrow();
doc.setStatus("CANCELLED");
elasticsearchRepo.save(doc);
}
}
}
// Update the cache (hot data)
@KafkaListener(topics = "orders.order.created", groupId = "order-cache-updater")
public void updateCache(OrderCreatedEvent event) {
OrderSummary summary = OrderSummary.from(event);
redisRepo.save(event.getOrderId(), summary, Duration.ofHours(24));
}
}
// Query side: reads from the read models, not from Kafka
@RestController
@RequestMapping("/api/orders")
public class OrderQueryController {
@GetMapping("/search")
public List<OrderSearchResult> search(@RequestParam String query) {
return elasticsearchRepo.search(query);
}
@GetMapping("/{id}")
public OrderSummary getOrder(@PathVariable String id) {
return redisRepo.get(id) // cache first
.orElseGet(() -> orderRepo.findById(id).map(OrderSummary::from).orElseThrow());
}
}Sources
- @KafkaListener annotation (Spring for Apache Kafka) (opens in a new tab)
- Design (Kafka 4.2 documentation) (opens in a new tab)
Checked against Spring for Apache Kafka 4.1.1, Spring Boot 4.1.1 and Kafka 4.2 documentation on 11 October 2026. Draft: not yet reviewed by a Kafka specialist, and releases move on, so confirm against the current documentation before you act on it.