Skip to content

Commit ddd8ff4

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

1 file changed

Lines changed: 52 additions & 56 deletions

File tree

README.md

Lines changed: 52 additions & 56 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 big 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,7 +50,7 @@ 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> {
@@ -63,7 +63,7 @@ class Ping implements Command<String> {
6363
}
6464
```
6565

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

6868
```java
6969
class Ping implements Command<Voidy> {
@@ -93,11 +93,11 @@ class Pong implements Command.Handler<Ping, String> {
9393
```
9494

9595
## Pipeline
96+
9697
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.
9798

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

100-
101101
```java
102102
Pipeline pipeline = new Pipelinr()
103103
.with(
@@ -117,19 +117,28 @@ since v0.4, you can execute commands more naturally:
117117
new Ping("localhost").execute(pipeline);
118118
```
119119

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:
120+
`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:
121121

122122
```java
123-
// command validation + middleware
123+
class LoggingMiddleware implements Command.Middleware {
124124

125-
interface CommandValidator<C extends Command<R>, R> {
126-
void validate(C command);
125+
@Override
126+
public <R, C extends Command<R>> R invoke(C command, Next<R> next) {
127+
// log command
128+
R response = next.invoke();
129+
// log response
130+
return response;
131+
}
132+
}
127133

128-
default boolean matches(C command) {
129-
Generic<C> commandType = new Generic<C>(getClass()) { // since 0.10
130-
};
134+
class TxMiddleware implements Command.Middleware {
131135

132-
return commandType.resolve().isAssignableFrom(command.getClass());
136+
@Override
137+
public <R, C extends Command<R>> R invoke(C command, Next<R> next) {
138+
// start tx
139+
R response = next.invoke();
140+
// end tx
141+
return response;
133142
}
134143
}
135144

@@ -146,43 +155,29 @@ class ValidationMiddleware implements Command.Middleware {
146155
return next.invoke();
147156
}
148157
}
149-
```
150158

151-
```java
152-
// middleware that logs every command and the result it returns
153-
class LoggingMiddleware implements Command.Middleware {
154159

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-
}
160+
interface CommandValidator<C extends Command<R>, R> {
161+
void validate(C command);
163162

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

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;
167+
return commandType.resolve().isAssignableFrom(command.getClass());
173168
}
174169
}
175170
```
176171

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

179174
```java
180175
Pipeline pipeline = new Pipelinr()
181176
.with(() -> Stream.of(new Pong()))
182177
.with(() -> Stream.of(new LoggingMiddleware(), new TxMiddleware(), new ValidationMiddleware(...)));
183178
```
184179

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

187182
```java
188183
class LocalhostPong implements Command.Handler<Ping, String> {
@@ -196,7 +191,7 @@ class LocalhostPong implements Command.Handler<Ping, String> {
196191
```
197192

198193
```java
199-
class NonLocalhostPong implements Command.Handler<Ping, String> {
194+
class RemotePong implements Command.Handler<Ping, String> {
200195

201196
@Override
202197
public boolean matches(Ping command) {
@@ -212,8 +207,7 @@ Since version `0.5`, PipelinR supports Notifications, dispatched to multiple han
212207
For notifications, first create your notification message:
213208

214209
```java
215-
class Ping implements Notification {
216-
}
210+
class Ping implements Notification {}
217211
```
218212

219213
Next, create zero or more handlers for your notification:
@@ -243,6 +237,7 @@ new Ping().send(pipeline);
243237
```
244238

245239
💡 Remember to provide notification handlers to PipelinR:
240+
246241
```java
247242
new Pipelinr()
248243
.with(
@@ -269,19 +264,22 @@ new Pipelinr().with(() -> Stream.of(new Transactional()))
269264
```
270265

271266
### Notification handling strategies
267+
272268
The default implementation loops through the notification handlers and awaits each one. This ensures each handler is run after one another.
273269

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

276272
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.
273+
274+
- `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**.
275+
- `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.
276+
- `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.
277+
- `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.
278+
- `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.
279+
- `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.
283280

284281
You can override default strategy via:
282+
285283
```java
286284
new Pipelinr().with(new ContinueOnException());
287285
```
@@ -290,27 +288,26 @@ new Pipelinr().with(new ContinueOnException());
290288

291289
PipelinR works well with Spring and Spring Boot.
292290

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

295293
```java
296294
@Configuration
297295
class PipelinrConfiguration {
298296

299297
@Bean
300298
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);
299+
return new Pipelinr()
300+
.with(() -> commandHandlers.stream())
301+
.with(() -> notificationHandlers.stream())
302+
.with(() -> middlewares.orderedStream());
305303
}
306304
}
307305
```
308306

309307
Define a command:
310308

311309
```java
312-
class Wave implements Command<String> {
313-
}
310+
class Wave implements Command<String> {}
314311
```
315312

316313
Define a handler and annotate it with `@Component` annotation:
@@ -322,7 +319,6 @@ class WaveBack implements Command.Handler<Wave, String> {
322319
}
323320
```
324321

325-
326322
Optionally, define `Order`-ed middlewares:
327323

328324
```java
@@ -342,8 +338,7 @@ class Transactional implements Command.Middleware {
342338
To use notifications, define a notification:
343339

344340
```java
345-
class Ping implements Notification {
346-
}
341+
class Ping implements Notification {}
347342
```
348343

349344
Define notification handlers and annotate them with `@Component` annotation:
@@ -407,13 +402,14 @@ Sending `AsyncPing` to the pipeline returns `CompletableFuture`:
407402
CompletableFuture<String> okInFuture = new Ping().execute(pipeline);
408403
```
409404

410-
411405
## How to contribute
406+
412407
Just fork the repo and send us a pull request.
413408

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

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

418413
## Contributors
414+
419415
- Eduards Sizovs: [Blog](https://sizovs.net)[Twitter](https://twitter.com/eduardsi)[GitHub](https://github.com/sizovs)

0 commit comments

Comments
 (0)