ruby-on-rails design-patterns oop activerecord

ruby on rails - ¿Por qué odian todo el Registro Activo?



ruby-on-rails design-patterns (14)

La pregunta es sobre el patrón de diseño Active Record. No es una herramienta de orm.

La pregunta original está etiquetada con rieles y se refiere a Twitter, que está construida en Ruby on Rails. El marco ActiveRecord dentro de Rails es una implementación del patrón de diseño Active Record de Fowler.

A medida que aprendo más y más sobre OOP, y comienzo a implementar varios patrones de diseño, sigo volviendo a los casos donde las personas odian Active Record .

A menudo, las personas dicen que no se escala bien (citando a Twitter como su principal ejemplo), pero nadie realmente explica por qué no se escala bien; y / o cómo lograr los pros de AR sin los contras (a través de un patrón similar pero diferente?)

Afortunadamente, esto no se convertirá en una guerra santa sobre los patrones de diseño; todo lo que quiero saber es qué es lo que está mal con Active Record.

Si no escala bien, ¿por qué no?

¿Qué otros problemas tiene?


@BlaM: A veces justed implementé un registro activo para el resultado de una unión. No siempre tiene que ser la relación Tabla <-> Registro activo. ¿Por qué no "Resultado de una declaración Join" <-> Active Record?


Algunos mensajes me están confundiendo. Algunas respuestas van a "ORM" frente a "SQL" o algo así.

El hecho es que AR es solo un patrón de programación de simplificación en el que aprovecha sus objetos de dominio para escribir allí el código de acceso a la base de datos.

Estos objetos generalmente tienen atributos comerciales (propiedades del bean) y algunos comportamientos (métodos que generalmente funcionan en estas propiedades).

El AR simplemente dice "agregar algunos métodos a estos objetos de dominio" a las tareas relacionadas con la base de datos.

Y tengo que decir, desde mi opinión y experiencia, que no me gusta el patrón.

A primera vista, puede sonar bastante bien. Algunas herramientas modernas de Java como Spring Roo usan este patrón.

Para mí, el verdadero problema es solo con la preocupación de OOP. El patrón AR te obliga de alguna manera a agregar una dependencia desde tu objeto a los objetos de la infraestructura. Estos objetos de infraestructura permiten que el objeto de dominio consulte la base de datos a través de los métodos sugeridos por AR.

Siempre he dicho que dos capas son clave para el éxito de un proyecto. La capa de servicio (donde reside la lógica de negocio o puede exportarse a través de algún tipo de tecnología de comunicación remota, como servicios web, por ejemplo) y la capa de dominio. En mi opinión, si agregamos algunas dependencias (realmente no necesarias) a los objetos de la capa de dominio para resolver el patrón AR, nuestros objetos de dominio serán más difíciles de compartir con otras capas o aplicaciones (raras) externas.

La implementación de AR de Spring Roo es interesante, ya que no depende del objeto en sí, sino de algunos archivos AspectJ. Pero si luego no quiere trabajar con Roo y tiene que refactorizar el proyecto, los métodos AR se implementarán directamente en los objetos de su dominio.

Otro punto de vista. Imagine que no usamos una Base de datos relacional para almacenar nuestros objetos. Imagine que la aplicación almacena nuestros objetos de dominio en una base de datos NoSQL o solo en archivos XML, por ejemplo. ¿Implementaremos los métodos que hacen estas tareas en nuestros objetos de dominio? No lo creo (por ejemplo, en el caso de XM, agregaríamos dependencias relacionadas con XML a nuestros objetos de dominio ... Realmente triste). ¿Por qué entonces tenemos que implementar los métodos DB relacionales en los objetos de dominio, como dice el patrón Ar?

En resumen, el patrón AR puede sonar más simple y bueno para aplicaciones pequeñas y simples. Pero, cuando tenemos aplicaciones complejas y grandes, creo que la arquitectura estratificada clásica es un mejor enfoque.


