Обратные вызовы ActiveSupport
В моем последнем посте я коснулся pry
и того, как это помогло мне убедиться, что к моему классу прикреплены обратные вызовы around_perform
ActiveSupport. В этом посте я подробнее расскажу о том, что я пытался сделать.
[ActiveJob, Sidekiq] — [ActiveJob]
Я работал над проектом Rails 4.2.x, в котором выполнялась фоновая обработка заданий. Мы использовали ActiveJob в качестве нашего адаптера для наших фоновых заданий. Негласно мы использовали драгоценный камень Sidekiq.
В конце концов нам понадобились подробности, которые может предоставить только родной Sidekiq через его sidekiq_options
. Эти опции, которые предоставляет Sidekiq, изначально нам не нужны. Как упоминается в Sidekiq Wiki:
Обратите внимание, что более продвинутые функции Sidekiq (
sidekiq_options
) нельзя контролировать или настраивать через ActiveJob, например. сохранение следов.
Пришло время воспользоваться преимуществами мощных драгоценных камней и опций sidekiq, поэтому нам нужно переключиться с ActiveJob на собственный Sidekiq.
Бесплатные привилегии ActiveJob
ActiveJob автоматически предоставлял определенные функции, такие как использование GlobalID и настройка обратных вызовов, среди прочего. С нативным подходом Sidekiq мы теряем эти бесплатные привилегии.
Больше всего мы пропустили обратные вызовы, особенно around_perform
. У нас было несколько модулей, которые были смешаны с нашими классами заданий, с единственной обязанностью дополнить класс обратными вызовами.
Например:
module JobMetrics extend ActiveSupport::Concern
included do around_perform do |_job, block| MetricsLogger.timing(metrics_logger_key) { block.call } end
def metrics_logger_key @metrics_logger_key ||= signature.underscore.tr("/", ".") end end end
Этот модуль упаковывает #perform
фактического задания в файл MetricsLogger.timing
. В будущем посте я могу подробно рассказать о MetricsLogger
, но по своей сути он записывает ключ/значение и отправляет его в агрегатор журналов. Преимущество, которое мы получаем от этого модуля, заключается в возможности узнать временные показатели для заданий на основе идентифицирующей подписи.
Отходя от ActiveJob, нам нужен другой способ сделать то же самое (содержащие обратные вызовы) только с помощью Sidekiq.
Содержащиеся обратные вызовы
Цель состоит в том, чтобы содержать обратные вызовы, которые представляют собой просто отдельный модуль, который можно включать в задания, определяющие требуемый обратный вызов. Этот подход означает, что при удалении ActiveJob мало что нужно изменить, и мы можем повторно использовать все существующие содержащиеся обратные вызовы.
Добавить прокси
Я обнаружил, что для использования ActiveSupport::Callbacks
нужно изменить исполняемый метод, которым в нашем случае будет #perform
задания.
...
def perform
run_callbacks :perform do
# Actual perform's content here
end
end
...
Я не хотел изменять определения методов #perform
для всех заданий. Поэтому я придумал решение использовать prepend
для размещения прокси-сервера перед рабочими местами #perform
.
module SidekiqCallbacks extend ActiveSupport::Concern
def perform(*args) run_callbacks :perform do super(*args) end end end
Затем этот модуль может быть добавлен в классы заданий Sidekiq, и будут выполняться обратные вызовы — если они присутствуют. Следующая задача — поддержка обратного вызова around_perform
.
Поддержка настройки и запуска обратных вызовов
require "active_support/callbacks"
# Following approach used by ActiveJob # https://github.com/rails/rails/blob/93c9534c9871d4adad4bc33b5edc355672b59c61/activejob/lib/active_job/callbacks.rb module SidekiqCallbacks extend ActiveSupport::Concern
def perform(*args) if respond_to?(:run_callbacks) run_callbacks :perform do super(*args) end else super(*args) end end
module ClassMethods def around_perform(*filters, &blk) set_callback(:perform, :around, *filters, &blk) end end end
Теперь SidekiqCallbacks
определяет возможность добавления обратных вызовов, и они будут выполняться до #perform
, если они определены.
Завершение
Последнее, что я хочу сделать, — это инкапсулировать логику обратного вызова Sidekiq в отдельный модуль, определяющий фактический обратный вызов (т. е. JobMetrics
). Для этого нам нужно дополнительно изменить SidekiqCallbacks
.
require "active_support/callbacks"
# Following approach used by ActiveJob # https://github.com/rails/rails/blob/93c9534c9871d4adad4bc33b5edc355672b59c61/activejob/lib/active_job/callbacks.rb module SidekiqCallbacks extend ActiveSupport::Concern
def self.prepended(base) base.include(ActiveSupport::Callbacks)
# Check to see if we already have any callbacks for :perform # Prevents overwriting callbacks if we already included this module (and defined callbacks) base.define_callbacks :perform unless base.respond_to?(:_perform_callbacks) && base._perform_callbacks.present?
class << base prepend ClassMethods end end
def perform(*args) if respond_to?(:run_callbacks) run_callbacks :perform do super(*args) end else super(*args) end end
module ClassMethods def after_perform(*filters, &blk) set_callback(:perform, :after, *filters, &blk) end end end
Нам пришлось включить self.prepended
, чтобы класс задания имел доступ к определенным методам через содержащийся модуль обратного вызова. Здесь важно отметить, что мы включаем ActiveSupport::Callbacks
в базовый объект, который предшествует этому модулю. Мы также должны убедиться, что обратные вызовы определены только один раз (именно здесь в моем последнем посте я использовал pry
рисунок, почему не все мои обратные вызовы были определены).
module JobMetrics extend ActiveSupport::Concern
included do prepend SidekiqCallbacks
around_perform do |_job, block| MetricsLogger.timing(metrics_logger_key) { block.call } end
def metrics_logger_key @metrics_logger_key ||= signature.underscore.tr("/", ".") end end end
Наконец, мы можем видеть, как JobMetrics
имеет новый prepend SidekiqCallbacks
, и он включает всю необходимую логику ActiveSupport::Callback
, которая позволяет определять и выполнять обратные вызовы.
Победа
Преимущество такого подхода состоит в том, что реализация обратного вызова полностью содержится в модуле JobMetrics
. Модуль SidekiqCallbacks
обеспечивает отсутствующую поддержку обратного вызова ActiveJob для around_perform
. С помощью этого подхода также можно добавить отсутствующие обратные вызовы ActiveJob.
В конце конкретные классы заданий содержат только include
содержащийся модуль обратного вызова (т. е. JobMetrics
). SidekiqCallbacks
предназначен для размещения нескольких содержащихся модулей обратного вызова, включенных в один конкретный класс заданий.
Первоначально опубликовано на kevinjalbert.com.