Patrones de Diseño Esenciales para .Net Core WebApi

 

 

Probablemente al estar leyendo este post ya estes familiarizado con el concepto te patrones de diseño debido a la gran popularidad que han adquirido por el famoso libro  "Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994). Estos conceptos aún hoy son extremadamente importantes pues todos estos patrones son aplicables al desarrollo de software moderno y el saber cuando y donde utilizarlos nos garantizan un código mantenible y limpio.

En este post nos vamos a enfocar en algunos patrones de diseño que yo personalmente utilizo al trabajar con servicios REST especificamente con .Net Core, dando una breve explicación del patrón y después incluyendo algo de código a un pequeño proyecto que dejare al final del post para que lo descargues si te es de utilidad (No hace nada pero demuestra de manera gráfica lo que se va a explicar a continuación).

 

1.- Repositorio (Repository Pattern)

 

 

Este se define como un constructo común para evitar la duplicación de la lógica de acceso a datos dentro de nuestra aplicación ya sea a través de un ORM, WCF, etc. A nivel muy basico el proposito del patrón es esconder por decirlo de algún modo, el como se esta accesando a los datos. Este "repositorio" se comporta como una variable en memoria a la cual podemos acceder en todo momento para Añadir/Eliminar/Actualizar objetos. Este patrón dentro del contexto de .Net suele usarse en la capa de datos generalmente haciendo uso de Entity Framework y Linq to SQL (aunque también puede ser implementado con ADO.Net puro). Para nuestro ejemplo vamos a utilizar EF y un approach Database First por lo que tendrás que crear tu base de datos con una sola tabla llamada Test.

 

La primer cosa que debemos hacer es crear un Contexto que finalmente va a ser el componente que permita la comunicación con la base de datos. Al mismo tiempo vamos a utilizar una clase llamada Test.cs representando una tabla en sql server.

namespace Project.Data
{
    public class RepositoryContext: DbContext
    {
        public RepositoryContext(DbContextOptions options)
            :base(options)
        {
        }
        //Your Entities
        public DbSet<Test> Tests { get; set; }
    }
}
namespace Project.Data
{
    [Table("Test")]
   public class Test
    {
        public string Id { get; set; } // Id (Primary key)
        public string Texto { get; set; }
    }
}



A continuación, vamos a agregar una Interfaz generica que vendrá a representar una entidad de LINQ to SQL y va a proveer mecanismos para los metodos relacionados como lo son: Create,Update, Delete, Save.

 

   public interface IRepositoryBase<TAggregateRoot>
    {
        IEnumerable<TAggregateRoot> FindAll();
        IEnumerable<TAggregateRoot> FindByCondition(Expression<Func<TAggregateRoot, bool>> expression);
        void Create(TAggregateRoot entity);
        void Update(TAggregateRoot entity);
        void Delete(TAggregateRoot entity);
        void Save();
    }

La implementación de la interfaz anterior es bastante simple ya que solo recibe como parametro un tipo TAggregateRoot, que dentro de este patrón puede ser cualquiera de las entidades de nuestro contexto.

 
namespace Project.Data
{
    public class RepositoryBase<TAggregateRoot> : IRepositoryBase<TAggregateRoot> where TAggregateRoot : class
    {
        protected RepositoryContext RepositoryContext { get; set; }

        public RepositoryBase(RepositoryContext repositoryContext)
        {
            this.RepositoryContext = repositoryContext;
        }

        public IEnumerable<TAggregateRoot> FindAll()
        {
            return this.RepositoryContext.Set<TAggregateRoot>();
        }

        public IEnumerable<TAggregateRoot> FindByCondition(Expression<Func<TAggregateRoot, bool>> expression)
        {
            return this.RepositoryContext.Set<TAggregateRoot>().Where(expression);
        }

        public void Create(TAggregateRoot entity)
        {
            this.RepositoryContext.Set<TAggregateRoot>().Add(entity);
        }

        public void Update(TAggregateRoot entity)
        {
            this.RepositoryContext.Set<TAggregateRoot>().Update(entity);
        }

        public void Delete(TAggregateRoot entity)
        {
            this.RepositoryContext.Set<TAggregateRoot>().Remove(entity);
        }

        public void Save()
        {
            this.RepositoryContext.SaveChanges();
        }
    }
}



 

