Microsoft BizTalk Server orchestrations allow parallel execution branches, using the native Parallel Actions shape. However, the number of branches is static: to add an executionbranch, you need to modify and recompile an orchestration.The behavior for
Parallel Actions shapes doesn't fit in scenarios where you know only at
runtime the number of execution branches that you can spawn. An example
is the travel agent service scenario described at www.w3.org/TR/ws-arch-scenarios/,
where a travel agent requests in parallel a list of flights for each
airline included in a customer list. This sample can be generalized to
scenarios where a client application sends a request to a broker that
splits it into individual requests for similar target systems; then the
broker collects the responses from the target systems and aggregates
them into a single response for the client (see Figure 1).
One approach to solve this problem is to use the Recipient List
pattern as described by Enterprise Integration Patterns. The Recipient
List pattern is explained by the Enterprise Integration Patterns site as
the following:
A Content-Based
Router allows us to route a message to the correct system based on
message content. This process is transparent to the original sender in
the sense that the originator simply sends the message to a channel,
where the router picks it up and takes care of everything.
In some cases, though, we
may want to specify one or more recipients for the message. A common
analogy are [sic] the recipient lists implemented in most e-mail
systems. For each e-mail message, the sender can specify a list of
recipients. The mail system then ensures transport of the message
content to each recipient. An example from the domain of enterprise
integration would be a situation where a function can be performed by
one or more providers. For example, we may have a contract with multiple
credit agencies to assess the credit worthiness of our customers.When a
small order comes in we may simply route the credit request message to
one credit agency. If a customer places a large order, we may want to
route the credit request message to multiple agencies and compare the
results before making a decision. In this case, the list of recipients
depends on the dollar value of the order.
In another situation, we
may want to route an order message to a select list of suppliers to
obtain a quote for the requested item. Rather than sending the request
to all vendors, we may want to control which vendors receive the
request, possibly based on user preferences
How do we route a message to a list of dynamically specified recipients?
Define a
channel for each recipient. Then use a Recipient List to inspect an
incoming message, determine the list of desired recipients, and forward
the message to all channels associated with the recipients in the list.
The
logic embedded in a Recipient List can be pictured as two separate
parts even though the implementation is often coupled together. The
first part computes a list of recipients. The second part simply
traverses the list and sends a copy of the received message to each
recipient. Just like a Content-Based Router, the Recipient List usually
does not modify the message contents.
An alternative approach would have been using the Publish-Subscribe and Message Filter
patterns. We don't describe here this alternative approach, as this
implementation could be more resource consuming in terms of database
queries to resolve the filter conditions, and more error prone while
setting filter conditions on the channels.
Broker Implementation Overview
Our implementation of the broker requires using two different orchestrations.A parent orchestration builds the list of recipients, based on the
received document. The parent orchestration uses the Start Orchestration
shape to launch a child orchestration for each recipient. The child
orchestration executes the actual flow of messages with the recipient.
We assume that all the recipients share a common workflow model and
schema documents; otherwise you wouldn't need such a dynamic invocation
model, as a manual activity would be needed to introduce each additional
recipient! The child orchestration makes use of dynamic port binding to send messages to each different recipient.
The parent orchestration
collects the results returned by each child and builds an aggregated
response document. The parent orchestration makes use of a self-correlating binding port to receive the responses from the started child orchestrations.
Create the Parent Orchestration:
The following are the steps required to create the parent orchestration as shown in Figure 2 for the solution within Visual Studio.
Define the schemas for the Customer Request, Customer Response, Recipient Request, and Recipient Response.
Promote a property in the Recipient Request schema that can be used to build the dynamic address of the recipient.
Define
one message for each of the mentioned schemas, that is, a
CustomerRequest, a CustomerResponse, a RecipientRequest, and a
RecipientResponse message.
Drag a Receive shape to receive a CustomerRequest message from a client application.
Define variables to control the two loops of the parent orchestration: the SpawnChild loop and the CollectResponses loop.
Drag
an Expression shape that you use to calculate the recipient list from
the CustomerRequest message and initialize the SpawnChild loop control
variable. The customer request message must contain the number of
messages to spawn. The SpawnChild variable will contain this number.
Drag a Loop shape for the SpawnChild loop and define the Boolean looping control expression.
Drag a Loop shape for the CollectResponses loop and define the Boolean looping control expression.
Drag a Send shape to return the CustomerResponse message to the client application.
Drag
two ports to be used as PortFromCustomer and PortToCustomer; their
actual properties depend on the particular scenario and are not relevant
to the discussion.
Drag a port to be used as PortFromChild. In the Port Configuration Wizard, define the following properties:
In the Select a Port Type tab, choose to create a new port type named TypePortFromChild with the communication pattern One-Way.
In the Port Binding tab, choose "I'll always be receiving messages on this port" as port direction of communication.
Choose Direct as port binding and then Self Correlating.
Next, create the SpawnChild loop whose steps are defined here and shown in Figure 3.
Drag
a Message Assignment shape inside the SpawnChildLoop shape; in the
enclosing Construct Message shape, define RecipientRequest as the
message to be constructed.
In
the Message Assignment expression, you will build the RecipientRequest
message from the CustomerRequest message according to the current loop
cycle; you will probably want to use a helper .NET component to build
the message. You will also update an orchestration variable with the number of spawned children.
Drag a Start Orchestration shape below the ConstructMessage shape. Leave it unconfigured for the moment.
Once the preceding steps are completed, the final step is to create the wait responses loop as defined here:
Drag
a Receive shape inside the WaitResponsesLoop shape. Define
RecipientResponse as the message that will be received by this shape.
Drag
a Message Assignment shape below the Receive shape; in the Construct
Message shape, define CustomerResponse as the message to be constructed.
In the Message Assignment expression, you will build
the CustomerResponse message aggregating the RecipientResponse message
received in the current loop cycle. You will also update an
orchestration variable with the number of received responses that will
have to match the number of spawned children to exit the loop.
Create the Child Orchestration:
Once the parent
orchestration is created, the next step is to create the child
orchestration. The steps for this are defined here and the orchestration
is shown in Figure 4.
You will reuse the Recipient Request and Recipient Response schemas defined before.
In
the Orchestration View section, right-click Orchestration Parameter and
choose New Port Parameter; assign to this port parameter the port type
TypePortFromChild, defined previously in the parent orchestration;
assign to this port parameter the identifier PortToParent; change the
communication direction of this port parameter to Send.
Right-click
Orchestration Parameter again and choose New Message Parameter; assign
to this message parameter the message type RecipientRequest and the
identifier MsgFromParent.
Define one TargetResponse message that uses the Recipient Response schema.
Drag a port to be used as PortToTarget. In the Port Configuration Wizard, define the following properties:
In
the Select a Port Type tab, choose to create a new port type named
TypePortToTarget with the communication pattern Request-Response.
In the Port Binding tab, choose "I'll be sending a request and receiving a response" as port direction of communication.
Choose
Dynamic as port binding and then choose a receive pipeline and a send
pipeline suitable for your Recipient Request and Recipient Response
schemas.
Drag
a Send shape onto the Orchestration Designer surface and name it
SendToTarget; configure this shape to send the MsgFromParent message to
the PortToTarget port.
Drag
a Receive shape onto the Orchestration Designer surface that you will
name ReceiveFromTarget; configure this shape to receive the
TargetResponse message from the PortToTarget port.
Drag
a Send shape onto the Orchestration Designer surface that you will name
SendToParent; configure this shape to send the TargetResponse message
to the PortToParent port.
Drag
an Expression shape at the top of the orchestration that you will name
BuildTargetAddress; use this expression to assign the URI to the dynamic
port PortToTarget based on the value of a promoted property in the MsgFromParent message.
Bind the Parent Orchestration to the Child Orchestration:
To complete the exercise, you need to add the following additional configuration to the parent orchestration:
Double-click
the Start Orchestration shape to open its configuration box. In the
Select the orchestration you wish to start combo box, select the child
orchestration. The Orchestration Parameters grid is automatically
updated with the right matches between the variables in scope of the
parent orchestration and the parameter name of the child orchestration:
the PortFromChild variable is matched with the PortToParent parameter;
the RecipientRequest variable is matched with the MsgFromParent
parameter.
In
the Properties pane of the orchestration, change the transaction type
to Long Running and set a value for the timeout; otherwise, in case a
child orchestration is terminated abnormally, the parent orchestration
would wait indefinitely for a response.