Unit of Work, Repository y Entity Framework Code First

En esta entrada voy a hablar un poco sobre una arquitectura para la capa de datos que a mi me esta funcionando muy bien. Se trata de aplicar el patrón Unit Of Work junto al Repository Pattern y Entity Framework Code First para crear una capa de acceso a datos totalmente desacoplada de la capa de negocio, de forma que nuestro modelo de aplicación y las capas superiores no se vean afectadas por un cambio en la tecnología utilizada para acceso a datos.

Sé que Entity Framework ya implementa de por sí el patrón repositorio, facilitando métodos como Add, Remove, Find… Por lo tanto el objetivo final de esta arquitectura no es sobreescribir esta funcionalidad, nunca he sido partidario de reinventar la rueda. Lo que se persigue aquí es evitar referencias a entity framework en nuestra capa de dominio.

Se puede decir lo mismo en cuanto al patrón Unit Of Work, Entity Framework ya proporciona esta forma de trabajar con DbContext, que son unidades de trabajo en si mismos. No obstante vuelvo a repetir que el objetivo es desacoplar la capa de acceso a datos del resto de la aplicación.

Otro interesante efecto que logramos con esta arquitectura, es el de poder aplicar unit test a los métodos de la capa de dominio mockeando los repositorios, algo de lo que hablaré en otra entrada llegado el momento.

Y aún más interesante, incluso podrás testear tus repositorios aplicando mocking a los dbset de EntityFramework, de forma que sabrás incluso si tu capa de acceso a datos recuperará los datos como se espera. Y todo esto sin necesidad de acceso a bases de datos, lo que hará que tus test se ejecuten rápidamente y de forma aislada, sin dependencias.

Comencemos.

Modelo de la capa de dominio

Habrá 2 entidades Post y Comment

Interfaz de repositorio genérico usado por la capa de dominio

Las operaciones de acceso a datos de todas nuestras entidades usarán una interfaz de repositorio genérica. Si fuese necesario acciones mas específicas, para por ejemplo aplicar “includes” de EF o joins con otras entidades, se crearán repositorios específicos sobre este genérico con métodos de extensión como luego indicaré.

El motivo por el que se encuentra un Get y un Find, es para poder aplicar una distinción sobre cuando queremos obtener una entidad para hacer cambios sobre ella o cuando queremos obtenerla solo como objeto a ser consultado sin estar attachado al contexto. Esta distinción cobra sentido cuando en la capa de datos vayas a utilizar Entity framework, el cual es capaz de realizar un tracking sobre los cambios de la entidad.

Si realizas un Get permitirás que entity framework se comporte como de costumbre, con dicho tracking, en cambio si haces Find aplicarás el método AsNoTracking() sobre las entidades recuperadas de forma que Entity Framework no trackeará los cambios, de esto hablaré en otra pequeña entrada. Este método de Entity Framework aporta una mejora de performance de tu aplicación. Cuando se trata una pequeña cantidad de objetos no será muy notable, pero si la cantidad es grande, esta diferencia será mas significativa.

Ahora vamos a crear una interfaz de repositorio específico para obtener un post con sus cantidad de comentarios.

En caso de que estés trabajando con Entity Framework, la implementación de este repositorio será sencilla gracias a los DbSet, que harán gran parte del trabajo por ti.

En cambio si trabajas con otras tecnologías, en tu repositorio deberás mantener listas para insert, update, delete… y manejar los objetos de estas listas en la implementación de tu repositorio.

Al fin y al cabo el efecto conseguido será que ante un cambio de tecnología en la implementación, tu aplicación no sufrirá cambios de funcionalidad. De hecho tus test seguirán teniendo un resultado positivo porque los repositorios estarán mockeados. Y será la implementación de tus repositorios lo que deberás mockear. En caso de Entity Framework existen mecanismos para mockear DbSet, haré una entrada específica sobre esto, o por ejemplo si trabajas contra dynamoDb, puedes crear test que creen una instancia local de tu base de datos y creen sus tablas y datos, probando estos test de repositorio con el diseño adecuado.

Este cambio de tecnología en la capa de datos podrá aplicarse con mucha facilidad si estas trabajando con Unity, de forma que resuelvas cada interfaz con una implementación determinada con solo un cambio en un webconfig, también tengo intención de hablar sobre como configurar Unity y trabajar con él en otra entrada.

Interfaz de Unit Of Work en la capa de dominio

Para que todas tus operaciones de los diferentes repositorios se realicen sobre un mismo contexto, en una misma operación, bajo una misma unidad de trabajo, sin vincular Entity Framework a tu capa de dominio, necesitarás usar una interfaz de una unidad de trabajo que resuelva este trabajo.

Con el save se podrán persistir los cambios almacenados en los repositorios, en caso de usar Entity Framework será un SaveContext sin más en la implementación en la capa de datos, gracias a DbContext. En cambio si se trata de otra tecnología habrá que manejar las diferentes listas gestionadas en los repositorios sobre las diferentes operaciones para gestionar la ejecución de dichas operaciones sobre los diferentes objetos.

Para los post dispones de una interfaz extendida, por lo tanto es necesario aplicarla en la unidad de trabajo.

Con esto tu capa de dominio esta completa, no se ha utilizado en ningún momento ninguna dependencia con Entity Framework, por ello la implementación de estos repositorios y el save de la unidad de trabajo podrían contener cualquier tecnología usada adecuadamente.

