HTTP 経由で外部システムからデータをロードすることになっている ActiveJob があります。そのジョブが完了したら、後処理を行ってデータを別の外部システムに送信する 2 番目のジョブをキューに入れたいと考えています。
最初のジョブに 2 番目のジョブのことを知られたくないので、
- カプセル化
- 再利用性
- 基本的に初職の仕事じゃない
同様に、データの読み込みが失敗した場合に次に何が起こるかを最初のジョブに気にさせたくありません。おそらくユーザーに通知されるか、タイムアウト後に再試行するか、単にログに記録して手を投げるか、もう一度例外の詳細に基づいて異なる可能性があり、そのためのロジックや、それを処理するための他のシステムへの接続をジョブに含める必要はありません。
Java (私が最も経験を積んでいる場所) では、Guava のListenableFutureのようなものを使用して、事後に成功および失敗のコールバックを追加できます。
MyDataLoader loader = new MyDataLoader(someDataSource)
ListenableFuture<Data> future = executor.submit(loader);
Futures.addCallback(future, new FutureCallback<Data>() {
public void onSuccess(Data result) {
processData(result);
}
public void onFailure(Throwable t) {
handleFailure(t);
}
});
ただし、ActiveJob は、この種の外部コールバック メカニズムを提供していないようです。「アクティブ ジョブの基本」の関連 セクションafter_perform
からわかる限りでrescue_from
は、ジョブ クラス内から呼び出されることのみを意図しています。またafter_peform
、成功と失敗を区別するものではありません。
したがって、私が思いついた最善の方法は (そして、それが非常に優れていると主張しているわけではありません)、ジョブのperform
メソッドにいくつかのラムダを渡すことです。したがって、次のようになります。
class MyRecordLoader < ActiveJob::Base
# Loads data expensively (hopefully on a background queue) and passes
# the result, or any exception, to the appropriate specified lambda.
#
# @param data_source [String] the URL to load data from
# @param on_success [-> (String)] A lambda that will be passed the record
# data, if it's loaded successfully
# @param on_failure [-> (Exception)] A lambda that will be passed any
# exception, if there is one
def perform(data_source, on_success, on_failure)
begin
result = load_data_expensively_from data_source
on_success.call(result)
rescue => exception
on_failure.call(exception)
end
end
end
(補足:ラムダをパラメーターとして宣言するための yardoc 構文が何であるかはわかりません。これは正しいように見えますか、それとも失敗するともっともらしいですか?)
呼び出し元は、これらを渡す必要があります。
MyRecordLoader.perform_later(
some_data_source,
method(:process_data),
method(:handle_failure)
)
少なくとも呼び出し側では、それはひどいことではありませんが、不格好に思えます。これには、私が見つけていない一般的なパターンがあるのではないかと思わずにはいられません。また、Ruby/Rails の初心者として、ActiveJob を曲げて、本来意図されていないことを実行しているのではないかと少し心配しています。私が見つけたすべての ActiveJob の例は、「ファイア アンド フォーゲット」です。結果を非同期的に「返す」ことは、ActiveJob のユース ケースではないようです。
また、別のプロセスでジョブを実行する Resque のようなバックエンドの場合、これがまったく機能するかどうかもわかりません。
これを行うための「Rubyの方法」は何ですか?
更新: dre-hh によって示唆されたように、ActiveJob は適切なツールではないことが判明しました。また、信頼性が低く、状況が複雑すぎました。代わりにConcurrent Rubyに切り替えました。これは、ユース ケースにより適していて、タスクがほとんど IO バウンドであるため、GIL にもかかわらず、 MRI でも十分に高速です。