2009-10-23 13 views
14

Estoy mirando patrones de programación/diseño para la capa de modelo de mi aplicación, y me pregunto cuál encaja mejor con la situación en la que está realizando una recuperación que involucra combinaciones en varias tablas.El mejor patrón de diseño para la tabla de base de datos se une a

Por ejemplo, suponga que tiene las siguientes tablas/relaciones: Clientes -> 1..n Cuentas -> Características 0..n

donde una característica podría ser un talonario de cheques, o algún producto de alta calidad como seguro de viaje gratis.

Luego quiero hacer getCustomersForFeature() para recuperar a todos los clientes con cuentas que tienen seguro de viaje gratis.

El uso de ActiveRecord o Data Access Object no parece ajustarse, ya que estos generalmente se enfocan en una clase por tabla; del mismo modo para Data Mapper. Me doy cuenta de que podría descomponerlo en operaciones para cada tabla, p. getAccountsForFeature() y getCustomersForAccount(), pero quiero hacer la recuperación en un solo golpe.

Si tuviéramos que "doblar" los patrones de una clase por tabla y usar el patrón Objeto de acceso a datos, digamos, ¿debería el método getCustomersForFeature() continuar con CustomerDAO o FeatureDAO? Pero esto no me parece correcto porque estarás contaminando tus DAO con conocimiento de otras tablas.

Sugerencias por favor.

+0

Agregué una respuesta Wiki de la comunidad a esta pregunta, que resume lo que creo que son los enfoques disponibles basados ​​en las respuestas aquí y la lectura adicional. Esto no quita nada a las otras respuestas aquí, pero es el enfoque orientado a patrones lo que estaba buscando, con la idea Agregado incluida. Dudo en seleccionarlo como la "respuesta aceptada" (¿puedo hacerlo con mi propia respuesta?) Hasta que haya sido sujeto a más escrutinio. –

Respuesta

1

En C#/Linq a SQL sería algo como lo siguiente. (Supongo que en realidad hay una tabla de búsqueda de tipos de características, para que tenga una lista estándar de sus tipos de funciones y luego su relación con una cuenta es separada, por lo que FeatureTypeId sería su valor FK, tal vez seleccionado de un menú desplegable lista o algo así.)

// or whatever data type your keys are in 
public IEnumerable<Customer> getCustomersForFeature(int featureTypeId) 
{ 
    return from feature in dbContext.Features 
      where feature.FeatureTypeId == featureTypeId 
      select getCustomer(feature.Account.Customer.Id); 
} 

en algún nivel de su DAO/BAO tiene que ser consciente de las relaciones entre los objetos, por lo que el hecho de que esta es una relación abuelo no debería ser demasiado miedo.

En cuanto a dónde pertenece en su estructura BAO, un argumento probablemente podría hacerse de cualquier manera. Probablemente lo pondría en Cliente, ya que es a donde estoy tratando de llegar.

Editar: Como señaló Toby, las relaciones son bidireccionales; de nuevo en LINQ, usted podría ir a otro lado, así:

// or whatever data type your keys are in 
public IEnumerable<Customer> getCustomersForFeature(int featureTypeId) 
{ 
    return from customer in dbContext.Customers 
      from account in customer.Accounts 
      from feature in account.Features 
      where feature.FeatureTypeId == featureTypeId 
      select getCustomer(customer.Id); 
} 

Cualquier otro ORM debe tener un comportamiento muy similar, a pesar de que la sintaxis y la estructura pueden variar.

2

La forma en que Active Record en Rails modela esto es permitiendo que el objeto Cliente tenga muchas cuentas, lo que básicamente se traduce en una colección de objetos Cuenta, que a su vez tienen una colección de Características. Las relaciones pueden ser bidireccionales, por lo que cada modelo de AR puede "conocer" sus relaciones, según sus necesidades.

Creo que está bien que un objeto tenga conocimiento de otras tablas, ya que dichas relaciones son fundamentales tanto para OO como para RDBMS/SQL.

+0

Además, puede definir Funciones con has_many: users,: through => accounts. –

+0

¿Puede explicar cómo se hace el mapeo bajo las cubiertas, p. ¿La recuperación de un cliente hace que las cuentas y características asociadas se recuperen automáticamente (es decir, una combinación en 3 tablas) o son cargadas de manera diferida? Si quiero recuperar * algunos * atributos (es decir, un subconjunto) de Cliente/Cuenta/Función pero no el gráfico completo del objeto, ¿cómo lo haría? –

+0

