One of the new power features in .NET 3.5, sorely missing when .NET 3.0 was released is the integration between Windows Communication Foundation and Windows Workflow Foundation, in the way of the new Workflow Services model. Besides providing an easy way to invoke WCF services from WF without resorting to using BasicHttpBinding on the services or invoking a [wrapper] client class form a code activity in your workflow, it also provides a way to expose a workflow as a WCF service to the outside.
The second functionality is the one that, naturally, interests me the most. After looking at it for a while, I've made a few observations that I'd like to share.
A very interesting thing is how you implement services as workflows. If you know how to create WCF services the right way, then you [mostly] know how to implement services as workflows. The reason for this is that most of the tasks you'd normally when creating a WCF service still apply when creating a workflow service. That is, you will still declare your service contract (interface) as well as your message/data contracts. The only key differences really is how you implement that contract.
When using workflow services, you implement a contract by using the new Receive activity introduced in .NET 3.5, and select which operation of the contract you'd be implementing. You still need to do the activity binding dance for parameters as well as (possibly) create response messages for the incoming requests. Basically, the Receive activity works as a kind of sequential activity where the child activities represent the tasks to execute after the request message is received until the response message is sent (if this is a request/response service).
You'd also need to do a lot of the usual work in setting up your service host, only instead of using the regular ServiceHost class you'll be using the new WorkflowServiceHost class. Guy Burstein has a good entry on getting started on exposing workflows as services here you might want to check out.
One very interesting option in the Receive Activity is the CanCreateInstance property. You can use this property to have the WorkflowServiceHost automatically create and start a new instance of your Workflow when a new message arrives at the service endpoint for the implemented operation, without having to write manual code to receive the message and create the new workflow instance.
People familiar with BizTalk 2004/6 will recognize this option as being similar to the Activate property of the Receive Shape in BizTalk orchestrations. It also serves, in a way, a similar purpose as the CanCreateInstance property of the new [DurableOperationBehavior] attribute introduced for WCF in .NET 3.5 as part of WCF Durable Services infrastructure. Jesus Rodriguez has an excellent article introducing this new feature.
Something I found a bit curious about the CanCreateInstance property of the Receive Activity is that there's currently no validation around where you set it. As far as I can see, it doesn't make much sense to use it unless the receive activity is the entry point of your workflow, but currently nothing prevents you from setting it if is not. That said, I guess it might be difficult to validate this correctly given the WF execution model.
The Receive Activity doesn't limit you to starting new workflow instances; it also allows your workflow instance to wait until a message is received (through a service invocation from a consumer) to continue execution. This is a key scenario for long-running processes because the workflow will have all the necessary context within the business process to interpret the coming message correctly and act on it.
How does the host know which workflow instance should process a given message, then? The answer lies in the binding context. WF requires the consumer of the service to send a token alongside the request message that tells it what workflow instance the message is for. This token is, essentially, the WorkflowInstanceId, and can be sent either as an HTTP Header or as a SOAP Header.
Using the WorkflowInstanceId as the instance selection token is a logical choice for WF, as the Workflow Runtime, as well as the persistence service, already have clear knowledge of what the ID means and how to use it, so getting the right workflow instance to pass the message to is basically just a call to the GetWorkflow() method of the WorkflowRuntime object.
For this entire process to work, both the server and the client must use the special WSHttpBindingContext class, which helps makes this process a bit more transparent (particularly for the workflow service). On the client side, naturally, the service consumer must be, somehow, aware of the correct workflow instance ID to use as the token. Sometimes, this will be handled in a very transparent way by the infrastructure, but sometimes you'll need to make it more explicit. And by the way, the WSHttpBindingContext class is also used for WCF Durable Services.
Serge Luca has an extensive sample of using WF Services in several scenarios that is pretty interesting to check out.
The Limitation of IDs
This entire mechanism works fine for WF Services. However, after I saw this the first time, there was something that I didn't quite like about it, and it kept nagging in my head. A few days ago I finally realized what it was: As a first option for having this feature, it is pretty useful, but it is, in a way, a significant step back.
The problem with using the WorkflowInstanceId as the linking element between the service consumer and the workflow implementing the service is that it has, essentially, no business meaning. While it is a very useful technical discriminating ID, most business scenarios where this kind of long running services and processes are involved already have natural correlating identifiers in the problem domain, like a purchase order id, a loan id, or a combination of customer id and tracking id.
There are several implications of this simple fact:
- Consumers of the service become aware of the fact that there's a workflow on the other end of the service. This is actually merely an implementation detail of the service which should be transparent to the consumer as much as possible.
- Consumers might now be forced to track yet another id, and one that has no business meaning at all. So instead of just having, say, the order id around they also need to find where to store the workflow id.
- The WorkflowInstanceId becomes an added part of the service messaging, but it is one you might not always be aware of because it doesn't make part of the core messaging description.
Of all of this, I think the second one is what bothers me the most. If you're ever used the wonderful Correlation Set mechanism introduced in BizTalk 2004, then you'll definitely see where I'm coming from, as this is a bit more restrictive and less expressive than what we can do right now with BizTalk. In fact, this is more akin to the limited correlation mechanism we had back in BizTalk 2002 (though that required jumping through a lot more hoops).
All in all, this seems like a very welcome addition to both WCF and WF, even though there are limitations in this first release. I'll be watching closely how both develop as they take a bit more of the core messaging and orche stration strengths that Biztalk has enjoyed for the past few years.