Un Framework para Programación Orientada a Aspectos (AOP) en C#    Parte 3: Un aspecto para manejo de excepciones

Esta publicación es la cuarta parte (aunque diga parte tres) de una serie de cuatro publicaciones que estaré realizando sobre programación orientada a aspectos en .NET empleando el lenguaje de programación C#.

A parte del registro de eventos, otro elemento muy común en la programación orientada a objetos y en los lenguajes actuales (como Java o C#) es la gestión de excepciones.

El otro típico infierno al que se enfrenta un arquitecto o líder técnico a la hora de establecer la política de gestión de errores y excepciones en su diseño es la gobernabilidad de dicho diseño, y la monitorización de su cumplimiento por parte del equipo de desarrollo, cosa que se torna excesivamente complicada por la creatividad de algunos miembros del equipo que rompe la homogeneidad del mecanismo elegido como parte del modelo. Hasta aquí, este párrafo es prácticamente igual al de la segunda parte de esta serie de artículos.

Y es que el diseño y la gobernabilidad del mecanismo de gestión y manejo de excepciones adoloce de los mismos problemas y retos que que podemos encontrar con el registro de eventos.

Particularmente se encuentra que los dos (2) aspectos más difíciles de gestionar son el acto de capturar y apropiadamente manejar la excepción, seguidamente de la política a aplicar sobre ésta. Muchas veces podemos encontrarnos con aplicativos web que cuando una excepción ocurre nos muestra la temida pantalla amarilla de ASP.NET o directamente nos expone todo el stack trace de la excepción (con información de base de datos y todo), lo cual fácilmente podría usar un potencial atacante para literalmente destruir el aplicativo.

ASP.NET Yellow Page of Death

A través del enfoque de AOP, es posible interceptar las llamadas a los métodos y determinar cuando estos han generado una excepción para posteriormente aplicar la política de manejo más apropiada. Estas políticas puede ser parte del propio aspecto de gestión de excepciones. Como en el caso del registro de eventos, la implementación de este aspecto buscará reducir su implementación (y aplicación) a un atributo que decore el método cuyas posibles excepciones se quieren gestionar.

Un Apropiado Soporte

Al crear un mecanismo de gestión y manejo de excepciones, no deberíamos partir desde cero, ya que existe un montón de excelentes productos y frameworks que nos simplificarían esta tarea. Para este caso, emplearé los Microsoft Libraries, en particular el Exception Handling Application Block; sin embargo el enfoque de AOP y de este framework que he venido presentado permite emplear cualquier otra librería.

Implementación

Es importante que si no han leido las primeras partes de esta serie de artículos, aprovechen este momento para hacerlo, sobre todo la parte 1. A partir de este momento mis explicaciones considerarán que el conocimiento y los detalles técnicos explicados en esa parte ya son conocidos.

El primer paso es crear el atributo (Attribute) que decorará las clases/interfaces para capturar su ejecución e inyectar el código de manejo de excepciones. Nuestro atributo se llamará ExceptionHandlingAttribute y extiende de InterceptableAttribute, la clase base en el framework de AOP que sirve para definir atributos de intercepción.

    /// <summary>
    /// A custom attribute for exception handling purposes under an Aspect Oriented Programming paradigm.
    /// </summary>
    /// <remarks>
    /// It applies only to methods.
    /// </remarks>
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public sealed class ExceptionHandlingAttribute : InterceptableAttribute
    {
        /// <summary>
        /// Represents the exception handling politic to apply while intercepting with this concern.
        /// </summary>
        private ExceptionHandlingPolitic politic;

        /// <summary>
        /// Initializes a new instance of the <see cref="ExceptionHandlingAttribute" /> class.
        /// </summary>
        /// <param name="politic">The exception handling politic to follow.</param>
        public ExceptionHandlingAttribute(ExceptionHandlingPolitic politic)
        {
            this.politic = politic;
            this.Processor = Activator.CreateInstance(typeof(ExceptionHandlingProcessor)) as IProcessor;
        }

        /// <summary>
        /// Gets the specified exception handling politic.
        /// </summary>
        /// <value>
        /// Returns the politic to use when handling exceptions represented as a <see cref="ExceptionHandlingPolitic"/>.
        /// </value>
        public ExceptionHandlingPolitic Politic { get { return this.politic; } }
    }

Este atributo tiene una (1) propiedad muy importantes llamada Politic la cual permite establecer la política con la que se gestionará la excepción.

En el tema de políticas de manejo de excepciones hay muchas opiniones, controversias y religiones. En lo personal, dependen mucho de las características del sistema y del entorno donde se desplegará, pero habitalmente me decanto por las siguientes tres (3) políticas:

  • Encapsular: o encapsulate en inglés, es la política más habitualmente aplicada en la gestión de excepciones. Consiste en retornar una excepción nueva más significativa al llamador, pero agregando o anexando (encapsular) la excepción original en la nueva excepción. Esta política preserva el stack trace.
  • Reemplazar: o repace en inglés, consiste en retornar una nueva excepción más significativa al llamador del método, pero si agregar o anexar la excepción original; incluso pudiéndose cambiar el mensaje de la excepción. En esta política el stack trace no se preserva.
  • Propagar: o propagate en inglés, se refiere al acto de pasar la excepción al llamador del método. Esto no quiere decir que no se gestione la excepción per se, sino que no se transformará o encapsulará esta. El stack trace no sufre modificación alguna.

Utilizo estas políticas, porque la mayoría de mis diseños de software siguen un enfoque de capas, donde las excepciones que ocurren dentro de una misma capa son propagadas, cuando se pasa de una capa inferior a una capa superior se encapsulan, y cuando se tienen que llevar a la capa de UI o presentar la excepción al usuario, entonces se reemplaza (para evitar mostrar datos e información sensible a un potencial atacante). Otros enfoques arquitectónicos como el MVC o el MVVC muy probablemente empleen políticas totalmente diferentes.

Así, la propiedad Politic del atributo ExceptionHandlingAttribute admite valores del siguiente enumerado que representa las políticas antes mencionadas:

    /// <summary>
    /// Defines the exception handling politics to use.
    /// </summary>
    public enum ExceptionHandlingPolitic
    {
        /// <summary>
        /// Represents the propagation politic. 
        /// This politic specifies that every exception will be passed to the caller without any transformation.
        /// </summary>
        Propagate,

        /// <summary>
        /// Represents the wrap politic. 
        /// This politic specifies that every exception will be wrapped inside another exception and then passed to the caller.
        /// </summary>
        Wrap,

        /// <summary>
        /// Represents the replacement politic. 
        /// This politic specifies that every exception will be replaced for another exception; then the new exception will be passed to the caller.
        /// </summary>
        /// <remarks>
        /// The original exception will be lost.
        /// </remarks>
        Replace
    }

Una vez establecido el atributo de intercepción, procedemos a crear el Processor que se encargará de manejar la inyección de código antes y después de la ejecución de los métodos interceptados. Para esto crearemos la clase ExceptionHandlingProcessor que implementará la interfaz IProcessor del framework.

Esta clase es la que dependiento del valor de la propiedad Politic y basado en el Microsoft Enterprise Library Exception Handling Application Block procederá a manejar la excepción interceptada acorde a la configuración del mencionado Application Block.

La parte interesante es que para el caso de la gestión de excepciones no se necesitará el método ProcessCallMessage, ya que las excepciones como tal sólo ocurren como un retorno de método, con lo cual toda la acción ocurrirá en el método ProcessReturnMessage.

    /// <summary>
    /// Intercep processor for the calling and returning messages of the methods decorated as interceptable by this component.
    /// </summary>
    internal class ExceptionHandlingProcessor : IProcessor
    {
        /// <summary>
        /// Constant that identifies the exception handling policy of propagation.
        /// </summary>
        private const string PROPAGATE = @"PROPAGATE";

        /// <summary>
        /// Constant that identifies the exception handling policy of replacement.
        /// </summary>
        private const string REPLACE = @"REPLACE";

        /// <summary>
        /// Constant that identifies the exception handling policy of wrap.
        /// </summary>
        private const string WRAP = @"WRAP";

        /// <summary>
        /// Processes the call message, which happens before the execution of the body of the interceptable or processable method.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Since the exception handling only occurs after the execution of the body of the interceptable or processable method, this
        /// method does nothing.
        /// </para>
        /// <para>
        /// This is because the exception type is contained in the <c>Method Return Message</c>.
        /// </para>
        /// </remarks>
        /// <param name="callMessage">The <c>Method Call Message</c> to process.</param>
        /// <param name="processable">The processable decorated object to process.</param>
        public void ProcessCallMessage(IMethodCallMessage callMessage, InterceptableAttribute processable)
        {
            // Do Nothing.
        }

        /// <summary>
        /// Processes the return message, which happens after the execution of the body of the interceptable or processable method.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Since the <c>Method Return Message</c> contains the exception type, this method processes such message to retrieve the exception
        /// information.
        /// </para>
        /// <para>
        /// This method leverages the Microsoft Enterprise Library Exception Handling Application Block.
        /// </para>
        /// </remarks>
        /// <param name="returnMessage">The <c>Method Return Message</c> to process.</param>
        /// <param name="processable">The processable or interceptable object.</param>
        public void ProcessReturnMessage(IMethodReturnMessage returnMessage, InterceptableAttribute processable)
        {
            if (returnMessage != null && returnMessage.Exception != null)
            {
                string policyName = null;

                switch (((ExceptionHandlingAttribute)processable).Politic)
                {
                    case ExceptionHandlingPolitic.Propagate:
                        policyName = PROPAGATE;
                        break;

                    case ExceptionHandlingPolitic.Replace:
                        policyName = REPLACE;
                        break;

                    case ExceptionHandlingPolitic.Wrap:
                        policyName = WRAP;
                        break;

                    default:
                        throw new InvalidOperationException(@"Invalid or not defined exception handling politic.");
                }

                // Call to the static main entrance of the Microsoft Enterprise Library Exception Application Block.
                if (ExceptionPolicy.HandleException(returnMessage.Exception, policyName))
                {
                    throw returnMessage.Exception;
                }
            }
        }
    }

Como puede apreciarse en el método ProcessReturnMessage lo primero es saber si el método interceptado a retornado una excepción, lo cual se puede determinar fácilmente si la priedad Exception de la interfaz IMethodReturnMessage (a la cual pertenece el parámetro returnMessage) no es nula (null).

De haber una excepción, basta con recuperar el valor de la propiedad Politic del atributo para saber que política aplicar a la excepción interceptada. La configuración de estas políticas están delegadas al archivo de configuración del Exception Handling Application Block, y su aplicación se realiza en la línea 79.

Como en artículos anteriores, aquí tienen el código de este aspecto para estudiarlo. Espero que les sea de utilidad.

Anuncios

Cargar Dinámicamente Configuraciones de Entidades en Entity Framework y Code-First

En estos días he estado muy activo empleando la nueva versión del Entity Framework de Microsoft.

Una de las cosas que hace poderoso a este framework y que en particular a mi me encanta es la posibilidad de definir y configurar todo el repositorio de datos (la base de datos per se) completamente desde código fuente gestionado, un enfoque que se conoce como Code-First.

A través de Code-First es posible definir las clases POCO y también configuradores (clases que bien heradan de EntityTypeConfiguration o de ComplexTypeConfiguration) y que nos permiten definir las restricciones sobre el modelo (como claves primarias y no nulidades) o los nombres de las tablas y sus columnas.

La parte interesante es que en modelos grandes y complejos un desarrollo termina llenándose de muchos configuradores que deben ser agregados al modelo en el momento de su creación (lo cual ocurre durante el evento OnModelCreating de la clase DbContext) y que termina incrementando dos factores muy poco positivos de un código: la complejidad ciclomática y el acoplamiento de clases, ambos elementos que son reportados por el Visual Studio a través del Code Analyzer como una violación a la regla CA1506.

Para evitar esto, lo ideal seria encontrar un mecanismo para cargar los configuradores de forma dinámica; pero el problema es que el método Add para el ModelBuilder que se emplea para cargarlos no admite como parámetro una clase base o genérica que pudiera ayudarnos a través de herencia, sino que explícitamente sólo acepta un EntityTypeConfiguration o un ComplexTypeConfiguration.

Por suerte, la plataforma .NET y el lenguaje C# cuentan con una palabra que logra salvar la patria: dynamic; que se encargará de indicar al compilador que no haga validaciones del tipo de dato/objeto que se está pasando como parámetro al método y que más bien este tipo de verificaciones se practiquen en tiempo de ejecución, es decir, que el compilador confíe en que nosotros como desarrolladores sabemos que estamos haciendo.

Así, el primer paso sería utilizar Reflection sobre nuestro ensamblado (que como ya está cargado en memoria durante la ejecución del evento OnModelCreating no impactará [negativamente] en el desempeño de nuestro aplicativo)

/// <summary>
/// Method calleded when the model for a derived context has been initialized, but before the model has 
/// been locked down and used to initialize the context.
/// </summary>
/// <param name="modelBuilder">The builder that defines the model for the context being created.</param>
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    if (modelBuilder != null)
    {
        IEnumerable<Type> entityTypeConfigurationTypes = Assembly.GetExecutingAssembly().GetTypes().Where(type => !type.IsAbstract 
        && (type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration) || type.BaseType.GetGenericTypeDefinition() == typeof(ComplexTypeConfiguration)));

        foreach (Type type in entityTypeConfigurationTypes)
        {
            // The 'Add' method of the DbModelBuilder accepts both "ComplexTypeConfiguration<TComplexType>" or "EntityTypeConfiguration<TEntityType>".
            // At first glance, the type of configurator is not know during compile time.
            // The dynamic type enables the operations in which it occurs to bypass compile-time type checking, in other words,
            // types are not resolved or checked by the compiler; instead, these operations are resolved at run time (when the explicit configuration
            // type is known).
            // Reference: http://msdn.microsoft.com/en-us/library/dd264741.aspx
            dynamic entityTypeConfigurationInstance = Activator.CreateInstance(type);
            modelBuilder.Configurations.Add(entityTypeConfigurationInstance);
        }
    }
}

