Saturday, August 30, 2008

Deploying Workflow Foundation Part 1 - Introduction and Solution Setup

It's been a long time since I've gotten a chance to write. I certainly had a number of topics to discuss, just no time to sit down and put them into words. Well, words that made sense.

I've decided to write about a topic that I thought needed some attention. I've seen a number of posts of people struggling with deploying and upgrading Workflow Foundation as well as dealing with the same issue in my professional life. Before that though...

Over the past 8 weeks, my life has taken considerable changes both personally and professionally. No, I didn't leave my job, however, if you remember in previous posts, I work for a large financial institution, and my first few months were really nothing documentation. In my opinion not necessarily the most useful documentation, but I digress. That all changed about 12 weeks ago with me being the onshore lead for an offshore project at work.

Back to that in a moment...

I wouldn't normally share something as personal on a technical blog, but for this, a small snippet is acceptable. Just over a year ago my wife and I lost our son Ryan at birth (he was 38 weeks). Of course, a tragic time and difficult for both of us. However in April we found out my wife was pregnant again. We were incredibly excited and as we made it past the first trimester we were anxiously awaiting the day to find out the gender. Of course, like any parents we started shopping for names, we quickly selected a girls name without any question (just as we had selected Ryan's). But we were stuck between two boys names. Well, that day came and looks like there was a reason we couldn't decide... We need 'em both. ;)

Back to our regularly scheduled programming...

A little background on this project. We had 4 workflows, each being very simliar in soliciting some responses from various stakeholders in an operation and modifying data accordingly. Nothing too crazy. Now I obviously cannot share those workflows, so I've created a beginning to end + iterations example of using Windows Workflow in *any* environment which I will be sharing over the next few posts.

Ok let us take a process we all know. An online shopping cart system. To start off, I've created the most simple cart system that I can. It contains no data other than an ID (and that Id will be provided by the workflow engine). Below is what my state machine looks like.

The workflow has 3 states. ShoppingCartState, OrderPlacedState, and OrderShippedState. The workflow responds to events from ExternalDataExchange decorated interfaces.

But before we get into all that. I want to talk about the solution layout and a very important thing about workflow that is overlooked. That my friends, is versioning, and how to properly version Workflow.

Microsoft's Provided Workflow Templates for VS2005 & VS2008

I'm a huge Microsoft fan, so this isn't a complaint, just a clarification. The Workflow project templates that are included with the Workflow Extensions for VS2005 and VS2008 are not meant to be used in an almost *any* production environment other than one where either

  • The workflow never changes
  • If upgraded, all existing data is either destroyed or migrated (sometimes always painfully).

This doesn't sound like a very good situation in a large application or small. Truthfully, I've found that following the BizTalk solution layout seems to be the best way to easily change and upgrade workflows. And what exactly is that?

  • Interfaces Project - Version # should never change (next post I will cover how it can). This represents interfaces Workflow uses to communicate with External Services using the ExternalDataExchangeService.
    • NOTE: Your interfaces project should also include enum's and inherited ExternalDataEventArgs. If you don't, you will need to verify and possibly modify
  • Workflow Project - Version # should change on nearly every deployment
  • Services Implementation Project - Version # should change on an as needed basis.

Or, a picture is worth 1000 words.

I have three projects of interest represented here. Now I have actually broken one of my own rules in which case I have joined the services implementation with my Workflow project. Though it isn't necessarily a best practice, the version # on the services is not nearly as important as the interface version # for this example. So we can cheat a little in this example.

Also notice I've added a Strong Name key to my Workflow Project as well as the interface project. I have to add it to the interface project because it is referenced by the workflow project. The workflow project is strong named because I'm going to install it into the GAC. I could install the interface library in the GAC as well.

Why should the interface version never change?

This seems to be the biggest "gotcha" when developing Workflow enabled applications. It isn't a bug, but something very much meant by design. Under the hood WF has it's own runtime that handles events through it's own messaging infrastructure. If you look deep enough, you'll find the eventing model uses something similar to MSMQ to deliver an event message to a specified workflow. It may actually be MSMQ, but I cannot say for sure at this point. This way, the runtime handles the message delivery of an event without us having to worry about if the workflow is in the proper state to receive the event. This post will not go into the details of guarunteed asynchronous message delivery, but it's a fun topic if you have the time.

Why install the Workflow DLL in the GAC?

Simple, in most cases (even something like this shopping cart) we will have long running transactions. The process of getting the order into our warehouse may be fast, however, if we are shipping a physical item there may be a few days between shipping and updating it. Workflow offers persistence services for keeping these transactions alive through reboots, shutdowns, and whatever else may happen.

Lets say we add a new step to the process, such as AtVendorState where we ship part of our order from a 3rd party vendor. When we deploy this, we still want our existing workflow to finish running the way they started, that way we don't have to come up with some sort of upgrade program or a completly separate deployment of the services and workflows.

In order to allow both the old workflows and new workflows run in the same runtime with independent versions, we must install both workflow assemblies in the GAC and allow the runtime to determine which one it wants at any particular time.

We all admit, the workflow's have to change, not only that, but long running processes subscribe to one verison of the workflow and are intended to finish that way. After all, the runtime cannot determine how to properly transition version 1.0 of your workflow to version 1.1.

But you know what doesn't change? Interfaces. Specifically I'm talking about the interfaces that are used to raise events into the workflow instance, and for the workflow instance to contact external systems (I know you can use WCF with 3.5, but we are only allowed to use up to 3.0 at work, and thankful for that). This was where I found the most "problems" when deploying workflow.

However, it wasn't as if the deployment was hard, it was realizing the exceptions we were getting even though we never changed anything but a version were because of strong rules in place and not problems other than that which the code is delivered.

Workflow is a lot like BizTalk, in the way that it is *very* version dependent. However, BizTalk provides some niceties for binding between versions of send ports, receive ports and orchastrations. If you want that in workflow, you are doing it yourself.

Look forward to part 2 where I talk more about ExternalDataExchange services and Thread Safety. This is a huge topic and very easy to glaze over, but will start causing you more problems as the # of workflows (especially long running) continues to grow.