October 3, 2019

Creating an Active Job Adapter

Active Job adapter interface

I've created an Active Job adapter to run my background jobs on Google Cloud Run via Google Cloud Tasks. Although it is the first time for me to create an adapter for Active Job, this is working very well so far.

https://github.com/esminc/activejob-google_cloud_tasks-http

Creating an Active Job adapter itself is not so hard. You can see that by exploring the repository above. In fact, Active Job adapters are required to implement only two methods: enqueue and enqueue_at. For example, let's take a look at the implementation of Active Job Inline, which is one of the built-in implementations of Active Job.

module ActiveJob
  module QueueAdapters
    # == Active Job Inline adapter
    #
    # When enqueuing jobs with the Inline adapter the job will be executed
    # immediately.
    #
    # To use the Inline set the queue_adapter config to +:inline+.
    #
    #   Rails.application.config.active_job.queue_adapter = :inline
    class InlineAdapter
      def enqueue(job) #:nodoc:
        Base.execute(job.serialize)
      end
      def enqueue_at(*) #:nodoc:
        raise NotImplementedError, "Use a queueing backend to enqueue jobs in the future. Read more at https://guides.rubyonrails.org/active_job_basics.html"
      end
    end
  end
end

This adapter is, as its name suggests, supposed to run the job immediately. The method enqueue executes a job which it received synchronously by ActiveJob::Base.execute. Because of the nature of the adapter, it does not support scheduling time when the job should be executed. So enqueue_at just raises NotImplementedError.

As you can see, the adapter interface is quite simple. Once you glue your back-end mechanism onto Active Job by implementing an adapter, it can be used via Active Job’s interface.

Adapter lookup mechanism

To use an adapter, you have to tell Active Job which adapter you use like below:

Rails.application.config.active_job.queue_adapter = CloudCuckooLandAdapter.new

Conforming the naming convention of Active Job makes it a little convenience. If CloudCuckooLandAdapters placed at ActiveJob::QueueAdapters::CloudCuckooLandAdapter, you can simply pass the symbol of the underscore-ized name without the Adapter suffix: :cloud_cuckoo_land.

Rails.application.config.active_job.queue_adapter = :cloud_cuckoo_land

When passing the adapter name by symbol or string, Active Job expects the adapter class is placed under ActiveJob::QueueAdapters with Adapter suffix.

Inline mode

Sometimes, asynchronous processing make debugging and testing unnecessarily difficult. Thus, an easy way to disable async processing will be needed. For example, it can be done switching between async and sync by a flag, or overriding methods on test/development environment like below:

module Inlining
  def enqueue(job, *)
    Base.execute(job.serialize)
  end
  alias enqueue_at enqueue
end
CloudCuckooLandAdapter.prepend Inlining

activejob and active_job

When I created a rubygem to publish an Active Job adapter, I was a little confused. In spite of the module of Active Job is named ActiveJob, the gem name is activejob (not active_job). It causes a weird results when creating a gem which name is prefixed by activejob-.

$ bundle gem --coc --mit -t rspec activejob-cloud_cuckoo_land_adapter
Creating gem 'activejob-cloud_cuckoo_land_adapter'...
MIT License enabled in config
Code of conduct enabled in config
      create  activejob-cloud_cuckoo_land_adapter/Gemfile
      create  activejob-cloud_cuckoo_land_adapter/lib/activejob/cloud_cuckoo_land_adapter.rb
      create  activejob-cloud_cuckoo_land_adapter/lib/activejob/cloud_cuckoo_land_adapter/version.rb
      create  activejob-cloud_cuckoo_land_adapter/activejob-cloud_cuckoo_land_adapter.gemspec
      create  activejob-cloud_cuckoo_land_adapter/Rakefile
      create  activejob-cloud_cuckoo_land_adapter/README.md
      create  activejob-cloud_cuckoo_land_adapter/bin/console
      create  activejob-cloud_cuckoo_land_adapter/bin/setup
      create  activejob-cloud_cuckoo_land_adapter/.travis.yml
      create  activejob-cloud_cuckoo_land_adapter/.rspec
      create  activejob-cloud_cuckoo_land_adapter/spec/spec_helper.rb
      create  activejob-cloud_cuckoo_land_adapter/spec/activejob/cloud_cuckoo_land_adapter_spec.rb
      create  activejob-cloud_cuckoo_land_adapter/LICENSE.txt
      create  activejob-cloud_cuckoo_land_adapter/CODE_OF_CONDUCT.md
Gem 'activejob-cloud_cuckoo_land_adapter' was successfully created. For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html
$ cat activejob-cloud_cuckoo_land_adapter/lib/activejob/cloud_cuckoo_land_adapter/version.rb
module Activejob
  module CloudCuckooLandAdapter
    VERSION = "0.1.0"
  end
end

The generated module name is Activejob and its directory name is activejob. Although the result shouldn’t be a surprise, it is better to rename them to ActiveJob and active_job to avoid users’ confusion.

© Hibariya 2020

Powered by Hugo & Kiss.