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 中执行的方法是灾难的完美处方,但处理不当 :)
使用 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 中执行的方法是灾难的完美处方,但处理不当 :)