seinecle / nocodefunctions-web-app

The code base of the front-end of nocodefunctions.com
https://nocodefunctions.com
35 stars 7 forks source link

Navigating to a new page after a long running operation: welcoming suggestions #4

Closed seinecle closed 5 months ago

seinecle commented 8 months ago

[tl;dr] https://twitter.com/seinecle/status/1748615007363829827

Background: how is this part of the app architectured

This (free, open source) app helps users analyze their data with text mining and network analysis. Different operations are possible, they are called "functions".

Functions typically take 1 second to a few minutes to run. The function called "Cowo" is the one where I experiment new ways to make the wait and the experience as good to the user as I can, while the function is running. When I'll get it right, I'll model the other functions in the same way.

The architecture is quite exotic, but I stand by it. Here it is:

public void runAnalysis() {
        progress = 0;
        runButtonDisabled = true;
        gexfHasArrived = false;
        sendCallToCowoFunction();
        getTopNodes();
    }

sendCallToCowoFunction() sends a GET request to the API endpoint (locally hosted) which runs the actual function Cowo. The API endpoint executes the function asynchronously, so the GET request returns almost immediately.

The results of the function are stored on disk with a file.

getTopNodes() is used to extract a sample of the results returned by Cowo. The method creates a new thread and executes all its code in this new thread, so the method returns immediately.

Communication and orchestration between the front-end and the different parts of the backend

How are all these different steps orchestrated, and how is the user alerted that the end results have arrived?

Things that are weird (but I am ok with them)

I see it as a way to avoid using frameworks that would professionally handle of all of this. I have a taste for keeping things as framework-free as a I can.

Things that are not satisfying (and finally, the question!)


@Stateless
public class LongRunningProcessBean {

    @Asynchronous
    public void executeLongRunningOperation(Consumer<String> callback) {
        // retrieve topNodes...

        // Invoke the callback when top nodes have bee retrieved
        callback.accept("success!");
    }
}

@Named
@SessionScoped
public class CowoBean {

    @EJB
    private LongRunningProcessBean longRunningProcessBean;

    public void startLongRunningOperation() {
        longRunningProcessBean.executeLongRunningOperation(this::onOperationComplete);
    }

    private void onOperationComplete(Strint result) {
            FacesContext context = FacesContext.getCurrentInstance(); // **error, not able to retrieve Context**
            context.getApplication().getNavigationHandler().handleNavigation(context, null, "/cowo/results.xhtml?faces-redirect=true");
    }
}

If that would work, that would be more elegant than a polling or a while () loop running and waiting for a result, right?

But FacesContext context = FacesContext.getCurrentInstance(); returns an error because the multi-thread design doesn't go well with the single thread model of JSF (or is it JakartaEE?). As a solution, it might be possible to:

But I don't have a clear view on how doing that. Thanks for your help!

BalusC commented 8 months ago

The statement "the single thread model of JSF" makes no sense. It only shows a misunderstanding of how HTTP and web apps normally work.

The solution you're looking for is here: https://stackoverflow.com/q/3787514. More background info is here: https://stackoverflow.com/q/2803160.

seinecle commented 8 months ago

Thanks!