DEV Community

Cover image for Patrón Repositorio (Repository Pattern) y Unidad de Trabajo (Unit Of Work) en ASP.NET Core WebApi 3.0
Eduardo Barrios
Eduardo Barrios

Posted on • Updated on

Patrón Repositorio (Repository Pattern) y Unidad de Trabajo (Unit Of Work) en ASP.NET Core WebApi 3.0

Una aplicación de software típica naturalmente necesitará acceder a algún tipo de almacén de datos para llevar a cabo operaciones CRUD (Crear, Leer, Actualizar, Eliminar) en los datos, por lo general esto podría ser algún tipo de base de datos, sistema de archivos o cualquier tipo de mecanismo de almacenamiento utilizado para persistir de información.
En algunos casos guardar o crear datos puede requerir persistencia en archivos nuevos o crear una nueva fila en una tabla de una base de datos, en otros casos guardar datos nuevos puede requerir varias interacciones con varios servicios basados ​​en la web o aplicaciones y otros servicios que no son generalmente gestionados por nosotros mismos.

Para ambos casos podemos hacer uso del patrón repositorio (Repository Pattern) y una unidad de trabajo (Unit Of Work), pero, ¿qué es el patrón repositorio?
El patrón repositorio consiste en separar la lógica que recupera los datos y los asigna a un modelo de entidad de la lógica de negocios que actúa sobre el modelo, esto permite que la lógica de negocios sea independiente del tipo de dato que comprende la capa de origen de datos, en pocas palabras un repositorio media entre el dominio y las capas de mapeo de datos, actuando como una colección de objetos de dominio en memoria (M. Fowler), ahora bien, ¿qué es el patrón unidad de trabajo?
Este centraliza las conexiones a la base de datos realizando un seguimiento de todo lo que sucede durante una transacción cuando se usan capas de datos y revertirá la transacción si Commit() no se ha invocado o existen incongruencias lógicas.

Para este ejemplo utilizaremos un repositorio genérico y una unidad de trabajo aplicándolo al proyecto de Albumes y Artistas que contiene una base de datos en memoria de sql server. Este proyecto lo he utilizado en post anteriores, he realizado algunas adaptaciones al código del repositorio que me parecen geniales agregando programación asíncrona, separando servicios, desacoplando componentes para lograr una mejor infraestructura dentro del proyecto y que puedan utilizarla en sus proyectos.

Vamos a crear un proyecto con una solución en blanco en Visual Studio 2019 Preview, luego crearemos 3 carpetas de soluciones y dentro cada carpeta agregaremos un proyecto que hace referencia a las capas WebApi que contendrá el manejo de Controllers para exponer o almacenar datos, Entities que hace refencia a los modelos, en un escenario mas sencillo los modelos los crearíamos en el proyecto WebApi pero recuerda que buscamos desacoplar componentes, la tercer capa llamada DataAccess que se encargará de acceder a la información de la base de datos mediante un repositorio genérico y afectar la base de datos mediante una unidad de trabajo. Los proyectos Entities y DataAccess son bibliotecas de clases .NET Standard. La organización del proyecto quedaría de la siguiente manera:

Alt Text

En el proyecto DataAccess crearemos las Clases que implementan las Interfaces del repositorio genérico y la unidad de trabajo, seguidamente haremos uso de inyección de dependencias de ambos, si no sabes que es inyección de dependencias revisa mi post anterior acerca de Inyección de dependencias en. NET Core.

Creamos la unidad de trabajo, su respectiva Interface y Clase.
UnitOfWork utiliza el contexto de datos que pertenece a la capa de Entities, no olvidar hacer referencia a dicho proyecto.

Alt Text

Luego crearemos el repositorio genérico.
Puedes notar que tradicionalmente se inyecta una propiedad de contexto de datos en el constructor del repositorio pero para esta implementación inyectamos una propiedad de tipo IUnitOfWork, esto para lograr centralizar las operaciones a la base de datos.

Alt Text
Alt Text
Alt Text
Alt Text

Nos centraremos en las opciones leer y crear, sobrecargamos el método GetAsync para poder agregar una condición, ordenar por algún atributo propio del modelo y agregar relaciones con otros modelos mediante alguna propiedad de navegación.

Ahora vamos a agregar la dependencia en el contenedor de control de inversión IoC, está clase se crea en una carpeta llamada Middleware, este nombre es arbitrario y debe estar situada en el proyecto WebApi.

Alt Text

Seguidamente utilizamos el contenedor de inversión de control para inyectar el servicio en la clase Startup método ConfigureServices en el proyecto WebApi.

