CQRS Pattern
CQRS at a Glance
Command and Query Responsibility Segregation (CQRS) is a pattern that segregates the operations that read data (Queries) from the operations that update data (Commands) by using separate interfaces. This implies that the data models used for querying and updates are different. The models can then be isolated, although this is not an absolute requirement.
Compared to the single model of the data (from which developers build their own conceptual models) that is inherent in CRUD-based systems, the use of separate query and update models for the data in CQRS-based systems considerably simplifies design and implementation. However, one disadvantage is that, unlike CRUD designs, CQRS code cannot automatically be generated by using scaffold mechanisms.
The query model for reading data and the update model for writing data may access the same physical store, perhaps by using SQL views or by generating projections on the fly. However, it is common to separate the data into different physical stores to maximize performance, scalability, and security.
Event Sourcing
The CQRS pattern is often used in conjunction with the Event Sourcing pattern. CQRS-based systems use separate read and write data models, each tailored to relevant tasks and often located in physically separate stores. When used with Event Sourcing, the store of events is the write model, and this is the authoritative source of information. The read model of a CQRS-based system provides materialized views of the data, typically as highly denormalized views. These views are tailored to the interfaces and display requirements of the application, which helps to maximize both display and query performance.
Using the stream of events as the write store, rather than the actual data at a point in time, avoids update conflicts on a single aggregate and maximizes performance and scalability. The events can be used to asynchronously generate materialized views of the data that are used to populate the read store.
Because the event store is the authoritative source of information, it is possible to delete the materialized views and replay all past events to create a new representation of the current state when the system evolves, or when the read model must change. The materialized views are effectively a durable read-only cache of the data.
Mifos X and CQRS
Mifos X Financial Service Engine is built entirely around the CQRS pattern. All write operations are wrapped in a command and processed by a dedicated command handler. The command processor is responsible for tracing the event, find the right command handler, and observe the underlying transaction.
The main components are:
- org.mifosplatform.commands.annotation.CommandType
- An annotation used to mark a command handler for a specific entity and action
- org.mifosplatform.commands.provider.CommandHandlerProvider
- Provides command handlers for an entity and action combination
- org.mifosplatform.commands.domain.CommandWrapper
- A generic wrapper for all write operations, specifying the entity and action for this command
- org.mifosplatform.commands.service.SynchronousCommandProcessingService
- The command processor, taking care about sourcing, finding the right command handler, and observe the transaction
Sample
This sample is based on some retrofitting that will be available with release 15.06!
Imagine we would like to create a new command to approve a client. The entity is CLIENT, and the action is APPROVE.
We would start by implementing the command handler:
package org.mifosplatform.portfolio.client.handler; import org.mifosplatform.commands.annotation.CommandType; import org.mifosplatform.commands.handler.NewCommandSourceHandler; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.portfolio.client.service.ClientWritePlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service // Stereotype to allow auto-discovery and dependency injection @CommandType(entity = "CLIENT", action = "APPROVE") // Meta-data to register this command to the desired entity and action public class ApproveClientCommandHandler implements NewCommandSourceHandler { private final ClientWritePlatformService clientWritePlatformService; @Autowired public CreateClientCommandHandler(final ClientWritePlatformService clientWritePlatformService) { super(); this.clientWritePlatformService = clientWritePlatformService; } @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { return this.clientWritePlatformService.createClient(command); } }
Now we need to create the command and call the command processor. We only need to add an additional else block to the method activate in ClientApiResource
} else if (is(commandParam, "approve")) { commandRequest = CommandWrapper .create("CLIENT", "APPROVE") .entityId(clientId) .build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); }
That is it.