Friday, October 2, 2009

AOP and Transactions

A project I work on started out as a .NET 1.1 solution, we made a decision to use Serviced Components to handle transactions.

We had our reasons for this, things like :

  • Developers don’t need to worry about transactions, they are handled.
  • In some installations we need to span databases.
  • Transactional Message Queues.

It worked great, but then .NET 2 came along, and we had heard good things about System.Transaction, we wanted to use it, not just because it was cool and new but serviced components did give us a few headaches.

So, we had a fully transactional API, all of our Business classes derived from a common bases class, so.. to make them 'non serviced components' we just removed the base class, but how did we start a transaction for the first call the method and commit it when the last class was disposed.

That’s were AOP comes in, I looked on the web for some examples, I found one that fitted out requirements on MSDN :

http://msdn.microsoft.com/msdnmag/issues/02/03/AOP/default.aspx

This gave me a good start.

We needed AOP as we check each method call for an error, if we get an error we set a bool, then in the dispose we just abort the transaction.

The code below shows how we did this. Note, this code was taken from a production system, I have removed some parts of it, but it should work.

[TransactionSupport()]
public abstract class AOPServicedComponent : ContextBoundObject, IDisposable
{

}

#region AOP Transaction


internal class TransactionSupportedAspect : IMessageSink
{
private IMessageSink m_next;
private bool disposing;

TransactionScope ts;

bool inerror = false;

internal TransactionSupportedAspect(IMessageSink next)
{
// Cache the next sink in the chain
m_next = next;
}

public IMessageSink NextSink
{
get
{
return m_next;
}
}

public IMessage SyncProcessMessage(IMessage msg)
{
IMessage returnMethod;
if (preProcessTransaction(msg) == TransactionParticipation.Suppress)
{
using (new TransactionScope(TransactionScopeOption.Suppress))
{
returnMethod = m_next.SyncProcessMessage(msg);
}
}
else
{
returnMethod = m_next.SyncProcessMessage(msg);
}
postProcessTransaction(msg, returnMethod);
return returnMethod;
}

public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
{
throw new InvalidOperationException();
}

public static string ContextName
{
get
{
return "TransactionSupported";
}
}

private TransactionParticipation? preProcessTransaction(IMessage msg)
{

// We only want to process method calls
if (!(msg is IMethodMessage)) return null;

IMethodMessage call = msg as IMethodMessage;
Type t = Type.GetType(call.TypeName);

if (t.Name == "IDisposable")
disposing = true;
else
disposing = false;

if (!disposing)
{
// Create the Transaction
ts = new Transaction();
}

// set us up in the callContext
call.LogicalCallContext.SetData(ContextName, this);
return participation;
}

private void postProcessTransaction(IMessage msg, IMessage msgReturn)
{
// We only want to process method return calls
if (!(msg is IMethodMessage) ||
!(msgReturn is IMethodReturnMessage)) return;

IMethodReturnMessage retMsg = (IMethodReturnMessage)msgReturn;

if (disposing)
{
// Commit the transaction
if (!inerror)
{
ts.Complete();
}
// else it will be rolled back when it is disposed

ts.Dispose();
//ts = null;
}
Exception e = retMsg.Exception;
if (e != null) inerror = true;
}
}

public class TransactionSupportedProperty : IContextProperty, IContributeObjectSink
{
public IMessageSink GetObjectSink(MarshalByRefObject o, IMessageSink next)
{
return new TransactionSupportedAspect(next);
}
public bool IsNewContextOK(Context newCtx)
{
return true;
}
public void Freeze(Context newContext)
{
}
public string Name
{
get
{
return "TransactionSupportedProperty";
}
}
}

[AttributeUsage(AttributeTargets.Class)]
public class TransactionSupportAttribute : ContextAttribute
{
public TransactionSupportAttribute() : base("TransactionSupported") { }
public override void GetPropertiesForNewContext(IConstructionCallMessage ccm)
{
ccm.ContextProperties.Add(new TransactionSupportedProperty());
}
}
[AttributeUsage(AttributeTargets.Method)]
public class TransactionParticipationAttribute : Attribute
{
TransactionParticipation participation = TransactionParticipation.Required;
public TransactionParticipation Participation
{
get
{
return participation;
}
}
public TransactionParticipationAttribute() { }
public TransactionParticipationAttribute(TransactionParticipation participation)
{
this.participation = participation;
}
}
public enum TransactionParticipation
{
UseExisting,
Required,
Suppress
}
#endregion


We didn’t just leave it there, we added code to allow us to control the transactionscope with the use off attributes, just like ServicedComponents. we added code the 'post' method to to-do some tricks with exceptions (I may blog this later).

No comments:

Post a Comment