Scott Bellware discusses some interesting things here regarding transactions and extensibility hooks and even considering Ruby on Rails as a web front end. I'll ignore the Ruby thing, but I do want to talk about his comments on transactions and workflows.
If I understand right, Scott is basically providing a way to extend his application through user-defined workflows that are executed at specific and controlled extensibility points. This is a very cool scenario for WF that enables very interesting possibilities.
His concern, however, seems to center around controlling what happens when one of those user-defined workflows fails, to ensure the entire system is not left in an inconsistent state. This is certainly a valid concern, but it is one that I feel isn't quite as simple to solve as simply "use transactions".
Let's take WF out of the picture for a moment and assume we were using a good old code-based extensibility model (or even a script-based one). Even with the help of System.Transactions and distributed transactions, there's really no way to guarantee that whatever code users of the system put into the extensibility hooks would still work correctly in the event of failure. This revolves around the fundamental fact that there's no guarantee that whatever is put in there is even transaction-aware. Granted, there's a big chance that code put in there is indeed transaction aware, but that's only because a lot of what comes directly into the core .NET framework is tightly integrated in itself. Indeed, for all you know, the user might have even explicitly bring out his operations out of the transactional context (for example using "Enlist=false" in his connection strings).
So even if you wrapped your extensibility hooks with a transaction scope built with System.Transactions, there's no guarantee that a transaction rollback will really bring the system to a consistent state. That's a constant danger with extensible systems, and one that it is not easily addressed. If you've noticed, most framework facilities focused on extensibility only concern themselves with the security problems, but not with extension behavior in the general sense.
This fundamental issue doesn't really get any better just because your extensions are now written as workflows. Indeed, it can become a bit more complicated. To me, using System.Transactions with WF Workflows used in this matter doesn't really make much sense, for several reasons:
- Yes, WF supports System.Transactions (indeed this is what a TransactionScope activity uses underneath), but the same danger exists of someone using an activity that's not transaction-aware.
- Wrapping the entire workflow in a System.Transactions transaction limits substantially what the user can do with the extension workflows. Are you willing to restrict your users to running only short-lived, simple workflows?
The second one is to me the most significant one, because putting that restriction in place limits substantially the power of WF for these scenarios, particularly when you allow workflow persistence and tracking as part of your application extensibility.
The one benefit that WF gives you over code-based extensibility for handling errors is indeed mentioned by Scott: Compensations. This is a very powerful mechanism, particularly for long running workflows, and can certainly be leveraged by extension workflows to put the system back into a consistent state.
In the end, just like with code-based extensibility, you still need to put guidelines in place to make sure users extending your system do so in such a way that they don't jeopardize the behavior and consistency of the entire system, but even with WF, it is still hard to put those guidelines in executable form.
In some cases, however, WF can indeed make it easier, if you're willing to live with some restrictions. For example, here are some ideas to think about to improve your chances:
- Restrict the vocabulary: WF can be extended through the use of custom Activities. Ideally, you will have devised a set of custom activities specific to your problem domain that the users can use to compose their extension workflows. If you're willing to restrict users a bit, and your activity set is complex enough, consider putting restrictions in place so that users can only compose extension workflows using a set of activities you've "approved" (those on your own set and some basic flow control ones, for example). This won't be a perfect model, but can improve the chances that users don't extend the system incorrectly, and can easily be done.
- Use validators: Take advantage of WF's ActivityValidators to enforce rules on your extension workflows. If you've also restricted the vocabulary, you can certainly take advantage of domain=specific knowledge to write the validation rules. For example, if you've got an UpdateXXX activity, you could validate that it always is used within a TransactionScope activity.
- Create templates your users can use to get started writing extension workflows. Ideally, make these into top-level workflow activities your users can derive from. So, for example, you could have users create a workflow derived from InsuranceExtensionWorkflowActivity instead of a simple SequenceWorkflowActivity, Your base workflow could also have custom validators enforcing, for example, that a global compensation flow is implemented by the users (if necessary).
None of these are foolproof, but they can improve the odds in your favor when used correctly.