Diagnosing Slow Action0:00
Allow me to shed light on the problem we currently face in our action by running it again, but this time with a few customers selected. Let's go ahead and give them a discount. And the action is running. It's still running. Still running. Still running. Still running. Okay, it's finally finished. Why did it take so long?
Okay, it's finally finished. Why did it take so long? It was pretty snappy in the last episode. Well if you think about it, we have an M plus 1 problem on our hands. The discount service takes a few seconds to execute. So for every customer that we select, it's going to take a few more seconds. And the administrator is left to sit there and watch the screen until the action finishes. That's not a good user experience. Now for long running tasks in a standard Laravel project, what would you do? You would reach for a job.
Queuing Nova Actions0:55
Now for long running tasks in a standard Laravel project, what would you do? You would reach for a job. You would reach for the queue. And Nova allows you to queue actions. Let me show you how easy it is. Provided you haven't deleted this line here. If you have, you'll need to bring it back. Queuing an action is as simple as implementing the ShouldQueue interface on that particular action. Let's see the difference in the browser.
action. Let's see the difference in the browser. This time, when I select a few customers and send them the discount, the action completes almost immediately. Now it hasn't actually processed anything, but if we run the queue, we'll see the process taking place. Here you can see our sendCustomerDiscount job starting. And around 9 seconds later, the job has completed. So our administrator doesn't have to sit there twiddling their thumbs waiting for the action to complete.
Viewing Action Progress1:48
So our administrator doesn't have to sit there twiddling their thumbs waiting for the action to complete. The Laravel queue will gracefully handle it in the background. Our admin can carry on with other things. But the admin could do with some insight into whether the task has completed successfully or not. They're not going to have the terminal open in front of them, are they? Nova also has you covered on that front. From the model backing the resource you're interested in queuing an action up for, we can add the Actionable trait.
From the model backing the resource you're interested in queuing an action up for, we can add the Actionable trait. So this is the Customer model. I'm adding the Actionable trait. With that trait added, if I click on the related resource item that has had actions executed against it, at the bottom here we have a new actions panel. We can see which action was executed, which User executed the action, and when it happened. So our administrators can have full insight into running actions. They can see if an action is still waiting in the queue. Let's say they're sending an email and the Customer rings up and says, I haven't received
They can see if an action is still waiting in the queue. Let's say they're sending an email and the customer rings up and says, I haven't received it yet. They can very easily come in here and see, okay, yeah, well, it's still actually in the process of running. You should receive it very soon. I think that's incredibly cool how simple it is to add that functionality to our Nova administration panel. All right, let's dive a little bit deeper. Currently, if we select n customers and we execute the action, it will create one job.
Controlling Job Chunking3:09
All right, let's dive a little bit deeper. Currently, if we select n customers and we execute the action, it will create one job in the queue. One job in the queue for n customers. Well, actually that's not quite correct. It will create one job up to 200 customers, and after 200, it will create two jobs and it will chunk those customers by 200s, but you can control the chunk count. Let's say in our case, we want a single job for each customer that we send an email to. If we come into our action, I'm going to override the chunkCount property and I'll set this to 1.
If we come into our action, I'm going to override the chunkCount property and I'll set this to one. Again, you can set this to whatever number makes sense for your particular action, but if we set it to one, we'll have one Customer per job. Let me demonstrate this by selecting three Customers again and sending them the discount email. Now, I have the database queue connection set up, so let me open this in TablePlus and you can see in our jobs table, we do indeed now have three jobs set up in the queue ready to run. Now, although this way of doing things may be better for your particular scenario, splitting
Batching Jobs with Hooks4:15
to run. Now, although this way of doing things may be better for your particular scenario, splitting up the models into their own jobs, we do lose a little bit of insight. How do we know when all of the customers have received their discount email? Thankfully, Laravel already has support for job batching and Nova integrates directly with that feature set. In our action, I'm going to implement another contract called batchable action and I'll use another trait called batchable. With our contract and trait added, we can come down to the bottom here and I can override a withBatch method that receives a pendingBatch that we can use to add lifecycle hooks.
With our contract and trait added, we can come down to the bottom here and I can override a withBatch method that receives a pendingBatch that we can use to add lifecycle hooks to our jobs. So, for example, I could add batch and I could call the then method to register a hook that will be executed when all of the jobs for this batch of customers have successfully completed. Or, for example, I could call catch. Catch will be executed when a problem occurs, one of the jobs fails. I can call finally, which will be executed when all of the jobs complete, regardless of whether they pass or fail.
I can call finally, which will be executed when all of the jobs complete, regardless of whether they pass or fail. In our case, I'm just going to use the then method to add a success callback. The closure that you pass will receive an instance of a batch and that batch actually has a property on it called resourceIds. resourceIds will be the IDs of any customers, in this case, that we've selected in the frontend. So for now, I'm just going to ray out those resourceIds. Let's run our action again and see this work. With our three customers selected, we'll go ahead and send the customer discount. Hopefully we'll see, yeah, here's the first customer with its single job, the second customer
Key Takeaways Summary5:54
With our three customers selected, we'll go ahead and send the customer discount. Hopefully we'll see, yeah, here's the first customer with its single job, the second customer with the second job, the third customer with the third job, and then finally we see an array of IDs that we selected, 70, 69, and 68, once all of the jobs in this batch have successfully completed. So to wrap up, if you have actions that will take a while to complete, don't leave your administrators waiting. Instead, implement the ShouldQueue contract on your action to gracefully send it to Laravel's queue. Make sure to use the Actionable trait on your models to give insight into the current progress.
queue. Make sure to use the actionable trait on your models to give insight into the current progress of jobs running on your resources, and if you need fine-grained control over how many models will be executed per queued action, then chunk count and withBatch are your friends.
