2011-02-01 7 views
10

En una aplicación en la que estoy trabajando actualmente, veo muchos observadores. De hecho, esto me está creando muchos problemas mientras hago cambios en el código, agrego nuevas funcionalidades, ya que estos observadores causan toneladas de efectos secundarios.Observadores de rieles: cuándo y cuándo no utilizar observadores en rieles

Me gustaría saber las ocasiones que demandan un observador y las personas que tienen experiencias empíricas o personales cuando uno se siente tentado a caer en la trampa del observador.

Su valiosa experiencia, historias de guerra y pensamientos están en demanda. Por favor grita!

+0

Para aquellos en contra de Observadores, considere usar [Usar casos] (http://webuild.envato.com/blog/a-case-for-use-cases/) que es una secuencia de pasos (de lógica de negocios) para ser ejecutado. No es difícil ejecutar su propio caso de uso, pero también hay algunas gemas decentes, p. [solid_use_case] (https://github.com/mindeavor/solid_use_case) o [use_case] (https://github.com/cjohansen/use_case) – Dennis

Respuesta

28

Siento que los observadores reciben una mala reputación en gran parte porque las personas las agrupan con las devoluciones de llamada del ciclo de vida de ActiveRecord como si fueran lo mismo. Estoy de acuerdo con que gran parte de la opinión popular sobre las devoluciones de llamadas del ciclo de vida es fácil de usar, metiéndote en un enredo, pero personalmente soy un gran admirador de los observadores por mantener las cosas fuera de las clases modelo que no son la principal responsabilidad del modelo en particular. Aquí hay una pista: los observadores de Rails se inspiraron en parte en la programación orientada a aspectos: se trata de cuestiones transversales. Si pone la lógica comercial en observadores que están estrechamente relacionados con los modelos que están observando, lo está haciendo incorrectamente.

Son ideales para mantener el desorden fuera de las clases del modelo, como la caducidad de la caché (barrenderos), las notificaciones de varias clases, actualizaciones flujo de actividad, dando inicio a trabajos en segundo plano para realizar un seguimiento de eventos de análisis personalizados, cachés calientes, etc.

Enfáticamente estoy en desacuerdo con BlueFish sobre los observadores que son difíciles de probar correctamente la unidad. Este es precisamente el punto más importante que los distingue de las devoluciones de llamadas del ciclo de vida: puede poner a prueba a los observadores de forma aislada, y eso lo desanima a caer en muchas de las trampas de diseño pesadas de estado y orden a las que se refiere BlueFish (que de nuevo creo que es más frecuente verdadero de las devoluciones de llamadas del ciclo de vida).

Aquí está mi receta:

  1. Desactivar todos los observadores en su conjunto de pruebas por defecto. No deberían complicar las pruebas de su modelo porque deberían tener preocupaciones separadas de todos modos. No es necesario probar de forma unitaria que los observadores realmente disparen, porque el conjunto de pruebas de ActiveRecord hace eso, y sus pruebas de integración lo cubrirán. Utilice el formulario de bloque de ActiveRecord::Base.observers.enable si realmente cree que hay una buena razón para permitir que un observador realice algunas pruebas pequeñas de su unidad, pero es probable que sea un indicador de mal uso o un problema de diseño.
  2. Habilite los observadores para sus pruebas de integración solo. Las pruebas de integración, por supuesto, deben ser completas y usted debe verificar el comportamiento del observador en ellas, como todo lo demás.
  3. Pruebe por unidad sus clases de observador en aislamiento (invoque los métodos como after_create directamente). Si un observador no es parte de la lógica comercial de su modelo observado, probablemente no dependerá mucho de los detalles de estado de la instancia del modelo, y no debería requerir mucha configuración de prueba. A menudo puede burlarse de los colaboradores aquí si tiene una confianza razonable en que sus pruebas de integración cubren lo que más le importa.

Aquí está mi texto modelo estándar spec/support/observers.rb para aplicaciones usando 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 

Y here is a real-world example que espero que ilustra un buen caso para el uso de un observador, y la prueba sin dolor.

+0

¡Gran respuesta! Estoy de acuerdo contigo. Y, gracias por tomarse su tiempo libre respondiendo esta pregunta :-) – karthiks

+0