Aunque todos los demás comentarios sobre la optimización de SQL son ciertamente válidos, mi principal queja con el patrón de registro activo es que generalmente conduce a una falta de coincidencia de impedancia . Me gusta mantener mi dominio limpio y adecuadamente encapsulado, que el patrón de registro activo usualmente destruye toda esperanza de hacer.


Creo que hay una serie de razones muy diferentes entre por qué las personas están "odiando" a ActiveRecord y lo que está "mal" con eso.

En cuanto al tema del odio, hay mucho veneno por todo lo relacionado con Rails. En cuanto a lo que está mal, es probable que sea como toda tecnología y hay situaciones en las que es una buena opción y situaciones en las que hay mejores opciones. La situación en la que no puede aprovechar la mayoría de las funciones de Rails ActiveRecord, en mi experiencia, es donde la base de datos está mal estructurada. Si está accediendo a datos sin claves primarias, con cosas que violan la primera forma normal, donde hay muchos procedimientos almacenados necesarios para acceder a los datos, es mejor utilizar algo que sea más que un contenedor SQL. Si su base de datos está relativamente bien estructurada, ActiveRecord le permite aprovechar eso.

Para agregar al tema de responder a los comentaristas que dicen que las cosas son difíciles en ActiveRecord con una réplica de fragmento de código

@Sam McAfee Supongamos que tiene una clase de usuario en su dominio y necesita tener referencias o colecciones de otros objetos, ya cargados cuando recupera ese objeto de usuario. Los datos pueden venir de muchas tablas diferentes, y un patrón de ActiveRecord puede hacerlo realmente difícil.

user = User.find(id, :include => ["posts", "comments"]) first_post = user.posts.first first_comment = user.comments.first

Al usar la opción de inclusión, ActiveRecord le permite anular el comportamiento predeterminado de carga lenta.


El problema con ActiveRecord es que las consultas que genera automáticamente pueden causar problemas de rendimiento.

Terminas haciendo algunos trucos poco intuitivos para optimizar las consultas que te dejan preguntándote si hubiera sido más efectivo en el tiempo escribir la consulta a mano en primer lugar.


El problema que veo con Active Records es que siempre se trata de una sola tabla. Está bien, siempre y cuando realmente trabajes con esa única tabla, pero cuando trabajas con datos en la mayoría de los casos tendrás algún tipo de unión en alguna parte.

Sí, unirse suele ser peor que no participar cuando se trata de rendimiento, pero unirse es mejor que unirse a "falso" leyendo primero toda la tabla A y luego usando la información obtenida para leer y filtrar la tabla B.


Hay ActiveRecord the Design Pattern y ActiveRecord the Rails ORM Library , y también hay un montón de imitaciones para .NET y otros idiomas.

Estas son todas cosas diferentes. En su mayoría siguen ese patrón de diseño, pero lo amplían y modifican de muchas maneras diferentes, por lo que antes de que alguien diga "ActiveRecord Sucks" necesita calificar diciendo "¿qué ActiveRecord, hay montones?"

Solo estoy familiarizado con ActiveRecord de Rails, intentaré abordar todas las quejas que se han planteado en el contexto de su uso.

@BlaM

El problema que veo con Active Records es que siempre se trata de una sola tabla

Código:

class Person belongs_to :company end people = Person.find(:all, :include => :company )

Esto genera SQL con las LEFT JOIN companies on companies.id = person.company_id , y automáticamente genera objetos Company asociados para que pueda hacer people.first.company y no necesite golpear la base de datos porque los datos ya están presentes.

@ pix0r

El problema inherente a Active Record es que las consultas de la base de datos se generan y ejecutan automáticamente para rellenar objetos y modificar los registros de la base de datos.

Código:

person = Person.find_by_sql("giant complicated sql query")

Esto se desaconseja, ya que es feo, pero para los casos en los que simplemente necesitas escribir SQL sin procesar, es fácil de hacer.

@Tim Sullivan

... y seleccionas varias instancias del modelo, básicamente estás haciendo un "select * from ..."

Código:

people = Person.find(:all, :select=>''name, id'')

Esto solo seleccionará las columnas de nombre e ID de la base de datos, todos los demás ''atributos'' en los objetos correlacionados serán nulos, a menos que recargue manualmente ese objeto, y así sucesivamente.


