Grokking Microservices Design Patterns
Ask Author
Back to course home

0% completed

Vote For New Content
CQRS Pattern: An Example
Table of Contents

Contents are not accessible

Contents are not accessible

Contents are not accessible

Contents are not accessible

Contents are not accessible

Let's consider a simple application that manages user profiles. In this application, you can create a new user (command), update their email (command), and view user details (query).

1. Command Model (Write Model)

  1. Command Class: Defines an action to be performed. For instance, CreateUserCommand with fields like userId, name, and email.
  2. Command Handler: Processes the commands. It would take CreateUserCommand, perform business logic, and persist the changes.
  3. Domain Model: Represents the state of the data (e.g., a User class).
  4. Repository: Handles data persistence.

2. Query Model (Read Model)

  1. DTO (Data Transfer Object): Represents the data we want to read, like UserProfileDTO.
  2. Query Class: Defines a query, for instance, GetUserProfileQuery with a userId.
  3. Query Handler: Handles the query and returns the requested data, often using a read-optimized storage.

3. Infrastructure

  1. Command Bus: Routes commands to their respective handlers.
  2. Query Bus: Routes queries to their respective handlers.
  3. Event Store: In a more advanced CQRS setup, this would store events resulting from commands.

Here is the sample code for the aforementioned components:

Command Model (Write Model)

1. Command Class

public class CreateUserCommand { private String userId; private String name; private String email; // Constructor, Getters, and Setters } public class UpdateUserEmailCommand { private String userId; private String newEmail; // Constructor, Getters, and Setters }

2. Command Handler

public class CreateUserCommandHandler { private UserRepository userRepository; public CreateUserCommandHandler(UserRepository userRepository) { this.userRepository = userRepository; } public void handle(CreateUserCommand command) { User user = new User(command.getUserId(), command.getName(), command.getEmail()); userRepository.save(user); } } public class UpdateUserEmailCommandHandler { private UserRepository userRepository; public UpdateUserEmailCommandHandler(UserRepository userRepository) { this.userRepository = userRepository; } public void handle(UpdateUserEmailCommand command) { if (!isValidEmail(command.getNewEmail())) { throw new IllegalArgumentException("Invalid email format"); } User user = userRepository.findById(command.getUserId()); if (user != null) { user.setEmail(command.getNewEmail()); userRepository.save(user); } } private boolean isValidEmail(String email) { // Simple email validation logic return email != null && email.contains("@"); } }

3. Domain Model

public class User { private String id; private String name; private String email; // Constructor, Getters, and Setters }

4. Repository

public interface UserRepository { void save(User user); User findById(String id); } public class UserRepositoryImpl implements UserRepository { // Implementation of save and findById methods }

Query Model (Read Model)

1. DTO (Data Transfer Object)

public class UserProfileDTO { private String id; private String name; private String email; // Constructor, Getters, and Setters }

2. Query Class

public class GetUserProfileQuery { private String userId; // Constructor, Getters, and Setters } // Read Email Query public class ReadUserEmailQuery { private String userId; // Constructor, Getters, and Setters }

3. Query Handler

public class GetUserProfileQueryHandler { private UserRepository userRepository; public GetUserProfileQueryHandler(UserRepository userRepository) { this.userRepository = userRepository; } public UserProfileDTO handle(GetUserProfileQuery query) { User user = userRepository.findById(query.getUserId()); return new UserProfileDTO(user.getId(), user.getName(), user.getEmail()); } } // Read Email Query Handler public class ReadUserEmailQueryHandler { private UserRepository userRepository; public ReadUserEmailQueryHandler(UserRepository userRepository) { this.userRepository = userRepository; } public String handle(ReadUserEmailQuery query) { User user = userRepository.findById(query.getUserId()); return user != null ? user.getEmail() : null; } }

Infrastructure

1. Command Bus

public interface CommandBus { void dispatch(CreateUserCommand command); } public class SimpleCommandBus implements CommandBus { private CreateUserCommandHandler handler; public SimpleCommandBus(CreateUserCommandHandler handler) { this.handler = handler; } @Override public void dispatch(CreateUserCommand command) { handler.handle(command); } }

2. Query Bus

public interface QueryBus { UserProfileDTO dispatch(GetUserProfileQuery query); } public class SimpleQueryBus implements QueryBus { private GetUserProfileQueryHandler handler; public SimpleQueryBus(GetUserProfileQueryHandler handler) { this.handler = handler; } @Override public UserProfileDTO dispatch(GetUserProfileQuery query) { return handler.handle(query); } }

3. Event Store (Simplified Example)

public interface EventStore { void storeEvent(UserEvent event); } public class SimpleEventStore implements EventStore { private List<UserEvent> events = new ArrayList<>(); @Override public void storeEvent(UserEvent event) { events.add(event); } }

Putting It All Together

Java
Java
. . . .

This example includes a basic implementation of a command bus, query bus, and a simplified event store. In a full-fledged application, an event store would be used for event sourcing, which is often paired with CQRS. The event store captures changes as a series of events, which can be replayed to rebuild the state of an entity.

.....

.....

.....

Like the course? Get enrolled and start learning!

Table of Contents

Contents are not accessible

Contents are not accessible

Contents are not accessible

Contents are not accessible

Contents are not accessible