In today’s distributed systems world, responsiveness, scalability, and loose coupling are top priorities. Event-Driven Architecture (EDA) is a powerful design paradigm that enables services to react to events rather than tightly-coupled function calls. Java, with its rich ecosystem—especially Spring Boot—makes it easier to build reliable and scalable event-driven systems.
This article explores how to design an event-driven system using Java, and walks you through a real-world example using Spring Boot, Kafka, and Lombok.
What is Event-Driven Architecture?
Event-Driven Architecture is a software design pattern in which decoupled services communicate with each other through the production and consumption of events.
Key Components:
- Event: A message that signifies a change in state (e.g.,
OrderCreated
,PaymentProcessed
) - Event Producer: Component that generates an event
- Event Consumer: Component that listens for and processes events
- Event Broker: Middleware that routes events (e.g., Kafka, RabbitMQ)
Benefits of EDA
- Loose Coupling: Services don’t need to know about each other directly
- Scalability: Easily scale consumers independently
- Flexibility: Add new consumers without changing producers
- Resilience: Isolate failures to individual services
Architecture Overview
Here’s what a simple EDA flow looks like:
- Order Service (Producer) publishes
OrderCreated
event. - Notification Service (Consumer) listens to
OrderCreated
events and sends notifications. - Kafka acts as the event broker.
Example Project: Order Event System
We’ll use:
- Spring Boot
- Spring Kafka
- Lombok
1. Project Structure
event-driven-system/
│
├── order-service/ # Produces OrderCreated events
├── notification-service/ # Consumes OrderCreated events
└── common/ # Shared models (e.g., OrderEvent)
2. Common Module: Event Model
// common/src/main/java/com/example/common/OrderCreatedEvent.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderCreatedEvent {
private String orderId;
private String customerEmail;
private double totalAmount;
}
3. Order Service: Event Producer
Kafka Producer Configuration
@Configuration
public class KafkaProducerConfig {
@Bean
public ProducerFactory<String, OrderCreatedEvent> producerFactory() {
return new DefaultKafkaProducerFactory<>(Map.of(
ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092",
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class,
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class
));
}
@Bean
public KafkaTemplate<String, OrderCreatedEvent> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
}
Order Controller
@RestController
@RequestMapping("/orders")
@RequiredArgsConstructor
public class OrderController {
private final KafkaTemplate<String, OrderCreatedEvent> kafkaTemplate;
@PostMapping
public ResponseEntity<String> createOrder(@RequestBody OrderCreatedEvent event) {
kafkaTemplate.send("orders", event);
return ResponseEntity.ok("Order published!");
}
}
4. Notification Service: Event Consumer
Kafka Consumer Config
@Configuration
@EnableKafka
public class KafkaConsumerConfig {
@Bean
public ConsumerFactory<String, OrderCreatedEvent> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(Map.of(
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092",
ConsumerConfig.GROUP_ID_CONFIG, "notification-group",
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class,
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class
), new StringDeserializer(), new JsonDeserializer<>(OrderCreatedEvent.class));
}
@Bean
public ConcurrentKafkaListenerContainerFactory<String, OrderCreatedEvent> kafkaListenerContainerFactory() {
var factory = new ConcurrentKafkaListenerContainerFactory<String, OrderCreatedEvent>();
factory.setConsumerFactory(consumerFactory());
return factory;
}
}
Listener Class
@Component
public class NotificationListener {
@KafkaListener(topics = "orders", groupId = "notification-group")
public void handleOrderEvent(OrderCreatedEvent event) {
System.out.println("Sending email to: " + event.getCustomerEmail());
// Simulate sending email
}
}
Testing the System
- Run Kafka locally (or use Docker)
- Start both microservices
- Call the
POST /orders
endpoint inorder-service
:
{
"orderId": "abc123",
"customerEmail": "john@example.com",
"totalAmount": 89.99
}
- Check logs of
notification-service
– it should print the email action.
Conclusion
Event-driven architecture is a great fit for modern microservices, offering scalability, flexibility, and decoupled services. With Java and Spring Boot, you can quickly get started building reliable EDA systems.