Trigger A message that tells the workflow how to change states. If the state is “Phone Ringing” and the trigger is “Answer Phone” the new state for the phone would be “Off hook”. StateConfig A StateConfig defines a pathway or transition from one state to another. It is comprised of a State, the Trigger and the Target State. In the course of your workflow you may have many of the same type of steps differentiated by date and time.
In other words, when you workflow has looping capability, the workflow step for a state may be issued many times. Answer The Step asks a question, waiting for the user response. The answer the user provides is the trigger that will start the transition from one state to another. The Answer becomes the Trigger that will change the State. Workflow A series of Steps compromised of States, Triggers and their respective transition expressed as a series of State Configs. Workflow Instance The Workflow Instance is a running workflow. The Steps of the Workflow Instance are governed by how the Steps are defined by a Workflow. Essentially a framework for providing an extensible workflow system boils down to answering the following questions asked in this order: • Is the user authorized to provide an Answer to trigger a change to the step’s State?
• Is a special data set required for this particular State that is not part of the Step properties? • Is the data provided from the user sufficient / valid for triggering a transition in the Workflow Step’s State? • Are there actions to be performed such as saving special data? • Can the system execute custom actions based on the State’s Trigger? This looks very similar to the Pipe and Filter pattern. Every time a workflow processes a trigger, the questions we asked above must be answered. Each question could be considered a filter in the pipe and filter scenario. The five questions above become the basis for our workflow processor components. For this post we’ll assume that all data will be simply fetched then saved with no special processing. We’ll also assume that a Workflow Step is considered to be valid when the following elements are correctly supplied: Our Workflow Processor will function in accordance with the Pipe and Filter pattern where no matter what type of workflow instance we wish to process, the questions that we listed above will be answered. Later we will return to discuss points of where the workflow can execute actions respective to the workflow’s definition. In other words, we want to process an actual workflow, but how will we know the workflow type and what to do? Some of configuration steps were previewed in Simple Workflows With ApprovaFlow and Stateless and the same principles apply here with the Configure method. Collect the States, the Triggers and the StateConfigs, load them into Stateless along with the current state and you are ready to accept or reject the Trigger for the next State. The Workflow Processor will conduct these steps and here is the code: The Workflow Processor will need to know the current state of a workflow instance, the answer supplied, who supplied the answer, as well as any parameters that the filters will need fetching special data. This will be contained in the class Step.cs: Our goal with the Workflow Processor is to accept the users answer, process actions, and create the next Step base on the new State all in one pass. We will create a pipeline of actions that will always be invoked. Each action or “filter” will be a component that performs and individual task, such as determining if the step is answered by the correct user. Each filter will point to the subsequent filter in the pipeline, and the succession of the filters can change easily if we see fit. All that is needed is to add the filters to the pipeline in the order we want. Here is the class schema for the Pipe and Filter processing: We’ll quickly find that the information regarding whether the result of an action or the condition of a Step will need to be accessible to each of the filters. The class Step is the natural place to store this information, so we will include a property CanProcess to indicate that a filter should be invoked, as well a List to act as an error log. This log can be passed back to the client to communicate any errors to the user. Note that the Step class has the Dictionary property named “Parameters” that allows a filter to pass data on to next filter in the sequence. The sequence of filter execution is controlled by the order that the filters are registered. The class Pipeline is responsible for registering and executing the chain of filters. Here is the method Register that accepts a filter and retains it for future processing: We also record the name of the filter so that we may interrogate the pipeline should we want to know if a filter has already been registered.
Pipeline.Register returns a reference to itself, so we can chain together commands fluently: The class FilterBase is the foundation of our filter components. As stated earlier, each component will point the subsequent filter in the filter chain. You’ll note that the class also has a Register method. This takes on the task of point the current filter to the next, and this method is called by the Pipeline as it registers all of the filters. Here is FilterBase: The method Execute accepts input of type T, and in the Workflow Processor instance this will Step. Basically the Execute method is a wrapper, as we call the abstract method Process. Process will be overridden in each filter, and this will contain the logic specific to the tasks that will be performed. The code for a filter is quite simple: Here we check to see if we can process, then perform specific actions if appropriate.
Given that the filters have no knowledge of each other, we can see that they can be executed in any order.