Pregunta

A medida que aprendo más y más sobre programación orientada a objetos y empiezo a implementar varios patrones de diseño, sigo volviendo a casos en los que la gente odia Registro activo.

A menudo, la gente dice que no escala bien (citando a Twitter como su principal ejemplo), pero en realidad nadie lo explica. por qué no escala bien;y/o cómo lograr las ventajas de la RA sin las desventajas (¿mediante un patrón similar pero diferente?)

Esperemos que esto no se convierta en una guerra santa sobre patrones de diseño; todo lo que quiero saber es ****específicamente**** qué le pasa a Active Record.

Si no escala bien, ¿por qué no?

¿Qué otros problemas tiene?

¿Fue útil?

Solución

hay ActiveRecord el patrón de diseño y ActiveRecord la biblioteca ORM de Rails, y también hay un montón de imitaciones para .NET y otros lenguajes.

Todas estas son 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 apesta", debe calificarlo 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 solo de una tabla.

Código:

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

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

@pix0r

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

Código:

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

Esto se desaconseja porque es feo, pero para los casos en los que simplemente necesita escribir SQL sin formato, se hace fácilmente.

@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 mapeados serán nulos, a menos que vuelva a cargar ese objeto manualmente, y así sucesivamente.

Otros consejos

Siempre he descubierto que ActiveRecord es bueno para aplicaciones rápidas basadas en CRUD donde el modelo es relativamente plano (es decir, no hay muchas jerarquías de clases).Sin embargo, para aplicaciones con jerarquías OO complejas, un Mapeador de datos Probablemente sea una mejor solución.Si bien ActiveRecord asume una proporció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 fallar en condiciones en las que su modelo es bastante complejo y sugiere una Mapeador de datos como alternativa.

He descubierto que esto es cierto en la práctica.En los casos en los que tiene mucha herencia en su dominio, es más difícil asignar la herencia a su RDBMS que asignar asociaciones o composición.

La forma en que lo hago es tener objetos de "dominio" a los que sus controladores acceden 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 cargadas cuando recupera ese objeto de Usuario.Los datos pueden provenir de muchas tablas diferentes y un patrón ActiveRecord puede hacerlo realmente difícil.

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

Básicamente, simplemente estás agregando otra capa de abstracción para hacer que el código sea más manejable.Si sus clases de DataMapper contienen SQL personalizado sin procesar, o llamadas a una API de capa de abstracción de datos, o incluso acceder a un patrón ActiveRecord, realmente no le importa al código del controlador que recibe un objeto Usuario agradable y poblado.

De todos modos, así es como lo hago.

Creo que es probable que exista un conjunto de razones muy diferentes entre por qué la gente "odia" ActiveRecord y qué tiene de "malo".

En cuanto al tema del odio, hay mucho veneno hacia todo lo relacionado con Rails.En cuanto a lo que tiene de malo, es probable que sea como toda la tecnología y hay situaciones en las que es una buena opción y situaciones en las que hay mejores opciones.En mi experiencia, la situación en la que no puedes aprovechar la mayoría de las características de Rails ActiveRecord es cuando la base de datos está mal estructurada.Si accede a datos sin claves primarias, con cosas que violan la primera forma normal, donde se requieren muchos procedimientos almacenados para acceder a los datos, es mejor que use 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 un 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 cargadas cuando recupera ese objeto de Usuario.Los datos pueden provenir de muchas tablas diferentes y un patrón ActiveRecord puede hacerlo realmente difícil.

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

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

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

1) versión corta:Active Record crea un "capa delgada" de "unión fuerte" entre la base de datos y el código de la aplicación.Lo cual no resuelve ningún problema lógico, ni ningún problema, ningún problema en absoluto.En mi humilde opinión, no proporciona NINGÚN VALOR, excepto algunos azúcar sintáctica para el programador (que luego puede usar una "sintaxis de objeto" para acceder a algunos datos que existen en una base de datos relacional).El esfuerzo por crear cierta comodidad para los programadores debería (en mi humilde opinión...) invertirse mejor en herramientas de acceso a bases de datos de bajo nivel, p.algunas variaciones de simple, fácil, sencillo 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 según el lenguaje utilizado).

2) versión larga:En cualquier proyecto basado en bases de datos en el que tuviera el "control conceptual" de las cosas, evité la RA y fue bueno.Normalmente construyo un arquitectura en capas (Tarde o temprano divides tu software en capas, al menos en proyectos de tamaño mediano a grande):

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

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

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

C) capa de objetos de aplicación:Los "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 compuesto objetos de todos modos, y tienen una lógica superior adjunta, por lo que invertir tiempo en objetos AR en este nivel es simplemente inútil, una pérdida de tiempo precioso para los codificadores, 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ías tener una abstracción de "Objetos de entrada de registro"?El código lógico de la aplicación los escribe, pero ¿debería tener la capacidad de actualizarlos o eliminarlos?Suena tonto y App::Log("I am a log message") algunas magnitudes son más fáciles de usar que 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 de 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 usará ese pequeño y hermoso Declaración SQL SELECT en la lógica de su aplicación (que rompe totalmente la idea de AR...), en lugar de envolver esa pequeña declaración en marcos rígidos y fijos de ideas de AR con mucho código envolviéndola y ocultándola.El tiempo que perdió escribiendo y/o construyendo código AR podría haberse invertido en una interfaz mucho más inteligente para leer listas de entradas de registros (de muchas, muchas maneras, el cielo es el límite).Los codificadores deberían atreverse a inventar nuevas abstracciones para realizar su lógica de aplicación que se ajuste a la aplicación prevista, y no volver a implementar estúpidamente patrones tontos, ¡Eso suena bien a primera vista!

D) la lógica de la aplicación: implementa la lógica de interacción de objetos y creación, eliminación y listado (!) de objetos lógicos de la aplicación (NO, esas tareas rara vez deben estar ancladas en los propios objetos lógicos de la aplicación:¿La hoja de papel sobre su escritorio le indica los nombres y ubicaciones de todas las demás hojas en su oficina?olvide los métodos "estáticos" para enumerar objetos, eso es una tontería, un mal compromiso creado para hacer que la forma de pensar humana encaje en [algún-no-todo-marco-como-AR-pensamiento-AR)

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 basan en AR a menudo descuidan la parte UI de una aplicación - se pierde tiempo en la creación de abstracciones oscuras .Al final, estas aplicaciones hicieron perder mucho tiempo a los programadores y se sienten como aplicaciones de codificadores para codificadores, inclinados a la tecnología por dentro y por fuera.Los programadores se sienten bien (trabajo duro finalmente hecho, todo terminado y correcto, según el concepto en papel...), y los clientes "sólo tienen que aprender que tiene que ser así", porque eso es "profesional".Vale, lo siento, estoy divagando ;-)

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

En proyectos pagos, a menudo escuché la demanda de comenzar con la creación de algunos objetos de "registro activo" como bloque de construcción para la lógica de la aplicación de nivel superior.En mi experiencia, esto notablemente a menudo Fue algún tipo 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 visión amplia, una visión general de lo que debería ser finalmente el producto.Esos clientes piensan en marcos rígidos ("en el proyecto de hace diez años funcionó bien..."), pueden desarrollar entidades, pueden definir relaciones entre entidades, pueden descomponer relaciones de datos y definir la lógica básica de la aplicación, pero luego dejan de hacerlo. y dártelo, y piensa que eso es todo lo que necesitas...a menudo carecen de un concepto completo de lógica de aplicación, interfaz de usuario, usabilidad, etc., etc.Les falta una gran visión y les falta amor por los detalles, y quieren que sigas esa forma de hacer las cosas AR, porque...Bueno, ¿por qué funcionó en ese proyecto hace años, mantiene a la gente ocupada y en silencio?No sé.Pero los "detalles" separan a los hombres de los niños, o...¿Cómo era el eslogan publicitario original?;-)

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 la alarma.Aprendí a intentar conseguirlos. De vuelta a esa fase concepcional esencial., déjeles que lo piensen dos veces, intente que muestren sus debilidades conceptuales o simplemente evítelos si no tienen discernimiento (al final, ya sabes, un cliente que aún no sabe lo que quiere, tal vez incluso crea que lo sabe pero no lo sabe). t, o intentar externalizar el trabajo conceptual a MÍ de forma gratuita, me cuesta muchas horas, días, semanas y meses preciosos de mi tiempo, la vida es demasiado corta...).

