martes, 11 de marzo de 2014

Resolver funcionalidades transversales sin Programación Orientada a Aspectos.

A mi me encanta la Programación Orientada a Aspectos (AOP). De hecho, la he aplicado exitósamente para gestionar la seguridad y el registro de trazas en varias aplicaciones utilizando mi librería AOP favorita en su version gratuita: PostSharp. Esta librería se encarga de inyectar el código de los aspectos en los ensamblados (Compile-Time Weaving) por lo que, al no funcionar sobre objetos virtuales, clases proxy y cosas por el estilo, no sufre de la problemática de otros frameworks AOP que obligan a limitar las formas de declarar las clases.

AOP es la mejor manera de gestionar y estructurar las funcionalidades transversales (cross-cutting concerns) de una aplicación; desgraciadamente está muy poco extendido y se está haciendo difícil adoptar estas tecnologías por parte del 'mainstream' del desarrollo de software (al menos en esta España garbancera).

Cuando un jefazo ignorante no me permite utilizar AOP para las funcionalidades transversales tengo un plan B que funciona a las mil maravillas: Usar un patrón decorador y construir una cadena de responsabilidades con la inyección de dependencias.




Supongamos un servicio que da de alta clientes en un sistema:

public class ClienteApplicationService

{

  private IClienteDomainService ds;

  private IClienteRepository repo;

  private ISecurityService seg;

  private ITraceService trac;



  void ClienteApplicationService(IClienteDomainService domainService, IClienteRepository repositorio, ISecurityService seguridad, ITraceService traceador)

  {

    ds = domainService;

    repo = repositorio;

    seg = seguridad;

    trac = traceador;

  }



  public void ModificarDatosCliente(DatosClienteDTO nuevosDatosCliente)

  {

    //traza

    trac.TraceAction("Registrando nuevo cliente");



    //comprobamos seguridad

    Roles rolNecesario = Roles.Manager;



    if (!seg.hasPermissions(rolNecesario))

    {
   
      throw new securityException("Permisos insuficientes");

    }



    //aplicamos reglas de negocio

    try

    {

      ClienteEntity cliente = repo.findClientBy(nuevosDatosCliente.ID);

      ds.ModificarDatosCliente(cliente, nuevosDatosCliente);

      repo.save(cliente);

    }

    catch (persistenceException ex)

    {

      trac.TraceError(ex); //traza

      throw PersistenceException("Error en el repositorio de persistencia.", ex);

    }

    catch (domainException ex)

    {

      trac.TraceError(ex);//traza

      throw domainError("Error de regla de negocio.", ex);

    }



    //traza

    trac.TraceSuccess("Operacion de modificacion de datos del cliente realizada correctamente");

  }

}



Se puede observar como el servicio de aplicación se encarga de coordinar el flujo de la aplicación. Usa la traza, comprueba la seguridad, utiliza la persistencia para pasar información pura al servicio de dominio (el cual aplica las reglas de negocio), persiste la nueva información en caso de ser necesario y hace el control definitivo de los errores antes de ser manejados por la UI para proporcionar un feedback adecuado al usuario.

Esto es en un principio correcto; pero quedaría mas elegante, flexible y mantenible si pudiésemos aislar las llamadas de las funcionalidades transversales como la traza o la seguridad. Para ello vamos a montar un patrón decorador.

Lo primero es crearse una interfaz que exponga las operaciones del servicio de aplicación:

  interface IClienteApplicationService

  {

    void ModificarDatosCliente(DatosClienteDTO nuevosDatosCliente);

  }



Después creamos una clase encargada de cada una de las funcionalidades transversales que "decora" el servicio de aplicación, esta clase tendrá como parámetros en el contructor únicamente los módulos con los que trabaje y la clase que va a "decorar".

Servicio de aplicación traceado (decora al servicio de aplicación seguro con trazas):

public class ClienteApplicationServiceTraceado : IClienteApplicationService

{

  private ITraceService trac;

  private PrototiposC.IClienteApplicationService innerClass;



  void ClienteApplicationServiceTraceado (ITraceService traceador, IClienteApplicationService innerService)

  {

    trac = traceador;

    innerClass = innerService;

  }



  public void ModificarDatosCliente(DatosClienteDTO nuevosDatosCliente)

  {

    trac.TraceAction("Registrando nuevo cliente");

    try

    {

      innerClass.ModificarDatosCliente(nuevosDatosCliente);

    }

    catch (Exception ex)

    {

      trac.TraceError(ex);

      throw;

    }

  

    trac.TraceSuccess("Operacion de modificacion de datos del cliente realizada correctamente");

  }

}



Servicio de aplicación seguro (decora al servicio de aplicación básico con seguridad) :

public class ClienteApplicationServiceSeguro : IClienteApplicationService

{

  private ISecurityService sec;

  private PrototiposC.IClienteApplicationService innerClass;



  void ClienteApplicationServiceSeguro (ISecurityService seguridad, IClienteApplicationService innerService)

  {

    sec = seguridad;

    innerClass = innerService;

  }



  public void ModificarDatosCliente(DatosClienteDTO nuevosDatosCliente)

  {



    Roles rolNecesario = Roles.Manager;

    if (!sec.hasPermissions(rolNecesario))

    {

      throw new securityException("Permisos insuficientes");

    }



    innerClass.ModificarDatosCliente(nuevosDatosCliente);

  

  }

}



Servicio de aplicación sin características especiales:

public class ClienteApplicationService : IClienteApplicationService

{

  private IClienteDomainService ds;

  private IClienteRepository repo;



  void ClienteApplicationService(IClienteDomainService domainService, IClienteRepository repositorio)

  {

    ds = domainService;

    repo = repositorio;

  }



  public void ModificarDatosCliente(DatosClienteDTO nuevosDatosCliente)

  { 

      ClienteEntity cliente = repo.findClientBy(nuevosDatosCliente.ID);

      ds.ModificarDatosCliente(cliente, nuevosDatosCliente);

      repo.save(cliente);

  }

}
Ahora sólo queda configurar el contenedor de inyección de dependencias (DI) para que vaya creando e insertando las instancias una dentro de otra en cascada. Para que se vea el proceso que seguiría el contenedor de DI voy ha mostrar una construcción a mano:
//implementaciones concretas

IClienteDomainService dm = new ClienteDomainService();

IClienteRepository  repo = new ClienteDataBaseRepository();

ISecurityService sec = new RoleInXMLFileSecurityService();

ITraceService trac = new FileSystemTraceService();



IClienteApplicationService servicioDeAplicacion = new ClienteApplicationService(dm, repo);

IClienteApplicationService servicioDeAplicacionSecurizado = new ClienteApplicationServiceSeguro(sec, servicioDeAplicacion);

IClienteApplicationService servicioDeAplicacionSecurizadoYTraceado = new ClienteApplicationServiceTraceado(trac, servicioDeAplicacionSecurizado);



servicioDeAplicacionSecurizadoYTraceado.ModificarDatosCliente(nuevosDatosCliente);

Precioso...

No hay comentarios:

Publicar un comentario