CDI/Spring enabled Java Command-Bus
Command- Marker InterfaceCommandHandler- One Implementation perCommand. Provideshandle(CommandImplementation)Method.CommandBus- Finds and calls theCommandHandlerfor eachCommand.CommandBuscan be decorated, in order to implement cross-cutting concerns, such as logging, transaction handling, validation, autorization, metrics etc.
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>
<groupId>com.cloudogu.cb</groupId>
<artifactId>command-bus-cdi</artifactId>
<version>1.0.1</version>
</dependency><dependency>
<groupId>com.cloudogu.cb</groupId>
<artifactId>command-bus-spring</artifactId>
<version>1.0.1</version>
</dependency>- Bootstrapping
- CDI: Having the
command-bus-cdidependency on the classpath triggers the CDI extension - Spring: All CommandHandlers must be within the application context (e.g.
@Componentin spring boot)
- CDI: Having the
- Implement your
Commands and the logic in appropriateCommandHandlers. - You can now just inject the
CommandBusand pass yourCommandsto itsexecute()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
CommandBusis the central place where decorators can be instantiated. It brings together yourCommandBus(e.g.CDICommandBus,SpringCommandBus) with decorators (see bellow). ExampleCommandBusFactorys:
The CommandHandlers for CDI and Spring both use a Registry (CDI /
Spring) to store Commands and
CommandHandlers. Difference:
- CDI: The
CDIExtensionfinds allCommands andCommandHandlers and puts them on theRegistry. - Spring: The
Registryitself gets allCommands andCommandHandlers from the application context.
First example is the logging decorator (LoggingCommandBus) that logs entering and leaving (including time of execution) of CommandHandlers.
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.
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.
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.
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.
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)
);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)
);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);Commands can specify return values. SeeHelloCommandandcom.cloudogu.cb.EchoCommandHandlerfor example.- If you don't want a return value, use
Void. SeeByeCommandandByeCommandHandlerfor example.