So, I'm working on a refactor of a reasonably complex procedural framework that is split into three separate applications using a php framework named Laravel.

What can happen

Traditionally in an old school MVC setup (model view controller), controllers can end up code heavy with procedures that should not actually reside in a controller. A controller is ideally meant to direct what the particular request should do, not actually perform said tasks. You could end up validating a form request, writing to a database, sending an email, or processing a file. The net result is that often your user would have to wait for a POST or PUT request to return with a delayed response. There are tons of web sites that still have slow responses due to this code style.

The first thing to do is to separate your domain logic from your controller (which is tied to a specific route).

Separation of Concerns

The goal here is to attempt to follow the S in SOLID principles.

Queue up a Job

A job can send an email or process that file so that the response can return to the user faster. The application can thus process this part of the request outside of the current HTTP request scope.

Create a Repository (maybe a Factory)

Repositories/Factories can handle the persistence of the data. In the case of a database interaction, this still should be a blocking request because the user needs to know that their request succeeded (i.e. page redirect, response to frontend, etc).

Latency Tip

Your application could also give the appearance of reduced latency for a form request. You would have the UI immediately respond with a positive response before the request completes on the server (note: this would require additional client side validation). In this case, you would still need to handle any failures from the server. At the end of the day, I currently prefer to wait for the server response on a form request.

Why?

Now you ask, why a repository/factory/job? The primary reason is for reusability. You can share these things with a set of API routes. You can also place them into a package if more than one application will require the same domain logic.