Intenta hacer una relación polimórfica de muchos a muchos. No tan fácil. Especialmente cuando no estás usando STI.


Lo principal que he visto con respecto a las quejas sobre Active Record es que cuando crea un modelo alrededor de una tabla y selecciona varias instancias del modelo, básicamente está haciendo un "select * from ...". Esto está bien para editar un registro o mostrar un registro, pero si quiere, por ejemplo, mostrar una lista de las ciudades para todos los contactos en su base de datos, puede hacer "seleccionar Ciudad de ..." y solo obtener las ciudades . Hacer esto con Active Record requeriría que esté seleccionando todas las columnas, pero solo con City.

Por supuesto, variadas implementaciones manejarán esto de manera diferente. Sin embargo, es un problema.

Ahora puede solucionar esto creando un nuevo modelo para lo específico que está tratando de hacer, pero algunas personas argumentarán que es más un esfuerzo que un beneficio.

Yo, me dedico a Active Record. :-)

HTH


Me encanta cómo SubSonic hace lo de una columna.
Ya sea

DataBaseTable.GetList(DataBaseTable.Columns.ColumnYouWant)

, o:

Query q = DataBaseTable.CreateQuery() .WHERE(DataBaseTable.Columns.ColumnToFilterOn,value); q.SelectList = DataBaseTable.Columns.ColumnYouWant; q.Load();

Pero Linq sigue siendo el rey cuando se trata de la carga perezosa.


Mi respuesta larga y tardía, ni siquiera completa, sino una buena explicación POR QUÉ odio este patrón, opiniones e incluso algunas emociones:

1) versión corta: Active Record crea una " capa delgada " de " enlace fuerte " entre la base de datos y el código de la aplicación. Lo cual no resuelve ningún problema lógico, no importa lo que suceda, ningún problema en absoluto. En mi humilde opinión no proporciona CUALQUIER VALOR, excepto algo de azúcar sintáctica para el programador (que luego puede usar una "sintaxis de objeto" para acceder a algunos datos, que existe en una base de datos relacional). El esfuerzo para crear algo de comodidad para los programadores debería (IMHO ...) invertirse mejor en herramientas de acceso a base de datos de bajo nivel, por ejemplo, algunas variaciones de simple, fácil, simple hash_map get_record( string id_value, string table_name, string id_column_name="id" ) y métodos similares (por supuesto, los conceptos y la elegancia varían mucho con el idioma utilizado).

2) versión larga: en los proyectos basados ​​en bases de datos donde tenía el "control conceptual" de las cosas, evitaba AR, y era bueno. Normalmente construyo una arquitectura en capas (tarde o temprano divide su software en capas, al menos en proyectos de tamaño mediano a grande):

A1) la base de datos en sí, tablas, relaciones, incluso alguna lógica si el DBMS lo permite (MySQL también es adulto ahora)

A2) muy a menudo, hay más que un almacén de datos: sistema de archivos (blobs en la base de datos no siempre es una buena decisión ...), sistemas heredados (imagínese "cómo" se accederá, muchas variedades posibles ... pero eso es no es la cuestión...)

B) capa de acceso a la base de datos (en este nivel, los métodos de herramienta, ayudantes para acceder fácilmente a los datos en la base de datos son bienvenidos, pero AR no proporciona ningún valor aquí, excepto un poco de azúcar sintáctico)

