A few weeks ago I've posted this entry
about my short, unsuccessful attempt to call an orchestration from another instance of itself using the call orchestration shape and thus introducing recursion.
In comments to that post G Michels pointed out that it seems to be impossible by design according to a very specific error he'd seen.Adam
, however descirbed a very neat approach that is pretty close to recursion (although not quite there in my view) and so I spent some time looking into this. here is the outcome -
The scenario that Adam intorduced is calling an orchestration from another instance of itself. as such this is not exactly a recursive call as, unless more effort is made, the call is completely asynchronouos - the calling orchestration will not wait for the called orchestration to complete, nor will it receive any feedback from it's operation.
The idea is to use the publish-subscribe mechanism BizTalk provides to kickstart another instance of the process; however, Adam's idea of using the Listen shape when activating the orchestration and using partner ports for exchanging messages between instances makes the implemenetation much simpler than it would have been if we tried to do the same thing using basic direct ports and subscriptions and thus it is much more maintainable.
Here's a screenshot of the process I've created following Adam's description -
There are two key points to this idea -
The first one is apparent from the start - the orchestration is started using a couple of activating receive shapes inside a listen shape.
This allows the orchestration to be activated by one of two ports; without this we'd have to fiddle around with promoted properties and subscriptions to get this to work which would have been much more confusing.
In my example the left receive port is late bound to a file drop that would start the process initially.
The right branch is another activating receive shape, directly bound to the message box, but we'll get to that in a minute.
The message I drop has a count attribute initially set to 1, and is configured as a distinguished proeprty.
This allows me to check in the decide shape that whether count is lower than 3 (just so I have some stop condition).
If it is - I contruct a new message by copying the existing one and increasing the count by one.
the message is then sent out using a send port, and this is where the second key point comes into play - the send port is direct bound to a partner port - the second receive port in the same orchestration.
What that means is that, again, without worrying too much about subscription, we are able to publish a message to the message box and ensure it will be picked up by an orchestration of our choice - in this case the very same orchestration, into a particular port - the second receive port linked to the listen shape.
The second half of this - the partner receive port is configured, quite counter intuitively, as Kevin Lam explains in his fantastic post
, to have itself as the partner port.
..and so, by sending a message from the partner send port, we are triggering a second instance of the same process in a recursive-like manner.
As I've mentioned earlier this is still somewhat lacking as the calling orchestration will simply carry on (and complete in my test case) and not wait for any called orchestration to complete and will not be able to receive any feedback from such processes.
To support that I had to add a few bits to the scenario, which did increase it's complexity beyond what I would have hoped - here's the modified example -
The first half remains largly unchanged -
As you can see I've added a boolean variable to indicate whether I'm "inside" a recursion or not and am setting it to True on the left branch of the listen shape (after making sure it's defaulted to False) - this would tell me later whether there's something waiting for a response from this instance of the orchestration or not.
Also, for convinience and nothing else, I'm now keeping the iteration number in a variable.
But in order to achieve my goal I had to make a few bigger changes - here's the second half of the process -
The first addition is a receive shape after publishing the recursion request - this would make the process sit and wait for a response from the initiated process before proceeding.
As you can imagine that involved initializing a correlation set in the previous send shape which uses the iteration number, and following on it in the receive shape, which in turn meant I had to introduce a property schema to define my context property (and changing the schema to promote it).
Another consequence of this is that I could no longer use the same messsage type all around - if I did I the receive shape will simply get the published message before any second process is initiated; I had to create a response schema and use that for the received message type.
The second change was - after the resposne is received - to check whether the current iteration is the first one. this is done by checking the boolean variable I've set in the listen shape - if I'm not within the first iteration, I need to publish a response to the message box initializing a similiar correlation set (with the iteration number, but using iterationNumber-1 to link it to the parent iteration) to ensure the calling process is resumed.
This message could be used to provide any feedback to the calling process if needed.
By now it should be obvious this is way more complicated than simply adding a call orchestration shape and selecting the same orchestration, which is all we had to do if BizTalk did support recusrions out-of-the-box. in fact - this grew to be so complicated I think I will have to consider 10 times before choosing to use this approach in any production environment.
Either way - I found this a very interesting exercise to go through. hope you have as well. and thanks to Adam for sharing the idea.
Labels: BizTalk, orchestration