DEV Community

Njuguna Mureithi
Njuguna Mureithi

Posted on

2 1 1

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

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

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.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay