Monday, May 20, 2013

Using ESB 2.1 with HL7 messages


Using ESB 2.1 with HL7 messages

I assume at this point you have your HL7 messages and its Message header and other schemas deployed in BizTalk. The scenario discussed here is where you receive a HL7 message and you return an ACK back in response to sender of message and send the message to MSMQ.

Using MLLP adapter Or ESB On-Ramp Services

In both cases, we are going to use Request-Response port because that is most typical HL7 message interchange scenario. As part of the sample I created an itinerary which will route the message to MSMQ based on the type of message. Also I have used BRE to resolve itinerary. The following steps I followed to make it work:

Create a new Receive pipeline

This most important step is to create a Receive Pipeline using ESB pipelines and HL7 Disassembler pipeline because none of the off the shelf ESB pipeline uses HL7 Disassembler component. Design the receive pipeline as shown in diagram below:
There are two other components used in addition to HL7 Disassembler component, ESB Itinerary Select, and ESB Dispatcher. These two components are required for ESB to select Itinerary and then execute the itinerary.

               



Create a new Send pipeline

Similar to receive pipeline, create a send pipeline which we will use on Receive port response side. Notice the two ESB pipeline components ESB and ESB Itinerary Cache which are in addition to HL7 Assembler component.




Create an Itinerary Select Rule

Create Itinerary Select Rules which is used by ESB Itinerary Select component to load the itinerary in message context. Please see a sample of Rule. You can use ESB Vocabulary to check for message type and set the required itinerary name and version.


 
Please note the OR condition is required otherwise your condition will not work. The reason for this is HL7 Disassembler component generates two messages if you have configured ACK and this rule will be executed for each of those messages and if you don’t have condition for ACK message then itinerary selection will fail and you will get an error like:
Reason: Procedure or function 'Itinerary_getitinerary' expects parameter '@name', which was not supplied. 
You might now question why do we have Itinerary Select Pipeline component in receive pipeline after HL7 Disassembler component and create our rule based on some other properties rather than message type?
The question is valid and ideally that’s where we should have it unless you want to use message context properties to create rules, however, this approach with HL7 Disassemble component does not work. The problem is when it generates ACK message it does not populate all the original message context properties and as a result your itinerary selection fails.

Create an End Point Set Rule

In this policy, we set the End point of message based on its message type. Since we want to send it to MSMQ we will set the end point accordingly, see the rule below:

Please note, there is no off-the-shelf MSMQ adapter provider, you can follow this article to create this, it’s very simple or you can use some other end point:

Setup Receive Location and Dynamic Send Port

MLLP Recieve Port

For MLLP adapter, setup a request response MLLP port and location and use your Receive pipeline on Receive pipeline properties as follows:

 
Note you use Itinerary Select Policy here for ESB load itinerary dynamically
There is no specific configuration required for send side.

WCF Generic Receive Port

For WCF Generic On-Ramp, use the ESB.ItineraryServices.Generic.Response.WCF receive location under Microsoft.Practices.ESB application and change it’s receive and send pipeline to your newly created pipelines and configure the receive side as shown above for MLLP.

Dynamic Send Port


Create the Dynamic send port as required by Itinerary offramp, setting filters for servicename, servicestate and servicetype, isrequestresponse.
 

Create Itinerary

Now the last part, Create your itinerary as it’s a simple scenario to route messages, I created a simple itinerary which takes message using On-Ramp one of your receive port (MLLP or OnRamp.Itinerary.Response) resolves the endpoint using our BRE policy using BRE resolver and routes the message accordingly. Now export and deploy your itinerary to Itinerary Db. Make sure you keep the name same as you have it in your Itinerary select rule.



Now with all artifacts deployed, you can test your scenario using mllpsend utility for MLLP adapter or for WCF Generic, you can create a unit test project and add a service reference and invoke the service.
Before you run it, do not forget to configure your HL7 party to configure ACK messages.
This scenario returns ACK in response as mentioned in the beginning.


