2

HTTP 経由で外部システムからデータをロードすることになっている ActiveJob があります。そのジョブが完了したら、後処理を行ってデータを別の外部システムに送信する 2 番目のジョブをキューに入れたいと考えています。

最初のジョブに 2 番目のジョブのことを知られたくないので、

  1. カプセル化
  2. 再利用性
  3. 基本的に初職の仕事じゃない

同様に、データの読み込みが失敗した場合に次に何が起こるかを最初のジョブに気にさせたくありません。おそらくユーザーに通知されるか、タイムアウト後に再試行するか、単にログに記録して手を投げるか、もう一度例外の詳細に基づいて異なる可能性があり、そのためのロジックや、それを処理するための他のシステムへの接続をジョブに含める必要はありません。

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 でも十分に高速です。

4

1 に答える 1

2

ActiveJob は、future や promise のような非同期ライブラリではありません。

バックグラウンドでタスクを実行するための単なるインターフェースです。現在のスレッド/プロセスは、この操作の結果を受け取りません。

たとえば、Sidekiq を ActiveJob キューとして使用すると、実行メソッドのパラメーターが redis ストアにシリアル化されます。Rails アプリのコンテキスト内で実行されている別のデーモン プロセスは、redis キューを監視し、シリアル化されたデータを使用してワーカーをインスタンス化します。

したがって、コールバックを渡すことは問題ないかもしれませんが、なぜそれらを別のクラスのメソッドとして持つのでしょうか。コールバックが動的である場合 (別の呼び出しで変化する場合)、コールバックを渡すことは理にかなっています。ただし、呼び出し元のクラスに実装しているため、これらのメソッドをジョブ ワーカー クラスに移動することを検討してください。

于 2015-03-16T19:38:34.273 に答える