Jon Flanders, PluralSight
July, 2008
Applies to:
- 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.
SequentialWorkflowActivity
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.
.jpg)
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.
.jpg)
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.
.jpg)
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).
StateMachineWorkflowActivity
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.
.jpg)
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).
.jpg)
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.
.jpg)
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.
.jpg)
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).
Summary
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.