2009-08-28 18 views
20

Así que estoy poniendo en práctica el modelo de repositorio en una aplicación y se encontró con dos "problemas" en mi comprensión del patrón:patrón Repositorio de Buenas Prácticas

  1. Consulta - He leído las respuestas que no debería IQueryable ser utilizado al usar repositorios. Sin embargo, es obvio que lo desearía para no devolver una Lista completa de objetos cada vez que llame a un método. ¿Debería implementarse? Si tengo un método de IEnumerable llamado Lista, ¿cuál es la "mejor práctica" general para un IQueryable? ¿Qué parámetros debería/no debería tener?

  2. Valores escalares: ¿Cuál es la mejor forma (utilizando el patrón Repositorio) para devolver un único valor escalar sin tener que devolver todo el registro? Desde el punto de vista del rendimiento, ¿no sería más eficiente devolver solo un único valor escalar en una fila entera?

Respuesta

30

Estrictamente hablando, un Repositorio ofrece una semántica de recopilación para obtener/poner objetos de dominio. Proporciona una abstracción alrededor de su implementación de materialización (ORM, laminado a mano, simulacro) para que los consumidores de los objetos de dominio se desacoplen de esos detalles. En la práctica, un Repositorio normalmente abstrae el acceso a las entidades, es decir, objetos de dominio con identidad, y generalmente un ciclo de vida persistente (en el sabor DDD, un Repositorio proporciona acceso a Raíces agregadas).

Una interfaz mínima para un repositorio es el siguiente:

void Add(T entity); 
void Remove(T entity); 
T GetById(object id); 
IEnumerable<T> Find(Specification spec); 

Aunque verá diferencias de nomenclatura y la adición de Guardar/saveOrUpdate semántica, lo anterior es la idea 'puro'. Obtendrá los miembros Agregar/Eliminar ICollection más algunos buscadores.Si no se utiliza IQueryable, también verá métodos de búsqueda en el repositorio como:

FindCustomersHavingOrders(); 
FindCustomersHavingPremiumStatus(); 

Hay dos problemas relacionados con el uso de IQueryable en este contexto. El primero es el potencial para filtrar detalles de implementación al cliente en forma de las relaciones del objeto de dominio, es decir, violaciones de la Ley de Demeter. El segundo es que el repositorio adquiere la búsqueda de responsabilidades que pueden no pertenecer al repositorio de objetos de dominio propiamente dicho, por ejemplo, encontrar proyecciones que son menos sobre el objeto de dominio solicitado que los datos relacionados.

Además, utilizando IQueryable 'rompe' el patrón: Un repositorio con IQueryable puede o no proporcionar acceso a 'objetos de dominio'. IQueryable brinda al cliente muchas opciones sobre lo que se materializará cuando la consulta se ejecute finalmente. Este es el objetivo principal del debate sobre el uso de IQueryable.

En cuanto a los valores escalares, no debe utilizar un repositorio para devolver valores escalares. Si necesita un escalar, normalmente obtendrá esto de la entidad misma. Si esto suena ineficiente, lo es, pero es posible que no lo note, dependiendo de sus características/requisitos de carga. En los casos en que necesita vistas alternativas de un objeto de dominio, por razones de rendimiento o porque necesita fusionar datos de muchos objetos de dominio, tiene dos opciones.

1) Utilice el repositorio de la entidad para buscar las entidades y el proyecto/mapa especificados en una vista aplanada.

2) Cree una interfaz de buscador dedicada a devolver un nuevo tipo de dominio que encapsula la vista aplanada que necesita. Esto no sería un Repositorio porque no habría semántica de Colección, pero podría usar los repositorios existentes debajo de las cubiertas.

Una cosa a considerar si utiliza un repositorio "puro" para acceder a entidades persistentes es que compromete algunos de los beneficios de un ORM. En una implementación 'pura', el cliente no puede proporcionar el contexto de cómo se usará el objeto de dominio, por lo que no puede decirle al repositorio: 'hey, voy a cambiar la propiedad customer.Name, así que don No te molestes en obtener esas referencias cargadas de entusiasmo. Por otro lado, la pregunta es si un cliente debe saber sobre eso. Es una espada de doble filo.

En cuanto al uso de IQueryable, la mayoría de la gente parece sentirse cómoda con 'romper' el patrón para obtener los beneficios de la composición dinámica de consultas, especialmente para las responsabilidades del cliente, como paginación/clasificación. En este caso, es posible que tenga:

Add(T entity); 
Remove(T entity); 
T GetById(object id); 
IQueryable<T> Find(); 

y, a continuación, puede acabar con todas esas Los métodos de búsqueda personalizados, lo que realmente estorban el Repositorio medida que crecen sus necesidades de consulta.

+1

Me encontré con la publicación de jbogard sobre este tema hoy: http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/09/02/ddd-repository-implementation-patterns.aspx – pfries

+3

+1: ". ..la mayoría de las personas parecen sentirse cómodas con 'romper' el patrón para obtener los beneficios de la composición dinámica de la consulta ... " –

3

En cuanto a 1: Por lo que yo puedo ver, no es la misma que se IQuerable el problema que se devuelve desde un repositorio. El objetivo de un repositorio es que debería verse como un objeto que contiene todos sus datos. Entonces puedes pedirle al repositorio los datos. Si tiene más de un objeto que necesita los mismos datos, el trabajo del repositorio es almacenar en caché los datos, de modo que los dos clientes de su repositorio obtendrán las mismas instancias, de modo que si un cliente cambia una propiedad, el otro verá que , porque apuntan a la misma instancia.

