Estamos desarrollando una aplicación ASP.NET MVC y ahora estamos creando las clases de repositorio / servicio. Me pregunto si existen ventajas importantes para crear una interfaz genérica de IRepository que implementen todos los repositorios, en comparación con cada Repository que tenga su propia interfaz única y un conjunto de métodos.
Por ejemplo: una interfaz genérica de IRepository podría verse así (tomada de esta respuesta ):
public interface IRepository : IDisposable
{
T[] GetAll<T>();
T[] GetAll<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter, List<Expression<Func<T, object>>> subSelectors);
void Delete<T>(T entity);
void Add<T>(T entity);
int SaveChanges();
DbTransaction BeginTransaction();
}
Cada repositorio implementaría esta interfaz, por ejemplo:
- CustomerRepository: IRepository
- ProductRepository: IRepository
- etc.
La alternativa que hemos seguido en proyectos anteriores sería:
public interface IInvoiceRepository : IDisposable
{
EntityCollection<InvoiceEntity> GetAllInvoices(int accountId);
EntityCollection<InvoiceEntity> GetAllInvoices(DateTime theDate);
InvoiceEntity GetSingleInvoice(int id, bool doFetchRelated);
InvoiceEntity GetSingleInvoice(DateTime invoiceDate, int accountId); //unique
InvoiceEntity CreateInvoice();
InvoiceLineEntity CreateInvoiceLine();
void SaveChanges(InvoiceEntity); //handles inserts or updates
void DeleteInvoice(InvoiceEntity);
void DeleteInvoiceLine(InvoiceLineEntity);
}
En el segundo caso, las expresiones (LINQ o de otro tipo) estarían completamente incluidas en la implementación del Repositorio, quien implementa el servicio solo necesita saber a qué función del repositorio llamar.
Supongo que no veo la ventaja de escribir toda la sintaxis de expresión en la clase de servicio y pasarla al repositorio. ¿No significa esto que el código LINQ fácil de enredar se está duplicando en muchos casos?
Por ejemplo, en nuestro antiguo sistema de facturación, llamamos
InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)
de algunos servicios diferentes (Cliente, Factura, Cuenta, etc.). Eso parece mucho más limpio que escribir lo siguiente en varios lugares:
rep.GetSingle(x => x.AccountId = someId && x.InvoiceDate = someDate.Date);
La única desventaja que veo al usar el enfoque específico es que podríamos terminar con muchas permutaciones de las funciones Get *, pero esto todavía parece preferible a empujar la lógica de expresión hacia las clases de Servicio.
¿Qué me estoy perdiendo?
fuente
Respuestas:
Este es un problema tan antiguo como el patrón del repositorio en sí. La reciente introducción de LINQ
IQueryable
, una representación uniforme de una consulta, ha provocado mucha discusión sobre este mismo tema.Prefiero repositorios específicos, después de haber trabajado muy duro para construir un marco genérico de repositorios. No importa qué mecanismo inteligente probé, siempre terminé con el mismo problema: un repositorio es una parte del dominio que se está modelando, y ese dominio no es genérico. No se pueden eliminar todas las entidades, no se pueden agregar todas las entidades, no todas las entidades tienen un repositorio. Las consultas varían enormemente; la API del repositorio se vuelve tan única como la entidad misma.
Un patrón que uso a menudo es tener interfaces de repositorio específicas, pero una clase base para las implementaciones. Por ejemplo, usando LINQ to SQL, podría hacer:
Reemplace
DataContext
con su unidad de trabajo de elección. Un ejemplo de implementación podría ser:Observe que la API pública del repositorio no permite que los usuarios sean eliminados. Además, exponer
IQueryable
es otra lata de gusanos: hay tantas opiniones como ombligos sobre ese tema.fuente
De hecho, estoy un poco en desacuerdo con la publicación de Bryan. Creo que tiene razón, que en última instancia todo es muy único, etc. Pero al mismo tiempo, la mayor parte de eso surge a medida que diseña, y descubro que obtener un repositorio genérico y usarlo mientras desarrollo mi modelo, puedo obtener una aplicación muy rápidamente, luego refactorizar a una mayor especificidad a medida que encuentro el Necesito hacerlo.
Entonces, en casos como ese, a menudo he creado un IRepository genérico que tiene la pila CRUD completa, y que me permite jugar rápidamente con la API y dejar que la gente juegue con la interfaz de usuario y realice pruebas de integración y aceptación del usuario en paralelo. Luego, cuando descubro que necesito consultas específicas sobre el repositorio, etc., comienzo a reemplazar esa dependencia con la específica si es necesario y a partir de ahí. Una impl subyacente. es fácil de crear y usar (y posiblemente conectar a una base de datos en memoria u objetos estáticos u objetos simulados o lo que sea).
Dicho esto, lo que comencé a hacer últimamente es romper el comportamiento. Entonces, si hace interfaces para IDataFetcher, IDataUpdater, IDataInserter e IDataDeleter (por ejemplo), puede mezclar y combinar para definir sus requisitos a través de la interfaz y luego tener implementaciones que se encarguen de algunos o todos ellos, y yo puedo todavía inyecto la implementación de todo para usar mientras estoy construyendo la aplicación.
Pablo
fuente
GetById()
. ¿Debo usarIRepository<T, TId>
,GetById(object id)
o hacer suposiciones y usoGetById(int id)
? ¿Cómo funcionarían las teclas compuestas? Me preguntaba si una selección genérica por ID era una abstracción que valía la pena. Si no es así, ¿qué otra cosa se verían obligados a expresar repositorios genéricos? Esa fue la línea de razonamiento detrás de abstraer la implementación , no la interfaz .Prefiero repositorios específicos que se derivan de un repositorio genérico (o una lista de repositorios genéricos para especificar el comportamiento exacto) con firmas de métodos reemplazables.
fuente
Tener un repositorio genérico envuelto por un repositorio específico. De esa manera, puede controlar la interfaz pública, pero aún tiene la ventaja de la reutilización de código que proviene de tener un repositorio genérico.
fuente
public class UserRepository: Repository, IUserRepository
¿No debería inyectar IUserRepository para evitar exponer la interfaz? Como la gente ha dicho, es posible que no necesite la pila CRUD completa, etc.
fuente