C) capa de objetos de aplicación: "objetos de aplicación" a veces son simples filas de una tabla en la base de datos, pero la mayoría de las veces son objetos compuestos y tienen una lógica más alta adjunta, por lo que invertir tiempo en objetos AR en este nivel es simplemente inútil , una pérdida de tiempo de codificadores preciosos, porque el "valor real", la "lógica superior" de esos objetos debe implementarse encima de los objetos AR, de todos modos, ¡con y sin AR! Y, por ejemplo, ¿por qué querría tener una abstracción de "Log entry objects"? El código de aplicación lógica los escribe, pero ¿debería tener la capacidad de actualizarlos o borrarlos? suena tonto, y App::Log("I am a log message") es algunas magnitudes más fáciles de usar que le=new LogEntry(); le.time=now(); le.text="I am a log message"; le.Insert(); le=new LogEntry(); le.time=now(); le.text="I am a log message"; le.Insert(); . Y por ejemplo: usar un "objeto de entrada de registro" en la vista de registro en su aplicación funcionará para 100, 1000 o incluso 10000 líneas de registro, pero tarde o temprano tendrá que optimizar, y apuesto a que en la mayoría de los casos, solo use esa pequeña y bella declaración SELECT de SQL en la lógica de su aplicación (que rompe totalmente la idea de AR ...), en lugar de envolver esa pequeña afirmación en marcos de ideas de AR rígidos y fijos con muchos envoltorios de código y ocultándolos. El tiempo que desperdiciaste escribiendo o construyendo código AR podría haber sido invertido en una interfaz mucho más inteligente para leer listas de entradas de registro (muchas, muchas formas, el cielo es el límite). Los codificadores deberían atreverse a inventar nuevas abstracciones para comprender su lógica de aplicación que se ajusta a la aplicación prevista, y no a implementar estúpidamente patrones tontos , ¡que suenan bien a primera vista!

D) la lógica de la aplicación: implementa la lógica de objetos interactivos y crea, elimina y enumera (!) Objetos lógicos de la aplicación (NO, esas tareas rara vez deben anclarse en los objetos lógicos de la aplicación: ¿la hoja de papel en su escritorio lo dice? usted los nombres y las ubicaciones de todas las otras hojas en su oficina? Olvidémonos de los métodos "estáticos" para enumerar objetos, eso es una tontería, un mal compromiso creado para hacer que la forma humana de pensar encaje en [algo-no-todo-AR-framework-like -] AR pensamiento)

E) la interfaz de usuario; bueno, lo que escribiré en las siguientes líneas es muy, muy, muy subjetivo, pero en mi experiencia, los proyectos que se basaban en AR a menudo descuidaban la parte de la interfaz de usuario de una aplicación; el tiempo se desperdiciaba en abstracciones desconocidas de la creación . Al final, tales aplicaciones desperdiciaron un montón de tiempo de codificadores y se sintieron como aplicaciones de codificadores para codificadores, con inclinaciones tecnológicas internas y externas. Los codificadores se sienten bien (el trabajo duro finalmente terminado, todo terminado y correcto, de acuerdo con el concepto en papel ...), y los clientes "solo tienen que aprender que debe ser así", porque eso es "profesional". ok, lo siento, estoy divagando ;-)

Bueno, es cierto que todo esto es subjetivo, pero es mi experiencia (Ruby on Rails excluido, puede ser diferente, y no tengo experiencia práctica con ese enfoque).

En proyectos pagos, a menudo escuché la demanda de comenzar creando algunos objetos de "registro activo" como un bloque de construcción para la lógica de aplicación de nivel superior. En mi experiencia, esto a menudo era una especie de excusa para que el cliente (una empresa de desarrollo de software en la mayoría de los casos) no tuviera un buen concepto, una gran visión, una visión general de lo que el producto debería ser finalmente. Esos clientes piensan en marcos rígidos ("en el proyecto hace diez años funcionó bien ..."), pueden dar forma a las entidades, pueden definir relaciones entre entidades, pueden descomponer las relaciones de datos y definir la lógica básica de la aplicación, pero luego se detienen y entregárselo, y pensar que eso es todo lo que necesita ... a menudo carecen de un concepto completo de lógica de aplicación, interfaz de usuario, usabilidad, etc. ... carecen de la gran visión y les falta amor por el detalles, y quieren que sigas ese camino de AR, porque ... bueno, ¿por qué funcionó en ese proyecto hace años, mantiene a las personas ocupadas y en silencio? No lo sé. Pero los "detalles" separan a los hombres de los niños, o ... ¿cómo fue el eslogan original del anuncio? ;-)

