How to Choose Your Workflow Model in WF
Jon Flanders, PluralSight
- Microsoft Windows Workflow Foundation
- Microsoft .NET Framework 3.0 or later
Different applications require different execution models. Some programs are quick to execute, while others may take weeks or months. Some programs have very specific steps for execution with little or no room for deviation from its set path; other programs are more flexible, reacting to user actions to make decisions, changing execution paths depending on context.
Windows Workflow Foundation (WF) is a programming model, set of tools, and runtime environment that allows you to write declarative workflows on the Windows platform to represent the execution model of your programs. The WF runtime is part of the .NET Framework, first appearing in .NET Framework 3.0, with improvements in .NET Framework 3.5 to more naturally integrate with Windows Communication Foundation (WCF).
WF programs typically model a process made up of work steps known as activities, which are implementations of the Activity type (specifically, System.Workflow.ComponentModel.Activity). To create WF workflows, we configure one or more activities provided with WF, usually along with custom activities, and connect them in some configuration that creates a workflow that expresses this process and satisfies the needs of our particular application.
At run time, the WF execution model also centers on the Activity type. The execution engine of WF, the WorkflowRuntime, knows only about the Activity type: it knows how to create, execute, and manage the lifetime of Activity instances. Conceptually, this Activity is expressed as either simple activities to execute simple tasks or composite activities to execute more complex tasks.
Simple activities (often referred to as leaf activities) can do things like execute arbitrary code, write data to a database, send an e-mail, or run a rule set. Composite activities are activities that don’t have a specific execution behavior, but rather execute their children in a well-known manner, controlling the flow of the workflow. A simple example could be a composite activity that executes two children: a child that sends an e-mail, and another that writes a file to the file system. In most cases, we create and run composite activities.
WF itself ships with many composite activities, all of which can be executed by the WorkflowRuntime directly. It also ships two special composite activities, which are often called “Root” models. These two activities are SequentialWorkflowActivity and StateMachineWorkflowActivity.It is important to note at this point that, although these two activities are known as “Root” workflow models, the WorkflowRuntime itself has no special affinity for these two activities over any other Activity. In fact, to the WorkflowRuntime all activities are the same, which is why I have placed “Root” in quotes. We’ll discuss this concept again later in this article, but the main focus of this article is to help you decide the question, “What Root workflow model is right to solve my problem?” This article assumes you have some basic knowledge of WF.
The first of the two “Root” models that ships with WF is named SequentialWorkflowActivity (which we’ll refer to as SequentialWorkflow for the rest of this article). For most developers, SequentialWorkflow is the easier of the two “Root” models to understand. Simply put, the SequentialWorkflow executes all of its children, one at a time, in sequential order from the beginning to the end of the workflow, much like a function that executes lines of code in the order they appear.
Figure 1: A SequentialWorkflow
Figure 1 represents an example SequentialWorkflow. Of the activities that make up the workflow, the green activities in the workflow are the “simple” or “leaf” activities, activities that execute some functionality important to our application. Notice that there is at least one flow-control Activity, the built-in IfElseActivity, which allows our workflow to conditionally execute different child branches of the IfElseActivity based on its condition evaluating to true or false.
Understanding the actual problem this workflow is meant to solve isn’t necessary to understanding how it executes. We can break down this workflow into its possible execution paths. We know, based on the design of this workflow, that there are two possible execution paths.
One potential path executes the first leaf Activity and the condition of the IfElseActivity evaluates to false, so the right branch of the IfElseActivity executes. This branch has no activities as children, so we get to the end of the workflow. There are no more children activities left to execute, so the workflow completes. You can see this in Figure 2.
Figure 2: Execution path one.
The other potential path executes the first leaf Activity and the condition of the IfElseActivity evaluates to true. This causes the left branch of the IfElseActivity to execute, which has another Activity (a “leaf”) which then executes. After this Activity completes, the branch has no more children to execute, and returns control to its parent, the SequentialWorkflow itself. The SequentialWorkflow has no other children to execute, so it completes. This path is highlighted in Figure 3.
These two execution paths of the workflow were predefined at design time. That is the essence of the SequentialWorkflow model: it has a top-to-bottom execution path, with all the potential execution paths dictated by the design of the workflow itself.
Figure 3: Execution path two.
The typical reason for picking the SequentialWorkflow as your “Root” model is that you want the workflow to be able to dictate the execution path of the application. This model sets all of the potential execution paths of the workflowat design time, providing the application with no choice at runtime but to follow these paths, removing such situations as an Activity at the bottom of the workflow to cause the workflow to go back to the first “leaf” Activity. But activity execution is not required to occur in one time-slice.
WF is built to natively support both short-lived and long-running workflows, and the SequentialWorkflow model supports both as well. It is easy to think of a SequentialWorkflow as a function that executes from top to bottom in one time-slice, but in reality the SequentialWorkflow can “pause” and “restart” based on conditions in the workflow and in the application, unloading and loading the workflow instance from memory, and can be triggered by time delays or events.
In the workflow in figures 2 and 3, the workflow is specifically written to wait for potentially long periods of time for either the first or second approval. The “leaf” activities for this workflow (DoWork and DoMoreWork) implement the IEventActivity interface, which classifies them as what is generally called Event activities—activities that are built specifically to wait for messages from the host application. These activities can cause the workflow to pause and even be unloaded from memory while it is waiting for the incoming message (assuming you have configured a PersistenceService with your WorkflowRuntime).
The other “Root” workflow model that ships with WF is the StateMachineWorkflowActivity (which we’ll call StateMachineWorkflow for simplicity). In contrast to the SequentialWorkflow, the StateMachineWorkflow has a much more flexible and event-guided execution model.
The StateMachineWorkflow is modeled as a collection of discrete named states, having one active state at a time. These states can contain one or more activities that derive from StateActivity. ,. When a state is the active state, it can listen for one or more events to be raised when it is modeled using an Activity that implements the IEventActivity. After an event is raised (usually by a message sent by the host application), a sequence of one or more activities can be executed to respond appropriately. As part of the sequence, a SetStateActivity is usually executed to transition the workflow from to another state. Each state can also contain a StateInitializationActivity and aStateFinalizationActivity, which act like a state constructor and destructor respectively.
The lifecycle of the StateMachineWorkflow is marked by how it starts and how it ends. When a StateMachineWorkflow instance starts, it immediately transitions to the state designated as the “Initial” state. When the StateMachineWorkflow instance transitions to the “Completed” state, this will cause the instance to complete.
You would choose to implement a StateMachineWorkflow if you need flexibility in the execution of your workflow. It is generally thought that the StateMachineWorkflow model is appropriate when the application or the users of the application determine how the workflow executes at run time. Since states can be transitioned to and from again, the workflow can execute the activities in a StateMachineWorkflow repeatedly.
Figure 4 demonstrates a simple StateMachineWorkflow.
Figure 4: StateMachineWorkflow
In this example, the MyInitialState is the “initial” state, which is indicated by the green icon next to its name (configured by setting the InitialStateName property of the StateMachineWorkflow). Inside of this state, there is a single child, the StateInitializationActivity, and an arrow from the stateInitializationActivity1 StateInitializationActivity, indicating that the workflow is being transitioned to the ChildState using a SetStateActivity. This illustrates that, although a StateMachineWorkflow is generally a wait-and-execute type of model, it can transition between states without messages being received.
The StateMachineWorkflow can also implement a concept known as recursive workflows. This is demonstrated in our example above: the ChildState state is contained inside of the ProcessingState. When a recursive state is implemented, the parent state is listening for events (which might be things like meta-events) at the same time as the child, although the parent state can never be the “active” state, and only one child at a time can be active.
And whenever a child is active, the parent event is also active and listening. In this example, the ProcessingState has one event ( ListenForCancelEvent), which serves as a meta-event to handle the more generalized events that are of interest to the child events—in this case, listening for a signal from the application that the document approval should be cancelled. It would be possible to extend this example to have multiple child states inside of ProcessingState, but the state contains only the ChildState state to keep it simple. In addition to listening for the cancellation event, the ChildState state also listens for the ListenForMainEvent event to signal that work is to be done.
The ChildState state has two transition arrows, one that goes to the CompleteState, and one that recursively returns back to the ChildState state. This means that the state can transition along one of these two paths when the state is signaled to change by SetStateActivity. The CompleteState has been configured as the “Completed” state on the workflow (configured by setting the CompletedStateName property of the StateMachineWorkflow). Inside of the ListenForMainEvent, we can assume there is a set of activities that, when executed, determines from the incoming message whether to complete the workflow. If the answer is no, the SetStateActivity sets the state to ChildState again; if the current approval is sufficient, the SetStateActivity will set the state to CompleteState.
If the workflow transitions to the CompleteState state, the StateMachineWorkflow knows that it is done, and it completes and notifies the WorkflowRuntime. If the workflow transitions along the recursive transition, the workflow transitions back to the ChildState again, which causes the active state of the workflow to continue to be the ChildState, and the workflow will be waiting for the ListenForMainEvent event again (as well as the ListenForCancelEvent because of the recursive states).
As you can see, the StateMachineWorkflow model provides a lot more flexibility at run time than the SequentialWorkflow model, allowing you to create as complex of a workflowas is necessary to support your modeled process. The StateMachineWorkflow will generally be chosen over the SequentialWorkflow model when the workflow requires either flexibility at run time in your application or when your application can be more naturally modeled as a set of states.
Custom Root Models
Another point that must be made about WF “Root” models is that the two models provided with WF are not the only “Root” models that WF supports. The Windows Workflow Foundation WorkflowRuntime processes only instances of the Activity class, and doesn’t know anything at all about either the SequentialWorkflow or StateMachineWorkflow. It knows how to create, initialize, execute, and manage the lifetime of an Activity instance. As composite activities of the base Activity class, SequentialWorkflow and StateMachineWorkflow contain all of the necessary logic about their respective execution models to execute as these two models.
SequentialWorkflow and StateMachineWorkflow are, therefore, two composite Activity models that are intended to cover a large percentage of application workflow scenarios. WF makes it clear that those two models might not cover all application scenarios, and allows for the creation of other custom composite activities. To make a custom composite Activity your “Root” model, all you have to do is decide that it will be your “Root;” there aren’t any special interfaces or any special configuration that is necessary for your composite Activity to be the “Root” model.
As an educational exercise, I once created a “Root” Sequential model that executes the child activities in a random order. While this is totally useless in the real world, it does illustrate the flexibility and extensibility of the WF model.
But there are real-world examples of when you might want to create a custom “Root” activity. One example that I like to use is a case where we needed to unify disparate, event-driven processes into a larger, much more deterministic process. In this case, the StateMachineWorkflow model was the natural model for the individual business processes, but they needed to be unified into a coherent end-to-end process (or multiple coherent end-to-end processes).
Thiswas accomplished by creating a custom “Root” Activity that is a Sequence, executing a design-time workflow from top to bottom, but where the “leaf” activities are actually StateMachineWorkflow instances, servicing the event-driven individual processes.This isn’t supported out of the box because there is no way to configure a StateMachineWorkflow as a child of a SequentialWorkflow, but it is possible to create such a model with a little coding because the WorkflowRuntime has no hardcoded notion of either the sequential or state machine execution models.
Sample Scenario: Document Workflow
Let’s imagine a real application scenario and apply what we’ve learned so far. Bob, a developer at Contoso, is tasked with building an application that executes a document workflow. After meeting with the business owners and gathering the requirements, Bob goes back to his desk and models the workflow using Visio (shown in Figure 5).
Figure 5: Document approval workflow in Visio
The business rules for this application (agreed to in the meeting that Bob has just attended) are that after a document is received to be processed, the program is to wait for an approval from a manager. The manager has the option of asking for another manager to confirm the approval of the document. So the flow of the workflow will be either (a) to wait for one approval and then be done, or (b) to get the first approval, wait for another approval, and then be done.
Bob has decided to use WF to implement the logic of this application, so he goes about picking the “Root” model he wants to use. Since the paths of his application logic are well defined, and it seems like the more natural model to him based on his experience, he picks the SequentialWorkflow model for this “Root” model.Figure 6 shows what his workflow designer in Visual Studio looks like for the project.
Figure 6: Sequential Document Workflow
This will execute exactly as the SequentialWorkflow example introduced earlier did. After the first approval activity executes, one of the two paths of the IfElseActivity will execute. If there needs to be an additional approval, the left branch will execute the workflow, will wait for another approval message, and, once that message is received, the workflow will complete. If there isn’t a need for another approval, the workflow will execute the right branch of the IfElseActivity, which is empty, and then the workflow will complete.
Bob completes this model, deploys his application, and documents start passing through his system. In a short amount of time, however, the business owners come to Bob and ask for a small change. Now they would like to have one, two, or three approvals. Returning to his desk and looking at the designed workflow, Bob could add another IfElseActivity to his SequentialWorkflow model, or add a looping Activity like While to his design, but he decides he should create something with a little more flexibility.
He decides to rework his design into a StateMachineWorkflow, which you can see in Figure 7.
Figure 7: StateMachine Document Workflow
By changing his workflow to use the StateMachineWorkflow model, Bob has introduced slightly more complexity, but the model is generally more flexible and capable of adjusting to the user needs. Now he can place the necessary application logic inside of the ListenForApproval EventDrivenActivity, which can handle changes in the business, instead of having to change the workflow, creating a complex chain of nested IfElseActivity activities. This model is more reactive to the application, rather than controlling the application (like the SequentialWorkflow model).
Once you’ve made the decision to adopt WF inside of your application, the next decision you need to make is what kind of “Root” workflow you’re going to use.
WF ships with two “Root” models, the SequentialWorkflow and the StateMachineWorkflow. These two models cover most if not all of your “Root” model choices (again, “Root” is in quotes because the WorkflowRuntime has no notion of a “Root;” to the WorkflowRuntime, an Activity is an Activity). As a general rule, you can think of the SequentialWorkflow model as being appropriate for deterministic processes, where the workflow is more constrained and dictates the next action, and the StateMachineWorkflow model as being appropriate for event-driven processes that allow the user or the application to dictate what the workflow does next.
Additionally, if you find that neither of the two models provided with WF fit your needs in terms of execution model, remember that you can also easily build your own composite Activity model and use that as your “Root.”This is part of the extensibility of WF that makes it a long-term viable platform for building applications.