Monday, September 1, 2008

Introducing .NET Throwable Pattern (The Exception By Contract Class).

If you are a Java developer, do not expect that i am referring to the Java Throwable class, which is the ultimate superclass for all errors and exceptions thrown by the JVM (Java Virtual Machine). I must confess that i love this name, and thats why i am sticking to it in .Net.

Most .NET application developers, architects, programmers etc understand fully well the use of the throw keyword. The throw keyword can be used to throw new exception in a catch block, it can also be used to throw the actual exception while still retaining the stack trace. I may need to explain with code :

Here is an example of the throw keyword :

1. Throw new Exception :


try
{

}
catch(Exception x)
{
throw x; or
throw new IamLovingItException("I am loving it", x);
}


The code above makes use of the throw keyword to throw a new exception different from the originally raised exception. This ignores the stack trace and creates a new exception to be thrown to the caller.

2. Just Throw.


try
{

}
catch(Exception ex)
{
throw;
}


The code above throws the same exception including the stack trace that was raised in the try ... catch block. Whatever situation you find yourself, using option one and two is good but not better, when we are dealing with a business focused application that makes use of throwing and handling of exception to communicate changes or business constraints to the caller application. An example of this will be AccountBlockedException, ZeroBalanceException, TransactionRolledBackException : There is no end to the ways we will be using custom exception handling.

A typical Scenairo. (Scenario One)

You have created a payment webservice (Lets say a WCF service) that serves as an abstraction layer over any kind of third party credit card vendors like PayPal, WorldPay, Nochex etc Our Payment webservice can be configured to use any type of payment gateway because it is very generic and open to extensibility.

Now, any thing can go wrong while processing payment or registring our card details via the Payment service abstraction layer. Lets assume we are trying to process £100 , and along the way, the real payment server shuts down, what do we do in this scenario :

Option 1: we throw the same exception that was thrown by the payment gateway
Option 2: we re-brand that exception and bake a new one from it.
Option 3: we handle that exception in our custom Payment service, and return status (As Enum), to our clients.

Think for a minute, which approach is best in our scenario. Even if the payment gateway did not throw an error, but return a status like Payment Failed to our custom Payment service, do we act upon that status and throw a new exception from there, but how do we act upon the status, the best bet may be to do :


if(paymentGateWayStatus == Status.PaymentFailed)
{
throw new PaymentFailedException("Failed");
}
else if(paymentGateWayStatus == Status.ServerShutDown)
{
throw new PaymentServerShutDownException("Shut Down");
}


There, we can go on and on until we have a very messy if statement with many throw exception block, we can even have this statements in almost all the methods implemented by our Payment service abstraction, if not all. What do we need to do in this case to reduce the amount of redundant code that we have?

I have got an excellent Idea. Why not let us create a pattern for this scenario, at least we are trying to avoid repeatability, we need to slve it with a pattern. To create a pattern, you must have been doing one thing for along time, before you realize that it is time consuming, and you would want to make it re-usable in code wide, applicatuion wide etc.

The New Throwable Pattern.

in our case, we need to create like a factory class called Throwable, this class has the abilities to check a boolean statement and decide on wether to throw an exception or not. This class can navigate through a switch statement to see which status matches the given status, and throw an exception from there. To go strat to the point, the throwable class is defined as follows :


public static class Throwable
{
public delegate bool Is();
private static void Throw(Exception exception)
{
throw exception;
}

public static void CauseThrow(T exception)
{
throw exception as Exception;
}

public static void ThrowWhenIsNull(object instance,string message)
{
if(null == instance)
throw Activator.CreateInstance(typeof(T), message) as Exception;
}

public static void CauseThrowOnTrue(Is anonymous, string details)
{
if (anonymous.Invoke())
throw Activator.CreateInstance(typeof(T), details) as Exception;
}

public static void CauseThrowOnTrue(bool status, string details)
{
if(status)
throw Activator.CreateInstance(typeof(T), details) as Exception;
}

public static void CauseThrowOnFalse(bool status, string details)
{
if (!status)
throw Activator.CreateInstance(typeof(T), details) as Exception;
}

public static void CauseThrowOnFalse(Is anonymous, string details)
{
if (!anonymous.Invoke())
throw Activator.CreateInstance(typeof(T), details) as Exception;
}

public static void CauseThrow(ResponseStatus status, string details)
{
switch (status)
{
case Status.ERROR:
Throw(new PaymentServerErrorException(details));
break;
case Status.INVALID:
Throw(new InvalidPaymentErrorException(details));
break;
case Status.MALFORMED:
Throw(new MalformedPaymentException(details));
break;
case Status.NOTAUTHED:
Throw(new AuthenticationException(details));
break;
case Status.REJECTED:
Throw(new PaymentServerErrorException(details));
break;
default:
return;
}
}
}


The above code is our Throwable class, and we can call the Throwable in our code as the following :


Throwable.CauseThrow(new CardException("Card is not valid"));

Throwable.CauseThrowOnFalse((status == Status.AUTHENTICATED), "Not AUTH");

Throwable.ThrowWhenIsNull(creditCard,"Invalid credit card");


Throwable.CauseThrow(status, "Testing");


We can see that the Throwable approach is more cleaner and reusable across. You can contribute more to the implementation because this is just a simple prototype .

Cheers and enjoy.

No comments: