Estamos trabajando en algo propio llamado KODOTI. Click para unirte
Se viene KODOTI. Únete

Implementación de Unit Of Work, Services y Repository con Net Core 2.2

En este proyecto de CSharp vamos a implementar los patrones Unit Of Work, Services y Repository, y ver su ventaja en conjunto.

Rodríguez Patiño, Eduardo
1,901 lecturas
Rodríguez Patiño, Eduardo
Hemos migrado hace poco nuestras publicaciones del blog antiguo. Si crees que esta se encuentra incompleta o sin coherencia deja un comentario para restaurarla manualmente.

Continuamos con nuestra publicación de los patrones de diseño, ahora nos toca profundizar y complementar la publicación previa de Unit Of Work.

En el siguiente ejemplo he creado una solución de VS2017 usando Net Core 2.0. Asimismo, nuestro cliente es un proyecto consola para realizar las pruebas, en otras publicaciones tal vez hagamos un proyecto web con su respectivo videito.

Patrones a usar

En la entrada anterior sobre UoW prometimos crear otra publicación usando más patrones, patrones que yo creo que quedan de lujo como complemento a este, ahora lo haremos mediante el repository pattern.

  • Services Pattern: nos permite organizar la lógica de nuestro negocio. Anteriormente, las consultas a la DB la armabamos en esta capa pero con este patrón de UnitOfWork lo que vamos a usar ahora son repositorios.
  • Repository Pattern: nos permite manipular el acceso a la base de datos; es decir, mediante este podemos realizar las consultas a la DB.
  • En el repositorio de GitHub he adjuntado un patrón repositorio genérico porque sino, nos queda chico o nos da la sensación que falta más para poder hacer consultas a la DB. De todas formas, esta clase del patrón genérico ha sido adaptado para un proyecto mío, por lo cual en tu proyecto deberías modificarlo, expandirlo.
  • UnitOfWork Pattern: centraliza las conexiones a la base de datos y gestiona los cambios (context.SaveChanges();).

Distribución de capas

  • Clients: implementación de nuestros clientes. Para nuestro ejemplo usamos un proyecto consola.
  • Services: manipulación de los repositorios. Encapsulamos la lógica de las llamadas a los repositorios mediante un Services Layer, porque de esta manera evitamos que llamen a los repositorios directamente desde el controlador. Asimismo, podemos usar más de una lógica del repositorio desde un solo método de nuestro Services Layer.
  • Persistence
    • UnitOfWork: código del patrón UnitOfWork
    • Persistence: migraciones, instancia de la base de datos.
    • Repository: los repositorios a crear. Es un repositorio por modelo, por ejemplo UserRepository (referencia a tu tabla Usuario).

¿Cuál es el flujo?

Crear el repositorio En nuestro ejemplo creamos el UserRepository con su respectiva interface. Asimismo, este hereda de un repositorio genérico que contiene lógica básica como GetAll(), GetPage(), Single(), entre otros.

public interface IUserRepository : IPagedRepository<UserExample>, IReadRepository<UserExample>, ICreateRepository<UserExample>, IRemoveRepository<UserExample>, IUpdateRepository<UserExample>
{

}

public class UserRepository : GenericRepository<UserExample>, IUserRepository
{
    public UserRepository(
        ApplicationDbContext context
    )
    {
        _context = context;
    }
}

Si se dieron cuenta, nuestro IUserRepository implementa interfaces adicionales. Esto lo hago con el fin de que en nuestro proyecto a veces no queremos que dicho repositorio tenga acceso a todas las operaciones CRUD. Entonces, con dichas interfaces podemos cambiar el comportamiento de esta clase (Segregation Principle).

Inyectar el repositorio en UnitOfWorkRepository Cuando descarguen el proyecto verán que tienen una clase llamada UnitOfWorkRepository la cual gestionará todos los repositorios de tu proyecto. Asimismo, UnitOfWork es el único responsable de acceder a los repositorios.

