I am using Reactor 2 and Spring 4. Here's the typical code that I have - a Consumer
working with a repository
@Consumer
public class ApplicationService {
@Selector(value="/applications/id", type = SelectorType.URI)
@ReplyTo
public Application byApplicationId(String id) throws ApplicationNotFoundException {
Application app = appRepo.findOne(id);
if(app == null)
throw new ApplicationNotFoundException("Application `" + id + "` could not be found.");
return app;
}
}
Then I have a controller that passes the request to an eventBus
into which I pass requests and return a Promise
@RestController
@RequestMapping("/applications")
public class ApplicationsController {
@RequestMapping(value = "/{id}", method = GET, produces = APPLICATION_JSON_VALUE)
public Promise<Event<Application>> byApplicationId(@PathVariable final String id) {
final Promise<Event<Application>> p = Promises.prepare(env);
eventBus.sendAndReceive("/applications/id", Event.wrap(id), p);
return p;
}
}
Things work but in case of ApplicationService
throwing an exception Promise
s value is not set but I do get following in the console:
16:46:58.003 [main] ERROR reactor.bus.EventBus - null
java.lang.reflect.UndeclaredThrowableException
at org.springframework.util.ReflectionUtils.rethrowRuntimeException(ReflectionUtils.java:302)
...
Caused by: com.metlife.harmony.exceptions.ApplicationNotFoundException: Application `2860c555-0bc4-45e6-95ea-f724ae3f4464` could not be found.
at com.metlife.harmony.services.ApplicationService.byApplicationId(ApplicationService.java:46) ~[classes/:?]
...
Caused by: reactor.core.support.Exceptions$ValueCause: Exception while signaling value: reactor.bus.Event.class : Event{id=null, headers={}, replyTo=reactor.bus.selector.Selectors$AnonymousKey@4, key=/applications/id, data=2860c555-0bc4-45e6-95ea-f724ae3f4464}
Questions are:
do I use Reactor and eventBus
in the wrong way ? and if so, what is the right way
perhaps this functionality is not implemented yet
Handling exceptions in Project Reactor with the onErrorReturn() operator. The onErrorReturn() will emit the specified callback value when the error occurs. In this way, the code will recover from the exception. The above example shows that the code completely recovered and continued like the error never happened.
Spring MVC Framework provides following ways to help us achieving robust exception handling. Controller Based - We can define exception handler methods in our controller classes. All we need is to annotate these methods with @ExceptionHandler annotation. This annotation takes Exception class as argument.
Exception HandlerThe @ExceptionHandler is an annotation used to handle the specific exceptions and sending the custom responses to the client. Define a class that extends the RuntimeException class. You can define the @ExceptionHandler method to handle the exceptions as shown.
I guess I re-evaluated the strategy of using Reactor in my Spring application.
Now my controller looks like
@RestController
public class GreetingController {
@Autowired
private GreetingService greetingService;
@RequestMapping("/greeting")
public Promise<ResponseEntity<?>> greeting(final @RequestParam(value = "name", defaultValue = "World") String name) {
return greetingService.provideGreetingFor(name).map(new Function<Greeting, ResponseEntity<?>>() {
@Override
public ResponseEntity<?> apply(Greeting t) {
return new ResponseEntity<>(t, HttpStatus.OK);
}
}).onErrorReturn(WrongNameException.class, new Function<WrongNameException, ResponseEntity<?>>() {
@Override
public ResponseEntity<?> apply(WrongNameException t) {
return new ResponseEntity<>(t.getMessage(), HttpStatus.BAD_REQUEST);
}
}).next();
}
}
And the service looks like
@Service
public class GreetingService {
@Autowired
private Environment env;
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
public Stream<Greeting> provideGreetingFor(String name) {
return Streams.just(name).dispatchOn(env).map(new Function<String, Greeting>() {
@Override
public Greeting apply(String t) {
if (t == null || t.matches(".*\\d+.*"))
throw new WrongNameException();
return new Greeting(counter.incrementAndGet(), String.format(template, t));
}
});
}
}
What's bad is that now I have to use Stream<T>
as a result of the method in the service (which is supposedly a business logic), so anyone using the service is now aware of the Stream
-ish nature of the service and as a result Stream
bleeds into other parts of the code, e.g. now I may have to use await()
in the code using the service.
Full application is available at https://github.com/evgeniysharapov/spring-reactor-demo
A dedicated chapter for handling exception and errors in the Reactive reference is here to read:
https://projectreactor.io/docs/core/release/reference/#error.handling
In any case, reactive pipeline is "contiguous" in its neutral meaning. Hardly you could prevent it from being noticed by the consumer of your methods.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With