Así que finalmente:TODO ESTO es la razón por la que odio ese tonto "patrón de registro activo", y lo evitaré y lo evitaré siempre que sea posible.

EDITAR:Incluso llamaría a esto un No-Patrón.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 simplemente se esconde el viejo y potente SQL bien desarrollado detrás de una interfaz que, según la definición de patrones, es extremadamente limitada.

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

Piénselo, ¿qué problema le resuelve la RA?

Algunos mensajes me confunden.Algunas respuestas van a "ORM" frente a "SQL" o algo así.

El hecho es que AR es solo un patrón de programación simplificado en el que aprovecha los objetos de su dominio para escribir el código de acceso a la base de datos.

Estos objetos suelen tener atributos comerciales (propiedades del bean) y algún comportamiento (métodos que suelen trabajar sobre 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 estampado.

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

Para mí, el verdadero problema es simplemente la preocupación por la programación orientada a objetos.El patrón AR te obliga de alguna manera a agregar una dependencia de tu objeto a los objetos de 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 mediante algún tipo de tecnología remota, como Servicios Web, por ejemplo) y la capa de dominio.En mi opinión, si agregamos algunas dependencias (que no son realmente 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 externas (raras).

La implementación de AR en Spring Roo es interesante porque no se basa en el objeto en sí, sino en algunos archivos de AspectJ.Pero si luego no quieres trabajar con Roo y tienes que refactorizar el proyecto, los métodos AR se implementarán directamente en los objetos de tu dominio.

Otro punto de vista.Imaginemos que no utilizamos una base de datos relacional para almacenar nuestros objetos.Imaginemos que la aplicación almacena los objetos de nuestro dominio en una base de datos NoSQL o simplemente en archivos XML, por ejemplo.¿Implementaríamos los métodos que realizan estas tareas en los objetos de nuestro dominio?No lo creo (por ejemplo, en el caso de XM, agregaríamos dependencias relacionadas con XML a nuestros objetos de dominio... Creo que es verdaderamente triste).¿Por qué entonces tenemos que implementar los métodos de base de datos relacionales en los objetos de dominio, como dice el patrón Ar?

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

La pregunta es sobre el patrón de diseño de registro activo.No es una herramienta ORM.

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

Lo principal que he visto con respecto a las quejas sobre Active Record es que cuando creas un modelo alrededor de una mesa y seleccionas varias instancias del modelo, básicamente estás haciendo un "seleccionar * de ...".Esto está bien para editar un registro o mostrar un registro, pero si desea, 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 seleccione todas las columnas, pero solo usando Ciudad.

Por supuesto, las distintas implementaciones manejarán esto de manera diferente.Sin embargo, es una cuestión.

Ahora, puedes solucionar esto creando un nuevo modelo para lo específico que estás intentando hacer, pero algunas personas dirían que es más esfuerzo que beneficio.

A mí me encanta Active Record.:-)

HT

Me encanta la forma en que SubSonic hace lo de una sola columna.
Cualquiera

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 carga diferida.

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

Voy a hablar de Active Record como 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 Active Record viola el principio de responsabilidad ú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 es que los objetos de dominio en Active Record tienden a ser 1 a 1 con la base de datos, lo que puede considerarse una limitación en algún tipo de sistemas (principalmente de n niveles).

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

El problema que veo con Active Records es que siempre se trata de uno mesa.Está bien, siempre y cuando realmente trabajes solo con esa 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 normalmente es peor que no unirse en absoluto en lo que respecta al rendimiento, pero unirse generalmente es mejor que unirse "falso" leyendo primero toda la tabla A y luego usando la información obtenida para leer y filtrar la tabla B.

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 hacen preguntarte si habría sido más efectivo escribir la consulta a mano en primer lugar.

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 desajuste de impedancia.Me gusta mantener mi dominio limpio y encapsulado adecuadamente, algo que el patrón de registro activo generalmente destruye toda esperanza de lograr.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top