public interface IUnitOfWorkRepository
{
    IUserRepository UserRepository { get; }
}

public class UnitOfWorkRepository : IUnitOfWorkRepository
{
    public IUserRepository UserRepository { get; }

    public UnitOfWorkRepository(ApplicationDbContext context)
    {
        UserRepository = new UserRepository(context);
    }
}

Crear el Service Finalmente creamos nuestro UserServices que invocará como instancia solo a UnitOfWork para que pueda trabajar.

public interface IUserService
{
    IEnumerable<UserExample> GetAll();
    void Create(UserExample model);
    DataCollection<UserExample> Paged(int page, int take);
}

public class UserService : IUserService
{
    private readonly IUnitOfWork _uow;

    public UserService(
        IUnitOfWork unitOfWork
    )
    {
        _uow = unitOfWork;
    }

    public void Create(UserExample model)
    {
        // Call to your repository
        _uow.Repository.UserRepository.Add(model);

        // Save changes
        _uow.SaveChanges();
    }

    public IEnumerable<UserExample> GetAll()
    {
        return _uow.Repository.UserRepository.GetAll();
    }

    public DataCollection<UserExample> Paged(int page, int take)
    {
        return _uow.Repository.UserRepository.GetPaged(
            page: page,
            take: take,
            orderBy: x => x.OrderByDescending(y => y.Id)
        );
    }
}

Si se dan cuenta, nuestro Service permite llamar a UnitOfWork y trabajar con el repositorio que querramos.

Ejemplos de prueba

using (var context = new ApplicationDbContext())
{
    // Prepare unit Of Work
    IUnitOfWork uow = new UnitOfWorkContainer(context);

    // Prepare service
    IUserService userService = new UserService(uow);

    // Call service method
    userService.Create(new UserExample
    {
        LastName = $"LastName {value}",
        Name = $"User {value}",
        WebSite = "http://anexsoft.com"
    });
}

Cuando veamos el ejemplo con un proyecto web podemos gestionar mejor el uso de las depdencias (dependecy injection) obteniendo un código más limpio y aprovechando las características de Net Core.

Resumen

La ventajas que yo encuentro particularmente de trabajar con estos 3 patrones es la siguiente:

  • Nuestra capa Services encapsula toda la lógica, de esta manera evitamos tener el código acumulado directamente en nuestro controlador o capa cliente.
  • UnitOfWork nos permite manipular mejor las conexiones a la base de datos y realizar cambios o saveChanges() cuando yo lo especifique.
  • El uso del patrón Repository brinda la facilidad de reutilizar lógica. Por ejemplo, si quiero validar que un usuario existe en la Db y no tuvieramos esta capa tendría que duplicar el código por cada Service que lo vaya a utilizar. En cambio, al tenerlo este patrón implementado es más sencillo llamar desde cualquier servicio llamar a dicho método y si hacemos un cambio de este se actualizará para todos automáticamente (porque es una sola llamada). Eliminamos código repetitivo.

No todo es bonito, las desventajas que encuentro son:

  • Mayor inversión de código, pero luego todo es reutilizable.
  • Algunos queries muy pesados pueden ser jodidos de implementar. Por ejemplo, si quisiera un INNER JOIN de varias tablas para retornar un query con campos que no hacen referencia a una entidad, lo que estoy haciendo es crear una clase personalizada llamada por ejemplo UserReportAggregate y esta solo existiría desde el repositorio.
  • Al quitar el acceso directo al DbContext vamos a tener que seguir optimizando nuestro repositorio genérico.

Yo vengo trabajando un proyecto personal que en un par de meses va a salir a la luz y se enterarán y he venido usando esta arquitectura la cual me permite escalar muy bien.

Bueno, pueden descargar el proyecto en los links adjuntos (parte inferior) y/o dejar sus dudas.

¿Te gustó nuestra publicación?
Suscríbete a nuestro boletín