使者,一个快速的开源Java消息库。
Emissary, a fast open-source Java messaging library

原始链接: https://github.com/joel-jeremy/emissary

## 使者:快速消息库 使者是一个极速、无依赖的消息库,用于使用请求和事件解耦应用程序。它简化了构建利用CQRS等模式的系统,但并不局限于它们。与许多替代方案不同,使者通过利用`java.lang.invoke.LambdaMetafactory`避免了缓慢的反射,与Spring的`ApplicationEventPublisher`或Pipelinr等库相比,实现了高达1000%的吞吐量提升和90%的性能加速。 主要特性包括基于注解的处理程序注册 (`@RequestHandler`, `@EventHandler`),与依赖注入 (DI) 框架(Spring, Guice等)的轻松集成,以及用于自定义注解和调用策略的扩展点。 核心库先前名为`deezpatch-core`(至v1.1.0),现在为`emissary-core`(v2.0.0及更高版本)。它通过`InstanceProvider`接口提供处理程序实例化灵活性,并支持自定义处理程序注解,适用于避免在核心层使用外部依赖项的项目。 [仓库链接](在此处添加链接) – 点赞将不胜感激!

## Emissary:一款快速的Java消息库 Emissary是一个新的开源Java库,专为高性能消息处理而设计。它允许使用诸如`@RequestHandler`和`@EventHandler`之类的注解轻松解耦消息(请求和事件)与其处理程序,但关键的是*避免*了传统反射带来的性能损失。 Emissary利用`java.lang.invoke.LambdaMetafactory`来实现与直接方法调用相当的速度。基准测试表明,它的**吞吐量提高了1000%**,并且比Spring的`ApplicationEventPublisher`、Pipelinr和EventBus等流行的替代方案**快90%**。 该项目可在GitHub上找到 ([https://github.com/joel-jeremy/emissary](https://github.com/joel-jeremy/emissary)),它没有依赖项,旨在为Java开发者提供一个简单而强大的消息传递解决方案。一位评论员建议它适合在Hacker News上发布“Show HN”帖子。
相关文章

原文

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-to-use, no dependency, yet 🗲BLAZING FAST🗲 messaging library for decoupling messages (requests and events) and message handlers 🚀

Emissary aims to take advantage of the simplicity of using the annotations for handlers (e.g. @RequestHandler/@EventHandler) without the drawbacks of reflection (slow).

Emissary aims to make it easy to build applications that apply the Command Query Responsibility Segregation (CQRS) pattern, but it is not in any way limited to that pattern only.

Please consider giving the repository a ⭐. It means a lot! Thank you :)

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.

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

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

What differentiates Emissary from other messaging/dispatch libraries? It takes advantage of 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's ApplicationEventPublisher, Pipelinr, EventBus)

~ 90% faster compared to other similar libraries (Spring's ApplicationEventPublisher, Pipelinr, EventBus)

Requests are messages that either:

  1. Initiate a state change/mutation
  2. Retrieve/query data
public class CreateFooCommand implements Request<Void> {
    private final String name;
    
    public CreateFooCommand(String name) {
        this.name = name;
    }
    
    public String name() {
        return name;
    }
}

public class GetFooByNameQuery implements Request<Foo> {
    private final String name;
    
    public GetFooByNameQuery(String name) {
        this.name = name;
    }
    
    public String name() {
        return name;
    }
}

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 CreateFooCommandHandler {
    @RequestHandler
    public void handle(CreateFooCommand command) {
        insertFooToDatabase(command.name());
    }
}

public class GetFooQueryHandler {
    @RequestHandler
    public Foo handle(GetFooByNameQuery query) {
        return getFooFromDatabase(query.name());
    }
}

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(CreateFooCommandHandler.class, GetFooQueryHandler.class))
        .build();

    // Send command!
    dispatcher.send(new CreateFooCommand("Emissary"));

    // Send query!
    Optional<Pong> pong = dispatcher.send(new GetFooByNameQuery("Emissary"));
}

Events are messages that indicate that something has occurred in the system.

public class FooCreatedEvent implements Event {
    private final String name;

    public FooCreatedEvent(String name) {
        this.name = name;
    }

    public String name() {
        return name;
    }
}

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 FooEventHandler {
    @EventHandler
    public void notifyA(FooCreatedEvent event) {
        helloFoo(event.name());
    }

    @EventHandler
    public void notifyB(FooCreatedEvent event) {
        kumustaFoo(event.name());
    }
}

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(FooEventHandler.class))
        .build();

    // Publish event!
    publisher.publish(new FooCreatedEvent("Emissary"));
}

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.class).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 CustomRetryOnErrorInvocationStrategy()))
    .events(config -> config.invocationStrategy(new CustomOrderGuaranteedInvocationStrategy()))
    .build();

SonarQube Cloud

联系我们 contact @ memedata.com