In the latest
round of documentation that was released for BizTalk 2004 there is an "orchestration
operator" defined that was not previously documented: `succeeded()`
The documentation states that this operator can be used to determine the outcome of
a transactional scope or orchestration. When
might this operator be needed
Well, it turns out that the orchestration compiler has some interesting rules about
what you can do in an exception handler that might not be entirely intuitive at first
(though as you reflect on analogies to C# or other exception-enabled languages, it
begins to make sense.)
Suppose that you have defined a Request/Response port as the means of interacting
with an orchestration, and you want to ensure that some response
is generated regardless of the failure conditions you encounter. Your
first attempt might look like this (I know mine did…)
Stretch this JPG out to full size to see it clearly (IE will shrink it.)
This will generate a compiler error: error X2162: must
receive before sending a fault message on an implemented port
What is going on here It sure seems
as if we have received a message already -
we did it in the Rcv_SomeDoc shape. However,
we have the Snd_ResponseDoc shape inside of Scope_WorkThatMightFault, and the orchestration
compiler is assuming that we might have already executed
that shape prior to the catch block executing (i.e. prior to an exception being raised.) A
Request/Response port must only have one response for any given request…and
our Snd_FaultDoc shape has the potential to violate this rule. It
sure would be nice if X2162 could be more explanatory in this regard…
How do we overcome this It isn`t terribly
obvious…We must wrap the Snd_ResponseDoc shape in an (additional) transactional
scope, and check in our catch block to ensure that the associated transaction did
not succeed before performing Snd_FaultDoc. See
this diagram (Acrobat required).
What we are doing here is structuring the flow
such that exactly one response will be sent for the original Rcv_SomeDoc shape. The
way we do this is to use a Decide shape, with an expression such as "!succeeded(Transaction_SndResponse)"
in the rule branch. The Snd_FaultDoc
will be in the `true` side of the branch (i.e. we did not successfully
perform Snd_ResponseDoc), while the `false` side will likely be empty.
This is a pretty subtle bit of enforcement that the orchestration compiler is performing. It
is somewhat analogous to a typical language compiler ensuring that all code paths
have a return value for non-void functions or methods. And,
of course, even though it is not enforced, it is certainly the case that `catch` and
`finally` blocks in standard languages often have to be aware of what has or hasn`t
taken place in the associated `try` block. The
orchestration compiler (apparently) just has some well-defined & strict rules
it wants orchestrations to adhere to (such as "exactly one response for each request
emanating from a Request/Response port".)
There is a somewhat similar case that is described briefly in the BizTalk documentation. Imagine
we wish to make reference to a message or variable in our `catch` block that was initialized
within the associated scope. In this
case, the orchestration compiler will assume that we might not have gotten around
to initializing that variable/message prior to the exception being thrown - and a
compiler error will be generated as a result: error X2109:
use of unassigned local variable `Variable_Blah`
In this case, we can wrap the portion of the scope`s
work that is responsible for initializing the variable/message of interest in an (additional)
transactional scope (i.e. "Scope_InitWork"), and we can use a Decide shape with an
expression such as "succeeded(Transaction_InitWork)"
in the rule branch. This will allow the
orchestration to compile…
Wow! One might start to agree with Charles`
boss...