Saturday, 14 November 2009

Custom Retry Logic in Message Only Solution Using Pipeline

Custom Retry Logic in Message Only Solution Using Pipeline

I struggled a lot while implementing retry interval and retry frequency while working on message only solution. So I thought to document it and post on my blog. It’s easy to configure retry interval and retry frequency using Biztalk administration console. But this configuration is not dynamic, I mean this is static configuration and can not be modified based on some logical decisions. So if we like to modify retry interval and frequency according to some logical decisions then we should use Dynamic send port and expression shape in orchestration to reset these values. What if we are not using orchestration and developing a message only solution based on publisher and subscriber model?

Then only place to do this is in send pipeline. So I wrote a pipeline component for this purpose.

Using send pipeline I configured three type of retry logic.

a) Deadline based retry

b) Limited number of retry

c) Unlimited retry

I divided my schema into three categories (as suggested by my architect “John Catlin”)

1) Public: - This is the schema which is exposed to outer world using receive ports, receive port will contain inbound mapping files which will convert it into internal schema and put on message bus.

2) Internal: - This is only used inside message bus and usually a copy of Public schema with some extra nodes and different namespace. These extra nodes will be helpful while implementing limited number retry. While calling some service or on off ramp (leaving message bus) will use an outbound mapping file and will get converted into abstracted schema.

3) Abstracted: - This schema belongs to others (other services which message bus will call)

Please see pipeline component code posted below.

[ComponentCategory(CategoryTypes.CATID_PipelineComponent)]

[ComponentCategory(CategoryTypes.CATID_Encoder)]

[System.Runtime.InteropServices.Guid("9d0e4103-4cce-4536-83fa-4a5040674ad6")]

public class CustomRetry : Microsoft.BizTalk.Component.Interop.IComponent, IBaseComponent, IComponentUI, IPersistPropertyBag

{

#region IComponent Members

public IBaseMessage Execute(IPipelineContext pipelineContext, IBaseMessage pbaseInMsg)

{

int retryCount = 0;

//DeadLine

if (RetryParameter == "Deadline")

{

object objectFulfilmentDeadline = pbaseInMsg.Context.Read(RetryParameter, RetryParameterNamespace);

DateTime fulfilmentDeadline = DateTime.Parse(objectFulfilmentDeadline.ToString());

if (fulfilmentDeadline > DateTime.Now)

{

retryCount = 1;

}

else

{

retryCount = 0;

}

}

//RetryCount

if (RetryParameter == "RetryCount")

{

object objectFulfilmentDeadline = pbaseInMsg.Context.Read(RetryParameter, RetryParameterNamespace);

object objectFulfilmentDeadline = pbaseInMsg.Context.Read(RetryParameter, RetryParameterNamespace);

retryCount = int.Parse(objectFulfilmentDeadline.ToString());

pbaseInMsg.Context.Write("RetryCount", "http://schemas.microsoft.com/BizTalk/2003/system-properties", --retryCount);

}

//UnlimitedRetry

if (RetryParameter == "UnlimitedRetry")

{

retryCount = 1; //Anynumber larger than 0, will keep this retry going for ever.

}

pbaseInMsg.Context.Write("RetryCount", "http://schemas.microsoft.com/BizTalk/2003/system-properties", retryCount);

pbaseInMsg.Context.Write("RetryInterval", "http://schemas.microsoft.com/BizTalk/2003/system-properties", int.Parse(RetryParameterValue));

return pbaseInMsg;

}

#endregion

#region IBaseComponent Members

public string Description

{

get { return "Resets RetryCount, RetryInterval, Action, OutboundTransportLocation, OutboundTransportType"; }

}

public string Name

{

get { return "CustomRetryPipelineComponent"; }

}

public string Version

{

get { return "1.0.0.0"; }

}

#endregion

#region Extra Properties

private string _retryParameter;

public string RetryParameter

{

get { return _retryParameter; }

set { _retryParameter = value; }

}

private string _retryParameterValue;

public string RetryParameterValue

{

get { return _retryParameterValue; }

set { _retryParameterValue = value; }

}

private string _retryParameterNamespace;

public string RetryParameterNamespace

{

get { return _retryParameterNamespace; }

set { _retryParameterNamespace = value; }

}

#endregion

#region IComponentUI Members

public IntPtr Icon

{

get { return new System.IntPtr(); }

}

public System.Collections.IEnumerator Validate(object projectSystem)

{

return null;

}

#endregion

#region IPersistPropertyBag Members

public void GetClassID(out Guid classID)

{

classID = new Guid("655B591F-8994-4e52-8ECD-2D7E8E78B25C");

}

public void InitNew()

{

//TODO

}

public void Load(IPropertyBag propertyBag, int errorLog)

{

object valueRetryParameter = null;

object valueRetryParameterValue = null;

object valueRetryParameterNamespace = null;

try

{

propertyBag.Read("RetryParameter", out valueRetryParameter, errorLog);

_retryParameter = valueRetryParameter.ToString();

}

catch (Exception ex)

{

valueRetryParameter = "Deadline";

propertyBag.Write("RetryParameter", ref valueRetryParameter);

}

try

{

propertyBag.Read("RetryParameterValue", out valueRetryParameterValue, errorLog);

_retryParameterValue = valueRetryParameterValue.ToString();

}

catch (Exception ex)

{

valueRetryParameterValue = "0";

propertyBag.Write("RetryParameterValue", ref valueRetryParameterValue);

}

try

{

propertyBag.Read("RetryParameterNamespace", out valueRetryParameterNamespace, errorLog);

_retryParameterNamespace = valueRetryParameterNamespace.ToString();

}

catch (Exception ex)

{

valueRetryParameterNamespace = "http://My.Custom.Application.Contracts.Internal.PropertySchema.PropertySchema";

propertyBag.Write("RetryParameterNamespace", ref valueRetryParameterNamespace);

}

}

public void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties)

{

object valRetryParameter = _retryParameter;

propertyBag.Write("RetryParameter", ref valRetryParameter);

object valRetryParameterValue = _retryParameterValue;

propertyBag.Write("RetryParameterValue", ref valRetryParameterValue);

object valRetryParameterNamespace = _retryParameterNamespace;

propertyBag.Write("RetryParameterNamespace", ref valRetryParameterNamespace);

}

#endregion

}

So this component exposes three properties, 1) RetryParameter 2) RetryParameterValue 3) RetryParameterNamespace. We can set these three properties while designing send pipeline and also while configuring pipeline in Biztalk Administration console.

For Unlimited retry we only need to set retry parameter count any number greater than 0.

And till our Deadline (which is suppose to be a datetime element) is not over, we will keep retryCount any number greater than 0 and will set it to 0 once Deadline is expired.

For RetryCount based retry we will pick RetryCount from schema and will write again to schema after reducing count by 1.

RetryParameterNamespace is used to get value of element from schema, I passed Namespace of Propertyschema which gets created when you promote any property.

Hope this post will save time of some other programmer if they like to implement retry thing using pipelines.

No comments:

Post a Comment