The issues you may encounter during this test run

1.       Reason: Procedure or function 'Itinerary_getitinerary' expects parameter '@name', which was not supplied. 
Check the Itinerary Select rule, most probably either you have not specified correct policy on Receive Pipeline properties or your condition is not being met.
2.       Reason: Value cannot be null
Check Receive pipeline config properties on receive location, most likely you have not specified Itinerary select rule. Make sure ResolverConnectionString is set to BRI:\\policy=;version=;useMsg= (version and useMsg are optional)

3.       Reason: Error 135008: The itinerary was not found in the repository. 
Check the Receive pipeline config properties on receive location, make sure you have set ItineraryFactKey to Resolver.Itinerary

Using WCF On Ramp services to execute this scenario, you may encounter following issues:

4.       There was a failure executing the receive pipeline: "Rcv_ESB, Pipelines, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c947d4e52faa0f65" Source: "Unknown " Receive Port: "OnRamp.Itinerary.Response" URI: "/ESB.ItineraryServices.Generic.Response.WCF/ProcessItinerary.svc" Reason: Could not load file or assembly 'file:///C:\Program Files (x86)\Microsoft BizTalk Server 2010\Pipeline Components\Microsoft.Solutions.BTAHL7.HL72fDasm.dll' or one of its dependencies. An attempt was made to load a program with an incorrect format.
In order to fix this issue, change IIS application pool CoreEsbWcfAppPool to Enable 32-bit Application to True. This is required because HL7 Disassembler component can be loaded in 32 bit process only.

5.       There was a failure executing the receive pipeline: “ESB, Pipelines, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c947d4e52faa0f65" Source: "BTAHL7 2.X Disassembler" Receive Port: "OnRamp.Itinerary.Response" URI: "/ESB.ItineraryServices.Generic.Response.WCF/ProcessItinerary.svc" Reason: Unable to configure the logstore.
Give access to IIS app pool “CoreEsbWcfAppPool” user to BTAHL7 database, db_datawriter, db_datareader
6.       The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter :part. The InnerException message was 'Element part from namespace  cannot have child contents to be deserialized as an object. Please use XmlNode[] to deserialize this pattern of XML.'.  Please see InnerException for more details.
{"Element part from namespace  cannot have child contents to be deserialized as an object. Please use XmlNode[] to deserialize this pattern of XML."}
{"End element 'part' from namespace '' expected. Found text 'MSH|^~\\'. Line 1, position 234."}
   at System.Xml.XmlExceptionHelper.ThrowXmlException(XmlDictionaryReader reader, String res, String arg1, String arg2, String arg3)
   at System.Xml.XmlExceptionHelper.ThrowEndElementExpected(XmlDictionaryReader reader, String localName, String ns)
   at System.Xml.XmlBaseReader.ReadEndElement()
   at System.Runtime.Serialization.ObjectDataContract.ReadXmlValue(XmlReaderDelegator reader, XmlObjectSerializerReadContext context)
The above error occurs due to ObjectDataContract Deserializer. Due to the & in Message header f HL7 message, the deserializer fails to deserialize the response received from service. This is a WCF service consumer issue and a possible bug in System.Xml.XmlSerialization assembly to deal with such xml. The Add service reference generates the proxy with following method because of any message type in our service wsdl http://localhost /ESB.ItineraryServices.Generic.Response.WCF/ProcessItinerary.svc?wsdl of WCF:
public void SubmitRequestResponse(ref object part)

As a work around of this issue, I changed the client proxy method as follows:
public void SubmitRequestResponse(ref string part)

With this change and other respective change to proxy class, the issue got resolved.
I also tested this interface using SoapUI (a web service testing tool) and it worked successfully. Since my version of soapui does not support all WS* standard which are used by ESB WCF service for message security, I exposed a basicHttpbinding service.

No comments: