Monday, September 03, 2007

This is turning into a hassle. I must confess that I feel that Microsoft does not have a good story on this one!

When thinking about versioning within the realm of workflow, there are a few things you have to know:

  • You will need to use strong signing for your processes, the activities, the External Data Exchange services and the items you put on the queue (we use these to correlate commands to queues, bypassing the weirdness of correlation in WF)
  • What is persisted to the datastore is a blob. That blob is created using serialization surrogates and use the normal binary serialization format. However, because of the surrogates, it is difficult (although not impossible) to touch your workflow instance directly, instead of going through the runtime. The surrogates are there for a reason: the serialization process of a workflowinstance is not a straight-forward process: all the activity contexts have to be serialized as well, as do the dependency properties etc.
  • The blob does not only persist your fields, but persists the complete structure of your running instance, called a template. So all the activities (initialized or not) are in that template.
  • Timers and their delays are persisted in a separate list by the surrogate. So, if your workflow instance is in a delay with 9 days left, this information is written in a timerCollectionList, with a guid pointing to the delay (remember, that delay is instantiated in a particular activityContext). It is not simple to correlate these. They are the main problem when you wish to just update your process.

Microsoft does not offer a smart way to upgrade version 1.0 to 2.0 of your workflow instance. When you have version 1.0 in your database, and make one little change to your process, dehydration will not work because of an index-out-of-bounds exception: remember that the persisted blob has the full template of the instance. So when you changed your process and added or removed an activity anywhere, the dehydration process is trying to map the persisted template to a type in your assembly and fails because of the different activity tree.

Therefor, you can do two things:

  1. Run both assemblies Side by Side
  2. Use workflow changes to change your current version to a 2.0 compatible version. 

Let's start by discussing option 2. Say you have created version 2.0 of your instance and try to rehydrate. Since you strongnamed, the runtime will throw an exception because it can not find your old assembly. You can place an assembly redirect in your config, telling the clr to try to use version 2.0 assemblies to instantiate your 1.0 blob. This will then fail because of that changed structure of your template.
The solution at hand is to use workflow changes to get in there, and change the structure of that 1.0 template to match that of a 2.0 version. You can of course only do this by loading in the old assembly side-by-side, but now you only have to do that once during an update-batch. After that, your normal application is able to use your 2.0 assembly to instantiate your 1.0 (but now structurally modified) instance.

The problem here, is that you have to build big workflow change scripts. I have not yet seen someone automate that (do a diff on the templates and generate the workflow changes). If that were available and rock-stable, this might be a good strategy to take. Until then, it's way too much work. (Let me know if this turns out to be super-simple!)

Option 1 is bad as well. Sure, loading in your old assemblies is possible. But what Microsoft forgot is that I want to change my external data exchange service as well (if only in version number) and the objects that I put in my queue. Since your old 1.0 process is expecting a 1.0 service to talk to, or 1.0 version commands, it will not be able to communicate!! This can be mitigated by adding the 1.0 External service to the runtime when loading the 1.0 assemblies, and maybe only using bcl types on the queues, but it's really a shame to have to do that. Certainly when you have processes that last 5 years, and you have 20 versions to keep up with.....

My advise is to really try to understand the way your application will use workflow. For us, I was able to make these assumptions:

  • There are a few states that need to be monitored by a delay activity of say 20 days.. When after that 20 days our process has not moved out of that state, something needs to happen.
  • Most states do not need that. Therefor, I can actually bring the process to a completed state. That actually is not what I prefer, however, since the state of a process can be derived from my domain objects, I can always construct the process at will and bring it in the correct state (with the new version!!). By completing the processes whenever possible, I will have a much smaller amount of processes to deal with in the datastore.
  • Most importantly: I found out that after processing an external event, the process will always return back into a state. It will never start a delay of more then a few minutes within a sequence. So I can guarantee that my workflow, when persisted, is not waiting inside a while-loop or whatever. All long delays are the first child within an eventdriven activity.

The last point reduces our problem big time, because it would be nearly impossible to build an update for a workflow instance that is waiting in the middle of a sequence. Basically, that is what we will be doing in our project: Build an update batch, that will load version 1.0 instances, kill them, create 2.0 version instances and write back to the database with the same guid.

The steps to build an update batch are:

  1. use workflow tracking or something to write the version of your process to your datastore. We have an oracle persistence layer. When we build it, we constructed a new column 'type' in the database and write the fullname of the type in there (which includes version number).
  2. load your old assembly so you can instantiate the blob
  3. instantiate the blob using reflection to directly get access to your instance
  4. do an export of your fields and other stuff you need. You know your processes intimately, so this should not be a problem
  5. delete the row from the datastore  (remember to start a transaction!)
  6. create a new type, using the runtime and the guid of the old instance
  7. call an import event or whatever, that the process will use to bring itself to the correct state
  8. persist

The hard part are the delays. Basically, you can find the list of timers using reflection. However, it is cumbersome to correlate the guids to the correct delay activities. My solution would be the following: during state changes within your process, keep a dictionary of the statename and the moment (Datetime) you transitioned to it. When importing, use this list and the delay.timeoutdurationEvent to setup your timer: normally, it would be DateTime.Now.Add(timeoutlenght). This time, you will use the original DateTime, and your delay activity will not have been 'reset'.

It's not pretty, and it will be necessary to put constraints on your processes. But it might work just fine for you! Let me know..

Tuesday, September 04, 2007 12:18:32 AM (Romance Standard Time, UTC+01:00)
Hello Ruurd,

When you update a new instance and then move the instance to a certain state, it would pass through State Initialization sequences.
These might send data to the outside world, which could have (unwanted?) side effects.
(Preferrably something with money being transferred multiple times.)

