https://github.com/joel-jeremy/emissary
A simple yet π²FASTπ² library to dispatch requests and events to corresponding handlers π
https://github.com/joel-jeremy/emissary
dispatcher event eventbus java jvm messaging publisher requests
Last synced: 5 months ago
JSON representation
A simple yet π²FASTπ² library to dispatch requests and events to corresponding handlers π
- Host: GitHub
- URL: https://github.com/joel-jeremy/emissary
- Owner: joel-jeremy
- License: apache-2.0
- Created: 2022-09-05T06:53:05.000Z (almost 4 years ago)
- Default Branch: main
- Last Pushed: 2026-01-14T21:47:10.000Z (5 months ago)
- Last Synced: 2026-01-14T22:57:14.256Z (5 months ago)
- Topics: dispatcher, event, eventbus, java, jvm, messaging, publisher, requests
- Language: Java
- Homepage:
- Size: 688 KB
- Stars: 64
- Watchers: 2
- Forks: 5
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
- awesome-java - Emissary - Simple, lightweight, yet FAST messaging library for decoupling messages (requests and events) and message handlers. (Projects / Messaging)
- awesome-java - Deezpatch
- fucking-awesome-java - Emissary - Simple, lightweight, yet FAST messaging library for decoupling messages (requests and events) and message handlers. (Projects / Messaging)
README
# Emissary (formerly Deezpatch)
[](https://github.com/joel-jeremy/emissary/blob/main/LICENSE)
[](https://github.com/joel-jeremy/emissary/actions/workflows/gradle-build.yaml)
[](https://github.com/joel-jeremy/emissary/actions/workflows/codeql.yaml)
[](https://maven-badges.sml.io/sonatype-central/io.github.joel-jeremy.emissary/emissary-core)
[](https://codecov.io/gh/joel-jeremy/emissary)
[](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)
[](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)
[](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)
[](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)
[](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)
[](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)
[](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)
[](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)
[](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)
[](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)
[](https://discord.gg/bAfgXRVx3T)
A simple yet π²FASTπ² library to dispatch requests and events to corresponding handlers π
The library aims to take advantage of the intuitiveness of using the annotations for handlers (e.g. `@RequestHandler`/`@EventHandler`) without the drawbacks of reflection.
The library aims to help build applications which apply the [Command Query Responsibility Segregation](https://martinfowler.com/bliki/CQRS.html) (CQRS) pattern.
## Like the project?
Please consider giving the repository a β. It means a lot! Thank you :)
## Get Emissary
> [!IMPORTANT]
> Up until v1.1.0, the core library is published under the old `deezpatch-core` name. This has been renamed to `emissary-core` starting from v2.0.0 onwards.
### Gradle
```groovy
implementation "io.github.joel-jeremy.emissary:emissary-core:${version}"
```
### Maven
```xml
io.github.joel-jeremy.emissary
emissary-core
${version}
```
### Java 9 Module Names
> [!IMPORTANT]
> Up until v1.1.0, the core library has the module name `io.github.joeljeremy.deezpatch.core`. This has been renamed to `io.github.joeljeremy.emissary.core` starting from v2.0.0 onwards.
Emissary jars are published with Automatic-Module-Name manifest attribute:
- Core - `io.github.joeljeremy.emissary.core`
Module authors can use above module names in their module-info.java:
```java
module foo.bar {
requires io.github.joeljeremy.emissary.core;
}
```
## Performance
What differentiates Emissary from other messaging/dispatch libraries? The library takes advantage of the benefits provided by [java.lang.invoke.LambdaMetafactory](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html) to avoid the cost of invoking methods reflectively. This results in performance close to directly invoking the request handler and event handler methods!
### ~ 1000% more throughput compared to other similar libraries (Spring Events, Pipelinr, etc)
### ~ 90% less time compared to other similar libraries (Spring Events, Pipelinr, etc)
### [Java 11 Benchmarks](https://jmh.morethan.io/?source=https://raw.githubusercontent.com/joel-jeremy/emissary/main/emissary-core/src/jmh/java/io/github/joeljeremy/emissary/core/benchmarks/results-java11.json)
### [Java 17 Benchmarks](https://jmh.morethan.io/?source=https://raw.githubusercontent.com/joel-jeremy/emissary/main/emissary-core/src/jmh/java/io/github/joeljeremy/emissary/core/benchmarks/results-java17.json)
## Requests
Requests are messages that either:
1. Initiate a state change/mutation
- Commands in [CQRS](https://martinfowler.com/bliki/CQRS.html)
2. Retrieve/query data
- Queries in [CQRS](https://martinfowler.com/bliki/CQRS.html)
```java
public class GreetCommand implements Request {
private final String name;
public GreetRequest(String name) {
this.name = name;
}
public String name() {
return name;
}
}
public class PingQuery implements Request {}
```
## Request Handlers
Requests are handled by request handlers. Request handlers can be registered through the use of the [@RequestHandler](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/RequestHandler.java) 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.)**
```java
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!");
}
}
```
## Request Dispatcher
Requests are dispatched to a single request handler and this can be done through a dispatcher.
```java
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();
// Emissary implements the Dispatcher interface.
Dispatcher dispatcher = Emissary.builder()
.instanceProvider(applicationContext::getBean)
.requests(config -> config.handlers(
GreetCommandHandler.java,
PingQueryHandler.java
))
.build();
// Send command!
dispatcher.send(new GreetCommand("Deez"));
// Send query!
Optional pong = dispatcher.send(new PingQuery());
}
```
## Events
Events are messages that indicate that something has occurred in the system.
```java
public class GreetedEvent implements Event {
private final String greeting;
public GreetedEvent(String greeting) {
this.greeting = greeting;
}
public String greeting() {
return greeting;
}
}
```
## Event Handlers
Events are handled by event handlers. Event handlers can be registered through the use of the [@EventHandler](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/EventHandler.java) annotation.
An event can have zero or more event handlers.
```java
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!
}
}
```
## Event Publisher
Events are dispatched to zero or more event handlers and this can be done through a publisher.
```java
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();
// Emissary implements the Publisher interface.
Publisher publisher = Emissary.builder()
.instanceProvider(applicationContext::getBean)
.events(config -> config.handlers(
GreetedEventHandler.java
))
.build();
// Publish event!
publisher.publish(new GreetedEvent("Hi from Deez!"));
}
```
## Easy Integration with Dependency Injection (DI) Frameworks
The library provides an [InstanceProvider](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/InstanceProvider.java) 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.
### Example with No DI framework
```java
// Application.java
public static void main(String[] args) {
Emissary emissary = Emissary.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() + ".");
}
```
### Example with Spring's ApplicationContext
```java
public static void main(String[] args) {
ApplicationContext applicationContext = springApplicationContext();
Emissary emissary = Emissary.builder()
.instanceProvider(applicationContext::getBean)
.requests(...)
.events(...)
.build();
}
```
### Example with Guice's Injector
```java
public static void main(String[] args) {
Injector injector = guiceInjector();
Emissary emissary = Emissary.builder()
.instanceProvider(injector::getInstance)
.requests(...)
.events(...)
.build();
}
```
## Custom Request/Event Handler Annotations
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), Emissary provides a way to use custom request/event handler annotations (in addition to the built-in [RequestHandler](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/RequestHandler.java) and [EventHandler](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/EventHandler.java) annotations) to annotate request/event handlers.
This way, Emissary can still be used without adding the core Emissary 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.
```java
// 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.
Emissary emissary = Emissary.builder()
.instanceProvider(applicationContext::getBean)
.requests(config ->
config.handlerAnnotations(AwesomeRequestHandler.class)
.handlers(MyRequestHandler.class))
.events(config ->
config.handlerAnnotations(AwesomeEventHandler.java)
.handlers(MyEventHandler.class))
.build();
}
```
## Custom Invocation Strategies
The library provides [Emissary.RequestHandlerInvocationStrategy](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/Emissary.java) and [Emissary.EventHandlerInvocationStrategy](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/Emissary.java) interfaces as extension points to let users customize how request/event handler methods are invoked by the Dispatcher and Publisher.
Built-in implementations are:
- [SyncRequestHandlerInvocationStrategy](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/invocationstrategies/SyncRequestHandlerInvocationStrategy.java) (Default)
- [SyncEventHandlerInvocationStrategy](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/invocationstrategies/SyncEventHandlerInvocationStrategy.java) (Default)
- [AsyncEventHandlerInvocationStrategy](emissary-core/src/main/java/io/github/joeljeremy/emissary/core/invocationstrategies/AsyncEventHandlerInvocationStrategy.java)
Users can create a new implementation and override the defaults by:
```java
// Register custom invocation strategy.
Emissary emissary = Emissary.builder()
.requests(config ->
config.invocationStrategy(
new LoggingInvocationStrategy(
new RetryOnErrorInvocationStrategy())))
.events(config ->
config.invocationStrategy(
new LoggingInvocationStrategy(
new OrderGuaranteedInvocationStrategy())))
.build();
```
---
[](https://sonarcloud.io/summary/new_code?id=io.github.joel-jeremy.emissary)