OTUser
OTUser

Reputation: 3848

Springboot: How to make Rest Service Controller non-blocking

I have a spring boot application which validates a file for a given client id and returns a json response of validation errors and warnings, while performing load test we noticed most the requests being blocked, so am trying to make our application non blocking by leveraging Spring's non blocking api

Below is my spring version

springBootVersion = '1.5.3.RELEASE'
springVersion = '4.3.8.RELEASE'

Below is my springboot ValidationController.groovy which is blocking requests

@Controller
@ResponseBody
class ValidationController {

    @RequestMapping(value = "/validate", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    ResponseEntity<ValidationResult> validate(@RequestParam(value = "file", required = true) MultipartFile file,
                                    @RequestParam(value = "client_id", required = true) String clientId)
    {
        if (clientId.isEmpty()) {
                String msg = "client id is required"
                if (LOGGER.isErrorEnabled()) {
                    LOGGER.error(msg)
                }
                return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
            }

            String contentType = file.contentType.toLowerCase();
            if (LOGGER.isDebugEnabled()) LOGGER.debug("content type = $contentType");

            Client client = clientRepository.findByExternalId(clientId)

            if (client == null) {
                String msg = "client id is invalid"
                if (LOGGER.isErrorEnabled()) {
                    LOGGER.error(msg)
                }
                return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
            }

            if (file.isEmpty()) {
            String msg = "file is empty"
            if(LOGGER.isErrorEnabled()) {
                LOGGER.error(msg)
            }
            return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
        }

        ValidationResult result = validationService.validate(file, client);

        return ResponseEntity.ok(result)
    }
}

class ValidationResult {

    private List<Warning> warnings
    private List<Error> errors
    //getters setters for warnings and errors
}

class Warning {
    private String message
    private String type
    //getters setters for message and type
}

class Error {
    private String message
    private String type
    //getters setters for message and type
}

I have modified my ValidationController.groovy as below

@Controller
@ResponseBody
class ValidationController {

    @Autowired
    @Qualifier("postRequestExecutorService")
    private ExecutorService postRequestExecutor;


    @RequestMapping(value = "/validate", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public DeferredResult<ResponseEntity<ValidationResult>> validate(@RequestParam(value = "file", required = true) MultipartFile file,
                                                              @RequestParam(value = "client_id", required = true) String clientId)
    {
        DeferredResult<ResponseEntity<ValidationResult>> deferredResult = new DeferredResult<>();

       CompletableFuture.supplyAsync(() -> validate(clientId, file), postRequestExecutor)
                .whenComplete((result, throwable) ->
                {                    
                    deferredResult.setResult(result);
                }        );


    }

private ResponseEntity<ValidationResult> validateLedes(String clientId, MultipartFile file) {

    ValidationResult result;
    try{
        if (clientId.isEmpty()) {
            String msg = messageSource.getMessage("client.id.required", null, Locale.getDefault())
            LOGGER.error(msg)
            return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
        }

        String contentType = file.contentType.toLowerCase();
        if (LOGGER.isDebugEnabled()) LOGGER.debug("content type = $contentType");

        Client client = clientRepository.findByExternalId(clientId)

        if (client == null) {
            String msg = messageSource.getMessage("client.id.invalid", null, Locale.getDefault())
            LOGGER.error(msg)
            return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
        }

        if (file.isEmpty()) {
            String msg = messageSource.getMessage("ledes.file.empty", null, Locale.getDefault())
            LOGGER.error(msg)
            return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
        }

        result = validationService.validate(file, Ledesxmlebilling21.class, client);
    }
    catch (Exception ex){
        LOGGER.error("Exception in validateLedes = "+ex.message)
        LOGGER.error("StackTrace in validateLedes = "+ex.stackTrace)
    }

    return ResponseEntity.ok(result)
}

}

And below is my ExecutorServiceConfiguration

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

@Configuration
public class RequestsExecuterServiceConfiguration {

    /**
     * Dedicated Thread Modeling to handle POST Requests
     */
    @Bean
    public ExecutorService postRequestExecutorService() {
        final ThreadFactory threadFactory = new ThreadFactoryBuilder()
                .setNameFormat("postRequestExecutor-%d")
                .setDaemon(true)
                .build();
        ExecutorService es = Executors.newFixedThreadPool(10,threadFactory);
        return es;
    }
}

Since my controller is a groovy class am seeing some compiler errors for the CompletableFuture lambda expression, can someone please help me make it work for groovy controller?

UPDATE1 As per the Answer I've changed the labda expression to closure as below

@RequestMapping(value = "/validate", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public DeferredResult<ResponseEntity<ValidationResult>> validate(@RequestParam(value = "file", required = true) MultipartFile file,
                                                          @RequestParam(value = "client_id", required = true) String clientId)
{
    DeferredResult<ResponseEntity<ValidationResult>> deferredResult = new DeferredResult<ResponseEntity<ValidationResult>>();

    CompletableFuture.supplyAsync({ -> validateLedes(clientId, file) }, postRequestExecutor)
            .whenComplete({ futureResult, throwable -> deferredResult.setResult(futureResult);})

    deferredResult
}

With the above controller, am not getting below errors

2018-04-11 15:07:45 - Exception in validateLedes = failed to lazily initialize a collection of role: com.validation.entity.Client.ruleConfigurations, could not initialize proxy - no Session
2018-04-11 15:07:45 - StackTrace in validateLedes = org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:587), org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:204), org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:148), org.hibernate.collection.internal.PersistentSet.size(PersistentSet.java:143)

Looks like the issue is, Hibernate session is not bound to ExecutorService and the new thread in which validateLedes method is executing it's unable to read from the database, can someone please me to binding Hibernate session to the the ExecutorService's thread pool?

Upvotes: 0

Views: 1565

Answers (1)

tim_yates
tim_yates

Reputation: 171084

You can't just stick lambdas into Groovy (until Groovy 3)

You'll need to translate them to Closures, so for example:

() -> validate(clientId, file)

becomes:

{ -> validate(clientId, file) }

And

(result, throwable) ->
{                    
    deferredResult.setResult(result);
} 

would be:

{ result, throwable -> deferredResult.setResult(result) } 

Upvotes: 1

Related Questions