ESB 2.0 Custom Resolver: Part 2

In part one, we discussed what code was required for a custom resolver. In part two, we will be talking about what steps need to be undertaken in order to have the ESB recognize the new resolver as well as the topic of caching and making the resolver configurable.

Caching

Resolution caching is accomplished using the Microsoft.Practices.ESB.Cache.Cache<T> object. Each resolver maintains its own unique cache. Some resolvers, such as BRE, do not maintain a cache at all, as they are expected to update in real-time when the repository changes.

Adding a resolution to the cache is as simple as calling the Add method of the Cache<T> object with a key and the resolution. A resolution should be unique to its configuration, with that assumption in mind the standard key for a resolution in the cache is its config string. Resolutions are defined as type Dictionary<string, string> the resolution Dictionary should be the second argument in the call to the Add method. Note, that some verification should be done to ensure that the resolution is not empty/corrupted, as it will come back each time the provided config is resolved until the cache expires. The last argument for the Add method is the optional expiration interval. In this implementation a configurable value cacheTimeoutValue was crated that is used to define how long a cache value should persist. A value of 0 denotes that the cache has been disabled.

Retrieving a resolution from the cache requires a call to the Get method of the Cache<T> object. The Get method requires the key (the config string) in order to retrieve the resolution from the cache. The benefit of caching is that it enhances performance when under higher volumes. The database is only queried once per cache expiration interval for a unique configuration. The drawback is that the repository could take one entire expiration interval to update when it is changed. With this balancing act in mind, the expiration interval was made configurable.

Configuration

Configuration includes wiring up the resolver to the ESB and configurable resolution values. The wire-up portion is required for all implementations, so we will start the discussion with that.

Contained in the ESB install is a configuration file called esb.config. This configuration is referenced from both the machine.config and the btsntsvc.exe.config files. The resolution configuration settings are contained in the esb.config file. To wire-up the custom adapter following node needs to be added to the configuration/esb/resolvers node:

<resolver name="SQL" type="ESB.Resolver.SQL.ResolveProvider, ESB.Resolver.SQL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=42bcd53db1111111"/>

The name attribute denotes the Moniker name (see part one for a discussion of Monikers). The type attribute is the fully qualified assembly name for the custom resolver that was created in part one.

To add configurable values for a resolver a resolverConfig node can be added to the aforementioned resolver node:

<resolver name="SQL" type="ESB.Resolver.SQL.ResolveProvider, ESB.Resolver.SQL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=42bcd53db1111111">
                <resolverConfig>
                    <add name="cacheTimeoutValue" value="120"/>
                    <add name="cacheName" value="SQL Cache Manager" />
                </resolverConfig>
</resolver>

The configuration can be parsed in the custom resolver class with the following code:

string cacheName = ResolverConfigHelper.ReadResolverConfigByKey(resolverConfig, "cacheName");

To make use of the SQL Cache Manager, it must be configured in the esb.config. Add the following node to the configuration/cachingConfiguration/cacheManagers node:

<add name="SQL Cache Manager"
              type="Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager, Microsoft.Practices.EnterpriseLibrary.Caching, Version=4.0.0.0,Culture=neutral, PublicKeyToken=31bf3856ad364e35"
              expirationPollFrequencyInSeconds="2"
              maximumElementsInCacheBeforeScavenging="1000"
              numberToRemoveWhenScavenging="10"
              backingStoreName="inMemory" />

Future Plans

Future plans for the SQL resolver is to make it more configurable. There are plans to be able to query services by other fields such as Author, Version, etc.

This concludes my two part discussion of custom resolvers in ESB 2.0.

ESB 2.0 Custom Resolver: Part 1

Recently I encountered a situation where we wanted to leverage SQL server as and repository for endpoints. The resolver consists of two parts: the repository (SQL Table in this example) and the custom resolver assembly. I will be focused on the custom resolver for this  discussion.

The custom resolver consists of an assembly and configuration entries. In part one we will discuss the code that goes into the assembly. In part two we will talk about wiring up the assembly so that the ESB will use the new assembly, as well as how to make the resolver configurable.

Per the pattern set up by the ESB Guidance team, each resolver should be in its own assembly. This assembly will need to include references to the following assemblies:

  • Microsoft.Practices.ESB.Cache
  • Microsoft.Practices.ESB.Common
  • Microsoft.Practices.ESB.Configuration
  • Microsoft.Practices.ESB.Exception.Management
  • Microsoft.Practices.ESB.Resolver
  • Microsoft.XLANGs.BaseTypes

The naming convention for the assembly is ESB.Resolver.MyResolver. In our example the assembly is ESB.Resolver.SQL. A class that implements IResolverProvider is required for the custom resolver. The naming convention for this class is ResolveProvider.

The IResolverProvider interface implements three overloaded variations of the Resolve method. Each of these need to be implemented to ensure that the resolver works in all traditional contexts. All three methods should be executing the same logic. With that in mind, the ESB Guidance team created a ResolveMyResolver(string config, string resolver, Dictionary<string, string> resolutionOrig) method that each Resolve method calls. I have included my sample below.

private Dictionary<string, string> ResolveSql(string config, string resolver, Dictionary<string, string> resolutionOrig)
        {
            // fix the resolver if it needs it
            if (!resolver.Contains(ResolutionHelper.MonikerSeparator))
                resolver = resolver + ResolutionHelper.MonikerSeparator;

            try
            {
                // check for cache...return data structure
                Dictionary<string, string> sqlValues = RetrieveFromCache(config);

                //No Resolution cached
                if (sqlValues == null)
                {
                    sqlValues = new Dictionary<string, string>();

                    //Extract Parameters
                    Dictionary<string, string> queryParams = ResolutionHelper.GetFacts(config, resolver);
                    string serverName =
ResolutionHelper.GetConfigValue(queryParams, false, "serverName");
                    string dbName = ResolutionHelper.GetConfigValue(queryParams, false, "dbName");
                    string serviceName = ResolutionHelper.GetConfigValue(queryParams, false, "serviceName");
                    //Build connection String
                    string connectionString = String.Format("Data Source={0}; Initial Catalog={1}; Integrated Security=SSPI;", serverName, dbName);
                    //Execute SQL query to retrieve EndPoint
                    SqlDataReader sqlReader = null;

                    try
                    {
                        sqlReader = ResolveSqlEndpoint(serviceName, connectionString, sqlReader);
                        if (sqlReader.HasRows)
                        {
                            sqlReader.Read();

                            //Transfer result to Resolution
                            Resolution resolution = new Resolution();
                            resolution.EndpointConfig = sqlReader["EndPointConfig"].ToString();
                            resolution.TransportLocation = sqlReader["TransportLocation"].ToString();
                            resolution.FixJaxRpc = Convert.ToBoolean(sqlReader["JaxRPC"]);
                            resolution.MessageExchangePattern = sqlReader["MessageExchangePattern"].ToString();
                            resolution.TransportType = sqlReader["TransportType"].ToString();
                            resolution.TransportNamespace = sqlReader["TransportNamespace"].ToString();
                            resolution.Action = sqlReader["Action"].ToString();

                            //Transfer results to the Dictionary
                            ResolverMgr.SetResolverDictionary(resolution, sqlValues);

                            if (null != sqlValues)
                                resolutionOrig = sqlValues;
                        }

                        sqlReader.Close();

                    }
                    catch (Exception ex)
                    {
                        EventLogger.Write(MethodInfo.GetCurrentMethod(), ex);
                        throw;
                    }

                    //add result to cache
                    SetCache(sqlValues, config);
                }
            }
            catch (Exception ex)
            {
                EventLogger.Write(MethodInfo.GetCurrentMethod(), ex);
                throw;
            }

            return resolutionOrig;
        }

