Workers

loco integrates with a full blown background job processing framework: sidekiq-rs. You can enqueue jobs in a similar ergonomics as Rails' ActiveJob, and have a similar scalable processing model to perform these background jobs.

Using workers

To use a worker, we mainly think about adding a job to the queue, so you use the worker and perform later:

    // .. in your controller ..
    DownloadWorker::perform_later(
        &ctx,
        DownloadWorkerArgs {
            user_guid: "foo".to_string(),
        },
    )
    .await

Unlike Rails and Ruby, with Rust you can enjoy strongly typed job arguments which gets serialized and pushed into the queue.

Adding a worker

Adding a worker meaning coding the background job logic to take the arguments and perform a job. We also need to let loco know about it and register it into the global job processor.

Add a worker to workers/:

#[async_trait]
impl Worker<DownloadWorkerArgs> for DownloadWorker {
    async fn perform(&self, args: DownloadWorkerArgs) -> Result<()> {
        println!("================================================");
        println!("Sending payment report to user {}", args.user_guid);

        // TODO: Some actual work goes here...

        println!("================================================");
        Ok(())
    }
}

And register it in app.rs:

#[async_trait]
impl Hooks for App {
//..
    fn connect_workers<'a>(p: &'a mut Processor, ctx: &'a AppContext) {
        p.register(DownloadWorker::build(ctx));
    }
// ..
}

Generate a Worker

To automatically add a worker using loco generate, execute the following command:

cargo loco generate worker report_worker

The worker generator creates a worker file associated with your app and generates a test template file, enabling you to verify your worker.

Configuring Workers

In your config/<environment>.yaml you can specify the worker mode. BackgroundAsync and BackgroundQueue will process jobs in a non-blocking manner, while ForegroundBlocking will process jobs in a blocking manner.

The main difference between BackgroundAsync and BackgroundQueue is that the latter will use Redis to store the jobs, while the former does not require Redis and will use async within the same process.

# Worker Configuration
workers:
  # specifies the worker mode. Options:
  #   - BackgroundQueue - Workers operate asynchronously in the background, processing queued.
  #   - ForegroundBlocking - Workers operate in the foreground and block until tasks are completed.
  #   - BackgroundAsync - Workers operate asynchronously in the background, processing tasks with async capabilities.
  mode: BackgroundQueue

Testing a Worker

You can easily test your worker background jobs using Loco. Ensure that your worker is set to the ForegroundBlocking mode, which blocks the job, ensuring it runs synchronously. When testing the worker, the test will wait until your worker is completed, allowing you to verify if the worker accomplished its intended tasks.

It's recommended to implement tests in the tests/workers directory to consolidate all your worker tests in one place.

Additionally, you can leverage the worker generator, which automatically creates tests, saving you time on configuring tests in the library.

Here's an example of how the test should be structured:

#[tokio::test]
#[serial]
async fn test_run_report_worker_worker() {
    // Set up the test environment
    let boot = testing::boot_test::<App, Migrator>().await.unwrap();

    // Execute the worker in 'ForegroundBlocking' mode, preventing it from running asynchronously
    assert!(
        ReportWorkerWorker::perform_later(&boot.app_context, ReportWorkerWorkerArgs {})
            .await
            .is_ok()
    );

    // Include additional assert validations after the execution of the worker
}