[Patrones] Intercepción de llamadas a métodos (I) Patrón Decorator

Ayer a ráiz de un post de Javier Torrecilla salió una discursión acerca de como tratar el tema de la seguridad en nuestras aplicaciones. Javier hablaba sobre el uso la clase PrincipalPermission y mi comentario fue a la hora de ver que el código de Javier, mezclaba un tema como la seguridad en la vista (Entiendo que es un ejemplo) pero yo soy un poco toca… (En el buen sentido de la palabra XD ) y le comentaba que el tema de la seguridad, en mi caso lo trato como una funcionalidad transversal a toda la aplicación (CrossCutting).

Comentaba que podemos implementarlo de varias formas, entre ellas podemos hacer uso del patrón Decorator, AOP (Programación orientada a aspectos) y por último con la intercepción de llamadas dinámicas que nos ofrecen los contenedores de dependencias (Para mí la mejor opción).

La cosas es que me gustaría exponer porque a mi entender es la mejor manera y para ello nada mejor que hacer unos ejemplo sencillos. Así que vamos a empezar por el patrón Decorator

 

Definición

Responde a unos de los principios SOLIDOpen Closed Principle, es decir, una clase debe estar abierta a nueva funcionalidad pero cerrada a cambios, por lo que nos permite añadir funcionalidad dinámicamente a una clase sin hacer cambios sobre ella.

Esta es la represenatción UML del patrón

decorator_simple

Ejemplo

Imaginar que partimos de un repositorio abstracto como este

public abstract class OrderRepository

{

    public abstract void Save(Order order);

}

Ahora vamos a la implementación de nuestro repositorio para SQL Server (Componente)

public class SqlOrderRepository : OrderRepository

{

    public override void Save(Order order)

    {

        using (var connection = new SqlConnection())

        {

            // Save order to SQL Server database

        }

    }

}

Creamos el Decorador
 
public abstract class DecoratorOrderRepository : OrderRepository

{

    private readonly OrderRepository _orderRepository;

 

    protected DecoratorOrderRepository(OrderRepository orderRepository)

    {

        if (orderRepository == null) 

            throw new ArgumentNullException("orderRepository");

 

        _orderRepository = orderRepository;

    }

 

    public override void Save(Order order)

    {

        _orderRepository.Save(order);

    }

}

Implementamos un decorador concreto para la seguridad:

public class SecurityDecoratorOrderRepository : DecoratorOrderRepository

{

    private readonly string[] _roles;

 

    public SecurityDecoratorOrderRepository(OrderRepository orderRepository, string[] roles) : 

        base(orderRepository)

    {

        if (roles == null)

            throw new ArgumentNullException("roles");

 

        _roles = roles;

    }

 

    public override void Save(Order order)

    {

        var principal = Thread.CurrentPrincipal;

        var allowed = _roles.Any(principal.IsInRole);

 

        if (!allowed)

        {

            throw new SecurityException("No tiene permisos suficientes!");

        }

 

        base.Save(order);

    }

}

Como podemos observar, estamos añadiendo funcionalidad pero no estamos modificando la implementación de nuestro repositorio de SQL Server, ¿verdad?

 
Para terminar, vamos a crear un servicio al que llamaremos OrderService
 
public class OrderService

{

    private readonly OrderRepository _orderRepository;

 

    public OrderService(OrderRepository orderRepository)

    {

        if (orderRepository == null) 

            throw new ArgumentNullException("orderRepository");

 

        _orderRepository = orderRepository;

    }

 

    public void Submit(Order order)

    {

        _orderRepository.Save(order);

    }

}

Y por último vamos a crear una aplicación de consola para probar nuestro código y ver como añadimos esa funcionalidad dinámicamente a nuestro repositorio de SQL Server

static void Main(string[] args)

{

    var order = new Order

                    {

                        Id = Guid.NewGuid(),

                        Total = 102.4M

                    };

 

    var orderRepository = new SecurityDecoratorOrderRepository(new SqlOrderRepository(),

                                                               new[] {"Contributors"});

    var orderService = new OrderService(orderRepository);

 

    try

    {

        orderService.Submit(order);

    }

    catch(SecurityException securityException)

    {

        Console.WriteLine(securityException.Message);

    }

 

    Console.Read();

}

El resultado es que como mi usuario no tiene el rol de “Contributor” pues nos salta una excepción de seguridad:

image

Como resumen de lo que hemos hehco en el código, creamos un repositorio decorado (Que añade funcionalidad, en este caso seguridad a nuestra aplicación) y le pasamos como parámetro la implementación de nuestro repositorio al que añadiremos dicha funcionalidad y  que almacena la orden en SQL Server y a su vez los roles que debe tener el usuario para poder hacer submit de la orden. ¿Ok?

Perooo!!!! como comentaba en el post de Javier, el problema de usar este patrón para este caso concreto de la seguridad, es que no cumple con el principio DRY (Don’t Repeat Yourself) porque si por ejemplo implementamos también el método Update, Delete… del repositorio  la orden y de otros repositorios, al final siempre tendremos código repetido:

public override void Update(Order order)

{

    var principal = Thread.CurrentPrincipal;

    var allowed = _roles.Any(principal.IsInRole);

 

    if (!allowed)

    {

        throw new SecurityException("No tiene permisos suficientes!");

    }

 

    base.Update(order);

}

Para este caso concreto, me gusta mucho más como lo hacen los interceptores de los contenedores de dependencias, que en el siguiente post, vamos a ver un ejemplo usando Castle Windsor.

Un saludo y espro que os haya resultado interesante.

2 comentarios en “[Patrones] Intercepción de llamadas a métodos (I) Patrón Decorator”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *