/command-bus

Java implementation of the Command-Bus pattern for Spring and CDI

Primary LanguageJavaMIT LicenseMIT

command-bus

Build Status Quality Gates Coverage Technical Debt

CDI/Spring enabled Java Command-Bus

Table of contents

Concepts

  • Command - Marker Interface
  • CommandHandler - One Implementation per Command. Provides handle(CommandImplementation) Method.
  • CommandBus - Finds and calls the CommandHandler for each Command.
  • CommandBus can be decorated, in order to implement cross-cutting concerns, such as logging, transaction handling, validation, autorization, metrics etc.

Usage

Add the latest stable version of command-bus to the dependency management tool of your choice. You can also get snapshot versions from our snapshot repository (for the most recent commit on develop branch). To do so, add the following repo to your pom.xml or settings.xml:

<repository>
    <id>snapshots-repo</id>
    <url>https://oss.sonatype.org/content/repositories/snapshots</url>
    <releases><enabled>false</enabled></releases>
    <snapshots><enabled>true</enabled></snapshots>
</repository>

There are different versions of command-bus for either CDI or spring.

Dependency for CDI

<dependency>
    <groupId>com.cloudogu.cb</groupId>
    <artifactId>command-bus-cdi</artifactId>
    <version>1.0.1</version>
</dependency>

Maven Central

Dependency for Spring

<dependency>
    <groupId>com.cloudogu.cb</groupId>
    <artifactId>command-bus-spring</artifactId>
    <version>1.0.1</version>
</dependency>

Maven Central

API

  • Bootstrapping
    • CDI: Having the command-bus-cdi dependency on the classpath triggers the CDI extension
    • Spring: All CommandHandlers must be within the application context (e.g. @Component in spring boot)
  • Implement your Commands and the logic in appropriate CommandHandlers.
  • You can now just inject the CommandBus and pass your Commands to its execute() method. It will automatically pass it to the appropriate handler.
  • Examples:
  • If you want to decorate your command bus (for logging, metrics, etc.), a factory/producer for the CommandBus is the central place where decorators can be instantiated. It brings together your CommandBus (e.g. CDICommandBus, SpringCommandBus) with decorators (see bellow). Example CommandBusFactorys:

Internals

The CommandHandlers for CDI and Spring both use a Registry (CDI / Spring) to store Commands and CommandHandlers. Difference:

  • CDI: The CDIExtension finds all Commands and CommandHandlers and puts them on the Registry.
  • Spring: The Registry itself gets all Commands and CommandHandlers from the application context.

Command Bus Decorators

First example is the logging decorator (LoggingCommandBus) that logs entering and leaving (including time of execution) of CommandHandlers.

Prometheus metric decorators

The Command Bus provides two Prometheus metrics decorators. More information on Prometheus can be found on the project's website. In order to use them, make sure to provide the io.prometheus:simpleclient dependency on the classpath.

PrometheusMetricsCountingCommandBus

The PrometheusMetricsCountingCommandBus counts every executed command, using a Prometheus Counter. The counter to be used must be provided as a constructor parameter. For each type of command (i.e. it's class name) a label is created automatically.

PrometheusMetricsTimingCommandBus

The PrometheusMetricsTimingCommandBus captures the time a command's execution takes and provides the metric as a Prometheus Histogram. Similarly to the PrometheusMetricsCountingCommandBus, the Histogram needs to be provided as a constructor parameter.

Micrometer metric decorators

The Command Bus provides two Micrometer metrics decorators. More information on Micrometer can be found on the project's website. In order to use them, make sure to provide a micrometer registry implementation such as prometheus io.micrometer:micrometer-registry-prometheus.

See cloudogu/springboot-micrometer-demo-command-bus for a complete example.

MicrometerCountingCommandBus

The MicrometerCountingCommandBus counts every executed command, using a Micrometer Counter e.g.:

CommandBus commandBusImpl = ...;
MicrometerCountingCommandBus commandBus = new MicrometerCountingCommandBus(commandBusImpl, 
  commandClass -> Counter.builder("command.counter")
    .description("command execution counter")
    .tags("command", commandClass.getSimpleName())
    .register(Metrics.globalRegistry)
);

MicrometerTimingCommandBus

The MicrometerTimingCommandBus measures the elapsed time for every command execution by using a Micrometer a Micrometer Counter e.g.:

CommandBus commandBusImpl = ...;
MicrometerTimingCommandBus commandBus = new MicrometerTimingCommandBus(commandBusImpl, 
  commandClass -> Timer.builder("command.timer")
    .description("command execution timer")
    .tags("command", commandClass.getSimpleName())
    .register(Metrics.globalRegistry)
);

Validating command bus

The ValidatingCommandBus uses the javax.validation API to validate the command, before execution. The CommandBus will throw an ConstraintViolationException, if the command violates validation rules. Make sure to provide javax.validation implementation at runtime, such as org.hibernate:hibernate-validator.

public class NotifyCommand implements Command<Void> {
  @Email
  private String email;

  public SampleCommand(String email) {
    this.email = email;
  }
}

CommandBus commandBusImpl = ...;
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
ValidatingCommandBus commandBus = new ValidatingCommandBus(commandBusImpl, factory.getValidator());
NotifyCommand command = createNotifyCommand();
commandBus.execute(command);

Return values

Examples

Spring