In the first part of the article, we discussed the advantages and disadvantages of Dynamic Send Ports and we introduced two custom components that allow, respectively, to use Static Send Ports instead of Dynamic Send Port with an ESB Itinerary and to speed up message transformation. In this post I will move into detailing the test scenarios that I built to measure and compare performance of standard and custom Routing and Transform Services, while in the third part I'll provide results and I'll show how using profiling tools (Visual Studio Profiler and SQL Server Profiler) to narrow down and determine the cause of performance problems.
In order to verify that my custom Routing and Transform components were working properly and to measure and compare performance with those of the equivalent Routing and Transform services provided out-of-the-box by the ESB Toolkit 2.0, I created 3 different scenarios composed of 8 test use cases all belonging to the same BizTalk application called ESB.Itinerary.Services. A secondary objective was to observe and analyze the runtime behavior of Dynamic Send Ports in order to verify that certain Adapters like the WCF-Custom Adapter behave in a different way with respect to when they run in Static Send Port. In order to generate traffic against test use cases, I used Visual Studio 2008 Test Edition. For more information on this topic, you can review the following article:
Note All the Receive Locations used by test cases have been configured to run within a different host (BizTalkServerReceiveHost64) other than the host (BizTalkServerSendHost64) used to execute Send Ports.
The main objective of the first scenario was measuring the performance of the 4 possible combinations of the custom and out-of-the-box (here indicated with the prefix OOB) Routing and Transform services and observe the runtime behavior of the WCF-Custom Adapter and SqlBinding when used with either a Dynamic or Static Send Port. To this purpose, I used the Northwind database that is available on CodePlex along with the other SQL Server code samples. In particular, I created a new stored procedure called CustomersByCountry whose T-SQL code is shown below:
CustomersByCountry Stored Procedure
USE [Northwind] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO IF OBJECT_ID('CustomersByCountry') > 0 DROP PROCEDURE CustomersByCountry GO CREATE PROCEDURE [dbo].CustomersByCountry @Country nvarchar(15) = 'ALL' AS IF @Country = 'ALL' SELECT [CustomerID] ,[CompanyName] ,[ContactName] ,[ContactTitle] ,[Address] ,[City] ,[Region] ,[PostalCode] ,[Country] ,[Phone] ,[Fax] FROM [Northwind].[dbo].[Customers] ORDER BY [CompanyName] ELSE SELECT [CustomerID] ,[CompanyName] ,[ContactName] ,[ContactTitle] ,[Address] ,[City] ,[Region] ,[PostalCode] ,[Country] ,[Phone] ,[Fax] FROM [Northwind].[dbo].[Customers] WHERE [Country] = @Country ORDER BY [CompanyName] GO
As you can easily note, the stored procedure returns the customers belonging to a certain country or all customers if the @Country is NULL or equal to ‘ALL’.
All the 4 test use cases of the first scenario implement the same pattern:
The following figure shows the map used by all the test cases of Scenario 1 to transform the response message from the native WCF-SQL format to a canonical format. I intentionally used the Uppercase, String Left Trim and String Right Trim Functoids to increase the complexity of the map.
CustomersByCountryResponseToCustomersResponse Map
The first test use case represents the typical flows modeled with the ESB Toolkit where the Transform and Routing services against the inbound request message are run within the On-Ramp Port. As the name of the use case suggests, all the Transform and Routing components utilized by this test are those provided out-of-the-box by the ESB Toolkit 2.0.
The following figure depicts the Itinerary used by the OOBRoutingOOBTransform test use case:
As mentioned above, the Transform and Routing services are both applied to the inbound message within the OnRamp Receive Location. The following picture shows how the STATIC Route Resolver used by the RouteRequest service has been configured:In particular, the Action property contains the name of the target stored procedure while the Endpoint Configuration property has been configured as follows:
BindingConfiguration=<binding name="sqlBinding" maxConnectionPoolSize="1000" useAmbientTransaction="false" />&BindingType=sqlBinding&IsFault=true&PropagateFaultMessage=true&EnableTransaction=false
The following figure depicts the architecture of the OOBRoutingOOBTransform test use case.
Compared to the first one, the second test case continues to use the default Routing Service provided out-of-the-box by the ESB Toolkit, but replaces the original Transform Service with my custom version that uses the XslCompiledTransform class.
The Transform and Routing services are both executed against the request message within the OnRamp Receive Location. In particular, the following picture shows the configuration of the TransformRequest service:
The following figure depicts the architecture of the OOBRoutingCustomTransform test use case.
The third test case uses my custom Routing Service along with a Two-Way WCF-Custom Static Send Port, while for mapping, it uses the original Transform Service provided out-of-the-box by the ESB Toolkit.
The following figure depicts the Itinerary used by the CustomRoutingOOBTransform test use case:
Note that while the TransformRequest step continues to be executed on the OnRamp Receive Location, the RouteRequest step is now executed on the OffRamp Port. However, the definition of the STATIC Route Resolver was not changed. The WCF-Custom Static Send Port used by the use case has been configured to use the sqlBinding. The following figure shows the General Tab of the WCF-Custom Adapter configuration:
Note that the Address (URI) and Action fields have not been properly configured on the Static Send Port to invoke the CustomersByCountry stored procedure on SQL Server because it’s the responsibility of the RouteRequest Itinerary step to perform this operation at runtime.
The following figure depicts the architecture of the CustomRoutingOOBTransform test use case.
The fourth test case uses both my custom Routing and Transform components along with a Two-Way WCF-Custom Static Send Port.
The following figure depicts the Itinerary used by the CustomRoutingCustomTransform test use case:
This Itinerary is identical to the one used by the CustomRoutingOOBTransform test case with the exception of the Transform service that has been replaced by my custom TransformService component.
The following figure depicts the architecture of the CustomRoutingCustomTransform test use case.
The main objective of the second scenario was measuring the performance and comparing the runtime behavior of the standard message routing pattern, based on the use of Dynamic Send Ports, with the custom message routing pattern, based on the use of Static Send Ports, without message mapping. This time the target system was represented by a WCF service called CalculatorService that runs within IIS 7.5 and exposes a NetTcpBinding-based endpoint. This latter exposes a single method called Calculate which receives a request message like the one depicted in the figure below:
CalculatorRequest Message
<CalculatorRequest xmlns="http://microsoft.biztalk.cat/10/samples/esb/calculatorrequest"> <Operations> <Operation> <Operator>+</Operator> <Operand1>82</Operand1> <Operand2>18</Operand2> </Operation> <Operation> <Operator>-</Operator> <Operand1>30</Operand1> <Operand2>12</Operand2> </Operation> <Operation> <Operator>*</Operator> <Operand1>25</Operand1> <Operand2>8</Operand2> </Operation> <Operation> <Operator>\</Operator> <Operand1>100</Operand1> <Operand2>25</Operand2> </Operation> </Operations> </CalculatorRequest>
and returns a response message that contains the result of each operation:
CalculatorResponse Message
<CalculatorResponse xmlns="http://microsoft.biztalk.cat/10/samples/esb/calculatorresponse"> <Results xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <Result> <Value>100</Value> <Error>None</Error> </Result> <Result> <Value>18</Value> <Error>None</Error> </Result> <Result> <Value>200</Value> <Error>None</Error> </Result> <Result> <Value>4</Value> <Error>None</Error> </Result> </Results> </CalculatorResponse>
For you convenience, I’m including the code of the CalculatorService below:
CalculatorService Class
#region Copyright //------------------------------------------------- // Author: Paolo Salvatori // Email: paolos@microsoft.com // History: 2010-06-22 Created //------------------------------------------------- #endregion #region Using Directives using System; using System.Diagnostics; using System.ServiceModel; using System.Transactions; using System.Configuration; #endregion namespace Microsoft.BizTalk.CAT.ESB.Services { [ServiceContract(Namespace = "http://microsoft.biztalk.cat/10/samples/esb/calculatorservice")] public interface ICalculator { #region Contract Operations [OperationContract(Action = "Calculate", ReplyAction = "CalculateResponse")] CalculatorResponse Calculate(CalculatorRequest request); #endregion } // Service class which implements the service contract. [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single, ConcurrencyMode=ConcurrencyMode.Multiple)] public class CalculatorService : ICalculator { #region Public Methods [OperationBehavior] public CalculatorResponse Calculate(CalculatorRequest request) { CalculatorResponse response = new CalculatorResponse(); Operation operation = null; string error = null; double value = 0; try { if (request != null && request.Operations != null && request.Operations.Count > 0) { for (int i = 0; i < request.Operations.Count; i++) { operation = (Operation)request.Operations[i]; error = "None"; value = 0; switch (operation.Operator) { case "+": value = operation.Operand1 + operation.Operand2; break; case "-": value = operation.Operand1 - operation.Operand2; break; case "*": case "x": value = operation.Operand1 * operation.Operand2; break; case "/": case "\\": case ":": value = operation.Operand1 / operation.Operand2; break; default: error = string.Format("Unknown Operator: {0}", operation.Operator); break; } response.Results.Add(new Result(value, error)); } } } catch (Exception ex) { Trace.WriteLine(string.Format("[CalculatorService] Exception: [{0}]", ex.Message)); throw; } return response; } #endregion } }
This test case uses a simple Itinerary where the Routing Service is defined on the OnRamp Port, while a Dynamic Send Port is used to communicate with the underlying WCF service. No message transformation is carried out by this test case.
The following figure depicts the Itinerary used by the OOBRoutingNoTransform test use case:
The standard Routing service is executed within the OnRamp Receive Location. No transformation is applied to either the request or response message. The following picture shows how the STATIC Route Resolver used by the RouteRequest service has been configured:
In particular, the Action property contains the Action of the target method exposed by the CalculatorService while the Endpoint Configuration property has been configured as follows:
BindingType=netTcpBinding&BindingConfiguration=<binding name="netTcpBinding" transferMode="Buffered" maxConnections="200"><security mode="None"/></binding>&IsFault=true&PropagateFaultMessage=true&EnableTransaction=false
Note Regardless if transactions are enabled and used when invoking an underlying system (SQL database or WCF service) using a WCF-Custom Dynamic Send Port and regardless the binding being used (SqlBinding, NetTcpBinding, etc.), it's extremely important that you explicitly set the EnableTransaction property to false or true, otherwise the WCF-Custom Adapter will try to determine (checking whether the actual binding includes the TransactionFlowElement binding element), at runtime whether a System.Transactions TransactionScope was required when sending a message over a channel. For more information you can read Mustansir’s post. I run a test to measure the impact of defining or not defining the EnableTransaction property.
The following figure depicts the architecture of the OOBRoutingNoTransform test use case.
This use case adopts my custom Routing Service along with a Two-Way WCF-Custom Static Send Port. Once again, no message transformation is carried out by this test case.
The following figure depicts the Itinerary used by the CustomRoutingNoTransform test use case:
With respect to the preceding test case, this time the Itinerary makes use of the custom Routing Service and the RouteRequest step is now run to the OffRamp Port. However, the definition of the STATIC Route Resolver was not changed.
The following figure depicts the architecture of the CustomRoutingNoTransform test use case.
The third scenario has been specifically created to measure and compare the performance of the standard and custom Transform Service when processing a medium-sized message (80 KB) with a map of average complexity. To this purpose, I reused the CalculatorRequest and CalculatorResponse messages that I exploited in the previous Scenario, but this time calculations are performed by the map shown in the figure below rather than by the WCF CalculatorService.
CalculatorRequestToCalculatorResponse Map
Note: The map has 4 pages, one for each possible math operation.
Then I created 2 different use cases, one using the standard Transform Service while the other uses my custom Transform Service.
This use case implements a one-way flow and uses a simple Itinerary where both the standard Transform and Routing Service are defined on the OnRamp Port. Therefore, at runtime, all the message processing happens within the One-Way WCF Receive location, while a One-Way Dynamic Send Port is simply used to write the response message to an output folder.
The following figure depicts the Itinerary used by the SimpleRoutingSlipOOBTransform test use case:
The TransformService step has been configured to use the standard Transform Service provided out-of-the-box by the ESB Toolkit, while the ROUTE Static Resolver used by the RouteMessage step has been configured to write the transformed message to a certain folder, as shown by the following picture:
The following figure depicts the architecture of the SimpleRoutingSlipOOBTransform test use case.
This use case is identical to the previous one except that the original Transform Service provided out-of-the-box by the ESB Toolkit has been replaced with my custom Transform Service.
The following figure depicts the Itinerary used by the SimpleRoutingSlipCustomTransform test use case:
The TransformService step has been configured to use the custom Transform Service in place of the one provided out-of-the-box by the ESB Toolkit, while the ROUTE Static Resolver used by the RouteMessage step was not changed with respect to the previous use case.
Note If you use a Microsoft.BizTalk.CAT.ESB.Itinerary.Services.Transform custom service within an Itinerary along with a STATIC Resolver and you receive the error ‘Transport Name' property value should not be empty or null in resolver '<Resolver Name>' when you try to validate or export the Itinerary, just select (None) as Transport Location on the STATIC Resolver configuration.
The following figure depicts the architecture of the SimpleRoutingSlipCustomTransform test use case.
In the next episode of the series we will walk through performance tests results. Stay tuned for Part 3!
You can immediately download the code of the custom ESB services and test cases here. As always, you are kindly invited to provide feedbacks and comments.