Last week, I sent a lot of emails — after all, I was working on creating my very first mailer. But in the process, I also realized something else: waiting for an email to send is not fun. Especially if you can’t do anything else while you’re waiting.
This problem pretty much encapsculates the reason for the existence and invention of background jobs. I’ve actually been playing around with creating and running jobs in different applications for the past few months now, yet I never really felt comfortable with them. I knew that having jobs run certain processes was clearly very important since they kept popping up all over the place. However, I was having trouble wrapping my head around what exactly a background job was, and when and why I would ever need to use one.
Working with ActionMailer turned out to be the perfect stepping-stone to Active Job, which helped me to better understand inline processes, asychronous requests, and how all of these things fit into running a job. Since we already know how to create mailers, that will be the perfect introduction to running our very first job! Integrating ActionMailer and Active Job is a piece of cake, so we’ll start with that first. Baby steps, my friends!
Jobs: Inline Or Background?
Background jobs existed far before ActiveJob came into being in December of 2014. In fact, if we take a look at one of our more complex, pre-Rails 4.2 projects, it’s likely that we’ll see a some classes that are suffixed with Worker
. These classes are a little bit like service objects in that they are responsible for taking in an object as a parameter and performing a certain action with or on that object, sometimes implementing a service class in the process of doing so. The “worker” term really just refers to classes that we use in the background of our application.
But, why would we ever need to run a class in the background of our application? And honestly, what does that even mean? Well, we can start by defining what exactly makes a background job, beginning by peeking inside of our config/application.rb
file. There are a lot of things happening inside of here, all of which are setting different configurations inside of our application. But there’s one line in particular that we should pay more attention to:
Rails.application.config.active_job.queue_adapter = :inline
Without even worrying about what queue_adapter
is doing for now, we know one thing for sure: our current active_job
configuration is set to run inline
. Okay, let’s try to deduce what inline
means, exactly. A quick search in the Rails guides leads us to the documentation for something called the InlineAdapter
, which explains:
“When enqueueing jobs with the Inline adapter, the job will be executed immediately. To use the Inline set the queue_adapter config to
:inline
.”
Interesting! So, if running a job inline
means that it happens right away, running a job that is not inline means that it will be delayed in its execution – that is to say, it won’t happen immediately, but it’ll happen later on. So the next logical question, then, is why?
After doing some digging, the best explanation I’ve found for why we’d want to run a process not inline but rather in the background, at a later point, comes from Ryan Selk. I really like the way that he explains the thought-processes behind choosing when to use a background job:
“One common situation for needing background jobs is consuming external APIs. It is unrealistic to think that all of your external APIs will have 100% uptime. By allowing API interaction to happen in a background job we can manage failures more effectively. Instead of having the user wait for a task to end, we send that task to the background queue. The User can then continue interacting with the app. Background jobs also give us the opportunity to prioritize jobs which are waiting in out background job queue. In many cases using background jobs can significantly reduce the required resources of the app.”
Ultimately, it all comes down to waiting. If there is ever a situation where your application would have to wait for a process – an email to send, a request to complete, or a response to be received – that’s a sign that you probably should be using a background job, rather than an inline process. In his blog post, Ryan talks about using 3rd party APIs, or performing some form of “computation intensive work, such as solving equations or image processing”; basically, anything that would require enough time or effort to make our application lag or to keep our user waiting would be a prime candidate for a background job.
So, let’s implement a job using Active Job…that’ll be easy, right?
A Round of Applause for Active Job
When Active Job was first introduced, one of its most impressive aspects was the fact that you could easily create a job and then later specify the job runner that you wanted to use. Active Job was essentially built to be its own mini-framework, which made it incredibly useful for declaring a job, and then making it run on any backend that you wanted (for example, delayed_job
or resque
). This also meant that it became super easy (think changing a single line in our application) to switch between different queuing backends. An important thing to remember is that Rails itself doesn’t have a job runner; by default, it comes with an “immediate runner” queuing implementation, which means that each job that is enqueued will run immediately (in other words, it will run inline
).
Setting up Active Job is incredibly easy, which is another thing that makes it so beautiful. It comes with a generator, which means that we can run a simple command like this to create a job that would send a newsletter to new users of our application:
♥ bin/rails g job new_user_newsletter
Of course, we could also just create our own file inside of app/jobs
, as long as it inherited from ActiveJob::Base
. But let’s hold off on writing a complex job for now. Let’s try to first get Active Job to work with ActionMailer and send our order confirmation email via our OrderMailer
for today.
The first thing we need to do is to choose a queuing backend. I’ve found delayed_job_active_record
to be a pretty good starting point, so we can add that to our Gemfile
and run bundle install
. This backend also requires two additional forms of setup: first, a migration that sets up Job
objects in Active Record. We can run these commands easily in our terminal:
1 2 |
|
And secondly, inside of our config/application.rb
, we’ll need to set our queue_adapter
configuration to use delayed_job
rather than be set to inline
processes:
config.active_job.queue_adapter = :delayed_job
This last part is very important, as the documentation explains that Rails doesn’t have it’s own queuing adapter:
“Rails itself does not provide a sophisticated queuing system and just executes the job immediately if no adapter is set.”
Without setting up our queuing backend, Active Job won’t know what to use to send our email. And speaking of emails, it’s finally time to integrate Active Job with ActionMailer!
BFFS 4EVA: Active Job + ActionMailer
Active Job is neatly integrated with ActionMailer so that we can easily send emails outside of a single request-response cycle (in other words, asynchronously). There are two methods that exist on the ActionMailer module that we can use. In fact, we already started using one last week: deliver_now
.
Inside of our Order
class, we were using deliver_now
to send a confirmation email that was written inside of our OrderMailer
. We probably both need a refresher, so here’s the code that uses our favorite thing ever: a state machine!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
If we instead change our send_confirmation_email
to use deliver_later
, we can send our order confirmation email through Active Job, rather than sending it inline, within the same request-response cycle. All of this magic is built into the deliver_later
method. In fact, if we take a look at the source code for Action Mailer, we’ll learn that there are actually two different types of deliver_later
methods: deliver_later
and deliver_later!
. They’re both similar in that they both enqueue our email to be delievered through Active Job.
However, when deliver_later!
is invoked, it relies on deliver_now!
, which delivers an email without checking perform_deliveries
and raise_delivery_errors
. But, because deliever_later!
depends on deliver_now!
, which in turn bypasses these two important methods, deliver_later!
it will never, ever raise any errors. The documentation suggests that we use this particular method with caution, so if you’re unsure, it’s probably best to stick with plain old deliver_later
.
Integrating ActionMailer with Active Job is probably the easiest way to start using background jobs. But there are a lot of other ways to implement Active Job in our applications! Now we know enough to start writing our own custom jobs that will run asynchronously! In fact, that’s exactly what we’ll do next week, in part 2 of this series. Get ready to become an Active Job boss and walk around like this:
tl;dr?
- Active Job requires you to set up a queuing backend and use the
deliver_later
method in order to integrate with ActionMailer. - The Rails Guides for cover all the basics for Active Job; head over here to read more.
- Even though Active Job is a relatively new feature of Rails, there are a lot of great blog posts that walk through how to use it. Check out this one, another one, and one more.