Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

The Traditional Approach

In traditional data management systems, both commands (updates to the data) and queries (requests for data) are executed against the same set of entities in a single data repository. These entities may be a subset of the rows in one or more tables in a relational database such as MySQL.

Typically, in these systems, all create, read, update, and delete (CRUD) operations are applied to the same representation of the entity. For example, a data transfer object (DTO) representing a customer is retrieved from the data store by the data access layer (DAL) and displayed on the screen. A user updates some fields of the DTO (perhaps through data binding) and the DTO is then saved back in the data store by the DAL. The same DTO is used for both the read and write operations.

Panel

On this page:

Table of Contents

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 an explicit 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

Info

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:

Code Block
languagejava
linenumberstrue
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

Code Block
languagejava
linenumberstrue
} else if (is(commandParam, "approve")) {
    commandRequest = CommandWrapper
            .create("CLIENT", "APPROVE")
            .entityId(clientId)
            .build();
    result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}

That is it.