Si el repositorio era en realidad el proveedor de Linq, eso encajaría perfectamente. Pero la mayoría de las personas simplemente dejan pasar el IQuerable del proveedor de Linq a sql, que en efecto elude la responsabilidad del repositorio. Entonces, el repositorio no es un repositorio en absoluto, al menos según mi comprensión y uso del patrón.

En relación con 2: Naturalmente, es más eficaz en función del rendimiento simplemente devolver un único valor de la base de datos que el registro completo. Pero al usar un patrón de repositorio, no devolvería ningún registro, devolvería objetos comerciales. Por lo tanto, la lógica de la aplicación no debe preocuparse por los campos, sino por los objetos de dominio.

¿Pero qué tan efectivo es devolver un solo valor en comparación con un objeto de dominio completo? Probablemente no podrá medir la diferencia si su esquema de base de datos está razonablemente bien definido.

Es mucho más importante tener un código limpio y fácil de entender, en lugar de optimizaciones de rendimiento microscópicas por adelantado.

7

En respuesta a @lordinateur Realmente no me gusta la manera defacto de especificar una interfaz de repositorio.

Porque la interfaz en su solución requiere que cada implementación de repositorio requiera al menos un Agregar, Eliminar, GetById, etc. Considere ahora un escenario en el que no tiene sentido guardar a través de una instancia particular de un repositorio, usted todavía tiene que implementar los métodos restantes con NotImplementedException o algo así.

que prefiere dividir mis declaraciones de interfaz de repositorio de este modo:

interface ICanAdd<T> 
{ 
    T Add(T entity); 
} 

interface ICanRemove<T> 
{ 
    bool Remove(T entity); 
} 

interface ICanGetById<T> 
{ 
    T Get(int id); 
} 

Una aplicación particular del repositorio para una entidad SomeClass lo tanto, podría tener el siguiente aspecto:

interface ISomeRepository 
    : ICanAdd<SomeClass>, 
     ICanRemove<SomeClass> 
{ 
    SomeClass Add(SomeClass entity); 
    bool Remove(SomeClass entity); 
} 

Vamos a dar un paso atrás y Eche un vistazo a por qué creo que esta es una mejor práctica que implementar todos los métodos CRUD en una interfaz genérica.

Algunos objetos tienen requisitos diferentes de que otros. No se puede eliminar un objeto de cliente , no se puede actualizar un PurchaseOrder y se puede crear un objeto ShoppingCart solo . Cuando uno está utilizando la interfaz genérica IRepository, este obviamente causa problemas en la implementación .

Aquellos aplicación de la anti-patrón menudo pondrá en práctica todo su interfaz continuación se lanzar excepciones para los métodos que no hacen apoyo. Aparte de no estar de acuerdo con numerosos principios OO esto rompe su esperanza de ser capaz de utilizar su IRepository abstracción efectivamente a menos que también empezar a poner métodos en él para objetos o no dados son compatibles e implementar más ellos.

Una solución común a este problema es para mover a las interfaces más granulares como ICanDelete, ICanUpdate, ICanCreate, etc, etc, mientras que Este trabajo alrededor de muchos de los problemas que han surgido en términos de principios OO también reduce en gran medida la cantidad de reutilización de código que está siendo visto como la mayoría de las veces uno no podrá ser capaz de usar la instancia concreta de Repository .

A ninguno de nosotros nos gusta escribir el mismo código una y otra vez. Sin embargo, un repositorio contrato como es una costura arquitectónica es el lugar incorrecto para ampliar el contrato para hacerlo más genérico.

Estos ejercicios se han tomado de manera vergonzosa desde this post donde también puede leer más discusión en los comentarios.

+0

¿Cómo sería un repositorio que no guardas como 'Guardar'? Si suelta la parte Agregar/Quitar, solo tiene una interfaz de Buscador. Si todo lo que quiere hacer es encontrar material, no necesita un repositorio, sino algún tipo de implementación de ICustomerFinder.Dicho esto, el repositorio podría implementar un buscador: interfaz ICustomerRepository: IRepository , ICustomerFinder Solo para aclarar, que en realidad no 'Guardar' a través de la interfaz de repositorio, que sería la responsabilidad de algún tipo de unidad de trabajo implementación. Tal sesión se gestiona mejor fuera del repositorio. – pfries

+0

El punto de Greg sobre la composición es bueno, es decir, que puede implementar un repositorio de objetos de dominio con un repositorio genérico interno. Así es como funciona en la práctica cuando se utiliza una interfaz como ICustomerRepository en torno a un ORM como NHibernate (que sustituye a un repositorio genérico). Su punto principal es no utilizar contratos genéricos en las vetas de su aplicación. Eso es bastante razonable; si su contrato es ICustomerRepository y no IRepository, evitará los problemas que describe. No amo a todos los buscadores a pesar de que expresan intención. ¿Qué pasa con los objetos de consulta? – pfries

+0

froghammr: después de toda mi investigación, me estoy conformando con Query Objects. Estoy desarrollando una aplicación móvil en MonoDroid/C# y no tengo LINQ, Entity o Hibernate, así que tengo que escribir todo desde cero. A pesar de eso, la sobrecarga de un Finder/Especificación es profunda. – samosaris

Cuestiones relacionadas