Finalmente para hacer uso del repositorio unicamente tendríamos que proporcionar la cadena de conexión :

            var optionsBuilder = new DbContextOptionsBuilder<RepositoryContext>();
            optionsBuilder.UseSqlServer(ConfigurationManager.ConnectionStrings["connString"].ConnectionString);

            using (var dataContext = new RepositoryContext(optionsBuilder.Options))
            {
                var testRepository = new RepositoryBase<Test>(dataContext);
                var allTests = testRepository.FindAll();
            }

 

En este ejemplo sencillo podemos ver el poder de este patrón que nos permite tratar la capa de datos desde un enfoque mas testeable y que nos permite a su vez inferir de manera mas simple dependencias dentro de la base de datos pero puede explotarse a niveles aún mas complejos dependiendo de las necesidades especificas que se tengan en mente. El ejemplo anterior serviría muy bien para un solo data source, pero ¿que tal si quisieramos que nuestro código tuviera la opción de elegir entre diversos ambientes dependiendo de la lógica de negocio?. Ahora vamos a ver un ejemplo donde exploraremos dos patrones nuevos: UnitOfWork y Factory, los cuales utilizados en conjunto nos van a permitir atender situaciones como la anterior de una manera bastante elegante.

 

2.- Fábrica (Factory Pattern) y Unidad de Trabajo (UnitOfWork Pattern)

 

 

Antes de empezar de lleno con el patrón Factory me gustaria entrar rapidamente a explicar UnitOfWork ya que es bastante sencillo de entender y implementar, ademas de que lo vamos a necesitar para este patrón.

UnitOfWork es utilizado para mantener una lista de objetos afectados por una transacción de negocio y coordina operaciones de escritura/lectura de los mismos. Lo que en español quiere decir, es que dentro de este patrón el objeto llamado UnitOfWork mantiene una lista de cambios realizados a las entidades ademas de operaciones a realizar que se ejecutan a manera de una transacción. Dicho esto podemos concluir que siempre que trabajemos con los objetos ObjectContext y DbContext ya tendremos nuestra unidad de trabajo, pues estos objetos ya implementan este patrón. Con lo anterior y adelantandome un poco al ejemplo, lo que vamos a hacer es modificar el código del ejemplo anterior para crear una fabrica generica de DbContexts<T> que nos permita trabajar con distintas bases de datos de una manera bastante flexible.

 

Factory define una interfaz para crear un objeto, pero aislando la lógica que determina el tipo final de la clase a instanciar. Para nuestro ejemplo vamos a comenzar definiendo nuestra unidad de trabajo creando una nueva interfaz y recordemos que vamos a asociar directamente el tipo DbContext con la misma:

    public interface IUnitOfWork
    {
        DbSet<T> Set<T>() where T : class;
        int SaveChanges();
        EntityEntry Entry<T>(T o) where T : class;
        void Dispose();
        bool EnsureCreated();
    }

Despues vamos a implementar nuestra unidad de trabajo en la clase RepositoryContext.cs :

   public class RepositoryContext : DbContext,IUnitOfWork
    {
        public RepositoryContext(DbContextOptions options)
            : base(options)
        {

        }

        public DbSet<Test> Tests { get; set; }

        public new DbSet<T> Set<T>() where T : class
        {
            return base.Set<T>();

        }

        public EntityEntry Entry<T>(T o) where T : class
        {
            return base.Entry<T>(o);
        }

        public bool EnsureCreated()
        {
            return Database.EnsureCreated();
        }
    }

 

Ahora tenemos que crear nuestra Fábrica que será la encargada de generar los distintos contextos tomando como parametro una cadena de conexión y una unidad de trabajo(Contexto). Para la cadena de conexión en este caso crearemos una interfaz cuyos valores hagan match con las cadenas de conexión almacenadas en el archivo de configuración del WebApi. 

    public static class ContextFactory
    {
        public static TContext Create<TContext>(ConnectionString dbContext) where TContext : class, IUnitOfWork
        {
            var optionsBuilder = new DbContextOptionsBuilder<DBContext>();
            optionsBuilder.UseSqlServer(ConfigurationManager.ConnectionStrings[dbContext.ToString()].ConnectionString);
            Type dataContextType = typeof(TContext);
            var context = (TContext)Activator.CreateInstance(dataContextType, optionsBuilder.Options);
            context.EnsureCreated();
            return context;
        }
    }
    public enum ConnectionString
    {
        DBContext,//Names must match with Project.Api -> app.config -> ConnectionStrings 
        AnotherDBContext
    }