Active Record in Rails es bastante sofisticado. El comportamiento predeterminado es que las colecciones se cargarán de forma diferida, lo que puede significar consultas adicionales. Sin embargo, los datos pueden cargarse ansiosamente mediante el uso de un mecanismo de "inclusión" en el descubrimiento inicial (de modo que cuando carga un Cliente, le dice que cargue también las Cuentas y AR creará una combinación para usted). También se pueden cargar campos específicos: simplemente dígale qué campos cargar. –

0

En general, modele sus datos según las capacidades de la base de datos SQL que esté utilizando. Ignore los ORM, Mappers, etc.

Una vez que tenga un buen modelo de datos, si ActiveRecord o DAO no lo accederán de la manera que desea, omítalos escribiendo SQL.

La asignación relacional de objetos se supone que facilita la programación con bases de datos, pero debido a las diferencias entre los modelos, a veces la manera más sencilla es ir directamente a SQL.

+0

Soy cauteloso al ignorar los ORM, etc. porque me gustaría encapsular el conocimiento de cómo mapear a/desde la base de datos en el menor número posible de lugares (preferiblemente uno). El escenario de pesadilla es cambiar la estructura de la base de datos y luego tener que buscar todas las consultas afectadas. ¿Qué límites (si corresponde) está imponiendo a ActiveRecord/DAO, p. una clase por mesa? –

+1

Es posible realizar migraciones de bases de datos sin la ayuda de ningún ORM, solo con scripts SQL puros. Esto se hace comúnmente en instalaciones de bases de datos grandes con DBA capacitados. Si vas a utilizar bases de datos SQL, tienes que venir en parte y cumplir con los DBA en el medio. Y a veces, adopta sus prácticas. Los ORM están bien para modelos de datos simples en bases de datos específicas de la aplicación, pero muchas aplicaciones no son propietarias de la base de datos, son solo uno de varios usuarios. –

0

De acuerdo con GalacticCowboy que ORM permite separar los detalles del mapeo de la clase de base de datos de las consultas orientadas a objetos. Que sólo quiere dar un ejemplo para ORM Hibernate Java - hay un número de maneras de definir association mappings y tras consulta HQL orientado a objetos funciona bien por encima de todos ellos

seleccione C del cliente c izquierda se unen c.accounts una combinación izquierda a.features F donde f.name = 'cool'

Tenga en cuenta que 'cliente' es un nombre de clase aquí y '' c.accounts, '' a.features y 'f. name ' son nombres de propiedad de nivel de clase, es decir, aquí no se mencionan detalles específicos de la base de datos.

8

En Model-Driven Design, que tienen lógicas Entidades en su aplicación, y estas entidades pueden asignar a varias tablas en la base de datos física. Por supuesto, necesita ejecutar SQL más complejo para obtener una entidad completa, y la clase de modelo es el lugar donde se implementan estas relaciones.

Creo que ActiveRecord y su tipo están bien para consultas sencillas en una sola tabla, pero tratar de forzar el uso de estos patrones para consultas complejas es muy difícil. Afortunadamente, ya contamos con un lenguaje conciso y específico de dominio que puede usar para especificar consultas complejas: SQL.

Por lo tanto, en su clase Model, tendría métodos para realizar tareas lógicas a nivel de aplicación, como getCustomersForFeature(). En el código para ese método, debe escribir una consulta específica, ya sea con métodos ActiveRecord o con SQL directo si es necesario. Por lo tanto, el diseño concreto de su base de datos está encapsulado en un solo lugar, en la clase Modelo.

Esto significa que debemos romper el acoplamiento entre el Modelo y la Mesa. La relación OO entre un modelo y una clase ActiveRecord no es IS-A - es HAS-A (o tiene muchas).


su comentario Re: ¿Cuál es nuestro modelo? Si su aplicación necesita trabajar principalmente con los clientes como una entidad, y trata las características como más o menos un atributo de los clientes, entonces su modelo sería Clientes, y ocultaría el hecho de que las características se almacenan en una tabla separada en la base de datos. . El modelo de Clientes usaría internamente ActiveRecord o SQL simple para recopilar los datos necesarios para proporcionar una vista completa del objeto complejo que es un cliente con sus atributos multivalor asociados.

Sin embargo, ¿qué ocurre si su aplicación también tiene la necesidad de trabajar con Características directamente? Por ejemplo, una pantalla de administrador donde puede obtener informes basados ​​en características o crear nuevas características. Entonces sería torpe acceder a las características a través del modelo del Cliente. Entonces necesitarías un modelo de Características después de todo. Solo tendría diferentes métodos para implementar las operaciones que necesita hacer con las Características.

