algunos problemas muy evidentes con la idea de un genérico IRepository<T>
:
Se supone que cada entidad utiliza el mismo tipo de clave, lo cual no es cierto en casi cualquier sistema no trivial. Algunas entidades usarán GUID, otras pueden tener algún tipo de clave natural y/o compuesta. NHibernate puede soportar esto bastante bien, pero Linq to SQL es bastante malo en eso; tienes que escribir una gran cantidad de código de hack para hacer un mapeo de teclas automático.
Significa que cada repositorio solo puede tratar exactamente con un tipo de entidad y solo admite las operaciones más triviales. Cuando un repositorio se relega a un contenedor CRUD tan simple, tiene muy poco uso. También podría entregarle al cliente un IQueryable<T>
o Table<T>
.
Supone que realiza exactamente las mismas operaciones en cada entidad. En realidad, esto va a estar muy lejos de la verdad. Claro, tal vez quiere obtener ese Order
por su ID, pero es más probable que quiera obtener una lista de objetos Order
para un cliente específico y dentro de algún rango de fechas. La noción de un IRepository<T>
totalmente genérico no permite el hecho de que seguramente querrá realizar diferentes tipos de consultas en diferentes tipos de entidades.
El punto del patrón de repositorio es crear una abstracción sobre los patrones de acceso de datos común. Creo que algunos programadores se aburren de crear repositorios, por lo que dicen "¡Oye, lo sé, crearé un über-repositorio que pueda manejar cualquier tipo de entidad!" Lo cual es genial, excepto que el repositorio es bastante inútil para el 80% de lo que estás tratando de hacer. Está bien como una clase/interfaz base, pero si esa es toda la extensión del trabajo que haces, entonces estás siendo perezoso (y garantizando futuros dolores de cabeza).
Lo ideal sería que podría comenzar con un repositorio genérico que se ve algo como esto:
public interface IRepository<TKey, TEntity>
{
TEntity Get(TKey id);
void Save(TEntity entity);
}
Se dará cuenta de que este no lo hace tienen una función List
o GetAll
- eso es porque es absurdo pensar que es aceptable recuperar los datos de una tabla completa a la vez en cualquier parte del código. Aquí es cuando necesita comenzar a entrar en repositorios específicos:
public interface IOrderRepository : IRepository<int, Order>
{
IEnumerable<Order> GetOrdersByCustomer(Guid customerID);
IPager<Order> GetOrdersByDate(DateTime fromDate, DateTime toDate);
IPager<Order> GetOrdersByProduct(int productID);
}
Y así sucesivamente - ya entendió. De esta manera tenemos el repositorio "genérico" para si realmente necesitamos la semántica de recuperación por ID increíblemente simplista, pero en general nunca vamos a pasar eso, ciertamente no a una clase de controlador.
Ahora en cuanto a los controladores, usted tiene que hacer esto derecha, si no has negado casi todo el trabajo que has hecho en la elaboración de todos los repositorios.
Un controlador necesita tomar su repositorio del mundo exterior. La razón por la que creó estos repositorios es para poder realizar algún tipo de Inversión de control. Su objetivo final aquí es poder cambiar un repositorio por otro, por ejemplo, para realizar pruebas unitarias, o si decide cambiar de Linq a SQL a Entity Framework en algún momento en el futuro.
Un ejemplo de este principio es:
public class OrderController : Controller
{
public OrderController(IOrderRepository orderRepository)
{
if (orderRepository == null)
throw new ArgumentNullException("orderRepository");
this.OrderRepository = orderRepository;
}
public ActionResult List(DateTime fromDate, DateTime toDate) { ... }
// More actions
public IOrderRepository OrderRepository { get; set; }
}
En otras palabras, el controlador tiene ni idea de cómo crear un repositorio, ni debe. Si tiene alguna construcción de repositorio allí, está creando un acoplamiento que realmente no desea. La razón por la que los controladores de muestra ASP.NET MVC tienen constructores sin parámetros que crean repositorios concretos es porque los sitios deben poder compilarse y ejecutarse sin forzarle a configurar un marco de inyección de dependencias completo.
Pero en un sitio de producción, si no transfiere la dependencia del repositorio a través de un constructor o propiedad pública, está perdiendo el tiempo con repositorios, porque los controladores aún están estrechamente conectados a la capa de la base de datos . Tienes que ser capaz de escribir código de prueba como esta:
[TestMethod]
public void Can_add_order()
{
OrderController controller = new OrderController();
FakeOrderRepository fakeRepository = new FakeOrderRepository();
controller.OrderRepository = fakeRepository; //<-- Important!
controller.SubmitOrder(...);
Assert.That(fakeRepository.ContainsOrder(...));
}
No se puede hacer esto si su OrderController
va fuera y crear su propio repositorio. Se supone que este método de prueba no permite el acceso a datos, solo se asegura de que el controlador invoca el método de depósito correcto en función de la acción.
Esto todavía no es DI, es solo cuestión de fingir/burlarse. Donde DI entra en escena es cuando decides que Linq to SQL no está haciendo lo suficiente para ti y realmente quieres HQL en NHibernate, pero te va a tomar tres meses llevar a cabo todo, y quieres ser capaz de hacer esto un repositorio a la vez. Así, por ejemplo, utilizando un marco DI como Ninject, todo lo que tiene que hacer es cambiar esta situación:
Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<LinqToSqlOrderRepository>();
Bind<IProductRepository>().To<LinqToSqlProductRepository>();
Para:
Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<NHibernateOrderRepository>();
Bind<IProductRepository>().To<NHibernateProductRepository>();
y ya está, ahora todo lo que depende de IOrderRepository
está utilizando En la versión de NHibernate, solo ha tenido que cambiar una línea de código en lugar de cientos de líneas. Y estamos ejecutando las versiones de Linq a SQL y NHibernate una al lado de la otra, portando la funcionalidad más de una pieza por pieza sin romper nada en el medio.
Entonces, para resumir todos los puntos que he hecho:
No se base estrictamente en una interfaz genérica IRepository<T>
. La mayor parte de la funcionalidad que desea de un repositorio es específico, no genérico. Si desea incluir un IRepository<T>
en los niveles superiores de la jerarquía de clase/interfaz, está bien, pero los controladores deben depender de los repositorios específicos para no tener que cambiar el código en 5 lugares diferentes cuando lo encuentre. al repositorio genérico le faltan métodos importantes.
Los controladores deben aceptar repositorios desde el exterior, no crear los propios. Este es un paso importante para eliminar el acoplamiento y mejorar la capacidad de prueba.
Normalmente, querrá conectar los controladores utilizando un marco de Inyección de Dependencia, y muchos de ellos se pueden integrar sin problemas con ASP.NET MVC.Si eso es demasiado para que lo asimile, al menos debería usar algún tipo de proveedor de servicios estáticos para poder centralizar toda la lógica de creación de repositorios. (A la larga, probablemente le resultará más fácil simplemente aprender y usar un marco DI).
Genérico Static repositories! ¡HURRA! SingletonSquared, ¡justo lo que necesitamos! ;-) –
@Sky, uno nunca puede tener suficiente. Personalmente estoy pensando en repositorios genéricos parcialmente parciales. –