De esta manera incluso podemos modificar el acceso a las clases de configuración como internal; sin embargo será entonces necesario ignorar el warning CA1812 del Code Analyzer (la cual podemos suprimir de forma segura ya que sabemos que sí son consumidas por Reflection).

Actualización 21/01/2013: En la nueva versión del Entity Framework, la 6.0, encontraremos en la clase ConfigurationRegistrar un nuevo método llamado AddFromAssembly que nos permitirá hacer exactamente lo que se describe en este artículo. La clase ConfigurationRegistrar habitualmente la accedemos desde el método/evento OnModelCreating a través de la propiedad Configurations.

Referencias:

Un Framework para Programación Orientada a Aspectos (AOP) en C#    Parte 2: Un aspecto para registro de eventos

Esta publicación es la tercera parte (aunque diga parte dos) de una serie de cuatro publicaciones que estaré realizando sobre programación orientada a aspectos en .NET empleando el lenguaje de programación C#.

El que es quizás sea el ejemplo más sencillo de un aspecto es el registro de eventos de un sistema o aplicación. Así mismo, resulta ser el más significativo también.

El típico infierno al que se enfrenta un arquitecto o líder técnico a la hora de establecer la política de registro de eventos en su diseño es la gobernabilidad de dicho diseño, y la monitorización de su cumplimiento por parte del equipo de desarrollo, cosa que se torna excesivamente complicada por la creatividad de algunos miembros del equipo que rompe la homogeneidad de los mensajes que se ha establecido como parte del modelo de eventos.

