springspring-bootmonospring-webfluxeither

How to Refactor Duplicate Logic in Mono-Based Service Methods Without Changing Return Types?


I have two methods in my Spring Boot controller implementation class. One deletes a user, and the other deletes a TnC (Terms and Conditions). Both methods contain duplicate logic for error handling and response construction.

I want to refactor this code to extract the common parts into a reusable method to improve maintainability and reduce duplication. Below are the current implementations of the methods:

 @Override
public Mono<ResponseEntity<Void>> deleteUser(String userId, ServerWebExchange exchange) {
    HttpHeaders responseHeaders = createJsonHeaders();
    return userService
        .deleteUser(userId, exchange)
        .fold(
            error -> {
                log.error("Error in deleteUser: {}", error);
                return new ResponseEntity<>(null, responseHeaders, error.getStatusCode());
            },
            response -> new ResponseEntity<>(HttpStatus.NO_CONTENT));
}

@Override
public Mono<ResponseEntity<Void>> deleteTnC(String version, ServerWebExchange exchange) {
    HttpHeaders responseHeaders = createJsonHeaders();
    return tncService
        .deleteTnC(version, exchange)
        .fold(
            error -> {
                log.error("Error in deleteTnC: {}", error);
                return new ResponseEntity<>(null, responseHeaders, error.getStatusCode());
            },
            response -> new ResponseEntity<>(HttpStatus.NO_CONTENT));
}

I attempted to refactor the common logic into a reusable method called handleServiceCall. Here's the implementation I wrote:

private <T, R> Mono<ResponseEntity<R>> handleServiceCall(
    Mono<T> serviceCall,
    HttpStatus successStatus,
    java.util.function.Function<T, R> responseTransformer
) {
    HttpHeaders responseHeaders = createJsonHeaders();
    return serviceCall.map(response -> new ResponseEntity<>(
            responseTransformer.apply(response),
            responseHeaders,
            successStatus
        ))
        .onErrorResume(error -> {
            log.error("Error during service call: {}", error);
            return Mono.just(new ResponseEntity<>(null, responseHeaders, HttpStatus.INTERNAL_SERVER_ERROR));
        });
}

My goal was to eliminate duplicate code in the deleteUser and deleteTnC methods. I wanted to use this refactored method for other service calls, such as updateDomainsVerificationToken.

However, I'm facing issues with type compatibility. For example:

The fold method used by the services (userService.deleteUser and tncService.deleteTnC) returns EitherMono<ResourceError, ?>, which doesn't directly conform to Mono. I'm confused about the return type of the refactored method and how to integrate it with the services without modifying the existing service call methods or their return types. I want to refactor this code while ensuring that the return types of the methods (Mono<ResponseEntity>, Mono<ResponseEntity>) remain unchanged. Any guidance on resolving these issues or improving the approach would be appreciated.


Solution

  • common parts into a reusable method to improve maintainability and reduce duplication.

    If you mean so-called exception handling here are the common parts, Spring WebFlux annotation-based programming style also supports the @ExceptionHandleer, as it is provided in Spring WebMvc.

    Since Spring 5.x and Spring 6.x, Spring provided varied programming model options to RESTful API developers, either annotation controller declaring or functional programming style. Check my example projects here: https://github.com/hantsy/spring-puzzles/tree/master/programming-models

    I have used Spring WebFlux in a real-world project for over 4 years. But your codes confused me. In the software design domain, separation of concerns (sometimes abbreviated as SoC) is a design principle for separating a computer program into distinct sections. In the Spring application, we can categorize the functionalities by beans for different purposes, such as Repository, Service, Controller, Exception Handler, etc. It is easy to assemble these components by Spring IOC in the background.