Finalmente nuestra implementación final quedaría de la siguiente manera:

       using (var dataContext = ContextFactory.Create<RepositoryContext>(ConnectionString.DBContext))
            {
                var testRepository = new RepositoryBase<Test>(dataContext);
                var allTests = testRepository.FindAll();
            }


Finalmente podemos ver que el resultado es bastante práctico y fácil de mantener a largo plazo, por no decir adaptable a distintos ambientes si ese fuera el requerimiento.

 

3.- Fachada (Facade Pattern) 

 

Este patrón provee una interfaz unificada para acceder a un grupo de interfaces de uno o varios subsistemas. Dicha interfaz tiene el proposito de ser muy simple de usar. En el caso que hemos estado manejando donde nuestro API es capaz de conectarse a varios dominios imaginemos el escenario en el que un cliente intenta hacer Login apuntando a nuestra API y nuestro código tiene la responsabilidad de saber de que base de datos obtener la información así como validar en cada request que tenga los permisos adecuados para ver la información que intenta acceder.

Para ejemplificar lo anterior, podrás encontrar en el código de ejemplo un proyecto llamado Project.DomainServices. Esta capa contiene varias fachadas que agrupan diferentes tipos de funcionalidad (En nuestro caso solo una: DummyAuthService.cs que se encargaría de todo lo referente a autenticación) como se muestra a continuación:

Dicha clase solo contiene un método que recibe un usuario y su contraseña y como se puede identificar en los comentarios internamente dicha función estaría haciendo sub llamadas a diferentes métodos e inclusive servicios para llevar a cabo la autenticación del usuario haciendo que el código sea mas mantenible al ofrecer a quién lo consuma una sola llamada y de esta manera los clientes siempre tengan un punto de entrada sencillo mientras la fachada se encarga de la parte complicada.

 

4.- Adaptador (Adapter Pattern) 

 

 

Este patrón es de los mas comunes y posiblemente ya lo hayas utilizado sin haberte dado cuenta. Este patrón permite convertir una interface de alguna clase en una completamente diferente que el cliente espera recibir en su lugar que pueden ir de algo tan sencillo como convertir una lista de elementos a un archivo JSON, o una cadena de texto a un objeto mas complejo y con cierta estructura que un servicio web espera recibir. En nuestro ejemplo simplemente vamos a referirnos al mismo proyecto que en el patrón Fachada pero en la carpeta Mappers. Imaginemos que cuando un usuario se registra y envía los datos a nuestro servicio usa la siguiente clase:

 

 

Agregaremos también una nueva función a nuestro servicio DummyAuthService.cs que en nuestro ejemplo sería la encargada de registrar al usuario en la base de datos asi como informar si hubo algún error en el proceso:

 

En el código se muestra que al intentar el nuevo usuario, el repositorio (repo.Add() ) espera recibir una entidad por lo que tenemos que crear una función que haga la conversión a la interfaz destino que en este caso vendría a ser un método de extensión dentro de la carpeta Mappers que hace precisamente lo que necesitamos, tomar la información del DTO y transformarla a la entidad que espera nuestro contexto.

    public static class Mappers
    {
        public static Data.Entities.User ToEntity(this RegisterUserDTO user)
        {
            return new Data.Entities.User()
            {
                Username = user.Username,
                Password = user.Password,
                Address = user.Address,
                FirstName = user.FirstName,
                LastName = user.LastName,
                LastLogin = DateTime.Now
            };
        }
    }

Lo anterior es un ejemplo extremadamente sencillo de este patrón en acción. En otros ejemplos reales puede verse mucho cuando se trabaja con sistemas/servicios legacy que muchas veces trabajan con sus propias interfaces y sobre las que muchas veces se tiene que hacer un proceso similar al anterior para transformar nuestros objetos en objetos válidos para el servicio destino.

 

5.- Proxy Pattern

 

 

Es un patrón bastante sencillo que como su nombre lo indica tiene como proposito el proporcionar un intermediario (representa al original) para acceder a un punto B desde un punto A. Los ejemplos mas sencillos de este patrón son los servicios Web en general. Imagina que estas utilizando el REST API de Twilio desde tu WebApi para enviar un SMS, realmente lo que estas utilizando es un Proxy asi como nuestro WebAPi de .NetCore también lo es pues permitimos a los usuarios interactuar con la base de datos a través de un intermediario (WebApi) pero bajo nuestras reglas, lo que provee una capa de seguridad adicional.

 

