PipelinR Documentation

repository·master·Indexed 19 days ago

https://github.com/sizovs/pipelinr

A lightweight, dependency-free Java (1.8+) and Kotlin library for implementing a command processing pipeline. Similar to .NET MediatR, it enables the Single Responsibility Principle by decoupling logic into discrete command handlers. Features include support for command middlewares, notifications with multiple handling strategies (StopOnException, ContinueOnException, Async, Parallel), asynchronous processing via CompletableFuture, and seamless integration with Spring/Spring Boot.

Tokens
2.3K
Snippets
8
Records
8
Agent score
17%

What's inside PipelinR

  1. Define Commands and Handlers

    master

    A Command is a request that can return a value. Implement the Command<R> interface where R is the return type.

    • If a command returns nothing, use the built-in Voidy type.

    A Handler is a class that implements Command.Handler<C, R>, where C is the command type and R is the return type. The handler's return type must match the command's return type.

    By default, handlers are resolved using generics. You can also override the matches(C command) method in the handler to implement dynamic selection logic.

    // Define a command
    class Ping implements Command<String> {
        public final String host;
        public Ping(String host) { this.host = host; }
    }
    
    // Define a handler
    class Pong implements Command.Handler<Ping, String> {
        @Override
        public String handle(Ping command) {
            return "Pong from " + command.host;
        }
    }
    
    // Dynamic handler selection
    class LocalhostPong implements Command.Handler<Ping, String> {
        @Override
        public boolean matches(Ping command) {
            return command.host.equals("localhost");
        }
    }
  2. Use Notifications and Notification Handlers

    master

    Notifications are messages dispatched to multiple handlers simultaneously. Unlike commands, they do not return a value.

    1. Create a notification class implementing Notification.
    2. Create zero or more handlers implementing Notification.Handler<N>.
    3. Register handlers in the pipeline.
    4. Dispatch using new MyNotification().send(pipeline).
    // 1. Define notification
    class Ping implements Notification {}
    
    // 2. Define handlers
    public class Pong1 implements Notification.Handler<Ping> {
        @Override
        public void handle(Ping notification) { System.out.println("Pong 1"); }
    }
    
    // 3. Register and send
    Pipeline pipeline = new Pipelinr().with(() -> Stream.of(new Pong1()));
    new Ping().send(pipeline);
  3. Integrate PipelinR with Spring/Spring Boot

    master

    PipelinR integrates seamlessly with Spring. You can configure the Pipeline as a @Bean by injecting ObjectProvider instances for handlers and middlewares. This allows Spring to automatically discover and inject all @Component annotated handlers and middlewares.

    To ensure middlewares run in the correct order, use the @Order annotation on your middleware components.

    @Configuration
    class PipelinrConfiguration {
        @Bean
        Pipeline pipeline(
            ObjectProvider<Command.Handler> commandHandlers, 
            ObjectProvider<Notification.Handler> notificationHandlers, 
            ObjectProvider<Command.Middleware> middlewares) {
          return new Pipelinr()
            .with(() -> commandHandlers.stream())
            .with(() -> notificationHandlers.stream())
            .with(() -> middlewares.orderedStream());
        }
    }
    
    @Component
    @Order(1)
    class Loggable implements Command.Middleware { /* ... */ }
    
    @Component
    class WaveBack implements Command.Handler<Wave, String> { /* ... */ }
  4. Install PipelinR via Maven or Gradle

    master

    PipelinR is a lightweight (~30kb) command processing pipeline for Java (1.8+) and Kotlin. It has no external dependencies.

    Maven:

    <dependency>
      <groupId>net.sizovs</groupId>
      <artifactId>pipelinr</artifactId>
      <version>0.11</version>
    </dependency>

    Gradle:

    dependencies {
        compile 'net.sizovs:pipelinr:0.11'
    }
  5. Handle Asynchronous Commands

    master

    PipelinR supports asynchronous processing by allowing commands to return a CompletableFuture. When a command returns a future, the execute() method also returns that same future, allowing the caller to react to the completion of the command.

    class AsyncPing implements Command<CompletableFuture<String>> {
        @Component
        static class Handler implements Command.Handler<AsyncPing, CompletableFuture<String>> {
            @Override
            public CompletableFuture<String> handle(AsyncPing command) {
                return CompletableFuture.completedFuture("OK");
            }
        }
    }
    
    // Usage
    CompletableFuture<String> okInFuture = new AsyncPing().execute(pipeline);
  6. Use the Pipeline to execute Commands

    master

    The Pipeline mediates between commands and handlers. You construct a pipeline using the Pipelinr implementation by providing a stream of command handlers.

    To execute a command, you can either use pipeline.send(command) or the more natural command.execute(pipeline) syntax.

    // Construct the pipeline
    Pipeline pipeline = new Pipelinr().with(() -> Stream.of(new Pong()));
    
    // Execute via pipeline
    pipeline.send(new Ping("localhost"));
    
    // Execute via command (preferred)
    new Ping("localhost").execute(pipeline);
  7. Implement Command Middlewares

    master

    Middlewares allow you to add cross-cutting concerns (logging, transactions, validation) to command execution. Every command passes through an ordered list of middlewares before reaching the handler.

    Implement the Command.Middleware interface. Use the Next<R> next object to continue the pipeline chain.

    class LoggingMiddleware implements Command.Middleware {
        @Override
        public <R, C extends Command<R>> R invoke(C command, Next<R> next) {
            // logic before handler
            R response = next.invoke();
            // logic after handler
            return response;
        }
    }
    
    // Registering middlewares in a pipeline
    Pipeline pipeline = new Pipelinr()
        .with(() -> Stream.of(new Pong()))
        .with(() -> Stream.of(new LoggingMiddleware(), new TxMiddleware()));
  8. Configure Notification Handling Strategies

    master

    PipelinR allows you to choose how notification handlers are executed. By default, it uses a sequential strategy that stops on the first exception.

    Available strategies:

    • StopOnException (Default): Runs handlers sequentially; stops if an exception occurs.
    • ContinueOnException: Runs handlers sequentially; captures all exceptions in an AggregateException.
    • Async: Runs all handlers asynchronously; returns when all are finished; captures exceptions in an AggregateException.
    • ParallelNoWait: Runs handlers in a thread pool; returns immediately without waiting; cannot capture exceptions.
    • ParallelWhenAny: Runs handlers in a thread pool; returns when any handler finishes; captures previous exceptions in an AggregateException.
    • ParallelWhenAll: Runs handlers in a thread pool; returns when all handlers finish; captures exceptions in an AggregateException.
    // Override default strategy
    Pipeline pipeline = new Pipelinr().with(new ContinueOnException());