I have just completed working on a small customer project that (at first sight) appeared trivial, but after putting together a prototype was anything but!
The client uses BizTalk Server 2004 to accept sales orders from a number of customers in several different formats. One of their larger customers sends messages in the xCBL format and provides a mechanism to accept ‘Smart Form’ data – information over and above that usually contained in a sales order, such as name badge, rubber stamp and business card details (layout, typography etc.)*
The required functionality was as follows:
- Accept orders in the custom format (in this case, xCBL) and translate into an intermediate format (this was already completed);
- Identify messages from those customers that could send Smart Form data;
- Determine which lines (if any) on the order contained Smart Form product codes through a database lookup;
- Send one e-mail per order containing the additional Smart Form detail to the customer services team for manual processing.
All of the message splitter patterns I have come across so far deal with the generation of new messages as each line is parsed (i.e. a 10 line order would produce 10 messages etc.). However, for this problem, I needed to update the state of each message (depending on whether it was a Smart Form product) and output a single e-mail/message containing only the affected lines at the end of the process.
(Yet Another) Message Splitter Pattern
The final orchestration is described in detail below (a large-ish screenshot is available) and is quite elegant (at least I think so!). It utilises a ‘global’ Xml Document variable to maintain the state of the order as each line is parsed, with that state being updated at the end of each line pass.
Before we get into the detail, let me provide a top level overview: A message is received containing all of the order lines. For each line we initiate a database call to determine whether the line contains a Smart Form product. If it does, an attribute on the line (the output attribute) is flagged as true (all lines start out flagged as false). After all lines have been parsed, we have a message that can be pushed through a final map that only outputs those lines where they are flagged as output = true. This acheives our goal of only sending one message per order, rather than one message per Smart Form line.
Lets get into the detail (you may wish to refer to the screenshot mentioned above)….
- We start by receiving a message from the MsgBox in the ‘SmartForm’ message format – a stripped down format of the intermediate order message that just contains the data we will be interested in for Smart Form processing;
- We obtain a count of the number of lines in the message by using XPath to count the number of instances of a particular node (the lines node). Jeff Lynch has a great intro to this on his blog.
- We instanciate a new instance of our global Xml Document (nothing more than SmartFormXmlDoc = new XmlDocument()) for later processing;
We then iterate over each line:
- After incrementing the line count, we build our Sql Adapter message to query the database and determine whether the product code on the current line is a Smart Form product (it actually passes across a customer identifier to allow this orchestration to be used in more than one customer scenario in the future).
- With the response message from the Sql Adapter, we can made a decision as to whether the line is a Smart Form product.
We’re now at the core of the functionality of this pattern as the next few steps update the line in our message and maintain the state in the global Xml Document (these steps only occur if the line contains a Smart Form product):
- We start by creating a ‘working’ instance of the Smart Form message into which we assign the current contents of the global Xml Document: WorkingSmartFormMsg = SmartFormXmlDocument.
- With a working copy of the message (which contains the current state of the Xml Document), we perform some quick XPath to update the output attribute of the current line from false to true (which will allow us later to identify which lines are to be output in the final message).
- Once we’ve made the update, we assign the message changes back to the Xml Document to maintain the state for the next iteration: SmartFormXmlDocument = WorkingSmartFormMsg, and throw away the working message. Simple!
Once we have completed our final iteration around each line in the message, we will be left with an Xml Document with updated output attributes on each line.
To build the final output message, we simply run our modified message through a map (XSLT of course) and only output those nodes where output = true. the resultant message only contains those lines where the product code is a Smart Form code which can be sent to the customer services team by e-mail, through the SMTP adapter. If there are no matching product codes, the message is not sent out of the orchestration.
Good luck with the pattern, if you would like any help with any of these steps, please drop me a line.
*The old system utilised Python as a pre-processor to BizTalk to identify the lines that contained Smart Form product codes and generated an e-mail to the relevant customer services team, as this information could not be passed through their existing ERP system. However, following the departure of their resident Python developer and a few high-profile failures of the scripts, they wanted to bring the business process onto the BizTalk platform.