{"id":13485954,"url":"https://github.com/sizovs/pipelinr","last_synced_at":"2025-10-12T12:28:53.881Z","repository":{"id":41202076,"uuid":"169682577","full_name":"sizovs/PipelinR","owner":"sizovs","description":"PipelinR is a lightweight command processing pipeline ❍ ⇢ ❍ ⇢ ❍ for your Java awesome app. ","archived":false,"fork":false,"pushed_at":"2023-02-03T08:57:17.000Z","size":306,"stargazers_count":395,"open_issues_count":1,"forks_count":58,"subscribers_count":17,"default_branch":"master","last_synced_at":"2024-05-03T00:15:56.499Z","etag":null,"topics":["android","architecture","cqrs","java","kotlin","library","mediator","mediatr","pipes-and-filters","service-layer","spring"],"latest_commit_sha":null,"homepage":"https://github.com/sizovs/PipelinR","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sizovs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-02-08T04:04:19.000Z","updated_at":"2024-05-01T17:15:38.000Z","dependencies_parsed_at":"2023-02-18T05:00:38.502Z","dependency_job_id":null,"html_url":"https://github.com/sizovs/PipelinR","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sizovs%2FPipelinR","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sizovs%2FPipelinR/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sizovs%2FPipelinR/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sizovs%2FPipelinR/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sizovs","download_url":"https://codeload.github.com/sizovs/PipelinR/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":213395777,"owners_count":15580789,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","architecture","cqrs","java","kotlin","library","mediator","mediatr","pipes-and-filters","service-layer","spring"],"created_at":"2024-07-31T18:00:34.968Z","updated_at":"2025-10-12T12:28:53.856Z","avatar_url":"https://github.com/sizovs.png","language":"Java","funding_links":[],"categories":["Projects","进程间通信","项目"],"sub_categories":["Miscellaneous","Spring Cloud框架","杂项"],"readme":"# PipelinR\n\n[![Build Status](https://github.com/sizovs/pipelinr/actions/workflows/build.yml/badge.svg)](https://github.com/sizovs/pipelinr/actions/workflows/build.yml)\n[![Test Coverage](https://codecov.io/gh/sizovs/pipelinr/branch/master/graph/badge.svg)](https://codecov.io/github/sizovs/pipelinr?branch=master)\n![Maven Central Version](https://img.shields.io/maven-central/v/net.sizovs/pipelinr)\n[![libs.tech recommends](https://libs.tech/project/169682577/badge.svg)](https://libs.tech/project/169682577/pipelinr)\n\n\n\u003e **PipelinR** is a lightweight command processing pipeline ❍ ⇢ ❍ ⇢ ❍ for your awesome Java app.\n\nPipelinR has been battle-proven on production as a service layer for some cool FinTech apps. PipelinR has helped teams switch from giant service classes handling all use cases to small handlers, each following the single responsibility principle. It's similar to a popular [MediatR](https://github.com/jbogard/MediatR) .NET library.\n\n⚡ Tested and works with plain Java, Kotlin, Spring, and Jakarta EE.\n\n## Table of contents\n- [How to use](#how-to-use)\n- [Commands](#commands)\n- [Handlers](#handlers)\n- [Pipeline](#pipeline)\n- [Notifications](#notifications)\n- [Spring Example](#spring-example)\n- [Async](#async)\n- [How to contribute](#how-to-contribute)\n- [Alternatives](#alternatives)\n- [Contributors](#contributors)\n\n## How to use\n\nPipelinR has no dependencies and weights only ~30kb. All you need is a single library:\n\nMaven:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003enet.sizovs\u003c/groupId\u003e\n  \u003cartifactId\u003epipelinr\u003c/artifactId\u003e\n  \u003cversion\u003e0.11\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nGradle:\n\n```groovy\ndependencies {\n    compile 'net.sizovs:pipelinr:0.11'\n}\n```\n\nJava version required: 1.8+.\n\n## Commands\n\n**Commands** is a request that can return a value. The `Ping` command below returns a string:\n\n```java\nclass Ping implements Command\u003cString\u003e {\n\n    public final String host;\n\n    public Ping(String host) {\n        this.host = host;\n    }\n}\n```\n\nIf a command has nothing to return, you can use a built-in `Voidy` return type:\n\n```java\nclass Ping implements Command\u003cVoidy\u003e {\n\n    public final String host;\n\n    public Ping(String host) {\n        this.host = host;\n    }\n}\n```\n\n## Handlers\n\nFor every command you must define a **Handler**, that knows how to handle the command.\n\nCreate a handler by implementing `Command.Handler\u003cC, R\u003e` interface, where `C` is a command type and `R` is a return type. Handler's return type must match command's return type:\n\n```java\nclass Pong implements Command.Handler\u003cPing, String\u003e {\n\n    @Override\n    public String handle(Ping command) {\n        return \"Pong from \" + command.host;\n    }\n}\n```\n\n## Pipeline\nA **pipeline** mediates between commands and handlers. You send commands to the pipeline. When the pipeline receives a command, it sends the command through a sequence of middlewares and finally invokes the matching command handler. `Pipelinr` is a default implementation of `Pipeline` interface.\n\nTo construct a `Pipeline`, create an instance of `Pipelinr` and provide a list of command handlers:\n\n\n```java\nPipeline pipeline = new Pipelinr()\n    .with(\n        () -\u003e Stream.of(new Pong())\n    );\n```\n\nSend a command for handling:\n\n```java\npipeline.send(new Ping(\"localhost\"));\n```\n\nsince v0.4, you can execute commands more naturally:\n\n```java\nnew Ping(\"localhost\").execute(pipeline);\n```\n\n`Pipelinr` can receive an optional, **ordered list** of custom middlewares. Every command will go through the middlewares before being handled. Use middlewares when you want to add extra behavior to command handlers, such as validation, logging, transactions, or metrics:\n\n```java\n// command validation + middleware\n\ninterface CommandValidator\u003cC extends Command\u003cR\u003e, R\u003e {\n    void validate(C command);\n\n    default boolean matches(C command) {\n        Generic\u003cC\u003e commandType = new Generic\u003cC\u003e(getClass()) { // since 0.10\n        };\n\n        return commandType.resolve().isAssignableFrom(command.getClass());\n    }\n}\n\nclass ValidationMiddleware implements Command.Middleware {\n   private final ObjectProvider\u003cCommandValidator\u003e validators; // requires Spring 5+. For older versions, use BeanFactory.\n\n   ValidationMiddleware(ObjectProvider\u003cCommandValidator\u003e validators) {\n      this.validators = validators;\n    }\n\n    @Override\n    public \u003cR, C extends Command\u003cR\u003e\u003e R invoke(C command, Next\u003cR\u003e next) {\n        validators.stream().filter(v -\u003e v.matches(command)).findFirst().ifPresent(v -\u003e v.validate(command));\n        return next.invoke();\n    }\n}\n```\n\n```java\n// middleware that logs every command and the result it returns\nclass LoggingMiddleware implements Command.Middleware {\n\n    @Override\n    public \u003cR, C extends Command\u003cR\u003e\u003e R invoke(C command, Next\u003cR\u003e next) {\n        // log command\n        R response = next.invoke();\n        // log response\n        return response;\n    }\n}\n\n// middleware that wraps a command in a transaction\nclass TxMiddleware implements Command.Middleware {\n\n    @Override\n    public \u003cR, C extends Command\u003cR\u003e\u003e R invoke(C command, Next\u003cR\u003e next) {\n        // start tx\n        R response = next.invoke();\n        // end tx\n        return response;\n    }\n}\n```\n\nIn the following pipeline, every command and its response will be logged, it will be wrapped in a transaction, then validated:\n\n```java\nPipeline pipeline = new Pipelinr()\n    .with(() -\u003e Stream.of(new Pong()))\n    .with(() -\u003e Stream.of(new LoggingMiddleware(), new TxMiddleware(), new ValidationMiddleware(...)));\n```\n\nBy default, command handlers are being resolved using generics. By overriding command handler's `matches` method, you can dynamically select a matching handler:\n\n```java\nclass LocalhostPong implements Command.Handler\u003cPing, String\u003e {\n\n    @Override\n    public boolean matches(Ping command) {\n        return command.host.equals(\"localhost\");\n    }\n\n}\n```\n\n```java\nclass NonLocalhostPong implements Command.Handler\u003cPing, String\u003e {\n\n    @Override\n    public boolean matches(Ping command) {\n        return !command.host.equals(\"localhost\");\n    }\n}\n```\n\n## Notifications\n\nSince version `0.5`, PipelinR supports Notifications, dispatched to multiple handlers.\n\nFor notifications, first create your notification message:\n\n```java\nclass Ping implements Notification {\n}\n```\n\nNext, create zero or more handlers for your notification:\n\n```java\npublic class Pong1 implements Notification.Handler\u003cPing\u003e {\n\n    @Override\n    public void handle(Ping notification) {\n      System.out.printn(\"Pong 1\");\n    }\n}\n\npublic class Pong2 implements Notification.Handler\u003cPing\u003e {\n\n    @Override\n    public void handle(Ping notification) {\n      System.out.printn(\"Pong 2\");\n    }\n}\n```\n\nFinally, send notification to the pipeline:\n\n```java\nnew Ping().send(pipeline);\n```\n\n💡 Remember to provide notification handlers to PipelinR:\n```java\nnew Pipelinr()\n  .with(\n    () -\u003e Stream.of(new Pong1(), new Pong2())\n  )\n```\n\n### Notification middlewares\n\nNotifications, like commands, support middlewares. Notification middlewares will run before every notification handler:\n\n```java\nclass Transactional implements Notification.Middleware {\n\n    @Override\n    public \u003cN extends Notification\u003e void invoke(N notification, Next next) {\n        // start tx\n        next.invoke();\n        // stop tx\n    }\n}\n\nnew Pipelinr().with(() -\u003e Stream.of(new Transactional()))\n```\n\n### Notification handling strategies\nThe default implementation loops through the notification handlers and awaits each one. This ensures each handler is run after one another.\n\nDepending on your use-case for sending notifications, you might need a different strategy for handling the notifications, such running handlers in parallel.\n\nPipelinR supports the following strategies:\n* `an.awesome.pipelinr.StopOnException` runs each notification handler after one another; returns when all handlers are finished or an exception has been thrown; in case of an exception, any handlers after that will not be run; **this is a default strategy**.\n* `an.awesome.pipelinr.ContinueOnException` runs each notification handler after one another; returns when all handlers are finished; in case of any exception(s), they will be captured in an AggregateException.\n* `an.awesome.pipelinr.Async` runs all notification handlers asynchronously; returns when all handlers are finished; in case of any exception(s), they will be captured in an AggregateException.\n* `an.awesome.pipelinr.ParallelNoWait` runs each notification handler in a thread pool; returns immediately and does not wait for any handlers to finish; cannot capture any exceptions.\n* `an.awesome.pipelinr.ParallelWhenAny` runs each notification handler in a thread pool; returns when any thread (handler) is finished; all exceptions that happened before returning are captured in an AggregateException.\n* `an.awesome.pipelinr.ParallelWhenAll` runs each notification handler in a thread pool; returns when all threads (handlers) are finished; in case of any exception(s), they are captured in an AggregateException.\n\nYou can override default strategy via:\n```java\nnew Pipelinr().with(new ContinueOnException());\n```\n\n## Spring Example\n\nPipelinR works well with Spring and Spring Boot.\n\nStart by configuring a `Pipeline`. Create an instance of `Pipelinr` and inject all command handlers and **ordered** middlewares via the constructor:\n\n```java\n@Configuration\nclass PipelinrConfiguration {\n\n    @Bean\n    Pipeline pipeline(ObjectProvider\u003cCommand.Handler\u003e commandHandlers, ObjectProvider\u003cNotification.Handler\u003e notificationHandlers, ObjectProvider\u003cCommand.Middleware\u003e middlewares) {\n        return new Pipelinr()\n          .with(commandHandlers::stream)\n          .with(notificationHandlers::stream)\n          .with(middlewares::orderedStream);\n    }\n}\n```\n\nDefine a command:\n\n```java\nclass Wave implements Command\u003cString\u003e {\n}\n```\n\nDefine a handler and annotate it with `@Component` annotation:\n\n```java\n@Component\nclass WaveBack implements Command.Handler\u003cWave, String\u003e {\n    // ...\n}\n```\n\n\nOptionally, define `Order`-ed middlewares:\n\n```java\n@Component\n@Order(1)\nclass Loggable implements Command.Middleware {\n    // ...\n}\n\n@Component\n@Order(2)\nclass Transactional implements Command.Middleware {\n    // ...\n}\n```\n\nTo use notifications, define a notification:\n\n```java\nclass Ping implements Notification {\n}\n```\n\nDefine notification handlers and annotate them with `@Component` annotation:\n\n```java\n@Component\nclass Pong1 implements Notification.Handler\u003cPing\u003e {\n    // ...\n}\n\n@Component\nclass Pong2 implements Notification.Handler\u003cPing\u003e {\n    // ...\n}\n```\n\n\u003e Remember that notifications, like commands, also support [Middlewares](#notification-middlewares).\n\nWe're ready to go! Inject `Pipeline` into your application, and start sending commands or notifications:\n\n```java\nclass Application {\n\n    @Autowired\n    Pipeline pipeline;\n\n    public void run() {\n        String response = new Wave().execute(pipeline);\n        System.out.println(response);\n\n        // ... or\n\n        new Ping().send(pipeline); // should trigger Pong1 and Pong2 notification handlers\n\n    }\n}\n\n```\n\n## Async\n\nPipelinR works well in async or reactive applications. For example, a command can return `CompletableFuture`:\n\n```java\nclass AsyncPing implements Command\u003cCompletableFuture\u003cString\u003e\u003e {\n\n    @Component\n    static class Handler implements Command.Handler\u003cAsyncPing, CompletableFuture\u003cString\u003e\u003e {\n\n        @Override\n        public CompletableFuture\u003cString\u003e handle(AsyncPing command) {\n            return CompletableFuture.completedFuture(\"OK\");\n        }\n    }\n}\n```\n\nSending `AsyncPing` to the pipeline returns `CompletableFuture`:\n\n```java\nCompletableFuture\u003cString\u003e okInFuture = new Ping().execute(pipeline);\n```\n\n\n## How to contribute\nJust fork the repo and send us a pull request.\n\n## Alternatives\n- [MediatR](https://github.com/jbogard/MediatR) – Simple, unambitious mediator implementation in .NET\n\n\n## Contributors\n- Eduards Sizovs: [Blog](https://sizovs.net) ⋅ [Twitter](https://twitter.com/eduardsi) ⋅ [GitHub](https://github.com/sizovs)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsizovs%2Fpipelinr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsizovs%2Fpipelinr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsizovs%2Fpipelinr/lists"}