Java Microservices - Event Sourcing



Introduction

In traditional systems, application state is stored as the current value of entities. For example, if a user updates their address, the database reflects only the latest address.

But in microservices, sometimes it's necessary to store a full history of changes - not just the final state.

Event Sourcing is a powerful design pattern that solves this by storing changes to application state as a sequence of events. Instead of only saving the current state, you store all events that led to it.

What is Event Sourcing?

Definition

Event Sourcing is a pattern in which every change to the state of an application is captured in an event object, and those events are persisted. The current state is then rebuilt by replaying the sequence of past events.

Example

Instead of storing:

{ "accountBalance": 1000 }

You store events like:

[
   { "type": "AccountCreated", "amount": 0 },
   { "type": "DepositMade", "amount": 1000 }
]

Replaying these events leads to the current balance.

Key Concepts of Event Sourcing

Sr.No. Concept Description
1 Event Immutable fact describing what happened
2 Aggregate Entity that applies events to rebuild state
3 Event Store Database or broker where events are saved
4 Projection Read model built from event stream
5 Replay Rebuilding state by applying past events

Benefits of Event Sourcing

Sr.No. Benefit Explanation
1 Auditability Full history of what happened and when
2 Debugging & Replayability Reconstruct bugs by replaying events
3 Temporal Queries View system state at any point in time
4 Decoupling Services can react to events asynchronously
5 Event-Driven Integration Pairs naturally with messaging patterns

Use Cases in Microservices

Sr.No. Domain Event Sourcing Use Case
1 Banking Transactions, audit trails
2 eCommerce Orders, inventory changes
3 Healthcare Patient record changes
4 Logistics Shipment events and delivery status

Spring Boot Example: Simple Event Sourcing for Account

We will build a simple Account microservice that −

  • Accepts commands like CreateAccount, DepositMoney

  • Persists events to an in-memory list (simulating event store)

  • Applies events to rebuild account balance

Technologies

  • Java 17+

  • Spring Boot 3.x

Model: Domain Event Base Class

public interface DomainEvent {
   LocalDateTime occurredAt();
}

Account Events

public class AccountCreatedEvent implements DomainEvent {
   private final String accountId;
   private final LocalDateTime occurredAt = LocalDateTime.now();

   public AccountCreatedEvent(String accountId) {
      this.accountId = accountId;
   }

   public String getAccountId() { return accountId; }
   public LocalDateTime occurredAt() { return occurredAt; }
}
public class MoneyDepositedEvent implements DomainEvent {
   private final String accountId;
   private final double amount;
   private final LocalDateTime occurredAt = LocalDateTime.now();

   public MoneyDepositedEvent(String accountId, double amount) {
      this.accountId = accountId;
      this.amount = amount;
   }

   public String getAccountId() { return accountId; }
   public double getAmount() { return amount; }
   public LocalDateTime occurredAt() { return occurredAt; }
}

Event Store (In-Memory)

@Service
public class EventStore {
   private final List<DomainEvent> events = new ArrayList<>();

   public void save(DomainEvent event) {
      events.add(event);
   }
 
   public List<DomainEvent> getEventsForAccount(String accountId) {
      return events.stream()
         .filter(e -> {
         if (e instanceof AccountCreatedEvent ac) {
            return ac.getAccountId().equals(accountId);
         } else if (e instanceof MoneyDepositedEvent md) {
            return md.getAccountId().equals(accountId);
         }
         return false;
      })
      .toList();
   }
}

Aggregate: Account

public class Account {
   private final String accountId;
   private double balance = 0;

   public Account(String accountId) {
      this.accountId = accountId;
   }

   public void apply(DomainEvent event) {
      if (event instanceof AccountCreatedEvent) {
         // no-op
      } else if (event instanceof MoneyDepositedEvent e) {
         this.balance += e.getAmount();
      }
   }

   public double getBalance() {
      return balance;
   }
}

Command Controller

@RestController
@RequestMapping("/accounts")
public class AccountController {

   @Autowired
   private EventStore store;

   @PostMapping("/{id}/create")
   public ResponseEntity<String> createAccount(@PathVariable String id) {
      AccountCreatedEvent event = new AccountCreatedEvent(id);
      store.save(event);
      return ResponseEntity.ok("Account created: " + id);
   }

   @PostMapping("/{id}/deposit")
   public ResponseEntity<String> deposit(@PathVariable String id, @RequestParam double amount) {
      MoneyDepositedEvent event = new MoneyDepositedEvent(id, amount);
      store.save(event);
      return ResponseEntity.ok("Deposited " + amount);
   }

   @GetMapping("/{id}")
   public ResponseEntity<String> getBalance(@PathVariable String id) {
      List<DomainEvent> events = store.getEventsForAccount(id);
      Account account = new Account(id);
      events.forEach(account::apply);
      return+ ResponseEntity.ok("Balance: " + account.getBalance());
   }
}

Combining with CQRS

Event Sourcing works beautifully with CQRS −

  • Command model modifies state via events

  • Query model uses projections of those events

  • Can use different databases for read/write

This enables high scalability and responsiveness for read-heavy systems.

Tools and Frameworks

Sr.No. Tool / Library Description
1 Axon Framework Java framework for CQRS + Event Sourcing
2 Eventuate Platform for event-driven microservices
3 Kafka Durable distributed event store
4 PostgreSQL Can be used as event store with event tables
5 Debezium CDC (Change Data Capture) tool for generating events from DB changes

Summary

Sr.No. Topic Key Takeaway
1 What is Event Sourcing Store state as events
2 Benefits Audit, scalability, debugging
3 Implementation Events + Aggregates + Event Store
4 Best Fit Complex domains, financial logs
5 Tools Axon, Kafka, Spring Boot

Conclusion

Event Sourcing is a powerful pattern that provides traceability, scalability, and flexibility. When combined with microservices and messaging tools like Kafka, it enables robust, event-driven architectures.

While it introduces complexity, especially around modeling and querying, the long-term benefits−especially in systems requiring audit, replay, and high scalability-are significant.

Start small with in-memory event logs or lightweight projections, and grow into full-fledged event-sourced systems as your microservices mature.

Advertisements