Inicio Automático de Transacciones en C# Por Lic. Esteban Calabria Fecha: 4 de Junio del 209 Resumen Veremos como interceptar llamadas a métodos de una capa de servicios en C# para implementar funcionalidad común, como el manejo de logs y las transacciones contra la base de datos. Tags AOP, C#, ContextBoundObject. ContextAttribute, Attribute, IMessageSink, IMessage, IMethodMessage Si nuestra aplicación posee un Service Layer[1], es probable que repitamos tareas en varios servicios. Por ejemplo, el manejo de las transacciones de la base de datos y el registro de la invocación del servicio en un log. Dichas operaciones llevan a tener código duplicado en distintos servicios. Veremos una forma de evitar dicha duplicidad aplicando algunos conceptos de AOP en C#. Supondremos un servicio para actualizar clientes que recibe un DTO [2] con la información que se desea actualizar, podemos tener algo de la siguiente manera: public class ServicioCliente
{ public static void ActualizarCliente( ClienteDto cliente) { Loguear Invocacion Servicio Iniciar Transaccion Con La Base (Start Transaction) try { //Actualizo los datos del cliente y toco varias tablas Ejecutar Servicio Hacer commit de la transaccion (Commit) } catch { Hacer rollback de la transaccion (Rollback) } finally { Finalizar Transaccion } } }
Ya teniendo tres o cuatro servicios que se ejecuten dentro de una transacción podemos notar que existen varias lineas de código que tienen que ver con el log y el manejo de transacciones que se repetirán y nos llevaran a cometer el pecado tener código duplicado. Utilizar un template method para reducir las líneas de código duplicadas no parece ser una solución que podamos aplicar en este caso. Tendremos que optar otra estrategia.
Es aquí donde utilizar algunas ideas de la programación orientada a aspectos (AOP) puede resultarnos de utilidad: tener un aspecto que se encargue de la funcionalidad común. Queremos lograr que al invocar un método de un servicio automáticamente se registre dica invocación en el log y, si se le indica explicictamente, manejar una transaccion contra la base de datos. Para implementarlo utilizaremos la infraestructura Remoting del framework de .Net. Internamente estaremos utilizando el patrón proxy[3]. De modo que cada vez que instanciemos un objeto se estara creando en forma transparente un proxy al mismo. Si queremos que al instanciar un objeto se genere automáticamente un proxy al mismo, debemos heredarlo de la clase ContextBoundObject. De hecho si inspeccionamos en el Visual Studio cualquier objeto que herede de ContextBoundObject vamos a ver que su tipo corresponde a {System.Runtime.Remoting.Proxies.__TransparentProxy}. El primer paso de nuestra implementación será indicar que métodos deben ejecutarse dentro de una transacción. Para ello los decoraremos mediante el atributo que se verá asi: [AttributeUsage(AttributeTargets.Method)] public class TransactionAttribute : Attribute { }
Luego codificaremos la clase ServiceMessageSink que sera la responsable de interceptar todos los mensajes (invocaciones a metodos en nuestro caso) que se le envíen a nuestro servicios. Para poder implementar lo que deseamos haremos que esta clase implemente la interfaz IMessageSink que nos permitirá acoplarnos al mecanismo que nos provee el framework para interceptar la llamada. Según la documentacion de .NET [5] la llamada a un método que se realiza a través de un proxy atraviesa una cadena enlazada de objetos que implementan la interfaz IMessageSink antes de llegar al objeto real. El mensaje se encapsula en un objeto del tipo IMessage y en particular nos interesan aquellos que sean del tipo IMethodMessage. Esta ultima interfaz nos proveera una forma de saber el nombre del método y los atributos que lo decoran mediante reflection. La clase nos quedara de la siguiente manera. class ServiceMessageSink : IMessageSink { private IMessageSink nextSink = null; public ServiceMessageSink(IMessageSink next) { this.nextSink = next; } #region IMessageSink Members public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink) { throw new NotImplementedException(); }
public IMessageSink NextSink { get { return this.nextSink; } } public IMessage SyncProcessMessage(IMessage msg) { IMethodMessage methodMessage = msg as IMethodMessage; if (methodMessage != null) { Loguear Invocacion Servicio } if ((methodMessage != null) && (StartsTransaction(methodMessage))) { Inicio Transaccion try { //Invoco al método IMessage m = this.nextSink.SyncProcessMessage(msg); Commit de la transaccio return m; } catch (System.Exception e) { Rollback de la transaccio throw; } finally { Finalizo la transaccio } } return this.nextSink.SyncProcessMessage(msg); } private static bool StartsTransaction(IMethodMessage methodMessage) { bool bStartsTransaction = (Attribute.GetCustomAttribute(methodMessage.MethodBase, typeof(TransactionAttribute)) != null); return bStartsTransaction; } #endregion }
El método SyncProcessMessage es aquel que interceptará la llamada y donde codificaremos toda la logica común a todos los servicios. En particular : private static bool StartsTransaction(IMethodMessage methodMessage)
Se fijará si el metodo que invocamos esta decorado con el atributo Transaction que vimos anteriormente.
Para que esto funcione primero debemos declarar otro atributo llamado TransactionAwareService para decorar a aquellas clases cuyos métodos serán interceptados. Deberá heredar de ContextAttribute e implementará la interfaz IContextObjectSink que nos demandará completar el método GetObjectSink para asociar la clase a la cual decora con el IMessageSink que acabamos de codificar. [AttributeUsage(AttributeTargets.Class)] public class TransactionAwareService : ContextAttribute, IContributeObjectSink { public TransactionAwareService() : base("Transaction") { } #region IContributeObjectSink Members public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink) { return new ServiceMessageSink(nextSink); } #endregion }
Por ultimos heredaremos todas nuestras clases que implementen servicios de una clase base. La llamaremos BaseService, decoraremos esa clase con el atributo TransactionAwareService y por último haremos heredar la misma de ContextBoundObject para utilizar el mecanismo de proxys transparentes que mencionamos anteriormente. La clase nos quedará así [TransactionAwareService] public class BaseService : ContextBoundObject { }
De esta forma logramos interceptar llamadas a los métodos de nuestros servicios de forma transparente y con pocas lineas de codigo utilizando las capacidades de remoting del framework. Logramos asi ahorrarnos unas cuantas lineas de código que de otra manera estarían condenada a ser repetidas en los distintos servicios. Si el día de mañana cambia algo relacionado con las transacciones y el loggin será un solo lugar el que haya que mantener. Referencias [1] http://martinfowler.com/eaaCatalog/serviceLayer.html [2] http://en.wikipedia.org/wiki/Data_Transfer_Object [3] http://es.wikipedia.org/wiki/Template_Method_(patrón_de_diseño) [4]http://en.wikipedia.org/wiki/Proxy_pattern [5]http://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.imessagesink.aspx