Mastering CQRS in Spring Microservices: From Basics to Event Sourcing

This article explains the CQRS architectural pattern, its benefits and pitfalls, and provides a step‑by‑step guide to implementing CQRS with Spring Boot, Axon, and Kafka, including command and query handling, event sourcing, and practical considerations for microservice architectures.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Mastering CQRS in Spring Microservices: From Basics to Event Sourcing

What is CQRS?

Command Query Responsibility Segregation (CQRS) separates data‑modifying operations (commands) from data‑retrieval operations (queries). Each side can use a model that is optimised for its purpose, which improves scalability, flexibility and code clarity.

Origins and Evolution

CQRS originates from Command Query Separation (CQS) introduced by Bertrand Meyer for the Eiffel language. CQS applies at the method level, while CQRS extends the principle to the architectural level, encouraging distinct components for handling commands and queries.

Why Use CQRS?

Scalability : Command and query services can be scaled independently, allowing resource optimisation for read‑heavy or write‑heavy workloads.

Flexibility : Different persistence mechanisms can be chosen for each side (e.g., relational DB for writes, denormalised views or search engines for reads).

Maintainability : Separate models reduce coupling, making the codebase easier to understand and evolve.

Security : Write paths can enforce stricter validation and authorization while reads remain performant.

Potential Pitfalls

Increased Complexity : Maintaining two separate code paths and data stores adds architectural overhead.

Consistency Challenges : The write and read stores must be kept in sync, often requiring eventual consistency.

Implementing CQRS with Spring Boot

Project Setup

Create a Spring Boot project (e.g., via Spring Initializr) with the following dependencies:

Spring Web

Spring Data JPA

Database driver of choice (e.g., PostgreSQL, MySQL)

Command Definition

public class CreateUserCommand {
    private final String userId;
    private final String username;
    // Constructor, getters
}

Command Handler

@Service
public class CreateUserCommandHandler implements CommandHandler<CreateUserCommand> {
    @Autowired
    private UserRepository userRepository;

    @Override
    public void handle(CreateUserCommand command) {
        User user = new User(command.getUserId(), command.getUsername());
        userRepository.save(user);
    }
}

Query Definition

public class GetUserByIdQuery {
    private final String userId;
    // Constructor, getters
}

Query Handler

@Service
public class GetUserByIdQueryHandler implements QueryHandler<GetUserByIdQuery, User> {
    @Autowired
    private UserRepository userRepository;

    @Override
    public User handle(GetUserByIdQuery query) {
        return userRepository.findById(query.getUserId()).orElse(null);
    }
}

Event Sourcing with Axon

Axon Framework simplifies CQRS and event sourcing in Spring. Commands publish events; events are persisted and can be replayed to rebuild aggregate state.

@Aggregate
public class Account {
    @AggregateIdentifier
    private String accountId;
    private int balance;

    @CommandHandler
    public void handle(WithdrawMoneyCommand cmd) {
        if (cmd.getAmount() > balance) {
            throw new InsufficientFundsException();
        }
        apply(new MoneyWithdrawnEvent(cmd.getAccountId(), cmd.getAmount()));
    }

    @EventSourcingHandler
    public void on(MoneyWithdrawnEvent evt) {
        this.balance -= evt.getAmount();
    }
}

Axon also supports projections that listen to events and update read‑optimized views.

Asynchronous Communication with Kafka

In a microservice landscape, command handlers can publish domain events to Kafka topics. Query services consume those topics to update their read models, achieving loose coupling and resilience.

Event Sourcing Overview

Instead of persisting the current state, event sourcing stores every domain event. Replaying the event stream reconstructs the aggregate’s state, providing natural audit trails and enabling temporal queries.

Challenges and Considerations

Architectural Complexity : Additional components such as an event store, command bus and event bus increase operational overhead.

Learning Curve : Teams must shift from CRUD‑centric designs to event‑driven thinking.

Eventual Consistency : Write‑side changes may not be immediately visible to the read side.

Event Ordering & Versioning : Correct ordering and schema evolution of events require careful design.

Storage & Replay Costs : Event stores can grow large; replaying long histories may impact performance.

Integration with Non‑CQRS Systems : Synchronising with external services that do not follow CQRS can be difficult.

Conclusion

CQRS, when combined with Spring Boot, Axon and Kafka, offers a robust pattern for building scalable, maintainable microservices. Teams should evaluate the added complexity, consistency trade‑offs and operational costs against the benefits for their specific domain.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Backend ArchitectureMicroservicesKafkaSpring BootCQRSEvent SourcingAxon
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.