¿Cuáles son los inconvenientes del patrón ActiveRecord?

30

Tengo curiosidad por saber cuáles son los inconvenientes de usar el patrón ActiveRecord para el acceso a datos / objetos comerciales. Lo único que se me ocurre es que viola el Principio de Responsabilidad Única, pero el patrón AR es lo suficientemente común como para que esta razón por sí sola no parezca "lo suficientemente buena" como para justificar no usarla (por supuesto, mi la vista puede estar sesgada, ya que a menudo ninguno de los códigos con los que trabajo sigue ninguno de los principios SÓLIDOS).

Personalmente soy no un fan de ActiveRecord (con la excepción de escribir una aplicación Ruby on Rails, donde AR siente "natural") porque se siente como la clase está haciendo demasiado, y el acceso a datos no debería ser hasta la propia clase manejar. Prefiero utilizar repositorios que devuelven objetos comerciales. La mayor parte del código con el que trabajo tiende a usar una variación de ActiveRecord, en forma de (no sé por qué el método es booleano):

public class Foo
{
    // properties...

    public Foo(int fooID)
    {
        this.fooID = fooID;
    }

    public bool Load()
    {
        // DB stuff here...
        // map DataReader to properties...

        bool returnCode = false;
        if (dr.HasRows)
            returnCode = true;

        return returnCode;
    }
}

o, a veces, la forma más "tradicional" de tener un public static Foo FindFooByID(int fooID)método para los buscadores y algo parecido public void Save()a guardar / actualizar.

Entiendo que ActiveRecord suele ser mucho más simple de implementar y usar, pero parece un poco demasiado simple para aplicaciones complejas y podría tener una arquitectura más robusta encapsulando su lógica de acceso a datos en un Repositorio (sin mencionar que es más fácil cambiarlo) estrategias de acceso a datos, por ejemplo, tal vez use Procs almacenados + conjuntos de datos y desee cambiar a LINQ o algo así)

Entonces, ¿cuáles son otros inconvenientes de este patrón que se deben considerar al decidir si ActiveRecord es el mejor candidato para el trabajo?

Wayne Molina
fuente

Respuestas:

28

El principal inconveniente es que sus "entidades" son conscientes de su propia persistencia, lo que lleva a muchas otras malas decisiones de diseño.

El otro problema es que los kits de herramientas de registro más activos básicamente asignan 1 a 1 a los campos de la tabla con cero capas de indirección. Esto funciona en escalas pequeñas, pero se desmorona cuando tienes problemas más difíciles de resolver.


Bueno, que tus objetos conozcan su persistencia significa que debes hacer cosas como:

  • Tener fácilmente conexiones de bases de datos disponibles en todas partes. Esto generalmente conduce a una codificación dura desagradable o algún tipo de conexión estática que se ve afectada desde todas partes.
  • sus objetos tienden a parecerse más a SQL que a objetos.
  • Es difícil hacer algo en la aplicación desconectada porque la base de datos está muy arraigada.

Termina siendo una gran cantidad de otras malas decisiones además de esto.

Wyatt Barnett
fuente
2
¿Puedes dar más detalles sobre "otras malas decisiones de diseño"?
Kevin Cline
2
Gracias. No he encontrado que estos problemas sean un problema en el desarrollo de Ruby on Rails. Todavía es posible probar el comportamiento y la persistencia por separado. La OMI que separa la persistencia del comportamiento tiene poco valor práctico.
Kevin Cline
@kevin: estas cosas son menos inconvenientes con características de rubí como mixins y tipeo de pato. Con lenguajes estáticos, como C #, que es lo que el OP usó en su pregunta, es un poco más difícil separarlos.
Wyatt Barnett
@Wayne: para mí, las clases son solo cuadros para colocar métodos: puedo separar la lógica de negocios de la persistencia al ponerlos en clases separadas, o simplemente puedo separarlos conceptualmente asegurándome de que los métodos de negocios no funcionen. O. En idiomas con poca compatibilidad de delegación (por ejemplo, Java), esto ahorra una gran cantidad de código. OTOH, me estoy metiendo en Hibernate para estar completamente equivocado.
Kevin Cline
44
Yo agregaría dos cosas; 1. El acoplamiento con el mecanismo de persistencia hace que el código sea difícil, si no imposible, para una prueba de unidad adecuada. 2. Active Record pega su código a las relaciones en un mecanismo de persistencia centralizado, lo que hace realmente difícil dividir su monolito si alguna vez lo decide.
istepaniuk
15