To avoid this create a dummy implementation of the external data interfaces which you only add to the runtime for the import process
(Or make a single version which is smart enough to detect an import is being executed.)
Tracking services require a similar strategy btw.


Best regards,

Peter
PjV
Tuesday, September 04, 2007 7:41:58 AM (Romance Standard Time, UTC+01:00)
Or pass a variable during import which signals the workflow that it is being imported and take appropriate actions (like skipping init).
Ruurd
Wednesday, October 03, 2007 11:48:04 AM (Romance Standard Time, UTC+01:00)
Ruurd,

The way I've got around the versioning problem is to break long running workflows into seperate pieces, and implemented a version aware CallWorkflow activity.

Say you have a workflow that deals with car insurance applications, and so in there is a delay for 11 months and then a email reminder is sent out. You'll have a load of these hanging around and then want to add something after the 'send an email' activity. Rather than putting this in the main workflow, I put it in a second flow.

When the delay fires, I then use my 'CallVersionedWorkflow' activity which does a lookup (in my case within a database) for the next workflow to execute, and then simply calls that workflow.

The upshot is that I can easily set a new version of what happens next by making a quick change to the database. I also use this scheme to find the right workflow to start when I begin an entirely new process - again the database indirection allows me to choose the latest version of a given workflow and execute that.

I'd agree that it's not a perfect solution (but then the problem isn't easy to solve either), but it seems to work well.

Cheers,

Morgan
Tuesday, February 05, 2008 4:31:35 PM (Romance Standard Time, UTC+01:00)
Hey Morgan,

takes me a while to respond ;-)

Yeah, that would work, and I am interested in seeing it actually. Would be great if you'd write an article about it. However, like you say, it is not a perfect solution. You now have to work around deficiencies of WF.
Also, what happens if the new version of your workflow states that it should not be 11 months, but 4??

Still, in some situations, this might work well!
Ruurd
Thursday, December 02, 2010 3:47:42 AM (Romance Standard Time, UTC+01:00)
This article was Very helpful. Actually, I am fond of reading online punjabi news. Thanks for writing such a complete ..
thank you for sharing.
Thursday, December 02, 2010 9:16:34 AM (Romance Standard Time, UTC+01:00)
xiaolaba Only after before a "concept, unlike other principles vile ugg boots on sale and difficult to understand, on the contrary boots uggs on sale, it is simple and easily arouses the child's confidence and high spirits, goals are small but specific ugg boot sale. In this goal, the child without understanding thoughts, solid, conscientious to do with bailey button, abandoned children in a meeting in surface. Small success to classic mini uggs, and gets the others recognized and appreciated, will gradually moving towards greater success mini uggs on sale, you'll make a big career. Words can ruin a man's confidence uggs mini on sale, even burst of his hopes of survival classic mini uggs on sale, But a word can also encourage one from loss to come out, or make one from a new Angle know yourself ugg classic cardy, changed his life. So at any time, we don't save said an encouraging word, give a trusting eyes ugg classic short, do a duck-yard trifle. A person's strength for himself perhaps is very limited ugg classic short navy, but he has probably help stimulate another person's infinite potential ugg boot sale.
Thursday, December 02, 2010 12:42:58 PM (Romance Standard Time, UTC+01:00)
Did you know that Skechers Resistance Runner has come out with a new line of Mens Skechers Resistance Runner? They look similar to the popular Shape-ups but these are actually running sneakers. The new Womens Skechers Resistance Runner retail for about $150. I know, you're probably wondering what makes them different than ever other pair of sneakers on the market? Let me break it down for you. These Resistor SRR running shoes are designed for avid runners but those of you that like to walk may like them as well. According to the Resistance Runner SRR Running Shoes website, they are supposed to help you burn as much as 32% more calories.Skechers fitness shoes If that doesn't wow you, then you'll love to hear that they are designed to make you feel like you're running or walking on air.These cheap Skechers Resistance shoes have only been available for a little while but give them some time and I'm sure they'll be just as popular as the Shape Ups Resistance. But you tell me. Do you think that these Resistance Runners will become a hit? Would you try them? Sound off in the comment section below.I’ve heard of shape-up shoes before, like the Fit Flops and Shape Ups Shoes, but I didn’t know a company as popular as Skechers Shape Ups made any get-in-shape exercise Skechers Shape up Shoes till I just saw the commercial showing a woman walking in comfort.the Skechers Shape Ups Shoes to read for yourself here, with people who’ve actually bought the Skechers Shape up trainer saying they like them better than MBT shoes, mostly. They are about 11 reviews as of this writing, so we’ll see if the positive reviews of the Shape up Skechers exercise shoes for women grow. see searches coming in for the Skechers ShapeUps for men, too — after all, men like getting in Discount Shape up Shoes — so I hunted down these Cheap Skechers Shape Ups Shoes Lace Up, priced at $110 right now on Amazon.Those shape ups outlet for men running? Skechers Womens Shape Ups — I guess you can run in them, huh? — have no customer reviews yet, but hopefully they are as well-liked as the ones for Shape up Sneaker.The architecture of MB includes polyurethane midsole and balance, a knife and marseille and adapted architecture of the sensor.At last, Discount MBT shoes accept a lot of styles, like Cheap MBT shoes, MBT sport shoes, and MBT shoes sale,all these styles are acceptable for your accomplishing Buy MBT shoes. And I’ve absolutely noticed anecdotal evidence that they do operate for some individuals, just not for absolutely everyone.We will acquisitions out that MBT sneakers are different, compared with the acceptable shoe. Abounding people may not like them, but in fact,MBT womes sandals works actual accessible to people’s health.

Comments are closed.