Después de muchos años (diez años de experiencia en desarrollo activo), cada vez que un cliente menciona un "patrón de registro activo", suena mi timbre de alarma. Aprendí a intentar que vuelvan a esa fase conceptual esencial , les pido que piensen dos veces, que intenten mostrar sus debilidades conceptuales o que simplemente los eviten si no saben nada (al final, ya saben, un cliente que todavía no lo ha hecho) sé lo que quiere, tal vez incluso piensa que sabe pero no lo hace, o trata de externalizar el trabajo conceptual para MÍ gratis, me cuesta muchas horas, días, semanas y meses preciosos de mi tiempo, la vida es demasiado corta ...).

Por lo tanto, finalmente: ESTO ES TODO es por qué odio ese "patrón de registro activo" tonto, y lo hago y lo evitaré siempre que sea posible.

EDITAR : incluso llamaría a esto No-Pattern. No resuelve ningún problema (los patrones no están destinados a crear azúcar sintáctico). Crea muchos problemas: la raíz de todos sus problemas (mencionados en muchas respuestas aquí ...) es que oculta el buen viejo y potente SQL detrás de una interfaz que es por definición de patrones extremadamente limitada.

¡Este patrón reemplaza la flexibilidad con azúcar sintáctico!

Piénselo, ¿qué problema resuelve AR para usted?


Siempre he encontrado que ActiveRecord es bueno para aplicaciones rápidas basadas en CRUD donde el Modelo es relativamente plano (como en, no muchas jerarquías de clase). Sin embargo, para aplicaciones con jerarquías OO complejas, un DataMapper es probablemente una mejor solución. Si bien ActiveRecord asume una relación de 1: 1 entre sus tablas y sus objetos de datos, ese tipo de relación se vuelve difícil de manejar con dominios más complejos. En su libro sobre patrones , Martin Fowler señala que ActiveRecord tiende a romperse en condiciones en las que su Modelo es bastante complejo, y sugiere un DataMapper como alternativa.

He encontrado que esto es cierto en la práctica. En los casos en que tiene una gran cantidad de herencia en su dominio, es más difícil mapear la herencia de su RDBMS que mapear asociaciones o composición.

La forma en que lo hago es tener objetos de "dominio" a los que acceden los controladores a través de estas clases de DataMapper (o "capa de servicio"). Estos no reflejan directamente la base de datos, sino que actúan como su representación OO para algún objeto del mundo real. Supongamos que tiene una clase de usuario en su dominio y necesita tener referencias o colecciones de otros objetos, ya cargados cuando recupera ese objeto de usuario. Los datos pueden venir de muchas tablas diferentes, y un patrón de ActiveRecord puede hacerlo realmente difícil.

En lugar de cargar el objeto User directamente y acceder a los datos mediante una API de estilo ActiveRecord, el código de su controlador recupera un objeto User llamando a la API del método UserMapper.getUser (), por ejemplo. Es ese mapeador el responsable de cargar los objetos asociados de sus respectivas tablas y devolver el objeto "dominio" de usuario completo a la persona que llama.

Básicamente, solo está agregando otra capa de abstracción para hacer que el código sea más manejable. Ya sea que sus clases DataMapper contengan SQL personalizado sin procesar, o llamadas a una API de capa de abstracción de datos, o incluso que accedan a un patrón ActiveRecord en sí mismas, realmente no le importa al código del controlador que está recibiendo un objeto de usuario agradable y poblado.

De todos modos, así es como lo hago.


Voy a hablar de Active Record como un patrón de diseño, no he visto ROR.

Algunos desarrolladores odian Active Record, porque leen libros inteligentes sobre cómo escribir código limpio y ordenado, y estos libros establecen que el registro activo viola el principio de resposobilidad única, viola la regla DDD de que el objeto de dominio debe ser ignorante persistente y muchas otras reglas de este tipo de libros .

La segunda cosa que los objetos de dominio en Active Record tienden a ser de 1 a 1 con la base de datos, que puede considerarse una limitación en algún tipo de sistema (n-tier principalmente).

Eso es solo cosas abstractas, no he visto la implementación real de Ruby on Rails de este patrón.