El mayor inconveniente del registro activo es que su dominio generalmente se une estrechamente a un mecanismo de persistencia particular. Si ese mecanismo requiere un cambio global, tal vez de persistencia basada en archivos a persistencia basada en DB, o entre marcos de acceso a datos, CADA clase que implemente este patrón puede cambiar. Dependiendo del idioma, el marco y el diseño, incluso algo tan simple como cambiar la ubicación de la base de datos o quién "posee" podría requerir pasar por cada objeto para actualizar los métodos de acceso a datos (esto es poco común en la mayoría de los idiomas que proporcionan un acceso fácil configurar archivos con cadenas de conexión).

También generalmente requiere que te repitas. La mayoría de los mecanismos de persistencia tienen mucho código común para conectarse a una base de datos e iniciar una transacción. DRY (Don't Repeat Yourself) te diría como codificador que centralices esa lógica.

También hace que las operaciones atómicas sean difíciles. Si un grupo de objetos debe guardarse de manera todo o nada (como una Factura y sus InvoiceLines y / o las entradas de Cliente y / o GL), uno de los objetos debe conocer todos estos otros objetos y controlar su persistencia ( que amplía el alcance del objeto de control; los grandes registros interconectados pueden convertirse fácilmente en "objetos divinos" que saben todo acerca de sus dependencias), o el control de toda la transacción debe manejarse desde fuera del dominio (y en ese caso, ¿por qué está utilizando ¿ARKANSAS?)

También es "incorrecto" desde una perspectiva orientada a objetos. En el mundo real, una factura no sabe cómo archivarse, entonces, ¿por qué un objeto de código de Factura sabe cómo guardarse en la base de datos? Por supuesto, la adhesión demasiado religiosa a "los objetos solo deberían modelar lo que pueden hacer sus contrapartes del mundo real" conduciría a modelos de dominio anémico (una factura tampoco sabe cómo calcular su propio total, pero dividiendo el cálculo de ese total en otro objeto generalmente se considera una mala idea).

KeithS
fuente
En Ruby, donde la persistencia puede definirse o abstraerse detrás de una palabra clave o comando muy simple, probablemente no sea un problema. En .NET, que requiere más LoC para configurar varios mecanismos de persistencia, generalmente es redundante tener una conexión SQL para cada objeto, especialmente si se deben guardar varios objetos en una transacción atómica. Conectarse a la base de datos se ve igual si está guardando una factura o un cliente. Puede resumir lo común en una clase base común a todos los objetos ActiveRecord en su base de código, o extraerlo a su propia clase (un Repositorio)
KeithS
¿Puede proporcionar ejemplos del uso de Ruby ActiveRecord que ilustren sus puntos? No experimenté estos problemas, pero mi aplicación era bastante pequeña. Podría escribir migraciones en Ruby e implementarlas en diferentes implementaciones de bases de datos. Descubrí que Ruby ActiveRecord era muy útil para eliminar la repetición, así que no veo por qué afirmarías que usar ActiveRecord tendría el efecto contrario. No tuve problemas para guardar: simplemente modifiqué el modelo de objetos y dejé que AR actualizara la base de datos para reflejar el modelo de objetos.
Kevin Cline
2

El inconveniente básico es que hace que su modelo de dominio sea complejo porque no solo contiene lógica empresarial sino también información de persistencia.

Entonces, la solución es hacer uso de la implementación de Data Mapper de ORM. Eso separa la capa de persistencia y ahora estamos más centrados en la lógica empresarial de la entidad. Doctrine es Data Mapper ORM.

Pero este enfoque también tiene cierta complejidad, para la consulta ahora depende demasiado de Data Mapper, crea un entorno orientado a consultas. Para simplificarlo, se introduce otra capa entre Modelo de dominio y Data Mapper que se llama Repositorio .

El repositorio abstrae la capa de persistencia. Tiene sentido de la programación orientada a objetos en el sentido, es una colección de todos los objetos del mismo tipo (como todas las entidades almacenadas en la tabla de la base de datos) y puede realizar operaciones en ellos como operación de recopilación, agregar , eliminar . contiene etc.

Por ejemplo, para la entidad de usuario , habrá UserRepository que representará la colección del mismo tipo de objetos de usuario (que se almacenan en la tabla de usuarios) sobre los que puede realizar la operación. Para consultar a la tabla de usuarios, utiliza el Asignador de datos de usuario, pero lo abstrae al usuario del modelo de dominio .

Patrón de repositorio es el tipo de acceso a datos de capa , otro es el acceso a datos de objetos únicas diferencias repositorio tiene agregada Raíz función

palash140
fuente
Los patrones de repositorio y DAO difieren, DAO es el acceso general a datos, el repositorio es para la persistencia de la recolección de todos los objetos del mismo tipo. Es decir, todos los repositorios deben tener la misma interfaz (como matriz o lista). Otro rey de métodos no pertenece al repositorio. DAO es de nivel inferior, el repositorio puede usar DAO. A menudo, los programadores (especialmente PHP) usan el repositorio como DAO, pero no es correcto.
xmedeko