Cada clase de modelo debe exponer una API de solo las cosas que necesita hacer con ese modelo. No hay necesidad de ser simétrico.El hecho de que su modelo de Cliente pueda captar a todos los clientes que tienen una característica determinada no significa necesariamente que su modelo de Características necesite obtener todas las funciones para un cliente determinado. Siga la regla YAGNI.

Pero después de haber creado su modelo de Cliente y su modelo de Características, ¿esto no conduce a la duplicación de la lógica que conoce acerca de las relaciones entre las tablas? Sí, podría. Este es uno de los muchos problemas que entran dentro del alcance del object-relational impedance mismatch.

+0

Tiene sentido. Entonces, en el ejemplo que di, ¿propondrías crear solo un Modelo de Cliente? –

0

Creo que este es un problema clásico (al menos para mí), en el que desea representar su modelo de datos en su aplicación. De acuerdo con mi propia experiencia, siempre hay una situación incómoda como la descrita. Mi solución es muy similar con alguna respuesta anterior. Haga una clase simple de uno a uno con su tabla y si esa tabla tiene relación con otra tabla, luego haga una propiedad en su clase.

Y sobre su refrán 'Quiero hacer la recuperación de un golpe. ', creo que debería escribir SQL Query personalizado o tal vez usar LINQ.

0

Mientras que Object to realtional stuff es excelente para el mantenimiento de tablas de tipo CRUD, no encuentro nada mejor que SQL real cuando se trata de tablas de consultas complejas.

El modelo ORM es solo una abstracción demasiado lejana.

+0

Sí y no. Sí, porque hay ciertos casos en que no se puede usar la API de ORM para expresar una consulta, y no, porque estos casos son pocos si tiene un ORM potente (Hibernate/NHibernate) y conoce su API lo suficientemente bien. Creo que pierdes la mayoría de las ventajas que ofrece un ORM si lo usas solo para las operaciones CRUD más simples. – Max

0

Los requisitos lo deciden por usted. Ya sea en su fecha límite, su cultura de trabajo o los requisitos de su proyecto. Su fecha límite puede indicar "código para herramientas" y funciones de corte. Su personal puede requerir "no nuevas bibliotecas o modos de falla". En mi propio entorno, me oirían contar esta historia:

"... no serviría introducir nuevas bibliotecas ORM, por lo que están fuera. Las fechas límite indicarían que no debería comenzar a enseñarme cómo para producir vistas de SQL. Mi cultura de trabajo me informa que lo haga rápidamente. Mi arquitectura gime en la serialización de tablas enteras y mis requisitos de rendimiento indican que no debo almacenar en caché ninguno de estos datos ... "

Las vistas de SQL pueden proporcionar una buena forma de abstraer las uniones de sus objetos, y podría permitirle cambiar su esquema más independientemente del código de su aplicación. La actualización de vistas es muy complicada, dependiendo de su base de datos. Si la portabilidad de la base de datos es importante, eso probablemente informe mucho a su diseño.

Puede encontrar la armonía con un enfoque híbrido. Si usa una puerta de enlace de tabla, no hay ninguna razón para agregarla servilmente en cada vista de nivel de aplicación de la tabla. ¿Por qué no utilizar gateways de tabla o registros activos para actualizar elementos en el ámbito de tabla, y clases personalizadas que se ocupan de las consultas orientadas a vista?

3

He pasado más tiempo leyendo sobre el tema (incluido el mini libro de diseño impulsado por dominio al que se hace referencia en la respuesta de Bill). Hay un concepto Agregado en ese libro, que representa más fielmente lo que estoy tratando de lograr; el Cliente encapsula y controla el acceso a la Cuenta y Función. Si seguimos con el enfoque de Diseño Dirigido por Dominio, podríamos usar un Repositorio para controlar la recuperación del Cliente, y me parece que aquí es donde se encapsularía el conocimiento de la estructura de la base de datos.

También revisé mi copia de Patterns of Enterprise Application Architecture, y si bien parece que el patrón ActiveRecord está destinado a una asignación directa de clase a la tabla de base de datos, si elige no seguir el enfoque Repository de DDD entonces Data Mapper sería adecuado para un mapeo compuesto.

Gracias a todos por sus contribuciones. He votado las respuestas que contribuyeron a esta conclusión. Como este puede no ser el último punto de esta discusión, he marcado esta respuesta como Community Wiki.

Cuestiones relacionadas