22

ドメイン ロジックにコールバックを使用することの長所と短所は何だと思いますか? (私は、Rails や Ruby プロジェクトのコンテキストで話しています。)

議論を始めるにあたり、コールバックに関する Mongoid ページからのこの引用に言及したいと思います。

ドメイン ロジックにコールバックを使用することは設計上の不適切な方法であり、チェーン内のコールバックが実行を停止すると、デバッグが困難な予期しないエラーが発生する可能性があります。バックグラウンド ジョブをキューに入れるなど、分野横断的な問題にのみ使用することをお勧めします。

この主張の背後にある議論または弁護を聞くことに興味があります. Mongo ベースのアプリケーションにのみ適用することを意図していますか? それとも、データベース技術全体に適用することを意図していますか?

少なくともリレーショナル データベースに関しては、Ruby on Rails Guide to ActiveRecord Validations and Callbacksは同意しないように思われるかもしれません。次の例を見てください。

class Order < ActiveRecord::Base
  before_save :normalize_card_number, :if => :paid_with_card?
end

私の意見では、これはドメイン ロジックを実装する単純なコールバックの完璧な例です。手っ取り早くて効果ありそうです。私が Mongoid のアドバイスを受けるとしたら、このロジックはどこに行くのでしょうか?

4

6 に答える 6

32

小さなクラスにコールバックを使用するのが本当に好きです。クラスが非常に読みやすくなることがわかりました。たとえば、次のようなものです。

before_save :ensure_values_are_calculated_correctly
before_save :down_case_titles
before_save :update_cache

何が起こっているかはすぐにわかります。

これはテスト可能です。メソッド自体が機能することをテストでき、各コールバックを個別にテストできます。

クラス内のコールバックは、そのクラスに属するアスペクトに対してのみ使用する必要があると強く信じています。オブジェクトが特定の状態にある場合にメールを送信したり、ログを記録したりするなど、保存時にイベントをトリガーする場合は、Observerを使用します。これは、単一責任の原則を尊重します。

コールバック

コールバックの利点:

  • すべてが 1 か所にあるので、簡単です
  • 非常に読みやすいコード

コールバックの欠点:

  • すべてが 1 つの場所にあるため、単一責任の原則を簡単に破ることができます
  • 重いクラスになる可能性があります
  • 1 つのコールバックが失敗するとどうなりますか? それはまだ連鎖をたどっていますか?ヒント: コールバックが失敗しないようにするか、モデルの状態を無効に設定してください。

オブザーバー

オブザーバーの利点

  • 非常にきれいなコードで、同じクラスに対して複数のオブザーバーを作成し、それぞれが異なることを行うことができます
  • オブザーバーの実行は結合されていません

オブザーバーのデメリット

  • 最初は、動作がトリガーされる方法が奇妙かもしれません (オブザーバーを見てください!)

結論

要するに:

  • 単純なモデル関連のもの (計算値、デフォルト値、検証) にはコールバックを使用します。
  • オブザーバーをより横断的な動作に使用する (例: メールの送信、状態の伝播など)

そしていつものように、すべてのアドバイスは一粒の塩で受け取る必要があります。しかし、私の経験では、オブザーバーは非常にうまくスケーリングします (また、ほとんど知られていません)。

お役に立てれば。

于 2012-06-19T23:48:02.937 に答える
9

編集:私はここで何人かの人々の推薦に関する私の答えを組み合わせました。

概要

いくつかの読書と思考に基づいて、私は私が信じていることのいくつかの(暫定的な)声明に到達しました:

  1. 「ドメインロジックにコールバックを使用することは悪い設計慣行です」という記述は、書かれているように誤りです。それは要点を誇張している。コールバックは、適切に使用されたドメインロジックに適した場所です。問題は、ドメインモデルロジックをコールバックに入れるべきかどうかではなく、どのような種類のドメインロジックを入れるのが理にかなっているのかということです。

  2. 「ドメインロジックにコールバックを使用すると...チェーン内のコールバックが実行を停止したときにデバッグが困難な予期しないエラーが発生する可能性があります」というステートメントは真です。

  3. はい、コールバックは他のオブジェクトに影響を与える連鎖反応を引き起こす可能性があります。これがテスト可能ではない程度に、これは問題です。

  4. はい、オブジェクトをデータベースに保存しなくても、ビジネスロジックをテストできるはずです。

  5. 1つのオブジェクトのコールバックがあなたの感性に対して肥大化しすぎる場合は、(a)オブザーバーまたは(b)ヘルパークラスを含む、考慮すべき代替設計があります。これらは、マルチオブジェクト操作をきれいに処理できます。

  6. 「バックグラウンドジョブのキューイングなど、横断的関心事にのみ[コールバック]を使用する」というアドバイスは興味深いものですが、誇張されています。(横断的関心事を検討して、おそらく何かを見落としているかどうかを確認しました。)