Alt Text

Ahora vamos a utilizar el repositorio genérico y la unidad de trabajo en nuestro Controller, debemos inyectar la interface IGenericRepository pero debemos darle un tipo y en este caso debe ser tipo Album, inyectamos la interface de unidad de trabajo IUnitOfWork, después asignamos la inicialización que el IoC se encargo de instanciar en el inicio y seguidamente podemos proceder a utilizar alguno de los métodos del repositorio, en este caso el método GetAsync del repositorio en el método Api Get, nótese que estamos pasando 3 parámetros: parámetro 1 una condición, parámetro 2 ordenar por algún atributo propio del modelo y parámetro 3 la relación con otro modelo en este caso Artista. Para el caso del método Api Create utilizamos el método del repositorio CreateAsync el cuál recibe un modelo serializado en formato json a través del verbo HTTP Post.

Alt Text

Finalmente vamos a probar el funcionamiento de este proyecto, nos apoyaremos de PostMan para realizar las peticiones HTTP hacía nuestros métodos Api.

Tras compilar, ejecutar y hacer una llamada HTTP Get al método api Get del WebApi obtenemos el listado de Albumes relacionados con el modelo Artista.

Alt Text

Comprobamos el funcionamiento del método api Create enviando un objeto json con la propiedades de Album y no olvidar cambiar el tipo de petición a HTTP Post en PostMan.

Alt Text

El nuevo objeto se creó correctamente, ahora volveremos a realizar la petición al método Get para comprobar la existencia del nuevo registro en la lista de Albumes.

Alt Text

Como podemos observar está implementación funciona correctamente, hemos utilizado el patrón repositorio y una unidad de trabajo.
Entre los beneficios de utilizar el patrón repositorio podemos mencionar centralización de la lógica de acceso a datos, punto de sustitución para las pruebas unitarias y arquitectura flexible que se puede adaptar a medida que evoluciona el diseño general de la aplicación.
En cuanto al patrón unidad de trabajo personalmente uso la unidad de trabajo para reducir mucha inyección de dependencia, puedo tener una unidad de trabajo de la base de datos y una vez que use la inyección de dependencias para inyectar el contexto de la Base de datos en esa unidad de trabajo, no necesito inyectar cada repositorio de modelos donde quiero usarlos, pero solo accederé a los repositorios desde el unidad de trabajo, esto también me ayuda a crear instancias de un repositorio solo cuando lo necesito en un método específico.

Conclusión

El patrón repositorio está destinado a crear una capa de abstracción entre la capa de acceso a datos y la capa empresarial para que pueda ayudar a aislar la aplicación de los cambios en el almacén de datos y facilitar las pruebas unitarias automatizadas para el desarrollo basado en pruebas.

En esta implementación hacemos uso de Entity Framework Core en una implementación de la Unidad de Trabajo y Patrón Repositorio.
Por lo tanto, no es del todo necesario en estos días envolver Entity Framework Core en un patrón de Unidad de Trabajo, porque básicamente se está envolviendo una abstracción sobre una abstracción, pero para fines de entender como funciona Unidad de trabajo hacemos uso de Entity Framework Core.
Además únicamente tenemos un repositorio y el objetivo de la unidad de trabajo es centralizar y unificar repositorios.

Link al repositorio en Github:
https://github.com/EbarriosCode/RepositoryUnitOfWorkNetCore3

Referencias:
https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-design
https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-implemenation-entity-framework-core
https://martinfowler.com/eaaCatalog/repository.html
https://www.martinfowler.com/eaaCatalog/unitOfWork.html

Oldest comments (11)

Collapse
 
gonzalocenturion profile image
gonzalocenturion

Buen post man! Saludos.-

Collapse
 
ebarrioscode profile image
Eduardo Barrios

Gracias. Saludos 😊

Collapse
 
lagarciasilva profile image
Leandro Garcia

Excelente post Eduardo, muchas gracias por compartir conocimiento.
Pero tengo una duda al respecto, en qué parte de la arquitectura recomendarías o debería ir interoperabilidades o consumo de servicios externos de la aplicación, de ante mano muchas gracias nuevamente.

Collapse
 
ebarrioscode profile image
Eduardo Barrios

Hola Leandro es un gusto saludarte, para cada problema existen múltiples maneras para resolver, personalmente te diría que podrías hacerlo dentro de la capa Services ya que está es la encargada de manipular los datos y su única responsabilidad es esa independientemente de si los datos vienen de una base de datos, archivos de texto, repositorios o servicios web que al final lo que trae todo lo anterior es información, por otro lado si quieres enfocarte en un desarrollo basado en muchas más capas podrías crear una capa que se encargue únicamente en consumo de servicios externos.

