Throwing an exception vs Mono.error() in Spring webflux - java

I'm working on a Spring webflux project and I want to understand the difference between throwing an exception vs using Mono.error().
If there is a validation class like this for example:
public class NameValidator {
public static boolean isValid(String name) {
if(StringUtils.isEmpty(name)) {throw new RuntimeException("Invalid name");}
return true;
}
}
public class NameValidator2 {
public static Mono<Object> isValid(String name) {
if(StringUtils.isEmpty(name)) {
return Mono.error(new RuntimeException("Invalid name"));}
return Mono.just(true);
}
}
What are the pros & cons with each approach. When to use one over the other while working with reactive streams using spring webflux?

As #Joao already stated, the recommended way to deal with an error is to call the error method on a Publisher(Mono.error/Flux.error).
I would like to show you an example in which the traditional throw does not work as you may expect:
public void testErrorHandling() {
Flux.just("a", "b", "c")
.flatMap(e -> performAction()
.onErrorResume(t -> {
System.out.println("Error occurred");
return Mono.empty();
}))
.subscribe();
}
Mono<Void> performAction() {
throw new RuntimeException();
}
The onErrorResume operator will never be executed because the exception is thrown before Mono is assembled.

Basically you will have the same result in the end and no difference between the two options (maybe performance wise but I have not found anything backing this opinion so I guess it can be negligible.
The only “difference” is that Mono.error follows the Reactive Streams specification and throwing an exception as is does not (read more at https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.3/README.md#2.13). However it is not prohibited, but if you like to follow standards and specifications (I guess you do) you should consider using Mono.error.

Related

How to handle Exception in controller for Spring Boot REST API?

I am confused of how I should handler the exception of the controller in a Spring Boot Rest API. Right now I throw some exception in my service classes like this:
public Optional<Item> getSpecificItem(Long itemId) throws Exception {
return Optional.ofNullable(itemRepository.findById(itemId).
orElseThrow(() -> new Exception("Item with that id doesn't exist")));
}
I don't know if this is the correct way to do it but it kind of works, I am open to criticism. For the controller classes I don't know how it should look, I saw some example with #ControllerAdvice and exception for each controller and that looked kind of bad to me. Can I have a global exception class for all controllers? Is it good practice ?
Saw some examples and I don't know if they were the correct way to do it.
#ControllerAdvice is good if you not use for general Exception. Example, if you define spec exception such as SkyIsRedException. So, it will be throw anywhere it will catch.
#ControllerAdvice
public class ExampleAdvice {
#ExceptionHandler(SkyIsRedException.class)
#ResponseCode(HttpStatus.NOT_FOUND) // <- not required
public void methodName1() { ... }
#ExceptionHandler(SkyIsGreenException.class)
public void methodName2() { ... }
}
And you can this #ExceptionHandler in controller too, so it will activate if any methods of controller will throw this SkyIsRedException.
I not recommend use Exception for everything. You are only harming yourself.
UPDATE:
// annotations
public class Controller {
// annotations
public Optional<Item> getSpecificItem(Long itemId) throws ItemNotExistException {
return Optional.ofNullable(itemRepository.findById(itemId).
orElseThrow(() -> new ItemNotExistException("Item with that id doesn't exist")));
}
// Controller specific exception handler, not central like #ControllerAdvice
#ExceptionHandler(ItemNotExistException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public String itemNotExistExceptionHandler(ItemNotExistException ex) {
return ex.getMessage(); // example
{
}

How I can turn on and off BlockHound check

I have some App with WebFlux and i want to use BlockHound, but i need to have a possible turn on and off it through parameter in application.properties or through spring profiling or somthing else.
Also I want to override action, when the lock operation is caught so that not throw error but log warning. And firstly, i did through parameter in application.properties:
#SpringBootApplication
#Slf4j
public class GazPayApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context =
SpringApplication.run(GazPayApplication.class, args);
BlockHoundSwitch blockHoundSwitch = (BlockHoundSwitch)context.getBean("BlockHoundSwitchBean");
if (blockHoundSwitch.isBlockHoundEnabled()) {
BlockHound.install(builder ->
builder.blockingMethodCallback(it ->
log.warn("find block operation: {}", it.toString())));
}
}
And my BlockHoundSwitch:
#Component("BlockHoundSwitchBean")
#Getter
public class BlockHoundSwitch {
#Value("${blockhound.enabled}")
private boolean blockHoundEnabled;
}
It works for me but in my opinion this solution quite difficult and a little unpredictable.
Next i tried resolve this task through profiling:
#Profile("blockhound_enabled")
#Slf4j
#Component()
public class BlockHoundSwitch {
public BlockHoundSwitch() {
BlockHound.install(builder ->
builder.blockingMethodCallback(it ->
log.warn("find block operation: {}", it.toString())));
}
}
And it works too. Well, I have a few questions:
Which way is better, why and maybe there is another solution?
I need to localize and log, where block operation happened. How can I get class name and method, where it`s happened?
I resolve it. Maybe someone it will be useful. I did it through profiling and my code bellow:
#Profile("blockhound_on")
#Slf4j
#Component()
#Getter
public class BlockHoundSwitch {
public BlockHoundSwitch() {
BlockHound.install(builder ->
builder.blockingMethodCallback(it -> {
List<StackTraceElement> itemList = Arrays.stream(new Exception(it.toString()).getStackTrace())
.filter(i -> i.toString().contains("application.package"))
.collect(Collectors.toList());
log.warn("find block operation: \n{}", itemList);
}));
}
}
where application.package - main package of my project, which i`m finding in stacktrace.

Spring Integration DSL Syntax problem - how to dynamically construct subflows?

I am trying to construct a complex flow in Spring Integration where the sub flows are dynamically defined at runtime. Code that functions perfectly well in the master flow definition fails the compile in the sub flow definition. Since the construct appears identical, it is not obvious what is going on. Any explanation would be appreciated.
Thank you in advance.
The master flow definition is coded something like this:
StandardIntegrationFlow flow = IntegrationFlows
.from(setupAdapter,
c -> c.poller(Pollers.fixedRate(1000L, TimeUnit.MILLISECONDS).maxMessagesPerPoll(1)))
// This one compiles fine
.enrichHeaders(h -> h.headerExpression("start", "start\")")
.headerExpression("end", "payload[0].get(\"end\")"))
.split(tableSplitter)
.enrichHeaders(h -> h.headerExpression("object", "payload[0].get(\"object\")"))
.channel(c -> c.executor(stepTaskExecutor))
.routeToRecipients(r -> this.buildRecipientListRouterSpecForRules(r, rules))
.aggregate()
.handle(cleanupAdapter).get();
buildRecipientListRouterSpecForRules is defined as:
private RecipientListRouterSpec buildRecipientListRouterSpecForRules(RecipientListRouterSpec recipientListSpec,
Collection<RuleMetadata> rules) {
rules.forEach(
rule -> recipientListSpec.recipientFlow(getFilterExpression(rule), f -> createFlowDefForRule(f, rule)));
return recipientListSpec;
}
createFlowDefForRule() is just a switch() wrapper to choose which actual DSL to run for the flow defined by the rule. Here is a sample
public IntegrationFlowDefinition constructASpecificFlowDef(IntegrationFlowDefinition flowDef, RuleMetadata rule) {
return flowDef
// This enrichHeaders element fails to compile,
// The method headerExpression(String, String) is undefined for the type Object
.enrichHeaders(h -> h.headerExpression("ALC_operation", "payload[0].get(\"ALC_operation\")"));
}
In general, it's better to put such explanations in the question text, rather than as comments in the code snippets; I completely missed that comment.
Can you provide a stripped-down (simpler) example (complete class) that exhibits this behavior so we can play with it?
I tried to simplify what you are doing, and this compiles fine and works as expected:
#SpringBootApplication
public class So65010958Application {
public static void main(String[] args) {
SpringApplication.run(So65010958Application.class, args);
}
#Bean
IntegrationFlow flow() {
return IntegrationFlows.from("foo")
.routeToRecipients(r -> r.recipientFlow("true", f -> buildFlow(f)))
.get();
}
private IntegrationFlowDefinition<?> buildFlow(IntegrationFlowDefinition<?> f) {
return f.enrichHeaders(h -> h.headerExpression("foo", "'bar'"))
.channel(MessageChannels.queue("bar"));
}
#Bean
public ApplicationRunner runner(MessageChannel foo, PollableChannel bar) {
return args -> {
foo.send(new GenericMessage<>("foo"));
System.out.println(bar.receive(0));
};
}
}
GenericMessage [payload=foo, headers={foo=bar, id=d526b8fb-c6f8-7731-b1ad-e68e326fcc00, timestamp=1606333567749}]
So, I must be missing something.

Method call after returning Mono<Void>

I want to call the method when previous returned Mono<Void>:
#Override
public Mono<Void> sendEmail(EmailDto emailDto) {
return mailReactiveClient.sendEmail(message ->
createMessage(emailDto, emailDto.getBody(), message))
.doOnNext(saveNotificationLog(emailDto)); //it's not work
}
private void saveNotificationLog(EmailDto emailDto) {
notificationLogReactiveRepository.save(NotificationLog.builder()
...
.build());
}
Method sendEmailreturns Mono<Void>.
So how to call saveNotificationLog?
UPD: Tring to make my question simplier:
#Override
public Mono<Void> sendEmail(EmailDto emailDto) {
return mailReactiveClient.sendEmail(message ->
createMessage(emailDto, emailDto.getBody(), message))
.doOnNext(System.out.print("Hello world!");
}
How to call doOnNextor similar method after sendEmail return Mono<Void>?
The Mono will not emit data, so doOnNext will not be triggered. You should use the doOnSuccess instead.
Also, your Mono need to be consumed. Without the code, we don't know if it is or not.
Some example here: I added subscribe() to consume the mono. Depending on the use of your Mono, you will have to do or not the same thing.
This print nothing:
Mono<String> m=Mono.just("test");
Mono<Void> v=m.then();
v.doOnNext(x->System.out.println("OK")).subscribe();
This print "OK":
Mono<String> m=Mono.just("test");
Mono<Void> v=m.then();
v.doOnSuccess(x->System.out.println("OK")).subscribe();
doOnNext, and in general all doOn* reactor methods are side-effect methods. You're not supposed to call them to do I/O work or chain operations, but rather log things and not do anything that would affect the state of the application.
In your code sample, notificationLogReactiveRepository.save returns Mono<Void>. The saveNotificationLog returns void and does not subscribe to the publisher returned by notificationLogReactiveRepository.save. This means the notification will not be saved, because nothing happens until you subscribe.
In this case, it seems you're trying to chain operations - then operators are just made for that. Your code should look like this:
#Override
public Mono<Void> sendEmail(EmailDto emailDto) {
return mailReactiveClient.sendEmail(message ->
createMessage(emailDto, emailDto.getBody(), message))
.then(saveNotificationLog(emailDto));
}
private Mono<Void> saveNotificationLog(EmailDto emailDto) {
return notificationLogReactiveRepository.save(NotificationLog.builder()
...
.build());
}
Try it this way:
Mono.empty().then()

Action composition and async in Play Framework 2.5 in Java

I want to create an action that I can use with the #With annotation style. This action will need to proceed to an RPC call so if I understood correctly the documentation I should rather put this in an async way.
This is what I tried to do until now:
public class GetUserIdAction extends play.mvc.Action.Simple {
#Override
public CompletionStage<Result> call(Http.Context context) {
String token = "";
if (StringUtils.isEmpty(token)) {
return delegate.call(context);
}
CompletionStage<Http.Context> promiseOfUpdatedContext = CompletableFuture.supplyAsync(() -> setUserIdForToken(context, token));
return promiseOfUpdatedContext.thenApply(ctx -> delegate.call(ctx));
}
private Http.Context setUserIdForToken(Http.Context context, String token) {
context.args.put("user_id", authenticationManager.getUserIdForToken(token));
// The AuthenticationManager is issuing an RPC call and thus may take some time to complete.
return context;
}
}
Set aside the fact that token is always empty and authenticationManager is not set, this is just a quick meaningless example, my IDE is complaining on the thenApply part. For what I understand, it is expecting a CompletionStage<Result> and gets something more like a CompletionStage<CompletionStage<Result>>.
What is a way to deal with it? Cause here all I want is to put some information in the Context and then continue the delegate.call chain.
Or maybe I'm trying to do something stupid and composed actions are already asynchronous?
You have a CompletionStage<Something> and want to end with a CompletionStage<Result>. The easiest way to achieve that is using thenCompose.
Here is an example, with a small change: I have a CompletableFuture to get the token and only then I add it to the HttpContext
#Override
public CompletionStage<Result> call(final Http.Context context) {
final String token = "";
if (StringUtils.isEmpty(token)) {
return delegate.call(context);
}
return CompletableFuture.supplyAsync(() -> {
// do something to fetch that token
return "your_new_token";
}).thenCompose(tokenReceived -> {
context.args.put("user_id", tokenReceived);
return delegate.call(context);
});
}

Categories

Resources