Наблюдатели Rails - когда и когда не следует использовать наблюдателей в Rails

В приложении, которое я сейчас работаю, я вижу много наблюдателей. Это действительно создает много проблем для меня, когда я делаю изменения кода, добавляю новые функции, поскольку эти наблюдатели вызывают массу побочных эффектов.

Я хотел бы знать случаи, которые требуют, чтобы Наблюдатель и те люди имели опыт, как эмпирический, так и личный, когда возникает соблазн попасть в ловушку наблюдателя.

Ваш ценный опыт, военные истории и мысли востребованы. Пожалуйста, кричите!

Ответ 1

Я чувствую, что наблюдатели получают плохой рэп, потому что люди объединяют их с обратными вызовами жизненного цикла ActiveRecord как одно и то же. Я согласен с тем, что многие популярные мнения о обратных вызовах жизненного цикла легко ошибаются, попадая в путаницу, но я лично являюсь большим поклонником наблюдателей за то, что они не учитывают модели классов, которые не являются основной ответственностью модели. Здесь намек: наблюдатели Rails были частично вдохновлены аспектно-ориентированным программированием - они касаются сквозных проблем. Если вы ставите бизнес-логику в наблюдателях, которые тесно связаны с моделями, которые они наблюдают, вы делаете это неправильно IMO.

Они идеально подходят для хранения беспорядка из классов моделей, таких как кеширование (подметальные машины), уведомления различного рода, обновления потока активности, запуск фоновых заданий для отслеживания пользовательских событий аналитики, теплых кешей и т.д.

Я категорически не согласен с тем, что BlueFish о том, что наблюдатели трудны правильно unit test. Это самый важный момент, который отличает их от обратных вызовов жизненного цикла: вы можете тестировать наблюдателей изолированно, и это препятствует тому, чтобы вы не попадали во многие из недостатков дизайна и дизайна, которые BlueFish ссылается (что опять же я думаю чаще true для обратных вызовов жизненного цикла).

Здесь мой рецепт:

  • Отключить всех наблюдателей в вашем тестовом наборе по умолчанию. Они не должны усложнять ваши модельные тесты, потому что в любом случае у них должны быть отдельные проблемы. Вам не нужно unit test, чтобы наблюдатели действительно стреляли, потому что набор тестов ActiveRecord делает это, и ваши интеграционные тесты будут охватывать его. Используйте блочную форму ActiveRecord::Base.observers.enable, если вы действительно считаете, что есть веская причина, позволяющая наблюдателю выполнить небольшую часть ваших модульных тестов, но это, вероятно, показатель неправильного использования или проблемы с дизайном.
  • Включить наблюдателей только для ваших тестов интеграции. Конечно, интеграционные тесты должны быть полными, и вы должны проверять поведение наблюдателей в них, как и все остальное.
  • Unit-test ваши классы наблюдателей изолированно (сразу вызовите методы, подобные after_create). Если наблюдатель не является частью наблюдаемой модельной бизнес-логики, он, вероятно, не будет сильно зависеть от сведений о состоянии экземпляра модели и не требует большой настройки теста. Вы часто можете издеваться над коллабораторами, если вы достаточно уверены в том, что ваши интеграционные тесты охватывают то, что вас больше всего волнует.

Здесь мой стандартный шаблон spec/support/observers.rb для приложений, использующих RSpec:

RSpec.configure do |config|
  # Assure we're testing models in isolation from Observer behavior. Enable
  # them explicitly in a block if you need to integrate against an Observer --
  # see the documentation for {ActiveModel::ObserverArray}.
  config.before do
    ActiveRecord::Base.observers.disable :all
  end

  # Integration tests are full-stack, lack of isolation is by design.
  config.before(type: :feature) do
    ActiveRecord::Base.observers.enable :all
  end
end

И вот пример реального мира, который, я надеюсь, иллюстрирует хороший пример использования наблюдателя и без него.

Ответ 2

ИМХО - Наблюдатели сосут

Я рассмотрю ряд причин, почему я думаю, что они это делают. Имейте в виду, что это применимо, в общем, к использованию методов before_x или after_x, которые являются более фрагментарными примерами общего Observer.

Сложно писать правильные модульные тесты

Как правило, при написании модульных тестов вы тестируете определенную функциональность. Однако, чтобы проверить наблюдателя, вам нужно "запустить" событие, чтобы проверить его, и иногда это просто неудобно.

например. Если вы подключите наблюдателя к before_save, то для запуска кода вам нужно сохранить модель. Это затрудняет тестирование, учитывая, что вы можете тестировать бизнес-логику, а не настойчивость. Если вы издеваетесь над сохранением, ваш триггер может не работать. И если вы позволите сохранить его, ваши тесты будут медленными.

Требуется состояние

Следуя тому факту, что наблюдатели, как правило, трудно тестировать, наблюдатели также нуждаются в большом количестве состояний. Причина в том, что логика в наблюдателе пытается различать различные "бизнес-события", и единственный способ сделать это - посмотреть на состояние объекта. Это требует большого количества настроек в ваших тестах и, следовательно, делает тестирование жестким, утомительным и проблематичным.

Непреднамеренные последствия

Несомненно, вы только что испытали это, потому что вы можете подключить несколько наблюдений, вы не представляете, что может вызвать различные виды поведения. Это приводит к непредвиденным последствиям, которые вы можете получить только через интеграцию/тестирование системы (медленная обратная связь). Отслеживание ваших наблюдателей также не очень весело.

Проблемы с допуском порядка

Нет гарантии, что наблюдатель может нанести удар. Вы гарантированы только тем, что он будет стартовать. Если у вас есть неявный порядок как часть ваших бизнес-правил, тогда наблюдатели ошибаются.

Приводит к плохому дизайну

Добавление вещей путем подключения наблюдателей приводит к плохим проектам. Это приводит к тому, что вы можете подключить все, чтобы сохранить, удалить, создать события, которые, в то же время удобные, также трудно понять. Например. сохранение пользователя может означать, что вы обновляете данные пользователя, или это может означать, что вы добавляете к нему новое имя учетной записи. Знание того, что вы можете сделать специально для объекта, является частью причины, по которой у вас есть методы и значащие имена на основе действий. Если все является наблюдателем, то это теряется, и теперь все реагирует на события, и в рамках вашей логики наблюдения вы пытаетесь отличить событие, принадлежащее к бизнес-событию.

Есть места, где наблюдатель хорош, но это обычно исключение. Гораздо лучше использовать то, что можно сделать явно, а не кодировать логику неявно через обратные вызовы.

Ответ 3

Я частично согласен с BlueFish, поскольку наблюдатели могут ввести излишнюю сложность, однако наблюдатели полезны для ветки проблем от объекта.

Например, в модели Payment AR вы можете отправить квитанцию ​​после создания. Используя обычный обратный вызов AR after_create, если метод deliver_receipt не выполнен, платеж не будет записан в базу данных - oops! Однако в наблюдателе платеж все равно будет сохранен. Можно утверждать, что неудачи должны решаться путем спасения, но я все еще думаю, что он не принадлежит к ним; он принадлежит наблюдателю.