简体   繁体   中英

Interrupting the reactive pipeline via exception VS propagating error object

What is the best in terms of reactive programming when there is a need of interrupting a reactive pipeline?

The logic is very straightforward.

The web service, web application will accept requests.

  • Step 1, from the request, make one first HTTP request to a third party API. The first HTTP service will either answer with what we need, in our example, a string starting with good , or something we do not need.

  • Step 2, only if step 1 responded with what is needed, make a second HTTP request to a second HTTP service, also no control over, to get the ultimate and greatest response.

Note, this is sequential, we cannot call step 2 unless we have the correct value from step 1.

Obviously, making an entire HTTP call to step 2 at this point does not make sense at all.

Therefore, I am thinking of doing:

@PostMapping(path = "/question")
public Mono<ResponseEntity<String>> createDummyMono(String theImportantKey) {
    return WebClient.create("http://first-service.com/get" + theImportantKey).get().exchangeToMono(clientResponse -> clientResponse.bodyToMono(String.class))
            .flatMap(extractGoodValueFromStepOne -> {
                if (extractGoodValueFromStepOne.startsWith("good")) {
                    System.out.println("Great! Step1 responded with something starting with good! Only with this we should invoke the second API");
                    return WebClient.create("http://second-service.com/get" + extractGoodValueFromStepOne.substring(4)).get().exchangeToMono(clientResponse -> clientResponse.bodyToMono(String.class));
                } else {
                    System.out.println("This is bad, Step 1 did not return something starting with good, no need to make the second API call then. Let's just propagate an error message all the way to response with a dummy Mono");
                    return Mono.just("Step 1 did not answer with something good, the ultimate answer is an error");
                }
            })
            .map(ResponseEntity::ok);
}

In this logic, the second step, represented by the flatMap will see if step 1 responded something we need. Only this case, a second HTTP request will be made to step 2. However, if it is not, I am building a dummy Mono to propagate and carry down the reactive pipeline.

A second solution, is to throw an exception, and catch it with @ExceptionHandler for instance

@PostMapping(path = "/question")
public Mono<ResponseEntity<String>> throwRuntimeException(String theImportantKey) {
    return WebClient.create("http://first-service.com/get" + theImportantKey).get().exchangeToMono(clientResponse -> clientResponse.bodyToMono(String.class))
    .flatMap(extractGoodValueFromStepOne -> {
        if (extractGoodValueFromStepOne.startsWith("good")) {
            System.out.println("Great! Step1 responded with something starting with good! Only with this we should invoke the second API");
            return WebClient.create("http://second-service.com/get" + extractGoodValueFromStepOne.substring(4)).get().exchangeToMono(clientResponse -> clientResponse.bodyToMono(String.class));
        } else {
            System.out.println("This is bad, Step 1 did not return something starting with good, no need to make the second API call then. Let's just propagate an error message all the way to response with a dummy Mono");
            throw new RuntimeException("Step 1 did not answer with something good, the ultimate answer is an error");
        }
    })
    .map(ResponseEntity::ok);
}

@ExceptionHandler
public Mono<ResponseEntity<String>> exception(final RuntimeException runtimeException) {
    return Mono.just(ResponseEntity.ok("Step 1 did not answer with something good, the ultimate answer is an error"));
}

Here, the logic is the same. Just if step 1 did not answer with what we need, I interrupt the pipeline by throwing a RuntimeException.

I kinda think, neither the first solution, passing down some dummy Mono or throwing an unchecked RuntimeException sounds the correct way to do in a reactive world.

May I ask which is the correct solution to answer to this problem and why please?

Your dummy Mono solution only works because there is nothing after in the chain that needs to do any additional processing, what if after your flatMap you need to do an additional flatMap on the successful value? then you will be in a pickle when a strange dummy Mono comes flying down the chain.

.flatMap(value -> {
    if (value.startsWith("good")) {
        System.out.println("good");
        return WebClient.create("http://second-service.com/get" + value.substring(4))
                        .get()
                        .exchangeToMono(clientResponse -> clientResponse.bodyToMono(String.class));
     } else {
         System.out.println("Boo");
         return Mono.just("some value");
     }
}).flatMap(value2 -> {
    // what now?
})

When an exception is thrown in an operator the exception will be propagatade through the stream as an onError event. Like when we for instance return a Mono#error .

Some exceptions like for instance OutOfMemoryException will not be considered as such, but are instead fatal events and will terminate the flow immediately.

But otherwise most commonly the exception will then be transferred through the chain and "regular" operators will se that, that is an error event so they will just skip that event and pass it down the chain either out to the calling client, or until any of the specialized error event handlers see it, or as in your case be snatched up by an Exception handler that you have defined.

The correct way would be in your cases is to return a Mono#error (so you are explicit in your return) saying that if this happens we return an error and then either you recover, drop the value or whatever you want to do, or as you have done, handled the exception using an exception handler.

Your first solution behaves more like a return empty, and then you have switchIfEmpty operator so you change to another publisher (Mono) if last operator returned empty. Or you could use onErrorResume that will, if a specific error comes along, return a fallback Publisher .

There are very, very many ways of handling errors in reactor and i suggest you read up on them and try them all out.

4.6.2. Handling Exceptions in Operators or Functions

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM