Using Policy Injection and Attributes to preempt calls to non-functioning systems

It’s a waste of processor cycles and user time to make web service calls to systems that are not currently functioning. I was involved in building a solution that allows code that depends on non-functioning systems to be skipped entirely. Code simply needs to be attributed with the systems it uses. Then a policy injection handler will throw an exception without even calling that code if a system is known to be unavailable.

I document the parts of the system I built in this article. The moving parts of involved in this solution are:

  • Agents. The agents are the classes that make web service calls to external systems.
  • FunctionalArea attributes. Agent interfaces are marked up with these attributes to indicate dependencies on external systems.
  • FunctionalAreaUnavailableException – thrown to indicate an agent method call has been made involving an unavailable system.
  • SystemStatusAgent. This service keeps track of the unavailability of systems, by receiving information from the application when a FunctionalAreaUnavailableException is thrown, and through its own monitoring. I don’t document it in this blog post.
  • The InterceptorBehavior. Policy injection causes this to be ran before each agent method call. It throws a FunctionalAreaUnavailableException when an attributed agent has a web service exception, or instead of a method call involving a system the SystemStatusAgent considers unavailable.
  • Global exception handling. Catches FunctionalAreaUnavailableExceptions, notifies the SystemStatusAgent of them, and shows the user an error indicating the system they are trying to work with is currently unavailable. I don’t document it in this blog post.

I was able to policy inject all Agents as they were constructed in our AgentFactory. I wished to use configuration based injection. But (if my memory serves me right) with Unity 2.0 policy injection, you can’t use configuration to generated an injected object that is of a concrete type. I had to specify interception behaviours (Unity synonym for policy handler) in code to use the required interceptor, TransparentProxyInterceptor.

PolicyInjectionHelper makes injection simple for us.

public class PolicyInjectionHelper
{
	private ReadOnlyCollection<Type> mInterceptorTypes;

	/// <summary>
	/// Construct a new interception helper that will provide objects with 
	/// policy injection, using interceptors of the given types, in the
	/// given order.
	/// </summary>
	/// <param name="interceptorTypes">
	/// Type of the interceptors - all must derive from IInterceptionBehavior
	/// </param>
	public PolicyInjectionHelper(IEnumerable<Type> interceptorTypes)
	{
		DBC.Assert(
			interceptorTypes.All(typeof(IInterceptionBehavior).IsAssignableFrom),
			"All interceptors must derive from IInterceptionBehavior");
		mInterceptorTypes = interceptorTypes.ToList().AsReadOnly();
	}

	/// <summary>
	/// Derive a policy inject object from the provided object
	/// </summary>
	public T PolicyInject<T>(T obj) where T : class
	{
		T result = obj;
		if (mInterceptorTypes.Any())
			result = Intercept.ThroughProxy<T>(
				result,
				new TransparentProxyInterceptor(),
				ConstructInterceptorInstances());
		return result;
	}

	private IEnumerable<IInterceptionBehavior> 
		ConstructInterceptorInstances()
	{
		return mInterceptorTypes.
			Select(type => Activator.CreateInstance(type) as IInterceptionBehavior);
	}
}

I utilised this in our AgentFactory as this simplified and abbreviated code shows. The PolicyInjectionHelper means that whilst the interception behaviours couldn’t be specified in config, they are easy to see in code (RequiresFunctionalAreaPolicyHandler below).

public class AgentFactory {
	private static readonly PolicyInjectionHelper mPolicyInjectionHelper = 
		mFunctionalAreaPolicyHandlerEnabled
		? new PolicyInjectionHelper(
			new List<Type> { typeof(RequiresFunctionalAreaPolicyHandler) })
		: new PolicyInjectionHelper(Enumerable.Empty<Type>());
				
	// Snipped singleton code

	// Real construction is more complex
	public T ConstructAgent<T>()
		where T : IBaseAgent
	{
		T agent = GetAgent<T>();
		return mPolicyInjectionHelper.PolicyInject(agent);
	}
}

A pair of FunctionalArea attributes allow agent interfaces and methods to be attributed to indicate dependencies

/// <summary>
/// Decorate agent interfaces and agent interface classes with this attribute to indicate
/// that they require a particular functional area to be available.
/// When decorated methods, or methods within a decorated interface are
/// called, a FunctionalAreaUnavailableException is thrown if
/// the area is unavailable, or if a SoapException occurs during
/// the method
/// </summary>
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class FunctionalAreaRequiredAttribute : Attribute 
{
	public FunctionalAreaRequiredAttribute(FunctionalArea pArea)
	{
		Area = pArea;
	}

	public FunctionalArea Area { get; private set; }

	public bool Equals(FunctionalAreaRequiredAttribute other)
	{
		return !ReferenceEquals(null, other) && 
			   (ReferenceEquals(this, other) || Equals(other.Area, Area));
	}

	public override bool Equals(object obj)
	{
		return !ReferenceEquals(null, obj) &&
			   (ReferenceEquals(this, obj) || Equals(obj as FunctionalAreaRequiredAttribute));
	}

	public override int GetHashCode()
	{
		return Area.GetHashCode();
	}
}

/// <summary>
/// Decorate methods with this attribute to indicate that the functional
/// area requirements of their containing class should be ignored
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class IgnoreRequiredFunctionalAreaAttribute : Attribute { }

Here is an example of how the attributes are used

[FunctionalAreaRequired(FunctionalArea.SystemX)]
public interface IAnAgent : IBaseAgent
{
	void AMethod();

	[IgnoreRequiredFunctionalArea]
	void NotThisMethod();
}
	
public interface IAnotherAgent : IBaseAgent
{
	[FunctionalAreaRequired(FunctionalArea.SystemY)]
	void AMethod();

	void NotThisMethod();
}

The FunctionalAreaUnavailableException itself is very simple.

public class FunctionalAreaUnavailableException : ApplicationException
{
	private const string MessageTemplate = 
		"Functional area {0} unavailable";

	public FunctionalAreaUnavailableException(FunctionalArea area, 
		Exception ex = null)
		: base(string.Format(MessageTemplate, area), ex)
	{
		Area = area;
	}

	public FunctionalArea Area { get; private set; }
}

I introduced a BaseInterceptionBehavior as I quickly found all my IInterceptionBehavior implementations had commonalities.

public abstract class BaseInterceptionBehavior : IInterceptionBehavior
{
	#region IInterceptionBehavior members

	/// <summary>
	/// Returns a flag indicating if this behavior will actually do anything when invoked.
	/// </summary>
	public bool WillExecute { get { return true; } }

	/// <summary>
	/// Check if an intercepted method invocation should be processed, and process it if
	/// interception is required.
	/// </summary>
	public IMethodReturn Invoke(IMethodInvocation input, 
		GetNextInterceptionBehaviorDelegate getNext)
	{
		return IsInterceptionRequired(input)
			? ProcessInvocation(input, () => getNext()(input, getNext))
			: getNext()(input, getNext);
	}

	/// <summary>
	/// Returns the interfaces required by the behavior for the objects it intercepts. 
	/// </summary>
	public virtual IEnumerable<Type> GetRequiredInterfaces()
	{
		return Enumerable.Empty<Type>();
	}

	#endregion

	/// <summary>
	/// Should this method invocation be intercepted?
	/// </summary>
	protected virtual bool IsInterceptionRequired(IMethodInvocation input)
	{
		// !input.MethodBase.IsSpecialName means properties 
		// aren't intercepted, and also ToString et. al.
		return input.MethodBase.IsPublic && 
			!input.MethodBase.IsSpecialName;
	}

	/// <summary>
	/// Process the intercepted method invocation
	/// </summary>
	protected abstract IMethodReturn ProcessInvocation(IMethodInvocation input, 
		Func<IMethodReturn> processNext);
}

This is the interception behaviour that throws FunctionalAreaUnavailableException as previously described.

/// <summary>
/// Provides agents with functional area enhancements. 
/// If an agent or method is attributed as requiring a functional area:
/// - Call to methods for which the System Status agent considers a 
///   functional area unavailable will throw a 
///   FunctionalAreaUnavailableException exception without the method 
///   being invoked.
/// - SoapExceptions in methods will be wrapped in a 
///   FunctionalAreaUnavailableException for the attributed functional area
/// </summary>
/// <remarks>
/// An assumption is made only methods should have this behaviour - 
/// it uses the standard BaseInterceptionBehavior.IsInterceptionRequired 
/// criteria
/// </remarks>
public class RequiresFunctionalAreaPolicyHandler 
	: BaseInterceptionBehavior
{
	protected override IMethodReturn ProcessInvocation(
		IMethodInvocation input, Func<IMethodReturn> processNext)
	{
		MethodBase method = input.MethodBase;
		FunctionalArea? area = GetFunctionalAreaRequired(method);
		IMethodReturn result;
		if (area.HasValue && !SystemStatus.IsOperational(area.Value))
		{
			// Don't even call the method if
			// the FunctionalArea is unavailable
			result = input.CreateExceptionMethodReturn(
				new FunctionalAreaUnavailableException(area.Value));
		}
		else
		{
			result = processNext();
            Exception ex = result.Exception;
			Type areaExType = typeof(FunctionalAreaUnavailableException);
			if (area.HasValue &&
				ContainsException(ex, typeof(SoapException)) &&
				!ContainsException(ex, areaExType))
			{
				result = input.CreateExceptionMethodReturn(
					new FunctionalAreaUnavailableException(area.Value, 
						ex));
			}
		}

		return result;
	}

	private static FunctionalArea? GetFunctionalAreaRequired(
		MethodBase pMethod)
	{
		return IgnoreRequiredAttributes(pMethod)
			 ? (FunctionalArea?)null
			 : GetAttrs(pMethod).
				 Select(attribute => attribute.Area).
				 SingleOrDefault();
	}
	
	private static IEnumerable<FunctionalAreaRequiredAttribute> GetAttrs(
		MethodBase pMethod)
	{
		var methodAttrs = pMethod.
			GetCustomAttributes<FunctionalAreaRequiredAttribute>();
		var typeAttrs = pMethod.DeclaringType.
			GetCustomAttributes<FunctionalAreaRequiredAttribute>(
				true, true);
		return methodAttrs.Union(typeAttrs);
	}

	private static bool IgnoreRequiredAttributes(MethodBase pMethod)
	{
		return pMethod.
			GetCustomAttributes<IgnoreRequiredFunctionalAreaAttribute>().
			FirstOrDefault() != null;
	}

	private static bool ContainsException(
		Exception pException, Type pSearch)
	{
		return pException != null && 
			(pSearch.IsAssignableFrom(pException.GetType()) ||
			ContainsException(pException.InnerException, pSearch));
	}
}

And that’s that.

Policy Injection has its costs. Apart from the additional complexity that policy injection introduces to your code, the usage of the proxy class introduces some overhead to every usage of a policy injected object. And you should be sure that a requirement is a cross cutting concern – do some reading about Aspect-oriented programming for some ideas about how policy injection should be used.

System availability is a cross-cutting concern for agents, which are defined as the set of classes that provide access to external systems in the application I’m dealing with. And the cost of accessing agents through a proxy is fractional compared to the cost of the actual web service calls involved.

This work resulted in a better user user experience when systems are down by providing fast failure, rather than making users wait for timeouts known to be inevitable. The SystemStatusAgent also provides monitoring of the health of the systems the application depends on. I found Unity policy injection wasn’t entirely intuitive. But I’m pleased with the outcome.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s