Está en la página 1de 4

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

También podría gustarte