Skip to content

Commit 3fcb64e

Browse files
committed
README: update Spring example to support Spring 6.2+
1 parent 12384de commit 3fcb64e

1 file changed

Lines changed: 54 additions & 77 deletions

File tree

README.md

Lines changed: 54 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
![Maven Central Version](https://img.shields.io/maven-central/v/net.sizovs/pipelinr)
66
[![libs.tech recommends](https://libs.tech/project/169682577/badge.svg)](https://libs.tech/project/169682577/pipelinr)
77

8-
98
> **PipelinR** is a lightweight command processing pipeline ❍ ⇢ ❍ ⇢ ❍ for your awesome Java app.
109
11-
PipelinR 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.
10+
PipelinR has been battle-proven on production as a service layer for some serious FinTech apps. PipelinR has helped teams switch from giant service classes handling all use cases to small handlers, following the single responsibility principle. It's similar to a popular [MediatR](https://github.com/jbogard/MediatR) .NET library.
1211

1312
⚡ Tested and works with plain Java, Kotlin, Spring, and Jakarta EE.
1413

1514
## Table of contents
15+
1616
- [How to use](#how-to-use)
1717
- [Commands](#commands)
1818
- [Handlers](#handlers)
@@ -50,11 +50,10 @@ Java version required: 1.8+.
5050

5151
## Commands
5252

53-
**Commands** is a request that can return a value. The `Ping` command below returns a string:
53+
**Commands** is a request that can return a value. The following `Ping` command returns a string:
5454

5555
```java
5656
class Ping implements Command<String> {
57-
5857
public final String host;
5958

6059
public Ping(String host) {
@@ -63,11 +62,10 @@ class Ping implements Command<String> {
6362
}
6463
```
6564

66-
If a command has nothing to return, you can use a built-in `Voidy` return type:
65+
If a command has nothing to return, use a built-in `Voidy` return type:
6766

6867
```java
6968
class Ping implements Command<Voidy> {
70-
7169
public final String host;
7270

7371
public Ping(String host) {
@@ -84,7 +82,6 @@ Create a handler by implementing `Command.Handler<C, R>` interface, where `C` is
8482

8583
```java
8684
class Pong implements Command.Handler<Ping, String> {
87-
8885
@Override
8986
public String handle(Ping command) {
9087
return "Pong from " + command.host;
@@ -93,16 +90,13 @@ class Pong implements Command.Handler<Ping, String> {
9390
```
9491

9592
## Pipeline
93+
9694
A **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.
9795

9896
To construct a `Pipeline`, create an instance of `Pipelinr` and provide a list of command handlers:
9997

100-
10198
```java
102-
Pipeline pipeline = new Pipelinr()
103-
.with(
104-
() -> Stream.of(new Pong())
105-
);
99+
Pipeline pipeline = new Pipelinr().with(() -> Stream.of(new Pong()));
106100
```
107101

108102
Send a command for handling:
@@ -117,19 +111,26 @@ since v0.4, you can execute commands more naturally:
117111
new Ping("localhost").execute(pipeline);
118112
```
119113

120-
`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:
114+
`Pipelinr` can receive an optional, **ordered list** of middlewares. Every command will go through the middlewares before being handled. Use middlewares to add extra behavior to command handlers, such as validation, logging, transactions, or metrics:
121115

122116
```java
123-
// command validation + middleware
124-
125-
interface CommandValidator<C extends Command<R>, R> {
126-
void validate(C command);
127-
128-
default boolean matches(C command) {
129-
Generic<C> commandType = new Generic<C>(getClass()) { // since 0.10
130-
};
117+
class LoggingMiddleware implements Command.Middleware {
118+
@Override
119+
public <R, C extends Command<R>> R invoke(C command, Next<R> next) {
120+
// log command
121+
R response = next.invoke();
122+
// log response
123+
return response;
124+
}
125+
}
131126

132-
return commandType.resolve().isAssignableFrom(command.getClass());
127+
class TxMiddleware implements Command.Middleware {
128+
@Override
129+
public <R, C extends Command<R>> R invoke(C command, Next<R> next) {
130+
// start tx
131+
R response = next.invoke();
132+
// end tx
133+
return response;
133134
}
134135
}
135136

@@ -146,58 +147,40 @@ class ValidationMiddleware implements Command.Middleware {
146147
return next.invoke();
147148
}
148149
}
149-
```
150150

151-
```java
152-
// middleware that logs every command and the result it returns
153-
class LoggingMiddleware implements Command.Middleware {
154-
155-
@Override
156-
public <R, C extends Command<R>> R invoke(C command, Next<R> next) {
157-
// log command
158-
R response = next.invoke();
159-
// log response
160-
return response;
161-
}
162-
}
151+
interface CommandValidator<C extends Command<R>, R> {
152+
void validate(C command);
163153

164-
// middleware that wraps a command in a transaction
165-
class TxMiddleware implements Command.Middleware {
154+
default boolean matches(C command) {
155+
Generic<C> commandType = new Generic<C>(getClass()) { // since 0.10
156+
};
166157

167-
@Override
168-
public <R, C extends Command<R>> R invoke(C command, Next<R> next) {
169-
// start tx
170-
R response = next.invoke();
171-
// end tx
172-
return response;
158+
return commandType.resolve().isAssignableFrom(command.getClass());
173159
}
174160
}
175161
```
176162

177-
In the following pipeline, every command and its response will be logged, it will be wrapped in a transaction, then validated:
163+
In the following pipeline, every command will be logged, wrapped in a transaction, and validated (in that order):
178164

179165
```java
180166
Pipeline pipeline = new Pipelinr()
181167
.with(() -> Stream.of(new Pong()))
182168
.with(() -> Stream.of(new LoggingMiddleware(), new TxMiddleware(), new ValidationMiddleware(...)));
183169
```
184170

185-
By default, command handlers are being resolved using generics. By overriding command handler's `matches` method, you can dynamically select a matching handler:
171+
By default, command handlers are resolved using generics. By overriding command handler's `matches` method, you can dynamically select a matching handler:
186172

187173
```java
188174
class LocalhostPong implements Command.Handler<Ping, String> {
189-
190175
@Override
191176
public boolean matches(Ping command) {
192177
return command.host.equals("localhost");
193178
}
194-
195179
}
196180
```
197181

198182
```java
199-
class NonLocalhostPong implements Command.Handler<Ping, String> {
200-
183+
class RemotePong implements Command.Handler<Ping, String> {
201184
@Override
202185
public boolean matches(Ping command) {
203186
return !command.host.equals("localhost");
@@ -212,23 +195,20 @@ Since version `0.5`, PipelinR supports Notifications, dispatched to multiple han
212195
For notifications, first create your notification message:
213196

214197
```java
215-
class Ping implements Notification {
216-
}
198+
class Ping implements Notification {}
217199
```
218200

219201
Next, create zero or more handlers for your notification:
220202

221203
```java
222204
public class Pong1 implements Notification.Handler<Ping> {
223-
224205
@Override
225206
public void handle(Ping notification) {
226207
System.out.printn("Pong 1");
227208
}
228209
}
229210

230211
public class Pong2 implements Notification.Handler<Ping> {
231-
232212
@Override
233213
public void handle(Ping notification) {
234214
System.out.printn("Pong 2");
@@ -243,11 +223,9 @@ new Ping().send(pipeline);
243223
```
244224

245225
💡 Remember to provide notification handlers to PipelinR:
226+
246227
```java
247-
new Pipelinr()
248-
.with(
249-
() -> Stream.of(new Pong1(), new Pong2())
250-
)
228+
new Pipelinr().with(() -> Stream.of(new Pong1(), new Pong2()))
251229
```
252230

253231
### Notification middlewares
@@ -256,7 +234,6 @@ Notifications, like commands, support middlewares. Notification middlewares will
256234

257235
```java
258236
class Transactional implements Notification.Middleware {
259-
260237
@Override
261238
public <N extends Notification> void invoke(N notification, Next next) {
262239
// start tx
@@ -269,19 +246,22 @@ new Pipelinr().with(() -> Stream.of(new Transactional()))
269246
```
270247

271248
### Notification handling strategies
249+
272250
The default implementation loops through the notification handlers and awaits each one. This ensures each handler is run after one another.
273251

274252
Depending on your use-case for sending notifications, you might need a different strategy for handling the notifications, such running handlers in parallel.
275253

276254
PipelinR supports the following strategies:
277-
* `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**.
278-
* `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.
279-
* `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.
280-
* `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.
281-
* `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.
282-
* `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.
255+
256+
- `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**.
257+
- `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.
258+
- `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.
259+
- `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.
260+
- `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.
261+
- `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.
283262

284263
You can override default strategy via:
264+
285265
```java
286266
new Pipelinr().with(new ContinueOnException());
287267
```
@@ -290,27 +270,25 @@ new Pipelinr().with(new ContinueOnException());
290270

291271
PipelinR works well with Spring and Spring Boot.
292272

293-
Start by configuring a `Pipeline`. Create an instance of `Pipelinr` and inject all command handlers and **ordered** middlewares via the constructor:
273+
Start by configuring a `Pipeline`. Create an instance of `Pipelinr` and inject all command handlers and **ordered** middlewares:
294274

295275
```java
296276
@Configuration
297277
class PipelinrConfiguration {
298-
299278
@Bean
300279
Pipeline pipeline(ObjectProvider<Command.Handler> commandHandlers, ObjectProvider<Notification.Handler> notificationHandlers, ObjectProvider<Command.Middleware> middlewares) {
301-
return new Pipelinr()
302-
.with(commandHandlers::stream)
303-
.with(notificationHandlers::stream)
304-
.with(middlewares::orderedStream);
280+
return new Pipelinr()
281+
.with(() -> commandHandlers.stream())
282+
.with(() -> notificationHandlers.stream())
283+
.with(() -> middlewares.orderedStream());
305284
}
306285
}
307286
```
308287

309288
Define a command:
310289

311290
```java
312-
class Wave implements Command<String> {
313-
}
291+
class Wave implements Command<String> {}
314292
```
315293

316294
Define a handler and annotate it with `@Component` annotation:
@@ -322,7 +300,6 @@ class WaveBack implements Command.Handler<Wave, String> {
322300
}
323301
```
324302

325-
326303
Optionally, define `Order`-ed middlewares:
327304

328305
```java
@@ -342,8 +319,7 @@ class Transactional implements Command.Middleware {
342319
To use notifications, define a notification:
343320

344321
```java
345-
class Ping implements Notification {
346-
}
322+
class Ping implements Notification {}
347323
```
348324

349325
Define notification handlers and annotate them with `@Component` annotation:
@@ -407,13 +383,14 @@ Sending `AsyncPing` to the pipeline returns `CompletableFuture`:
407383
CompletableFuture<String> okInFuture = new Ping().execute(pipeline);
408384
```
409385

410-
411386
## How to contribute
387+
412388
Just fork the repo and send us a pull request.
413389

414390
## Alternatives
415-
- [MediatR](https://github.com/jbogard/MediatR) – Simple, unambitious mediator implementation in .NET
416391

392+
- [MediatR](https://github.com/jbogard/MediatR) – Simple, unambitious mediator implementation in .NET
417393

418394
## Contributors
395+
419396
- Eduards Sizovs: [Blog](https://sizovs.net)[Twitter](https://twitter.com/eduardsi)[GitHub](https://github.com/sizovs)

0 commit comments

Comments
 (0)