Skip to content

Defining Command Handlers

Within CQRS applications command handling is responsible for validating business rules, before applying changes to the application's state by means of new events. In order to execute command handling logic, it is necessary to reconstruct the write model, in order to be able to decide, whether the command is applicable or not. This is why command handlers may not be executed directly, but will be executed by the command router, instead, as shown in command handling.

Commands

Commands express the intent and any information required to execute the associated command handling logic. Commands must implement Command, which:

  1. identifies the command subject used to source the events to reconstruct the write model
  2. an optional SubjectCondition specifying conditions with respect to the subject's existence.

An example of a pristine subject command used to purchase new (not yet existing) books within a library, may look as follows:

public record PurchaseBookCommand(
        String isbn, 
        String author,  
        String title,  
        long numPages
) implements Command {

    @Override
    public String getSubject() {
        return "/books/" + isbn();
    }

    @Override
    public SubjectCondition getSubjectCondition() {
        return SubjectCondition.PRISTINE;
    }
}

Command Handler Definitions

OpenCQRS requires developers to provide CommandHandlerDefinitions, encapsulating the command handling logic. These need to be registered with a CommandRouter, in order to execute commands. CommandHandlerDefinition requires the following parameters for its construction:

  1. A java.lang.Class identifying the type of the write model to be reconstructed prior to the actual command execution. This class is used to identify the state rebuilding handlers used to reconstruct the write model, i.e. those with the same instance type.
  2. A command class implementing Command expressing the intent and any information required to execute the command.
  3. A CommandHandler encapsulating the command handling logic, which will be executed with the given command, once the write model has been reconstructed.
  4. An optional SourcingMode specifying which events need to be sourced in order to reconstruct the write model.

Command Handlers

There are three different types of CommandHandler that may be extended to define the actual command handling logic; all of them are functional interfaces:

  1. ForCommand if accessing the Command is sufficient. This is typically used for creational commands in combination with SubjectCondition PRISTINE.
  2. ForInstanceAndCommand if additional access to the write model is required.
  3. ForInstanceAndCommandAndMetaData if additional access to any command meta-data is required.

All three types accept an additional CommandEventPublisher for publishing new events as part of the command handling logic. In addition, all types require the developer to specify the following generic types:

  1. the type of the write model to be sourced
  2. the command type to be handled
  3. the return type, or java.lang.Void for command handlers returning null

Choosing a CommandHandler Type

The choice of a suitable CommandHandler is for syntactical reasons only, especially when using Java or Kotlin lambda expressions to implement them. The choice has no effect on the command handling workflow itself, nor the presence of meta-data or the reconstructed write model.

Registration

CommandHandlerDefinitions need to be created and registered with the CommandRouter, before commands can be executed.

Command Ambiguity and Inheritance

All CommandHandlerDefinitions registered with the same CommandRouter instance must be non-ambiguous with respect to their Command for the router to know, where commands need to be routed to. Command inheritance is ignored for that matter, i.e. commands A and B extending A will be treated as distinct commands.

Manual Registration

A list of CommandHandlerDefinition can be registered with a manually configured CommandRouter programmatically as in the following example:

public static CommandRouter commandRouter(EventRepository eventRepository) {
    CommandHandlerDefinition<Book, PurchaseBookCommand, Void> purchaseCmdDef = new CommandHandlerDefinition<>(
            Book.class,
            PurchaseBookCommand.class,
            (CommandHandler.ForCommand<Book, PurchaseBookCommand, Void>) (command, publisher) -> {
                if (command.numPages <= 0) {
                    throw new IllegalArgumentException("Number of pages must be greater than 0.");
                }

                publisher.publish(
                        new BookPurchasedEvent(
                                command.isbn(),
                                command.author(),
                                command.title(),
                                command.numPages()
                        )
                );
                return null;
            }
    );

    return new CommandRouter(
            eventRepository,
            eventRepository,
            List.of(purchaseCmdDef),
            List.of(/* (1)! */) 
    );
}
  1. StateRebuildingHandlerDefinitions omitted, see

Spring Boot based Registration

When using the Spring Boot auto-configured command router CommandHandlerDefinitions can be defined as Spring beans, in order to be auto-wired with the CommandRouter instance created by the CommandRouterAutoConfiguration.

Command Handling Test Support

It is recommended to define CommandHandlerDefinition Spring beans within classes annotated with CommandHandlerConfiguration, which in turn is meta-annotated with org.springframework.context.annotation.Configuration. This enables command handling tests to automatically detect and load them without starting a full-blown Spring context, instead using the CommandHandlingTest test slice.

Command Handler Definition using @Bean Methods

CommandHandlerDefinitions can be defined using org.springframework.context.annotation.Bean annotated methods, as shown in the following example.

@CommandHandlerConfiguration
public class BookHandlers {

    @Bean
    public CommandHandlerDefinition<Book, PurchaseBookCommand, Void> purchaseBookCommandVoidCommandHandlerDefinition(BookValidator bookValidator) {
        return new CommandHandlerDefinition<>(
                Book.class,
                MyBookCommand.class,
                (CommandHandler.ForCommand<Book, PurchaseBookCommand, Void>) (command, publisher) -> {
                    bookValidator.checkValidPurchaseRequest(command);
                    publisher.publish(
                            new BookPurchasedEvent(
                                    command.isbn(),
                                    command.author(),
                                    command.title(),
                                    command.numPages()
                            )
                    );
                    return null;
                }
        );
    }
}

The CommandHandler hence may access additional auto-wired beans (e.g. BookValidator), which are defined as auto-wired dependencies within the @Bean annotated method signature.

Command Handler Definition using Annotations

CommandHandlerDefinitions may be defined - even in combination with @Bean definitions - using the CommandHandling annotation on any suitable command handling method. This greatly simplifies the definition, since developers are relieved from explicitly instantiating CommandHandlerDefinition and choosing a proper CommandHandler subtype, as shown in the following example:

@CommandHandlerConfiguration
public class BookHandlers {

    @CommandHandling
    public void purchase(
            PurchaseBookCommand command, 
            CommandEventPublisher<Book> publisher,
            @Autowired BookValidator bookValidator
    ) {
        bookValidator.checkValidPurchaseRequest(command);
        publisher.publish(
                new BookPurchasedEvent(
                        command.isbn(), 
                        command.author(), 
                        command.title(), 
                        command.numPages()
                )
        );
    }
}

The following rules apply with respect to CommandHandling annotated definitions, enforced by CommandHandlingAnnotationProcessingAutoConfiguration during Spring context initialization:

  • The method must be defined within a Spring bean with singleton scope.
  • The method name can be chosen arbitrary.
  • The method arguments can be ordered arbitrarily, as needed.
  • The method arguments must be non-repeatable and un-ambiguous.
  • One of the parameters must be of type Command.
  • At least one of the following two parameters needs to be defined:
    • CommandEventPublisher identifying the write model instance type via its generic type
    • a Java object representing the write model instance
  • An optional parameter of type java.util.Map<String, ?> may be defined to access the command meta-data.
  • Any number of @Autowired annotated parameters may be defined for injection of dependent Spring beans.

Lazy Resolution of Dependencies

Method parameters annotated with @Autowired are lazily injected upon command execution. Accordingly, failures to resolve those dependencies will not occur during Spring context initialization but when actually executing commands for the first time, resulting in a NonTransientException.