Designing an Event-Driven Architecture in Java with Spring Boot

1 min read

Designing an Event-Driven Architecture in Java with Spring Boot

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:

  1. Order Service (Producer) publishes OrderCreated event.
  2. Notification Service (Consumer) listens to OrderCreated events and sends notifications.
  3. 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

  1. Run Kafka locally (or use Docker)
  2. Start both microservices
  3. Call the POST /orders endpoint in order-service:
{
  "orderId": "abc123",
  "customerEmail": "john@example.com",
  "totalAmount": 89.99
}
  1. 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.

🤞 Never miss a story from us, get weekly updates to your inbox!

Leave a Reply

Your email address will not be published. Required fields are marked *