[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 SOLID - Open 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

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:

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.