Stepping through the code, the first step is to repair the resolver string, if needed. The resolver string is also referred to as the Moniker. A Moniker is formatted as follows Resolver:\\. For example the UDDI Moniker is UDDI:\\. In our example, our Moniker is SQL:\\. This first code snippet simply applies the :\\ if it missing.

The next step retrieves the resolution from the cache, if it exists. We will dive into this more in depth in part two.

Next, the parameters are extracted from the config string. The config string is the list of key/values that are included with the Moniker to create the EndPoint string. Our config string contains three key/values: Server Name, DB Name, and Service Name. These values are required to query our SQL repository. The ResolutionHelper.GetConfigValue method is used to extract the parameters from the config string.

Next, the SQL connection string is created and the SqlDataReader is initialized. A stored procedure that takes the Service Name and returns the resolution data for that Service is executed to populate the SqlDataReader. A Resolution object is instantiated and the values from the SqlDataReader are used to populate values in the Resolution class. The Resolution object is then used to create the output of the method using the ResolverMgr.SetResolverDictionary method.

The final step before returning the Dictionary result is to add the resolution to the cache. Caching will be described in more depth in part two.

In part two we will discuss caching, configuration, and wiring up the custom resolver to the ESB.

BizTalk 2009 Beta Documentation Now Available

Beta documentation for BizTalk 2009 is now available to download from the Microsoft.com website:

http://www.microsoft.com/downloads/details.aspx?familyid=38923F29-167F-497D-80A5-899602FEF8D7&displaylang=en

BizTalkCop For Naming Convention Enforcement in BizTalk 2006 and R2

Elton Stoneman has put together an extension to FxCop to assist with the enforcement of naming conventions around BizTalk applications.

The 1.0 release contains a set of FxCop rules for analysis against BizTalk assemblies and BizTalk applications. The rules in the package are configurable, by default they are based on the naming conventions Scott Colestock put out a few years ago.

A detailed usage guide is published here:

http://geekswithblogs.net/EltonStoneman/archive/2008/11/14/introducing-biztalkcop.aspx

An extensive demo of the tool is published here:

http://geekswithblogs.net/benny/archive/2009/03/01/biztalkcop.aspx

You can download the package from CodePlex here:

http://www.codeplex.com/BizTalkCop

Empty Node Gotcha with the Business Rules Engine

I've been on a BizTalk 2006 R2 project where we are using the business rules engine to enrich some data coming from a call center.

The data comes in using the vendor's format and is mapped on the inbound port into a cannonical format to be used internally for processing. Because of the complexity of the vendor's schema, we used some inline XPATH statements in the map to transform the data.

When testing against the endpoint DB2 database, we discovered the database was failing to process the messages, even though everything appeared fine on the surface. After some investigation we discovered some hexidecimal characters in what were supposed to be empty nodes. The characters we saw were representative of a line break and a tab: #xD; and #xA;.

Tracing the steps back, we discovered this was appearing after the Business Rules Engine processed the document.

In a normal map, empty nodes are created as <empty>, but using XPATH we were deriving empty nodes that looked like <empty></empty>, and when this type of node went to the rules engine, it created line breaks.

We needed the empty nodes because the endpoint demanded all the nodes be present, so we had to make an adjustment to the XPATH statements to use something similar to:

<xsl:choose>
<xsl:when test="@@XPATH HERE@@">
<xsl:element name="Empty">
<xsl:value-of select="@@XPATH HERE@@"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="Empty"></xsl:element>
</xsl:otherwise>
</xsl:choose>


Where the choose/otherwise allowed us to create the type of empty node we were looking for.

So, if you find yourself extracting data from the rules engine and these unwanted characters appear, this is one way to solve it.

Dundas Chart Removed from Sample Management Portal in ESB 2.0

Patterns and Practices has made a major change to the sample Management Portal that ships with the ESB Guidance 2.0 CTP2. The Dundas charting dependency has been removed. This is big news as it allows the sample portal to be used as a low-cost ESB Management Portal  without incurring the cost of Dundas Chart. Dundas Chart has been replaced by the MSChart control.