I was recently reviewing a BizTalk application where there were around a dozen orchestrations which had been exposed as either SOAP or WSE web services. The typical pattern that had been used in these orchestrations is illustrated in the below picture. You can see that there is a Request/Response port and in the orchestration initialises based of the request message then returns a very simple acknowledgement type message (just a message of .net type System.Int32 with a value of 0) to indicate the orchestration has the message and is going to do some additional work.
This is a pattern I've seen implemented particularly with inexperienced teams. Often when I've seen this pattern the code also had poor testing and the team did not have much confidence in their implementation. They ended up with this pattern to ensure that the caller knew they had the message. The main issue the customer had was that occasionally they would experience a timeout at the client end particularly during load, it wasn't a major issue as it only happened occasionally and the client could recover from this ok, but although the client got a timeout the orchestration still processed anyway and would make the appropriate updates to the original application to update it on the progress.
The challenge in this refactoring exercise was as follows:
- The application was already in use so we are not able to change any of the contracts used by client applications. This means the WSDL for the published WSE and SOAP web services can not change.
- We wanted to reduce the number of receive instances waiting around for a meaningless response and as result reduce the load a little
Before we get into the details of the refactoring, we will first look at the code samples from the previously generated SOAP and WSE services.
Existing WSE Web Service
In the below code snippet you can see that firstly I've had to censor some of the code from this article, but secondly it's a standard web service generated with the WSE adapter. It inherits from the WSEReceiver class and invokes the receive method and indicates that it's a 2 way call, then returns the response.
Existing SOAP Web Service
The below picture shows a sample of one of the SOAP web services generated from an orchestration. Again pretty standard generated web service using the SOAP generating wizard.
In addition to what I've mentioned above, some other things which affect this refactoring are:
- The web services are generated every time we run a build and they are also versioned
The following sections will describe how we refactored the solution to implement our desired improvements.
Manually Implementing the Web Service Projects
The first thing we needed to do was to change the web services from being automatically generated to being two projects within the solution. We creates these two projects but with the request and response types being generated from schemas within the solution we wanted to maintain the ability to update these web services automatically so that any changes to schema were reflected in the web services. In the msbuild for the new web services projects we extended the build process so that C# classes were generated in the web projects representing the schemas using xsd.exe. In the below picture you can see we are generating these files into a folder called Contract within the web services.
After this we basically copied the artefacts from the generated web services to the manually build web service projects. We also needed to ensure the correct references were added.
Changing the SOAP Web Service
In each of the web services we needed to make a few small changes from what was generated. The first change (the top red circle) was to change how we assign the bodyTypeAssemblyQualifiedName variable. In the generated web services this is hardcoded by inspecting the assembly, but when we want to do this manually I added a reference to the schemas assembly and then just used the highlighted code snippet to get that name.
The second change (the bottom red circle) is the change to the call into the BizTalk assemblies where we change the oneway parameter to indicate that we are not waiting for a response. Previously the zero response message came from the orchestration meaning the messages route had been into the message box, into an orchestration, back into the messagebox and then finally back to the waiting instance to reply to the caller. By doing this as a one way call the code will basically wait until the message has been persisted to the messagebox and then continue.
You can see I then return my own integer of value 0 to indicate it has worked.
Note I've also implemented some custom logging to log any errors.
Changing the WSE Web Service
The WSE Web service changes are very similar to the soap ones. In the picture you can again see the change to how the schema qualified name is obtained. We have also changed the oneway parameter when we call the invoke method (although I've missed this off the right of the screen shot)
Changing the Orchestration
Now we are managing the web services as projects within our solution we need to change the orchestrations. The picture below shows we now have a one way receive port which is no longer late bound like previously, but is now direct bound based on the message type. By the time we have initiated the orchestration the caller already knows its request hit the messagebox and it does not need to wait around for confirmation that the orchestration has started.
Changing the bindings
We finally needed to update our binding file template to change the receive locations to be one way.
We had already implemented a number of BizUnit tests and were able to use these to confirm everything that had worked before the refactoring still worked exactly as it did now and that none of the web service contracts had changed.
In this post we discussed a common anti-pattern you seen implemented with BizTalk. We were able to take a pragmatic approach to addressing some of the issues the customer was facing with this implementation while minimising the affects to the consumers of services published from this application.