6.- Fábrica Simple (Simple Factory Pattern)

 

 

Este patrón si bien no se incluye de manera oficial en GoF (Gang of Four) es uno de los mas utilizados en cualquier aplicación moderna y básicamente se define como un objeto que sirve para crear otros objetos. Este patrón viene a arreglar código como el que se muestra a continuación:

 

 

 

Ahora imagina que tienes 100 funciones diferentes que retornan una nueva instancia siempre de la misma manera que se muestra arriba. El problema viene en realidad cuando se hacen cambios al objeto que se regresa, en este caso ReturnCodeDto.cs ya que al hacerle cambios a la definición del objeto, tendriamos que replicarlos en las 100 funciones en donde estamos haciendo algo similar, y es aquí donde es mas conveniente crear una fábrica simple de tipo ReturnCodeDto donde la creación de esta instancia se mantenga de la manera mas homogénea en todos los puntos donde se utilice:

 

Implementación:

                                                                          

 

Definición:

 

De nuevo un ejemplo simple pero que implementado desde el principio puede ahorrar bastantes dolores de cabeza.

 

 

6.- Inyección de Dependencias

 

 

Es un patrón que ayuda a desacoplar las diferentes partes que conformar una aplicación. Provee un mecanismo para la construcción de grafos que son independientes a la definición de sus clases. Antes de .Net Core había pocas opciones para implementarlo como Autofac, Unity, Ninject, etc pero para nuestra fortuna .Net Core lo soporta por defecto y de una manera muy sencilla. Dentro de cualquier aplicación de .Net Core podemos configurar este mecanismo dentro del archivo Startup.cs en el método ConfigureServices():

public class Startup {
  public void ConfigureServices(IServiceCollection services) {
    services.AddTransient<IDummyAuthService, DummyAuthService>();
  }
  // ...
}

Si implementamos lo anterior sobre uno de nuestros controladores, la Implementación de la interface en cuestión sería inyectada a través del constructor cada vez que se dispare un request hacia alguna ruta dentro del mismo:

namespace Project.Api.Controllers
{
    /// <summary>
    /// API to get numbers
    /// </summary>
    [Route("api/[controller]")]
    [AllowAnonymous]
   
    public class OrdersController : Controller
    {

        private readonly IDummyAuthService _authService;

        public OrdersController(IDummyAuthService authService)
        {
            _authService = authService;
        }

        [HttpGet]
        public  IActionResult Login(string user,string pwd)
        {
            _authService.Login(user,pwd);
            return Ok();
        }
    }
}

Volviendo un poco al archivo Startup.cs, podemos configurar cada dependencia en base al uso que le vayamos a dar dentro de la aplicación:

  1. Transient: Una nueva instancia va a ser creada cada vez que sea solicitada.
  2. Scoped: Se crea una soloa instancia por scope (Web Request).
  3. Singleton: Una sola instancia es creada durante el primer request y va a ser utilizada para todos los clientes que la soliciten.

 

Si no te sientes suficientemente encantado por la solución que provee .Net Core por defecto siempre puedes usar uno de los proveedores que mencionamos al principio de este patrón. A medida que progreses en este patrón te podrás dar cuenta de que el usarlos o no usarlos es un tema bastante opinionado dentro la comunidad, y la verdad es que depende de ti y de lo que la experiencia te permita analizar, pues en aplicaciones gigantes por ejemplo, que están pensadas para crecer a largo plazo se vuelve un tema casi obligatorio pues al desacoplar las dependencias de todos los modulos se mejora la testabilidad y extensibilidad del código, haciendo que integrar nueva funcionalidad sea un proceso muy controlado y simple dentro de flujos que quizás son muy complejos.

 

¿Siempre se deben implementar todos los patrones?

 

La respuesta corta es NO ya que depende enteramente de los escenarios que te encuentres. Lo anterior es una receta meramente personal que siempre me ha servido para toda clase de problemáticas y algunas otras veces tengo que agregar modificaciones y recurrir a patrones distintos que ayuden en lo mejor posible a resolver mi problema. Así que mi recomendación sería que  juegues con todos los patrones de diseño que te sea posible para que los conozcas y sepas identificar cuando usar uno u otro en base a los beneficios que cada uno ofrece.

 

Código fuente:

https://app.box.com/s/t0flqst81s817b4uco5avkcz0e5umhjz

 

Add comment