Architecture
Anatomy of Qron
The architecture of Qron is pretty simple. It can be simpliefied by the following diagram:
It essentially consists of 3 main components:
- The Qron binary which is responsible for polling the data store for available jobs in order to execute them
- A Postgres database used as persistence layer for jobs and their execution state
- Your app which needs to be accessible over the internet
Qron
will make sure that the scheduled jobs and workflows are always run on time and they are resumed with the correct state.
What makes Qron
powerful is the simple semantic required to define workflows. In fact, creating a workflow with Qron
is as
simple as creating any other function. The only difference will be that the workflow function, on each execution, will receive
a state
as on of its input arguments. The state can be anything you would like it to be (well, anything that can be serialized to json, but stil).
Making easier to program custom logic that can be run over time repeatedly and reacting to specific conditions.
States
A job always have a state
associated with it. The state is an indicator for Qron
to know which operations can be performed
on the job. The state can be one of the following:
READY
- The job is waiting to be executedRUNNING
- The job is currently being executedPAUSED
- The job is paused and will not be executed until it is resumedFAILED
- The job has failed and will not be executed until it is manually retriedSUCCESS
- The job has been executed successfully and will not be executed again unless it is manually retried
States are updated upon each job execution. The state, unless external errors occur, will always be responsibility of the serverless function. Which will decide how the workflow should look like. Making it as simple or as complex as you like.
Primitives are provided in order to reprogram job executions at a future date. For example, this is a sample workflow that
will try to remind a user of their billing status 10 times. After 10 failed attempts, the job will be marked as FAILED
and will
not be executed again unless it is manually retried.
const remindq = createQueue('remindq', async ({ retry, fail, commit, state }) => {
const attempt = state.attempts + 1;
const hasPaid = await checkIfUserHasPaid(state.email);
if (hasPaid) {
// job will not be retried
return commit();
}
if (attempt >= 10) {
// job failed too many times, will not be retried
return fail();
}
// job will be retried in 1 day
await sendReminderEmail(state.email, 'Billing reminder');
return retry({
...state,
attempts: attempt
}).afterDays(1);
}, z.object({
email: z.string(),
attempts: z.number().default(0)
}));