Patrones de diseño que a menudo evito: patrón de repositorio

Los patrones de diseño proporcionan soluciones probadas a los problemas del mundo real que se enfrentan en los diseños de software. El patrón de repositorio se utiliza para desacoplar la lógica empresarial y las capas de acceso a datos en su aplicación.

La capa de acceso a datos generalmente contiene códigos y métodos específicos de almacenamiento para operar con los datos hacia y desde el almacenamiento de datos. La capa de acceso a datos que el repositorio extrae puede ser un ORM (es decir, Entity Framework o NHibernate), archivo XML, un servicio web, etc. Incluso puede ser una colección de sentencias SQL.

Al utilizar el patrón de diseño del repositorio, la capa de lógica empresarial de su aplicación no necesita tener ningún conocimiento sobre cómo ocurre la persistencia de datos debajo. Esencialmente, un repositorio media entre el dominio y las capas de mapeo de datos de su aplicación. Se supone que debe proporcionarle una encapsulación sobre la forma en que los datos realmente se conservan en la capa de almacenamiento de datos.

El patrón de repositorio puede ser beneficioso cuando tiene muchas entidades y tiene muchas consultas complejas para trabajar con esas entidades. En este caso, una capa adicional de abstracción puede ayudarlo a eliminar la duplicación de la lógica de la consulta.

El repositorio genérico

Un repositorio genérico es un tipo que comprende un conjunto de métodos genéricos para realizar operaciones CRUD. Sin embargo, es solo otro patrón anti y se usa con frecuencia con Entity Framework para abstraer llamadas a la capa de acceso a datos. En mi opinión, usar un repositorio genérico es una generalización excesiva. Es una mala idea abstraer las llamadas a Entity Framework utilizando un repositorio genérico.

Déjame explicarte esto con un ejemplo.

La siguiente lista de códigos ilustra un repositorio genérico: contiene métodos genéricos para realizar las operaciones CRUD básicas.

public interface IRepository

   {

       IEnumerable GetAll();

       T GetByID(int id);

       void Add(T item);

       void Update(T item);

       void Delete(T item);

   }

Para crear un repositorio específico, deberá implementar la interfaz genérica como se muestra en la lista de códigos a continuación.

public class AuthorRepository : IRepository

   {

       //Implemented methods of the IRepository interface

   }

Como puede ver, para crear cualquier clase de repositorio específica, necesitaría implementar cada uno de los métodos de la interfaz de repositorio genérico. El principal inconveniente de este enfoque es que tendría que crear un nuevo repositorio para cada entidad.

Aquí hay otro inconveniente de este enfoque: la intención básica del patrón de repositorio es desacoplar su capa de dominio de cómo la capa de acceso a los datos conserva los datos. Aquí hay una versión actualizada de la clase de repositorio que acabamos de crear.

public class AuthorRepository : IRepository

   {

       private AuthorContext dbContext;

       //Methods of the IRepository interface

   }

Como puede ver en la lista de códigos proporcionada anteriormente, AuthorRepository necesita la instancia de AuthorContext para realizar las operaciones CRUD para las que está destinado. Entonces, ¿dónde está el desacoplamiento, entonces? Idealmente, la capa de dominio no debería tener ningún conocimiento de la lógica de persistencia.

Una capa extra de abstracción

El modelo de dominio y el modelo de persistencia en una aplicación tienen responsabilidades claramente diferentes. Mientras que el primero modela el comportamiento, es decir, modela los problemas de la vida real y las soluciones a esos problemas, el segundo se utiliza para modelar cómo se almacenan realmente los datos de la aplicación en el almacén de datos.

La intención del patrón de repositorio debe ser abstraer la lógica de persistencia y ocultar las implementaciones internas de cómo se persisten los datos. Las operaciones del repositorio deben ser lo suficientemente expresivas y no genéricas. No puede tener un repositorio que sea genérico y que pueda contener operaciones que puedan caber en cualquier escenario. Esto se convierte en una abstracción innecesaria y, por tanto, hace que el patrón de repositorio genérico sea un antipatrón. Puede modelar todos los objetos de su dominio de la misma manera. Un repositorio genérico no define un contrato significativo y, de nuevo, necesitaría un repositorio específico que amplíe su repositorio genérico y proporcione el conjunto específico de operaciones que son significativas para esa entidad en particular.

Ahora que tiene bastantes tecnologías maduras de persistencia de datos (NHibernate, Entity Framework, etc.), ¿por qué necesita esta capa adicional de abstracción de todos modos? La mayoría de las tecnologías ORM maduras disponibles en la actualidad tienen las mismas capacidades. Al intentar usar un repositorio, simplemente agrega una capa adicional de abstracción sin ningún motivo. Como ejemplo, es posible que necesite métodos como el siguiente para su AuthorRepository.

FindAuthorById()

FindAuthorByCountry()

Esto empeora a medida que tiene más y más métodos y búsquedas complejas; terminaría teniendo un repositorio que se mapearía de cerca con la capa de almacenamiento persistente en uso debajo.