Collapse
 
edgargonzalo_m profile image
Edgar Gonzalo

Excelente post Eduardo, muchas gracias por compartir esta información...
Realmente había estado buscando un post que explicará bien a detalle estos temas, sin embargo, solo encontraba puras fugas de información.

Me interesa mucho el tema, pues deseo cambiar las estructuras de mis proyectos, al leer tu post me di cuenta de alguna cosas que me hacían falta comprender bien, y algo que no me ha dejado dormir es lo que comentaste en las conclusiones...

"no es del todo necesario en estos días envolver Entity Framework Core en un patrón de Unidad de Trabajo, porque básicamente se está envolviendo una abstracción sobre una abstracción..."

¿Quieres decir que no es necesario usar UnitOfWork?
¿Entonces qué buena practica recomiendas para omitirlo o simplemente así como tal no usarlo?

De ante mano, muchas gracias nuevamente.

Collapse
 
ebarrioscode profile image
Eduardo Barrios

Hola Edgar de nada, con respecto a tu pregunta ¿Quieres decir que no es necesario usar UnitOfWork? yo te diría que no es necesario pero tampoco significa que no se pueda o deba hacer, todo depende de que necesitas y como prefieres atacar un problema, ya que si tu quieres desacoplar tus componentes para facilitar el desarrollo de pruebas unitarias y aprovechar la separación de responsabilidades adelante hazlo, y si no lo haces será muy difícil o casi imposible probar componentes y también seria ineficiente tener controllers con mucho código lo cuál también tendría como consecuencia que no podrías reutilizar código debido a que no esta centralizado.
En respuesta a esta pregunta.
¿Entonces qué buena practica recomiendas para omitirlo o simplemente así como tal no usarlo? todo depende del escenario en el que estés y que es lo que necesitas resolver, el patrón Repository y Unit Of Work son buenas prácticas, personalmente los utilizo para proyectos en producción y todo va bien en cuanto a escalabilidad, rendimiento y otros factores, pero existen infinidad de buenas prácticas que puedes combinar y ajustar según tus necesidades. Espero te sirva mi respuesta, saludos.

Collapse
 
sandovalhlk profile image
sandovalhlk • Edited

Al crear un repositorio de Artista por ejemplo como le inyectarias el context ?
yo trabaje un poco diferenten el Uow y el Generic asi que mi forma de inyectar no funciona, pero me gusta la forma quedaria segun tu planteamiento private readonly DBMCAContext _DbContext;
public ArtistaRepository(DbContext context) : base(context)
{
this._DbContext = context;
}

Collapse
 
ebarrioscode profile image
Eduardo Barrios

private readonly DBMCAContext _DbContext;
public ArtistaRepository(DBMCAContext context)
{
this._DbContext = context;
}

Collapse
 
feralva profile image
feralva

Buenas, como aseguras que la unit of work que injectas en el controlador, es la misma que se injecta en el repository? por lo que veo, no la estas declarando como singleton. saludos

Collapse
 
jcdiazgm profile image
jcdiazgm

¿Podría prescindir de la UnitOfWork e inclusive de EntityFrmework y trabajar directamente con ADO?

Collapse
 
wilsacup profile image
wilsacup

Muy bueno el aporte de Eduardo, me acaba de sacar de lios con este articulo, porque estaba realizando una implementacion de Patron de Repositorios, pero con una logic algo diferente ya que no utilizaba la clase UnitOdWork y se me estaba presentando problemas con algo llamadoHttpContext.GetOwinContext.Get.

Me gustaria realizar un pequeño aporte y es el siguiente: si lo que nos interesa es separar las capas del repositorio de la capa de aplicación (Controllers) me parece interesante poder agregar una capa de "Servicio" que me independice la lógica del negocio de la logica del controlador, de tal manera que se haga más facil el mantenimiento de la aplicación. por lo que yo agregaria una sección de Servicios, que se encargue de aplicar la lógica que se requiere para traer los datos requeridos al controlador, y que el controlador solamente se encargue de invocar el metodo del servicio que requiere (abstraccción)
Ejemplo
Metodo Get de AlbunessController solo invocaria el servicio
Service.ConsultaArtistaMayor(int anio)
y en la capa de servicio
crearía un método
public Task> ConsultaArtistaMayor(int anio) {
return await _genericRepository.GetAsync(a => 1.anio > anio, a =A a.OrderByDescending(b => b.anio),"Artista");
}