ActiveJob:如何在没有完整作业的情况下进行简单操作 class?

ActiveJob: how to do simple operations without a full blown job class?

使用 delayed_job,我可以像这样进行简单的操作:

@foo.delay.increment!(:myfield)

是否可以对 Rails' new ActiveJob 做同样的事情?(无需创建一大堆作业 类 来执行这些小操作)

据我所知,目前不支持此功能。您可以使用接受模型或实例、要执行的方法和参数列表的自定义代理作业轻松模拟此功能。

但是,为了代码测试和可维护性,这种快捷方式并不是一个好办法。为要排队的所有内容指定一个特定的作业会更有效(即使您需要编写更多的代码)。它迫使您更多地考虑应用程序的设计。

ActiveJob 仅仅是各种后台作业处理器之上的抽象,因此许多功能取决于哪个提供者你实际上正在使用。但我会尽量不依赖任何后端。

通常,工作提供者由持久性机制跑步者组成。卸载作业时,您以某种方式将其写入持久性机制,然后其中一个运行器检索并运行它。所以问题是:你能否以一种格式表达你的工作数据,与你需要的任何动作兼容?

那会很棘手。

那么让我们来定义什么是工作定义。例如,它可以是单个方法调用。假设此语法:

Model.find(42).delay.foo(1, 2)

我们可以使用以下格式:

{
  class: 'Model',
  id: '42', # whatever
  method: 'foo',
  args: [
    1, 2
  ]
}

现在我们如何从给定的调用构建这样的哈希并将其排入作业队列?

首先,看起来,我们需要定义一个 class,它有一个 method_missing 来捕获被调用的方法名称:

class JobMacro
  attr_accessor :data
  def initialize(record = nil)
    self.data = {}
    if record.present?
      self.data[:class] = record.class.to_s
      self.data[:id]    = record.id
    end
  end
  def method_missing(action, *args)
    self.data[:method] = action.to_s
    self.data[:args] = args
    GenericJob.perform_later(data)
  end
end

作业本身必须像这样重建该表达式:

data[:class].constantize.find(data[:id]).public_send(data[:method], *data[:args])

当然,您必须在模型上定义 delay 宏。最好将它分解到一个模块中,因为定义非常通用:

def delay
  JobMacro.new(self)
end

它确实有一些限制:

  • 仅支持 运行 持久化 ActiveRecord 模型上的作业。一项工作需要一种方法来重建被调用者以调用该方法,我选择了最可能的方法。如果需要,您也可以使用编组,但我认为这不可靠:在作业开始执行时,未编组的对象可能无效。 "GlobalID".
  • 也一样
  • 它使用了Ruby的反射。对于许多问题来说,这是一个诱人的解决方案,但速度不快,而且在安全性方面有点冒险。因此请谨慎使用此方法。
  • 只有一个方法调用。没有触发(你可以用 ruby2ruby gem 做到这一点)。依靠作业提供者正确序列化参数,如果失败,请使用您自己的代码帮助它。例如,que 在内部使用 JSON,所以在 JSON 中起作用的任何东西,在 que 中起作用。例如,符号不会。

一开始事情会以惊人的方式破裂。
因此请确保在开始之前设置调试工具。


这方面的一个例子是 Sidekiq's backward (Delayed::Job) compatibility extension for ActiveRecord

我写了一个 gem 可以帮助你 https://github.com/cristianbica/activejob-perform_later。但请注意,我相信在您的代码周围使用可能在 worker 中执行的方法是灾难的完美处方,但处理不当 :)