また、この問題について話している私が読んだブログ投稿に対する私の反応のいくつかを共有したいと思います。

「ActiveRecordのコールバックが私の人生を台無しにした」への反応

Mathias Meyerの2010年の投稿、ActiveRecordのCallbacks Ruined My Lifeは、1つの視点を提供します。彼は書く:

Railsアプリケーションのモデルに検証とコールバックを追加し始めたときはいつでも[...]それはただ間違っていると感じました。そこにあるべきではないコードを追加しているように感じました。これにより、すべてがはるかに複雑になり、明示的なコードが暗黙的なコードに変わります。

この最後の主張は、「明示的なコードを暗黙的なコードに変える」というのは、まあ、不公平な期待だと思います。ここでレールについて話しているのですよね?!付加価値の多くは、Railsが「魔法のように」物事を行うことです。たとえば、開発者が明示的に行う必要はありません。Railsの成果を楽しみながら、暗黙のコードを批評するのは奇妙に思えませんか?

オブジェクトの永続性の状態に応じてのみ実行されるコード。

私はこれが不快に聞こえることに同意します。

ビジネスロジックの一部をテストするためにオブジェクトを保存する必要があるため、テストが難しいコード。

はい、これによりテストが遅くなり、困難になります。

したがって、要約すると、マティアスは火にいくつかの興味深い燃料を追加すると思いますが、すべてが魅力的であるとは思いません。

「クレイジー、異端、そして素晴らしい:Railsアプリの書き方」に対する反応

James Golickの2010年の投稿、Crazy、Heretic、Awesome:The Way I Write Rails Appsで、彼は次のように書いています。

また、すべてのビジネスロジックを永続オブジェクトに結合すると、奇妙な副作用が発生する可能性があります。このアプリケーションでは、何かが作成されると、after_createコールバックがログにエントリを生成します。このエントリは、アクティビティフィードの生成に使用されます。ロギングせずにオブジェクトを作成したい場合、たとえばコンソールでどうすればよいですか?私はできません。貯蓄と伐採は永遠にそして永遠に結婚しています。

後で、彼はその根源に到達します:

解決策は実際には非常に簡単です。問題の簡単な説明は、単一責任の原則に違反したことです。したがって、標準のオブジェクト指向手法を使用して、モデルロジックの関心を分離します。

彼がアドバイスをモデレートするために、それが適用される場合と適用されない場合を教えてくれたことに本当に感謝しています。

真実は、単純なアプリケーションでは、肥満の永続性オブジェクトが傷つくことは決してないかもしれないということです。CRUD操作よりも少し複雑になると、これらが積み重なって問題点になります。

于 2012-06-18T20:22:07.833 に答える
2

私の意見では、コールバックを使用するための最良のシナリオは、それを起動するメソッドがコールバック自体で実行されるものとは何の関係もない場合です。たとえば、商品は保存before_save :do_somethingに関連するコードを実行しないでください。それは、オブザーバーがどのように機能するかということのようなものです。

人々は自分のコードを乾かすためだけにコールバックを使う傾向があります。悪くはありませんが、コールバックが呼び出されていることに気付かsaveない場合、メソッドを読んでもすべてがわかるわけではないため、コードの保守が複雑で困難になる可能性があります。明示的なコードを作成することが重要だと思います(特に、多くの魔法が発生するRubyとRailsでは)。

保存に関連するすべてがメソッドに含まれている必要がありますsave。たとえば、コールバックでユーザーが認証されていることを確認する必要がある場合、これは保存とは関係ありません。これは、適切なコールバックシナリオです。

于 2012-06-19T12:26:33.230 に答える
2

ここでのこの質問(rspecの検証の失敗を無視する)は、コールバックにロジックを入れない理由として優れています:テスト容易性。

コード、時間の経過とともに多くの依存関係を発展させる傾向があり、そこでunless Rails.test?メソッドに追加し始めます。

コールバックにフォーマットロジックを保持し、before_validation複数のクラスに接触するものをServiceオブジェクトに移動することをお勧めします。

したがって、あなたの場合、normalize_card_numberをbefore_validationに移動し、カード番号が正規化されていることを検証できます。

しかし、どこかに行ってPaymentProfileを作成する必要がある場合は、別のサービスワークフローオブジェクトでそれを行います。

