Vapor and Job Queues: Getting Started
Using Vapor’s Redis and Queues libraries, learn how to configure, dispatch, and process various jobs in a queue. By Heidi Hermann.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Vapor and Job Queues: Getting Started
20 mins
- Getting Started
- Looking at the Vapor Project
- Running the Vapor Project
- Getting Started with Vapor Queues
- Configuring Your QueuesRedisDriver
- Start Your Redis Server
- Running Your Queue and Scheduled Jobs
- Dispatching Your First Job
- Creating RecipientWelcomeEmailJob
- Options When Dispatching to the Queue
- Scheduling Jobs
- Creating SendNewsletterJob
- Running Your Scheduled Job
- Where to Go From Here?
Dispatching Your First Job
When a new reader signs up to your newsletter, you want to send them a welcome email to say, “Thank you”.
This is a typical example of a process that you would dispatch to a job because you don’t want the responsiveness of your webpage to depend on a third-party email API to completing.
Creating RecipientWelcomeEmailJob
In Xcode, add a new Jobs folder to your app and add a RecipientWelcomeEmailJob.swift file.
Open the file and add the following:
// 1
import Queues
import Vapor
// 2
struct WelcomeEmail: Codable {
let to: String
let name: String
}
// 3
struct RecipientWelcomeEmailJob: Job {
// 4
typealias Payload = WelcomeEmail
// 5
func dequeue(
_ context: QueueContext,
_ payload: WelcomeEmail
) -> EventLoopFuture<Void> {
print("Send welcome email to \(payload.to). Greet the user as \(payload.name).")
return context.eventLoop.future()
}
// 6
func error(
_ context: QueueContext,
_ error: Error,
_ payload: WelcomeEmail
) -> EventLoopFuture<Void> {
return context.eventLoop.future()
}
}
Here’s what this file does:
- Imports
Queues
andVapor
, exposing their APIs in the file - Creates a struct —
WelcomeEmail
— to hold the information you need to send your welcome email - Creates a struct —
RecipientWelcomeEmailJob
— that conforms to theJob
protocol - Defines
WelcomeEmail
as your job payload - Adds a
dequeue(_:_:)
method that processes the job on dequeue. In this tutorial, it prints a greeting to the logs and returnsEventLoopFuture
. - Adds an
error(_:_:_:)
method, which handles any errors that might occur. This tutorial ignores error handling.
Now, open configure.swift and, between configuring and starting the queue, add the following:
let recipientJob = RecipientWelcomeEmailJob()
app.queues.add(recipientJob)
Here, you instantiate the RecipientWelcomeEmailJob
and register it in the queues
namespace.
Finally, open NewsletterRecipientAPIController.swift and replace the // TODO
on line 54 with:
.flatMap { recipient in
req.queue.dispatch(
RecipientWelcomeEmailJob.self,
WelcomeEmail(to: recipient.email, name: recipient.name)
)
.transform(to: recipient)
}
This closure dispatches RecipientWelcomeEmailJob
to the queue. It has a payload containing the email and name of the created recipient.
Finally, you transform the response back to the recipient
, and the method can finish.
Now, build and run.
In Paw, post a new recipient. You should see that the response doesn’t look different from before. This is expected.
Navigate back to Xcode, and you’ll see four new lines in your debug editor:
[ INFO ] POST /api/newsletter/sign-up [request-id: EBC14B25-4F8A-4AC9-87C8-0001859FD203]
[ INFO ] Dispatched queue job [job_id: 3A4B0EBE-09AA-4E7B-A9E0-8D715F0C75A9, job_name: RecipientWelcomeEmailJob, queue: default, request-id: EBC14B25-4F8A-4AC9-87C8-0001859FD203]
[ INFO ] Dequeuing job [job_id: 3A4B0EBE-09AA-4E7B-A9E0-8D715F0C75A9, job_name: RecipientWelcomeEmailJob, queue: default]
Send welcome email to test2@newslettering.com. Greet the user as Test User 2.
Here’s a breakdown of what the logs tell you:
- Which endpoint was called and what the unique
request-id
is - Your app dispatched a
RecipientWelcomeEmailJob
to the default queue with the providedjob_id
- A job from the default queue was dequeued
- The print message you created earlier
It’s now time to fill in some gaps regarding the job.
Options When Dispatching to the Queue
When you dispatch a job, you must provide the job’s type and the required payload.
In the example above, the job type was RecipientWelcomeEmailJob
and the payload was WelcomeEmail
.
When you dispatch a job, you also can provide two other options:
-
maxRetryCount
, which takes anInt
. It is zero by default. -
delayUntil
, which takes an optionalDate
. It isnil
by default.
maxRetryCount
is the number of times to retry the job on failure. This is especially important if you are calling an external API and you want to make sure it goes through.
By setting delayUntil
, you delay the process of the job until after the date you provided. If the driver dequeues the job too early, it’ll make sure to re-queue it until after the delay time.
When delayUntil
is nil
or some date in the past, the driver processes the job the first time it is dequeued.
Those are helpful. But what if you want jobs to repeat at specific days or times?
Scheduling Jobs
If you want to run a task at a certain time, such as:
- Sending a newsletter of the first of every month;
- Greeting all your friends and family with a Christmas email every December;
- Or reminding your kid that you are the coolest parent every hour.
You can arrange it as a scheduled job in your Vapor app.
Creating SendNewsletterJob
In Xcode, create a new file, SendNewsletterJob.swift, inside the Jobs folder.
Now, insert:
// 1
import Fluent
import Queues
import Vapor
// 2
struct SendNewsletterJob: ScheduledJob {
// 3
func run(context: QueueContext) -> EventLoopFuture<Void> {
return getNewsletter(on: context.application.db)
.and(self.getRecipients(on: context.application.db))
.map { newsletter, recipients in
// 4
let message: String = recipients
.map(\.name)
.map { "Send newsletter with title: \(newsletter.title) to: \($0)" }
.joined(separator: "\n")
print(message)
}
}
// 5
private func getNewsletter(on db: Database) -> EventLoopFuture<Newsletter> {
let today = Calendar.current.startOfDay(for: Date())
return Newsletter.query(on: db)
.filter(\.$sendAt, .equal, today)
.first()
.unwrap(or: Abort(.notFound))
}
// 6
private func getRecipients(
on db: Database
) -> EventLoopFuture<[NewsletterRecipient]> {
return NewsletterRecipient.query(on: db).all()
}
}
Here, you:
- Import
Fluent
,Queues
andVapor
and expose their APIs in the file - Create a
SendNewsletterJob
struct and make it conform toScheduledJob
- Create the required
run(context:)
method. It fetches the newsletter that should be sent out (#5) and all the newsletter recipients (#6). - Map the list of recipients to a list of messages that informs you who received the email. Then print the message.
- Create private helper method that fetches the current newsletter. If the newsletter doesn’t exist, the method returns a failed
EventLoopFuture
with an error. - Create private helper method to fetch all the recipients
Next, open configure.swift and, under the recipientJob
, add the following:
let sendNewsletterJob = SendNewsletterJob()
app.queues.schedule(sendNewsletterJob).minutely().at(5)
Here, you instantiate the SendNewsletterJob
and register it to run on the fifth second of every minute.
The queues package comes with a handful of convenient helpers to schedule your job:
-
at(_:)
takes a specific date for a job that should run only once. -
yearly()
identifies a yearly occurrence. It can be further configured with the month it should run. -
monthly()
sets a monthly schedule, and can be further configured with the day it should run. -
weekly()
specifies that the job should occur weekly, and you can further specify on which day of the week it should run. -
daily()
schedules the job to execute daily. You can further specify the time it should run. -
hourly()
sets the job to an hourly schedule, and can be further configured with the minutes it should run. -
minutely()
configures the job to run every minute. You can further specify which seconds it should run. -
everySecond()
schedules the job for every second and has no further configuration.
Ready to take this out for a spin?