Puedo sugerir la gema Wisper (https://github.com/krisleech/wisper) que resuelve muchos de los problemas establecidos en la respuesta. – Kris

6

mi humilde opinión - observadores Suck

voy a pasar por una serie de razones por las que creo que hacen. Ten en cuenta que esto también se aplica en general al uso de los métodos before_x o after_x, que son ejemplos más fragmentarios del observador general.

hace que sea difícil escribir pruebas unitarias adecuadas

Por lo general, al escribir pruebas de unidad, que está probando una pieza específica de funcionalidad. Sin embargo, para poner a prueba a un observador, es necesario "activar" el evento para probarlo, y en ocasiones es simplemente inconveniente.

E.g. Si conecta un observador a before_save, entonces para activar el código, debe guardar el modelo. Esto dificulta la prueba, dado que es posible que esté probando lógica comercial, no persistencia. Si simula el guardado, es posible que su desencadenador no funcione. Y si lo dejas guardar, entonces tus pruebas son lentas.

Requiere estado

A raíz del hecho de que los observadores tienden a ser difíciles de probar, los observadores también tienden a requerir una gran cantidad de estado. La razón es porque la lógica en un observador está tratando de distinguir entre varios "eventos de negocios" y la única manera de hacerlo es observar el estado del objeto. Esto requiere una gran cantidad de configuración en sus pruebas, y por lo tanto hace que las pruebas sean difíciles, tediosas y problemáticas.

consecuencias no intencionadas

sin duda, que acaban de experimentar debido a que se puede conectar múltiples observaciones, no tiene idea de lo que podría estar desencadenando diversos comportamientos. Esto conduce a consecuencias no deseadas, una que solo puede detectar a través de la integración/prueba del sistema (retroalimentación lenta). Rastrear a tus observadores tampoco es muy divertido.

problemas supuestos Solicitar

no hay garantía de que un observador puede patear. Sólo se garantiza que se inició. Si tiene un orden implícito como parte de las reglas de su negocio, entonces los observadores están equivocados.

conduce a una mala diseño

Añadiendo cosas mediante la conexión de los observadores conduce a diseños pobres. Tiende a llevarlo a conectar todo para guardar, eliminar y crear eventos, lo cual, aunque conveniente, también es difícil de entender. P.ej. salvar a un usuario podría significar que está actualizando los detalles del usuario, o podría significar que le está agregando un nuevo nombre de cuenta.Saber lo que puede hacer específicamente con un objeto es parte de la razón por la cual tiene métodos y nombres significativos basados ​​en la acción. Si todo es un observador, se pierde y todo está respondiendo a los eventos, y dentro de su lógica de observación, usted tiende a tratar de distinguir el evento, que pertenece a qué evento de negocios.

Hay algunos lugares donde un observador es amable, pero eso suele ser una excepción. Es mucho mejor injertar lo que se puede hacer explícitamente, en lugar de codificar la lógica implícitamente mediante devoluciones de llamada.

5

Estoy parcialmente de acuerdo con BlueFish, ya que los observadores pueden introducir una complejidad innecesaria, sin embargo, los observadores son útiles para separar las preocupaciones del objeto.

Por ejemplo, en un modelo AR de pago, es posible que desee entregar un recibo después de la creación. Utilizando una devolución de llamada AR after_create regular, si el método deliver_receipt fallara, el pago no se escribiría en la base de datos. ¡Vaya! Sin embargo, en un observador, el pago aún se guardará. Uno podría argumentar que las fallas deberían ser resueltas por rescate, pero todavía creo que no pertenecen allí; pertenece a un observador

+0

Punto de vista interesante. Sin embargo, ¿no sería mejor impulsar la generación de recibos como delayyed_job, como parte de after_create? De esta forma, los observadores son evitados y el código también es mantenible. – karthiks

+0

Agregarlo como un trabajo retrasado es sin duda una opción, sin embargo, enviar un correo electrónico no es una operación muy costosa, por lo que IMO no justifica agregar esa complejidad. – Zubin

+0

¿No debería el objeto de pago tener una máquina de estado y debería estar enganchado en la transición de incompleto -> fallido O incompleto -> exitoso? Algo como 'state_machine' le permite escribir observadores para tales cambios de estado, que se asignan a eventos de negocios en lugar de eventos DB/persistencia. –

Cuestiones relacionadas