class CreatesCustomer
  def create(new_customer_object)
    return new_customer_object unless new_customer_object.valid?
    ActiveRecord::Base.transaction do
      new_customer_object.save!
      PaymentProfile.create!(new_customer_object)
    end
    new_customer_object
  end
end

次に、有効でない場合、保存が行われない場合、支払いゲートウェイが例外をスローする場合など、特定の条件を簡単にテストできます。

于 2012-06-14T18:30:43.180 に答える
2

Avdi Grimm は、彼の著書Object On Railsにいくつかの優れた例を示しています。

ここここで、彼がコールバック オプションを選択しない理由と、対応する ActiveRecord メソッドをオーバーライドするだけでこれを取り除く方法を見つけることができます。

あなたの場合、次のような結果になります:

class Order < ActiveRecord::Base

  def save(*)
    normalize_card_number if paid_with_card?
    super
  end

  private

  def normalize_card_number
    #do something and assign self.card_number = "XXX"
  end
end

[コメント「これはまだコールバックです」の後の更新]

ドメインロジックのコールバックについて話しているとき、私はコールバックを理解ActiveRecordしています.Mongoidリファラーからの引用が何か他のものだと思う場合は修正してください.どこかに「コールバック設計」が見つからなかった場合.

ActiveRecordコールバックの大部分 (全体?) は、前の例で取り除くことができるシンタックス シュガーに過ぎないと思います。

まず、このコールバック メソッドがその背後にあるロジックを隠していることに同意します。 に慣れていない人はActiveRecord、コードを理解するために学習する必要があります。上記のバージョンでは、簡単に理解してテストできます。

ActiveRecordコールバックが「一般的な使用法」または「分離感」を生み出すと、どちらが最悪になる可能性があります。コールバック バージョンは最初は良さそうに見えるかもしれませんが、コールバックを追加するにつれて、コードを理解するのが難しくなります (どの順序で読み込まれるか、どのコードが実行フローを停止するかなど...)、それをテストします (ドメイン ロジックはActiveRecord永続化ロジックと結合されています)。

以下の私の例を読むと、このコードに嫌悪感を覚えます。臭いです。TDD/BDD を行っていた場合、おそらくこのコードで終わることはないActiveRecordと思いますcard_number=。この例がコールバック オプションを直接選択せず、最初に設計について考えるのに十分であることを願っています。

MongoId からの引用について ドメイン ロジックにコールバックを使用するのではなく、バックグラウンド ジョブのキューに使用するようにアドバイスする理由が気になります。バックグラウンドジョブのキューイングはドメインロジックの一部である可能性があり、コールバック以外のもの(オブザーバーとしましょう)を使用して設計する方が良い場合があると思います。

最後に、オブジェクト指向プログラミング設計の観点から ActiveRecord が Rails でどのように使用/実装されるかについていくつかの批判があります。この回答にはそれに関する良い情報が含まれており、より簡単に見つけることができます。また、ActiveRecord に取って代わる (しかしどれだけ優れている) 可能性があり、彼の弱点がないdatamapperデザイン パターン/ ruby​​ 実装プロジェクトを確認することもできます。

于 2012-06-18T11:07:23.530 に答える
1

答えはそれほど複雑ではないと思います。

確定的な動作をするシステムを構築するつもりなら、正規化などのデータ関連のものを扱うコールバックは問題ありませんが、確認メールの送信などのビジネス ロジックを扱うコールバックは問題です

OOP は、ベスト プラクティス1として創発的な動作で普及しました。私の経験では、Rails はこれに同意しているようです。MVC を導入した人物を含む多くの人々は、実行時の動作が決定論的であり、事前によく知られているアプリケーションにとって、これが不必要な苦痛を引き起こすと考えています。

オブジェクト指向の緊急動作の実践に同意する場合は、動作をデータ オブジェクト グラフに結合するアクティブ レコード パターンはそれほど重要ではありません。(私のように) そのような緊急システムを理解し、デバッグし、変更するという苦痛を見たり感じたりした場合は、動作をより決定論的にするためにできる限りのことをしたいと思うでしょう。

では、疎結合と決定論的動作の適切なバランスを備えたオブジェクト指向システムをどのように設計すればよいのでしょうか? 答えがわかったら本を書いて、買うよ!DCIドメイン駆動設計、そしてより一般的にはGoF パターンが始まりです:-)


  1. http://www.artima.com/articles/dci_vision.html、「どこが間違っていたのか?」. 一次情報源ではありませんが、私の一般的な理解と野生の仮定の主観的な経験と一致しています.
于 2012-06-22T18:09:17.520 に答える