1

I have a method which would be like this in traditional, imperative try-catch coding style:

void confirmCart(Checkout Order) {
    try {
        cartService.confirm(order.getCartId(), order.getCartHash());
    } catch (Exception ex1) {
        log.error("Confirm error: ", ex1);
        try {
            paymentService.cancel(order.getPaymentTransaction().getGatewayReference());
            throw ServiceException.wrap(ex1, CheckoutErrorCode.FAILED_CONFIRMING_CART);
        } catch (Exception ex2) {
            throw ServiceException.wrap(ex2, CheckoutErrorCode.FAILED_CANCELLING_PAYMENT);
        }
    }
}

The requirement is:

  • when cartService.confirm() works, returns nothing
  • when it fails, enter catch part, and try paymentService.cancel()
  • if 2 fails, throw ex2
  • if 2 succeeds, throw ex1(because confirm() has failed, anyway)
  • depending on the exception thrown here, controller will do different things

Now, in Reactive style, it is like:

    Mono<Void> confirmCart(CheckoutOrder order) {
        return cartService.confirm(order.getCartId(), order.getCartHash())
                .onErrorMap(throwable -> {
                    log.error("Failed confirming cart! cartId: '{}', cartHash: '{}'", order.getCartId(), order.getCartHash(), throwable);
                    return ServiceException.wrap(throwable, CheckoutErrorCode.FAILED_CONFIRMING_CART);
                })
                .onErrorResume(throwable -> {
                    log.trace("Cancelling payment with order id {}", order.getId().toString());
                    // when confirmation fails, 1. cancel payment and 2. throw original exception to notify controller -> exception handler
                    return paymentService.cancel(order.getPaymentTransaction().getGatewayReference())
                            .map(paymentCancellationResponse -> {
                                log.trace("payment cancellation response: {}, order id: {}", paymentCancellationResponse, order.getId().toString());
                                return paymentCancellationResponse;
                            }).then()
                            .onErrorMap(e -> {
                                log.error("payment payment cancellation failed. ", e);
                                return ServiceException.wrap(e, CheckoutErrorCode.FAILED_CANCELLING_PAYMENT);
                            });
                });
    }

But, I have spent a lot of time playing with Reactive operators and cannot find any way to rethrow the original exception. The code above compiles, but will not throw the first exception when confirmCart() fails. I guess because onErrorMap() and onErrorResume() cannot coexist; the latter one will be honored, maybe?

I have tried with onErrorMap because besides, it should be synchronous: confirmCart() should wait for paymentService.cancel() if it fails.

I was playing with then(), thenReturn() and so on, but I can make it compile, but it does not return/rethrow the first confirmation exception.

return cartService.confirm(order.getCartId(), order.getCartHash())
        .onErrorResume(throwable -> {
            log.error("Failed confirming cart! cartId: '{}', cartHash: '{}'", order.getCartId(), order.getCartHash(), throwable);
            log.trace("Cancelling payment with order id {}", order.getId().toString());
            // when confirmation fails, 1. cancel payment and 2. redirect user to checkout page
            return paymentService.cancel(order.getPaymentTransaction().getGatewayReference())
                    .map(paymentCancellationResponse -> {
                        log.trace("payment cancellation response: {}, order id: {}", paymentCancellationResponse, order.getId().toString());
                        return paymentCancellationResponse;
                    }).thenReturn(
                        ServiceException.wrap(throwable, CheckoutErrorCode.FAILED_CONFIRMING_CART)
                    )
                    .onErrorMap(e -> {
                        log.error("payment payment cancellation failed. ", e);
                        return ServiceException.wrap(e, CheckoutErrorCode.FAILED_CANCELLING_PAYMENT);
                    }).then();
        });

In the test I have no exception thrown when cartService.confirm() fails.

WesternGun
  • 11,303
  • 6
  • 88
  • 157

0 Answers0