Estas clases estarán ubicadas en tu capa de dominio, ahora toca enlazarla con la capa de datos, donde estarán las implementaciones del repositorio genérico, la unidad de trabajo y la fluent API de entity framework para los mappings a través de los cuales usar Code First.

Implementación del repositorio en la capa de datos

A continuación te presento la implementación del repositorio genérico.

Con esta implementación tienes listo un repositorio genérico aplicando Entity Framework en la capa de datos. Si tienes operaciones como Join o Include para añadir un objeto dentro de otro, recomiendo hacer el join en un método de extensión de este repositorio y hacer un pequeño mapeo del objeto para que devuelva la estructura que buscas.

El motivo de mi recomendación es que el mocking de dbset de entity framework funciona perfectamente con cualquier consulta linq, en cambio si usas includes será necesario que anides los objetos al crear el mocking de los dbset, lo cual puede llegar a ser confuso y difícilmente mantenible.

En el caso de la interfaz extendida para los post, debes también implementar su método de extensión.

Como comento, si creas los DbSet sobre Post y Comment, añadiendo varios objetos a sus colecciones, y los usas para crear un contexto mockeado que haga uso de estas colecciones en lugar de hacer uso de la base de datos, podrás testear esté método de repositorio de forma aislada y se comportará perfectamente. Ya lo podrás comprobar en entradas futuras.

Implementación de la unidad de trabajo en la capa de datos.

Ya queda menos para terminar, tan solo implementar la unidad de trabajo y los mappings de Entity Framework que darán soporte al code first.

Pues otra cosa menos, en esta implementación de la unidad de trabajo, he añadido las clases PostMap y CommentMap que habrá que implementar.

Mappings de Entity Framework Code First en la capa de datos

Es común en los proyectos donde se esta usando entity framework que las clases de configuración de mappings estén en el mismo proyecto que el modelo, dentro de una carpeta “mappings” dentro de la carpeta “model”. Esto es debido a dónde las plantillas o la utilidad Entity Framework Power Tool generan estas entidades.

Yo en cambio considero que es mas útil separar estos dos tipos de entidades. Dado que tu modelo en la capa de dominio debe responder a cualquiera que sea la tecnología utilizada en la persistencia a datos, y con esta entrada puedes ver que esto es perfectamente factible.

Dicho esto, procedo a implementar los mappings de las entidades utilizadas. Se trata de un ejemplo, podrían añadirse o quitarse cualidades adicionales a las propiedades de los objetos.

En este caso aplico para controlar la concurrencia el método IsRowVersion(), del que hablo en esta entrada sobre manejar la concurrencia.

Además aplico sobre la propiedad Comments un “Ignore”, esta propiedad es muy útil para que los objetos de modelo puedan alojar propiedades que son necesarias en la capa de dominio para representar información, pero no tienen porque figurar en la capa de datos. De hecho, en este caso, el repositorio sobre post tiene un repositorio extendido con el método encargado de poblar esta propiedad haciendo uso de linq para obtener el valor.

 

El mapping de Comments te lo puedes imaginar, muy similar al de Post. Por lo tanto hasta aquí ha llegado esta entrada sobre arquitectura centrada en la capa de datos y su conexión con la capa de dominio.

Para crear tu base de datos podrías utilizar migrations junto a esta arquitectura sin ningún problema, pues con los mappings y el OnModelBuilder es suficiente, utilizando la aproximación Code First. Hablaré en otra entrada sobre como aplicar migrations para crear tu base de datos desde cero usando tu modelo de la capa de datos.

Un saludo y disfruta programando.

5 comentarios en “Unit of Work, Repository y Entity Framework Code First

    • Buenas noches matias_p, perdona por la demora en la respuesta.

      No es exactamente el mismo código, si te fijas en la línea 3, el código inicial presenta el repositorio genérico de los post “IRepository“, mientras que en el ejemplo editado, estamos usando el repositorio que tiene la extensión, que en este caso es IRepositoryPost, el cual unas lineas mas arriba en esta entrada se puede ver como hereda de IRepository y añade un nuevo método a la interfaz (la extensión).

      La diferencia no es demasiado apreciable ahora que lo comentas, de hecho me he tenido que fijar unas cuantas veces volviendo a leer esa sección y la anterior.

      No tengo el código para descargar ya que faltarían otras zonas de la aplicación para darle funcionalidad “al blog”, como por ejemplo la capa de servicio que hace uso de esa unidad de trabajo y con la cual conecta el controlador. Únicamente quería ejemplificar con una visión sencilla y muy típica (el blog) esta arquitectura que actualmente uso en mis proyectos propios.

      Me lo plantearé cuando consiga algo de tiempo libre y te haré avisaré cuando lo tenga disponible.

      Un saludo y gracias por seguir estas líneas 😉

    • Actualmente estoy trabajando en un proyecto basado en microservicios donde en cada uno de ellos implemento está arquitectura con algunos extras más en otras capas, nada que tenga publicado en abierto.

      A ver si saco algo de tiempo y monto un ejemplo básico completo en Git que poder descargar y ver en acción.

      Lo que si que tengo aquí en el blog es otra entrada sobre arquitectura en la q presento la estructura interna del proyecto y una visión teórica esquematizada de cómo encaja todo.

      Gracias por tus palabras Sergio, y por leer mi blog 😉

Deja un comentario