Otro típico incidente es que los desarrolladores, muchas veces culpando a las restricciones de tiempo del proyecto o a la urgencia de la solicitud en la implementación de un determinado requerimiento, omiten agregar las líneas de código que generan el registro de eventos, lo cual a veces puede resultar incluso difícil de detectar en revisiones de código (a través de prácticas como los peer reviews.

A través del enfoque de AOP, es posible esconder el mensaje del evento y reducir su implementación a colocar un atributo que decore el método que se desea loggear, lo cual permitirá establacer el formato del mensaje e impedir que los miembros del equipo lo modifiquen, reforzando la arquitectura y la gobernabilidad del proyecto, a la vez que se simplifica el proceso de desarrollo y de verificación del código.

Un Apropiado Soporte

Al crear un sistema de registro de eventos, no deberíamos partir desde cero, ya que existe un montón de excelentes productos y frameworks que nos simplificarían esta tarea. Para este caso, emplearé los Microsoft Libraries, en particular el Logging Application Block; sin embargo el enfoque de AOP y de este framework que he venido presentado permite emplear cualquier otra librería como log4net o NLog.

Implementación

Es importante que si no han leido las primeras partes de esta serie de artículos, aprovechen este momento para hacerlo, sobre todo la parte 1. A partir de este momento mis explicaciones considerarán que el conocimiento y los detalles técnicos explicados en esa parte ya son conocidos.

El primer paso es crear el atributo (Attribute) que decorará las clases/interfaces para capturar su ejecución e inyectar el código de registro de eventos. Nuestro atributo se llamará LogAttribute y extiende de InterceptableAttribute, la clase base en el framework de AOP que sirve para definir atributos de intercepción.

    /// <summary>
    /// A custom attribute for logging purposes under an Aspect Oriented Programming paradigm.
    /// </summary>
    /// <remarks>
    /// It applies only to methods.
    /// </remarks>
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public sealed class LogAttribute : InterceptableAttribute
    {
        /// <summary>
        /// A stopwatch to use then the verbority level is set to its highest value, in order to
        /// log the amount of time consumed by methods calls.
        /// </summary>
        private Stopwatch timer;

        /// <summary>
        /// The verbosity level to log.
        /// </summary>
        private VerbosityLevel verbosity;

        /// <summary>
        /// Initializes a new instance of the <see cref="LogAttribute" /> class.
        /// </summary>
        /// <param name="verbosity">The verbosity level to log.</param>        
        public LogAttribute(VerbosityLevel verbosity)
        {
            this.verbosity = verbosity;
            this.Processor = Activator.CreateInstance(typeof(LogProcessor)) as IProcessor;

            if (verbosity.Equals(VerbosityLevel.Full))
            {
                this.timer = new Stopwatch();
                this.timer.Reset();
            }
        }

        /// <summary>
        /// Gets the verbosity level to log.
        /// </summary>
        /// <value>The verbosity level of the log trace as defined in <see cref="VerbosityLevel"/>.</value>
        public VerbosityLevel Verbosity { get { return this.verbosity; } }

        /// <summary>
        /// Gets the this log timer.
        /// </summary>
        /// <remarks>
        /// If the <c>Verbosity</c> is different than <see cref="VerbosityLevel.Full"/> then this property will return <c>null</c>.
        /// </remarks>
        /// <value>
        /// An instance of a <see cref="Stopwatch"/> to retrieve execution time when logging with <see cref="VerbosityLevel.Full"/>.
        /// </value>
        /// <see cref="Stopwatch"/>
        /// <see cref="AOPLoggingApplicationBlock.VerbosityLevel"/>
        public Stopwatch Timer { get { return this.timer; } }
    }

Este atributo tiene dos (2) propiedades muy importantes. La primera, llamada VerbosityLevel permite establecer que tanta información se generará/almacenará cuando se registre eventos con cada intercepción. En principio para este ejemplo he definido cuatro (4) niveles de verbosidad:

  1. None: no se registrarán eventos.
  2. Light: una mínima, pero significativa cantidad de información será generada.
  3. Medium: una cantidad moderada de significante información será generada.
  4. Full: mucha información será generada, no necesariamente todoa ella significativa, pero sí útil.

La segunda propiedad se llama Timer no es más que un Stopwatch y que para el caso de VerbosityLevel.Full nos medirá cuanto ha tardado en ejecutarse un método interceptado (con lo cual podríamos identificar cuellos de botella en tiempos de producción por ejemplo).

Una vez establecido el atributo de intercepción, procedemos a crear el Processor que se encargará de manejar la inyección de código antes y después de la ejecución de los métodos interceptados. Para esto crearemos la clase LogProcessor que implementará la interfaz IProcessor del framework.

Esta clase es la que dependiento del VerbosityLevel y basado en el Microsoft Enterprise Library Logging Application Block procederá a escribir registros acorde a la configuración del mencionado Application Block. Los métodos ProcessCallMessage y ProcessReturnMessage se encargarán de crear el mensaje del evento de una manera estandard y pre-establecida (evitando que los desarrolladores decidan el mensaje y rompan con la gobernabilidad).

Lamentablemente la implementación de la clase LogProcessor es un tanto extensa como para ponerla en esta publicación, sin embargo está disponible para descargarse (y estudiarse) justo aquí.