Pros and cons of using callbacks for domain logic in Rails

mongoid, ruby-on-rails

Solution

I really like using callbacks for small classes. I find it makes a class very readable, e.g. something like

before_save :ensure_values_are_calculated_correctly
before_save :down_case_titles
before_save :update_cache

It is immediately clear what is happening.

I even find this testable; I can test that the methods themselves work, and I can test each callback separately.

I strongly believe that callbacks in a class should only be used for aspects that belong to the class. If you want to trigger events on save, e.g. sending a mail if an object is in a certain state, or logging, I would use an Observer. This respects the single responsibility principle.

Callbacks

The advantage of callbacks:

- everything is in one place, so that makes it easy

- very readable code

The disadvantage of callbacks:

- since everything is one place, it is easy to break the single responsibility principle

- could make for heavy classes

- what happens if one callback fails? does it still follow the chain? Hint: make sure your callbacks never fail, or otherwise set the state of the model to invalid.

Observers

The advantage of Observers

- very clean code, you could make several observers for the same class, each doing a different thing

- execution of observers is not coupled

The disadvantage of observers

- at first it could be weird how behaviour is triggered (look in the observer!)

Conclusion

So in short:

- use callbacks for the simple, model-related stuff (calculated values, default values, validations)

- use observers for more cross-cutting behaviour (e.g. sending mail, propagating state, ...)

And as always: all advice has to be taken with a grain of salt. But in my experience Observers scale really well (and are also little known).

Hope this helps.

Problem

What do you see as the pros and cons of using callbacks for domain logic? (I'm talking in the context of Rails and/or Ruby projects.) To start the discussion, I wanted to mention this quote from the Mongoid page on callbacks: Using callbacks for domain logic is a bad design practice, and can lead to unexpected errors that are hard to debug when callbacks in the chain halt execution. It is our recommendation to only use them for cross-cutting concerns, like queueing up background jobs. I would be interested to hear the argument or defense behind this claim. Is it intended to apply only to Mongo-backed applications? Or it is intended to apply across database technologies? It would seem that The Ruby on Rails Guide to ActiveRecord Validations and Callbacks might disagree, at least when it comes to relational databases. Take this example: ``` class Order < ActiveRecord::Base before_save :normalize_card_number, :if => :paid_with_card? end ``` In my opinion, this is a perfect example of a simple callback that implements domain logic. It seems quick and effective. If I was to take the Mongoid advice, where would this logic go instead?

Original source