Skip to content

A simple yet ๐Ÿ—ฒFAST๐Ÿ—ฒ library to dispatch requests and events to corresponding handlers ๐Ÿš€

License

Notifications You must be signed in to change notification settings

joel-jeremy/emissary

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Emissary (formerly Deezpatch)

License Gradle Build Code QL Sonatype Central codecov Quality Gate Status Maintainability Rating Reliability Rating Security Rating Vulnerabilities Coverage Bugs Duplicated Lines (%) Lines of Code Technical Debt Discord

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 (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

implementation "io.github.joel-jeremy.emissary:emissary-core:${version}"

Maven

<dependency>
    <groupId>io.github.joel-jeremy.emissary</groupId>
    <artifactId>emissary-core</artifactId>
    <version>${version}</version>
</dependency>

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:

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 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)

Requests

Requests are messages that either:

  1. Initiate a state change/mutation
  2. Retrieve/query data
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> {}

Request Handlers

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.

(@RequestHandlers 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!");
    }
}

Request Dispatcher

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();

    // 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> pong = dispatcher.send(new PingQuery());
}

Events

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;
    }
}

Event Handlers

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! 
    }
}

Event Publisher

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();

    // 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 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

// 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

public static void main(String[] args) {
  ApplicationContext applicationContext = springApplicationContext();
  Emissary emissary = Emissary.builder()
      .instanceProvider(applicationContext::getBean)
      .requests(...)
      .events(...)
      .build();
}

Example with Guice's Injector

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 and EventHandler 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.

// 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 and Emissary.EventHandlerInvocationStrategy interfaces as extension points to let users customize how request/event handler methods are invoked by the Dispatcher and Publisher.

Built-in implementations are:

Users can create a new implementation and override the defaults by:

// 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();

SonarQube Cloud

About

A simple yet ๐Ÿ—ฒFAST๐Ÿ—ฒ library to dispatch requests and events to corresponding handlers ๐Ÿš€

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors 4

  •  
  •  
  •  
  •