DEV Community

Njuguna Mureithi
Njuguna Mureithi

Posted on

How to schedule and run cron jobs in Rust using apalis

For this tutorial, we're going to use apalis to run cron jobs in an async context. We will also look at how to decorate our jobs with tower middleware allowing us to unlock features like retries, prometheus, sentry etc

To begin, we will want to:

Setup a new project

cargo new my-cron-scheduler
cd my-cron-scheduler
Enter fullscreen mode Exit fullscreen mode

Add to Cargo.toml

[dependencies]
apalis = { version = "0.3", features = ["cron"] }
serde = { version = "1.0", features = ["derive"] }
tower = 0.4
Enter fullscreen mode Exit fullscreen mode

In this tutorial we will create a daily reminder example.

use apalis::prelude::*;
use serde::{Serialize,Deserialize};

#[derive(Serialize, Deserialize, Default, Debug, Clone)]
struct Reminder;

// Define an apalis job
impl Job for Reminder {
    const NAME: &'static str = "reminder::DailyReminder";
}
// Define a handler
async fn send_reminder(job: Reminder, ctx: JobContext) {
    // Do reminder stuff
}
Enter fullscreen mode Exit fullscreen mode

Lets define our entry point

use apalis::cron::{CronWorker, Schedule};
#[tokio::main]
async fn main() {
    // any valid cron string that points to the future should work
    let schedule = Schedule::from_str("@daily").unwrap();

    let worker = CronWorker::new(schedule, job_fn(send_reminder));

    Monitor::new()
        .register(worker)
        .run()
        .await
        .unwrap();
}
Enter fullscreen mode Exit fullscreen mode

This works perfectly but we can add some more functionality eg retries and extensions to a job context.

To use these features by apalis, you need to include the retry and extension features.

use apalis::layers::{Extension, DefaultRetryPolicy, RetryLayer};

#[derive(Clone)]
struct FakeService;

let service = ServiceBuilder::new()
    // Will retry 25 times
    .layer(RetryLayer::new(DefaultRetryPolicy))
    .layer(Extension(FakeService))
    .service(job_fn(send_reminder));

let worker = CronWorker::new(schedule, service);

Monitor::new()
    .register(worker)
    .run()
    .await
    .unwrap();

Enter fullscreen mode Exit fullscreen mode

Now we can access our FakeService in our handler

// Define a handler
async fn send_reminder(job: Reminder, ctx: JobContext) {
    let fake_service = ctx.data_opt::<FakeService>();
    // fake_service.fetch_from_db()
}
Enter fullscreen mode Exit fullscreen mode

You can repeat the process for different cron jobs.

Monitor::new()
    .register(daily_worker)
    .register(weekly_worker)
    .run()
    .await
    .unwrap();
Enter fullscreen mode Exit fullscreen mode

Further reading:
Background job processing with rust using apalis, actix and redis
Apalis on Github

Top comments (2)

Collapse
 
adophilus profile image
Adophilus

looks interesting, I've been trying out apalis recently with a custom MemoryStorage storage but I've been running into some issues: I can't schedule for a timeline such as 30 mins. It seems apalis uses a non-standard cron syntax.

Collapse
 
njugunamureithi profile image
Njuguna Mureithi

Consider using a backend with persistence such as sqlite, postgres or redis.