[Play 2.6 Java] Non-blocking action


#1

I’m trying to use non-blocking action. Following the documentation I’m able to use the first example. I need that the action returns more than one data, so I created a class for that.

private class ListProjectsData {

        private HashMap<String, Long> userProjects = new HashMap<>();
        private List<Project> projects;

        private ListProjectsData(HashMap<String, Long> userProjects, List<Project> projects) {
            this.userProjects = userProjects;
            this.projects = projects;
        }
        public HashMap<String, Long> getUserProjects() {
            return userProjects;
        }

        public List<Project> getProjects() {
            return projects;
        }
    }

    private HttpExecutionContext httpExecutionContext;
    @Inject
    public ProjectController(HttpExecutionContext ec) {
        this.httpExecutionContext = ec;
    }

    public CompletionStage<Result> listProject() {
        return calculateResponse().thenApplyAsync(ListProjectsData -> {
            // uses Http.Context
            //ctx().flash().put("info", "Response updated!");
            return ok(listProject.render(ListProjectsData.getProjects(), ListProjectsData.getUserProjects(), dataProject));
        }, httpExecutionContext.current());
    }
        //return ok(listProject.render(projects, userProjects, dataProject));
    private CompletionStage<ListProjectsData> calculateResponse() {
        List<Project> projects = projectRepository.findAll(new Sort(Sort.Direction.ASC, "name"));
        HashMap<String, Long> userProjects = new HashMap<>();

        //System.out.println(dataProject.size());
        for(Project project: projects) {
            Long n = userRepository.countByProjectsEquals(project.getName());
            if(!dataProject.containsKey(project.getName())) {
                List<Planning> plannings = planningRepository.findByDataContains(project.getName());
                dataProject.put(project.getName(), plannings.size());
            }
            userProjects.put(project.getName(), n);
        }
        return CompletableFuture.completedFuture(new ListProjectsData(userProjects, projects));
    }

The above code works fine. So I want to try the other documentation example because I’m using a long time mongo query.

 private MyExecutionContext myExecutionContext;

    @Inject
    public ProjectController(MyExecutionContext myExecutionContext) {
        this.myExecutionContext = myExecutionContext;
    }

    public CompletionStage<Result> listProject() {
        // Wrap an existing thread pool, using the context from the current thread
        Executor myEc = HttpExecution.fromThread((Executor) myExecutionContext);
        return supplyAsync(() -> calculateResponse(), myEc)
                .thenApplyAsync(ListProjectsData -> ok(listProject.render(ListProjectsData.getProjects(), ListProjectsData.getUserProjects(), dataProject)), myEc);
    }

But in this case, I’m unable to use the ListProjectsData getter to obtain the needed data. Where is the problem?

Thanks


(Aditya Athalye) #2

What do you mean by “unable to use the ListProjectsData getter to obtain the needed data.”? Can you elaborate?


#3

Using IntelliJ, the ListProjectsData.getProjects() reports cannot resolv method getProjects()


(Aditya Athalye) #4

I am able to see getProjects() as valid method call after using your source.


(Tim Moore) #5

Just a note that in the code provided, calculateResponse is a blocking method, and the use of CompletableFuture isn’t providing any benefit. If projectRepository.findAll is doing I/O, then it should be returning a CompletionStage itself, and must be implemented to perform any blocking I/O in an appropriate execution context.

If the whole method is intended to block, then it’s simpler to have it return ListProjectsData directly, rather than wrapping it in a CompletionStage.


(Aditya Athalye) #6

Correct. I guess probably the particular method call is part of a larger chain of calls not mentioned above and this method has been wrapped into a CompletableFuture so as to not break the chaining of futures.


#7

So, if I understand I have benefit only on methods that does I/O operations. In this case projectRepository.findAll is the method that does the mongo query.

For the other problem, the code compilation error is the same as IntelliJ:
play.sbt.PlayExceptions$CompilationException: Compilation error[cannot find symbol
symbol: method getProjects()


(Tim Moore) #8

findAll should return a CompletionStage, then, ideally using the native non-blocking API in the MongoDB driver.

There are some examples in this slide deck:


#9

Thanks, very very interesting. Actually I’m using Spring Data MongoDB, so to use reactive access to Mongo I’ve to rewrite all the queries. Could be an interesting challenge.

In the slides there are a lot of techniques example, which of them is better to learn and use?


#10

I had find that Spring Data MongoDB has @Async decorator to create asynchronous query. I don’t know if this is sufficient to create asynchronous query under Play. This matter is too complicated for me :-)


(Tim Moore) #11

The best options are the ones that use the async MongoDB driver, such as example 6 in that slide deck.

You could also use the reactive streams API, which can be integrated easily with Akka Streams. This might be better if you are expecting a large number of results from the query, as you could handle them in a streaming fashion without loading all results into memory at the same time.

Spring Data now offers a reactive Mongo repository implementation as well, though it uses Spring’s custom API, and would need to be adapted to CompletionStage or Akka Streams.


#12

I rewrote the method as the following:

public Result listProject() {
        List<Project> projects = projectRepository.findAll(new Sort(Sort.Direction.ASC, "name"));
        HashMap<String, Long> userProjects = new HashMap<>();
        for(Project project: projects) {
            Long n = userRepository.countByProjectsEquals(project.getName());
            if(!dataProject.containsKey(project.getName())) {
                CompletionStage<List<Planning>> plannings = planningRepository.findByDataContains(project.getName());
               plannings.thenApplyAsync(p -> dataProject.put(project.getName(), p.size()), httpExecutionContext.current());
              }
            userProjects.put(project.getName(), n);
        }
        return ok(listProject.render(projects, userProjects, dataProject));
    }

The query is:

 @Async
    CompletableFuture<List<Planning>> findByDataContains(String projects);

Do you think that this is sufficient to create a non blocking query?
Is there a way to debug if I’m using a blocking action or not?