A simple dispatch library.
The library aims to help build applications which apply the Command Query Responsibility Segregation (CQRS) pattern.
implementation "io.github.joel-jeremy.deezpatch:deezpatch-core:${version}"
<dependency>
<groupId>io.github.joel-jeremy.deezpatch</groupId>
<artifactId>deezpatch-core</artifactId>
<version>${version}</version>
</dependency>
Deezpatch jars are published with Automatic-Module-Name manifest attribute:
- Core -
io.github.joeljeremy.deezpatch.core
Module authors can use above module names in their module-info.java:
module foo.bar {
requires io.github.joeljeremy.deezpatch.core;
}
What differentiates Deezpatch from other messaging/dispatch libraries? The library takes advantage of the benefits provided by java.lang.invoke.LambdaMetafactory to avoid the cost of invoking methods reflectively. This results in performance close to directly invoking the request handler and event handler methods!
Requests are messages that either:
public class GreetCommand implements Request<Void> {
private final String name;
public GreetRequest(String name) {
this.name = name;
}
public String name() {
return name;
}
}
public class PingQuery implements Request<Pong> {}
Requests are handled by request handlers. Request handlers can be registered through the use of the @RequestHandler annotation.
A request must only have a single request handler.
(@RequestHandler
s fully support methods with void
return types! No need to set method return type to Void
and return null
for no reason.)
public class GreetCommandHandler {
@RequestHandler
public void handle(GreetCommand command) {
sayHi(command.name());
}
}
public class PingQueryHandler {
@RequestHandler
public Pong handle(PingQuery query) {
return new Pong("Here's your pong!");
}
}
Requests are dispatched to a single request handler and this can be done through a dispatcher.
public static void main(String[] args) {
// Use Spring's application context as InstanceProvider in this example
// but any other DI framework can be used e.g. Guice, Dagger, etc.
ApplicationContext applicationContext = springApplicationContext();
// Deezpatch implements the Dispatcher interface.
Dispatcher dispatcher = Deezpatch.builder()
.instanceProvider(applicationContext::getBean)
.requests(config -> config.handlers(
GreetCommandHandler.java,
PingQueryHandler.java
))
.build();
// Send command!
dispatcher.send(new GreetCommand("Deez"));
// Send query!
Optional<Pong> pong = dispatcher.send(new PingQuery());
}
Events are messages that indicate that something has occurred in the system.
public class GreetedEvent implements Event {
private final String greeting;
public GreetedEvent(String greeting) {
this.greeting = greeting;
}
public String greeting() {
return greeting;
}
}
Events are handled by event handlers. Event handlers can be registered through the use of the @EventHandler annotation.
An event can have zero or more event handlers.
public class GreetedEventHandler {
@EventHandler
public void sayHello(GreetedEvent event) {
// Well, hello!
}
@EventHandler
public void sayKumusta(GreetedEvent event) {
// Well, kumusta?
}
@EventHandler
public void sayGotEm(GreetedEvent event) {
// Got 'em!
}
}
Events are dispatched to zero or more event handlers and this can be done through a publisher.
public static void main(String[] args) {
// Use Spring's application context as InstanceProvider in this example
// but any other DI framework can be used e.g. Guice, Dagger, etc.
ApplicationContext applicationContext = springApplicationContext();
// Deezpatch implements the Publisher interface.
Publisher publisher = Deezpatch.builder()
.instanceProvider(applicationContext::getBean)
.events(config -> config.handlers(
GreetedEventHandler.java
))
.build();
// Publish event!
publisher.publish(new GreetedEvent("Hi from Deez!"));
}
The library provides an InstanceProvider interface as an extension point to let users customize how request/event handler instances should be instantiated. This can be as simple as new
-ing up request/event handlers or getting instances from a DI framework such as Spring's ApplicationContext
, Guice's Injector
, etc.
// Application.java
public static void main(String[] args) {
Deezpatch deezpatch = Deezpatch.builder()
.instanceProvider(Application::getInstance)
.requests(...)
.events(...)
.build();
}
private static Object getInstance(Class<?> handlerType) {
if (MyRequestHandler.class.equals(handlerType)) {
return new MyRequestHandler();
} else if (MyEventHandler.class.equals(handlerType)) {
return new MyEventHandler();
}
throw new IllegalStateException("Failed to get instance for " + handlerType.getName() + ".");
}
public static void main(String[] args) {
ApplicationContext applicationContext = springApplicationContext();
Deezpatch deezpatch = Deezpatch.builder()
.instanceProvider(applicationContext::getBean)
.requests(...)
.events(...)
.build();
}
public static void main(String[] args) {
Injector injector = guiceInjector();
Deezpatch deezpatch = Deezpatch.builder()
.instanceProvider(injector::getInstance)
.requests(...)
.events(...)
.build();
}
In cases where a project is built in such a way that bringing in external dependencies is considered a bad practice (e.g. domain layer/package in a Hexagonal (Ports and Adapters) architecture), Deezpatch provides a way to use custom request/event handler annotations (in addition to the built-in RequestHandler and EventHandler annotations) to annotate request/event handlers.
This way, Deezpatch can still be used without adding the core Deezpatch library as a dependency of a project's domain layer/package. Instead, it may be used in the outer layers/packages to wire things up.
// Let's say below classes are declared in a project's core/domain package:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AwesomeRequestHandler {}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AwesomeEventHandler {}
public class MyRequestHandler {
@AwesomeRequestHandler
public void handle(TestRequest request) {
// Handle.
}
}
public class MyEventHandler {
@AwesomeEventHandler
public void handle(TestEvent event) {
// Handle.
}
}
// To wire things up:
public static void main(String[] args) {
// Use Spring's application context as InstanceProvider in this example
// but any other DI framework can be used e.g. Guice, Dagger, etc.
ApplicationContext applicationContext = springApplicationContext();
// Register handlers and custom annotations.
Deezpatch deezpatch = Deezpatch.builder()
.instanceProvider(applicationContext::getBean)
.requests(config ->
config.handlerAnnotations(AwesomeRequestHandler.class)
.handlers(MyRequestHandler.class))
.events(config ->
config.handlerAnnotations(AwesomeEventHandler.java)
.handlers(MyEventHandler.class))
.build();
}