Wiring Up the SendPO Orchestration
With the .NET components and services referenced and ready to go, you'll wire up these components within the workflow, paying close attention to transaction semantics. For now just completely ignore messaging concerns such as ports, adapters, and receive locations until you're done with the orchestration itself. This will allow you to focus on solving the business problem, leaving the configuration of these lower-level artifacts as a deployment-time exercise.
- In the Orchestration Designer, right-click on the line directly below the green button, expand Insert Shape and select Receive. This places a Receive Shape directly below the green button. Select the Properties window for the Receive Shape and ensure the Message property is set to purchaseOrderMsg. Finally, provide an intuitive name such as Receive Purchase Order.
- Set the Activate property to True. This lets the orchestration know that if it receives a purchaseOrderMsg message, an instance of the orchestration should fire. This property is especially important when orchestrations are capable of receiving multiple intra-process messages, such as is the case when implementing sequential convoy patterns.
As discussed in Part 1 of this article
, you can use Scope Shapes to further demarcate activities and change the transaction type at the Scope level (if you need a refresher on transaction types, please refer to Part 1 of this article). This provides a logical grouping and isolation of the constituent activities within a given Scope.
|In the interest of interoperability, the trend with vendors (including Microsoft) is to abstract as much about the XML itself from the design-time experience and leave to the plumbing the task of adhering to a specific standard or schema.|
Most of the activities are going to be orchestrated as calls to .NET components, which can be called within an Atomic Scope that is either the outermost scope in an orchestration or is nested within a parent Scope that is marked as Long Running. A Long Running Scope can consist of other nested Long Running or Atomic Scopes, but an Atomic Scope cannot contain any child transactions (although nesting Scopes with Transaction Type set to None
The only exception is the Bill Customer activity, which will be fulfilled by our fictitious payment processing vendor via the Acme.Billing.BillingService SOAP endpoint. This activity will simply inherit the Transaction Type
property set to Long Running
(if this just went over your head, don't worry, after creating your first scope below, this will be much clearer).
- Click the Retail Activities Scope and note the properties. Change the Transaction Type to Long Running and provide a useful name for the Transaction Identifier such as RetailTx. Perform the same steps for each of the remaining Scopes (Inventory and Print), but instead of Long Running, choose Atomic. Notice that when changing the Transaction Type to Atomic, the Isolation property was changed to Serialized.
Going back to the Retail Activities Scope, an excellent example of Scope nesting is the Post Funds to GL Activity/Group. Since the IRetailOps interface will be used to bind to the RetailManager component, the Post Funds to GL Activity/Group will need to be isolated within an Atomic Scope because interfaces cannot be marked for serialization as it cannot ever be assumed that the implementation of an interface would itself be serializable:
- Right-click the line directly above the Post Funds to GL Activity/Group and insert a Scope Shape. Name the Scope Post Funds, provide a Transaction Identifier of PostFundsTx, and set the Transaction Type to Atomic. Now, simply drag the Post Funds to GL Activity/Group inside the new Post Funds Scope.
The Post Funds to GL activity will still execute within the RetailTx
long-running transaction; however, it will be executed as a nested Atomic Transaction within its own Scope. This approach is perfectly legal as Atomic Scopes can be nested within Long-Running transaction Scopes; however, remember that the same is not true for the inverse.
Consuming SOAP Services within Orchestration
|Figure 13: To add a Web Reference to a BizTalk Server Project, right-click References and select Add Web Reference.|
After preparing the Scope, it is time to start consuming the components and services that will perform the heavy lifting in our orchestration. This exercise demonstrates how to use the SOAP Adapter for consuming external SOAP services to simulate the billing services provided by our fictitious third-party vendor.
Working with Expression Shapes
- Add a Web Reference to the Northwind.BusinessWorkflows.RetailOps project just as you would for any other Visual Studio 2005 project (see Figure 13). For the web reference name, use ACME. The service can be hosted within Visual Studio's integrated web server for ease of debugging or published to an IIS server in your environment. Next, you'll configure a Web Port (which uses the SOAP Adapter) to connect the ACME billing service to the Retail Activities transaction.
|Figure 14: The Port Configuration Wizard allows you to choose the proxy generated by adding a new Web Reference as a Port Type.|
- On the right Port Surface, right-click and select New Configured Port from the shortcut menu. Name the Port (something like "SOAP Request") and select the Acme BillingService as the Web Port type (this new Port Type was created automatically when adding the Web Reference) as shown in Figure 14. A Request/Response Port is created. Now you must create a Message Variable for the SOAP request and the SOAP response Messages that will be wired up to the Request/Response Port just created.
- In the Orchestration View, under Messages, create a Message Variable called debitRequestMsg and select the appropriate Message Type from the list of Web Message Types. Although the name will be quite verbose and somewhat cryptic, the end of the type will indicate the operation and direction such as
debit_response. Perform the same step for the debitResponseMsg.
- Inside the Bill Customer activity, right-click and insert a Construct Message Shape from the shortcut menu. Provide a name such as "Construct Debit Request" for the Shape and associate it with the type of message to be constructed (that is, the debitRequestMsg Message Variable).
Expression Shapes provide a C#-like scripting syntax for working with .NET types and orchestration variables. You'll use Expression Shapes to instantiate the business components and cast to the appropriate interface.
- Ensure that you've defined the debitRequestMsg Message for the Message to be Constructed property within the Construct Message Shape. This is simply an initialization step, very similar in concept to a traditional variable initialization. The Orchestration Engine will automatically create an instance of the debitRequestMsg Message Variable for you, but you must initialize the fields with relevant request data, which you'll do using special C#-like expressions.
- Inside the Construct Message Shape, right-click and choose the Message Assignment Shape. Provide an intuitive name for the Shape such as "Prepare Debit Request." Now, double-click the Shape to bring up the Expression Editor. Simply enter the following code into the Expression Editor (you will notice that the Expression Editor has full IntelliSense support):
debitRequestMsg.amount = purchaseOrder.Amount;
The expression consists of a simple mapping from the current instance of the purchaseOrder
entity to the debitRequestMsg
entity that will be sent to the ACME billing service. The reason that the properties are available to us from the instance of the purchaseOrder
entity is because they were marked as Distinguished
. At run time, the Orchestration Engine will populate an instance of the debitRequestMsg
variable with these values.
|Figure 15: The Debit Customer Send Shape and Ack Response Receive Shape are connected to the generated Request/Response Web Port.|
- Within the Bill Customer activity, but outside of the Construct Message Shape, add a Send Shape (that will call the Billing Service) and a Receive Shape directly after that.
- Drag the Send Shape connector to the Web Port so that it locks with the Request. Take the same step to establish a connection between the Receive Shape and the Response. At this point, the orchestration looks like Figure 15.
Now only simple wiring activities remain. You'll add an Expression Shape to each activity and call the appropriate .NET component to do the work. Because you are dealing with .NET components, you must first configure the interface that you wish to bind to as a variable (as opposed to a Message Variable):
- On the Orchestration View, under Retail Activities, right-click Variables and create a new variable. Any variables created here are not global and are scoped to the Scope in which they are created. The same is true for Message Variables.
- Select ".NET Class" and browse to the Northwind.BusinessComponents.RetailOps.Interfaces package. Select the IRetailOps interface and click OK. Name the variable retailService.
In a roundabout way you've defined a variable, retailService
, as an IRetailOps interface, just as you would with any .NET type. The Orchestration Engine initializes it at run time, just as if it were in a C# or Visual Basic interface.
- Add an Expression Shape within the Post Funds to GL activity, and add the following code, which is the equivalent of an implicit interface cast in C#:
retailService = new
The IRetailOps.PostGeneralLedger method takes an instance of the PurchaseOrder class (purchaseorder
), performs some work, and returns a Boolean value indicating success or failure. As with any program, you need a variable to store the response so that you can use it as needed.
- Again, within the Retail Activities Scope, under Orchestration View, create a new variable, this time of type Boolean and provide a name, such as generalLedgerSuccess.
- Now simply place the call to the retailService instance, passing the purchaseOrder variable (which contains the actual purchase order submitted), and return the response to the generalLedgerSuccess variable:
Using the object model as a reference, cross checking with the use case and business process diagram, you'll take the same approach for the remaining activities for both Inventory and Print Scopes. The calling patterns are identical, only the variables and types change. Remember to always use implicit casting of the interfaces to keep the calls type-safe.
Because the API is modeled after the business domain, the steps should be fairly intuitive. If you do get lost or stuck, you may reference the solution which is available in the download.
The last thing to do from an orchestration perspective is to implement the Notify Activity/Group.
- Right click anywhere inside the Notify Activity/Group and select a Send Shape. Specify the purchaseOrderMsg as the message and "Send Purchase Order" for the Name.
This solution will simply pass through the processed purchaseOrderMsg
as a way to notify that the orchestration has completed successfully. Obviously, this is a somewhat primitive notification technique, but it will provide the ability to simulate a notification action and at the same time fully test one way, asynchronous invocation of the orchestration.
Configuring the Orchestration to Receive Messages
Although it is largely a matter of preference, I like to try to leave logical port configuration last so that the focus can remain on the business process itself. Whether you configure ports before or after creating and wiring up your workflow is up to you.
This sample solution will use a Receive Shape to actually receive the purchase order from the originating client. On the left Port surface, right-click anywhere to create a new Configured Port. Provide a Port Name of ReceivePurchaseOrderPort
and Port Type called ReceivePurchaseOrderPortType
. Specify the communication pattern as One Way as shown in Figure 16
. Specify the intent to use the Port to receive messages, and leave the default to specify bindings later. You will notice that under the ReceivePurchaseOrderPortType
, BizTalk created an Operation called Operation_1
. Rename the Operation to something a bit more intuitive such as SubmitPurchaseOrder
|Figure 16: A logical One Way Receive Port is created to route purchase orders to the orchestration.||
|Figure 17: The ReceivePOPort receive port is added to the left Port Surface.||
Notice that the port is completely logical and has no physical bindings or knowledge of any adapters. This is the beauty of orchestration; it requires absolutely no commitments regarding transport or other plumbing, leaving these largely as mere deployment details.
|Figure 18: The SendPOPort send Port is added to the right Port Surface.|
Connect the new Receive Port to the Receive Purchase Order Receive Shape. Figure 17
provides a snapshot of the orchestration after adding the logical Receive Port.
Configuring the Orchestration to Send Messages
Similarly to the Receive Purchase Order One Way Receive Port you just created, right-click the right Port Surface and create a corresponding One Way Send Port called SendPurchaseOrderPort
. Connect the new Send Port to the "Send Purchase Order" Send Shape. The orchestration should now resemble Figure 18
. This Send Port will simply pass through the original instance of the purchaseOrderMsg
to a destination that we will configure as a file drop during deployment.