Creating the first workflow

A workflow in Flower is a class defining the state of a process (its variables), its initialization procedure, the process flow, the service dependencies and other metadata of a process. These classes are contained by assemblies which are uploaded to the directory. Each assembly may contain multiple workflow definitions. Normally, each enterprise system should have one library containing all its workflows.

Workflow classes

To get started, create a class library project and add Flower.Workflow.dll to the references. You will also need System.Runtime.Serialization to be referenced. Then define a class like the following one:

[Path("FlowerTutorials")]
[DataContract]
class MyFirstWorkflow : Process, IWorkflow<object, object>
{
    public object Initialize(IInitializationContext context, object arg)
    {
        return null;
    }

    public void DefineLocalSets(ISetsBuilder bld)
    {
    }

    public void DefineProcess(IProcessBuilder bld)
    {
    }
}

(This section doesn't cover DefineLocalSets method. For more information on local sets see "Sets and inter-process communication in Flower".)

To be a valid process definition, a class must:
  • Have Flower.Workflow.Process as a base class.
  • Implement Flower.Workflow.IWorkflow<,> interface.
  • Must be data contract serializable.

When the management tool imports an assembly, it looks for IWorkflow<,> interface in every class in the assembly and, if it finds one, the class is assumed to be a process definition, so a workflow entry is created for it. By default, the namespace structure of a class becomes the namespace structure of the workflow, so that a class CompanyName.SystemName.ClassName creates a workflow '/Workflows/CompanyName/SystemName/ClassName'. However, the workflow definition class may be marked with Path attribute to override this behavior. The argument of the attribute specifies a part of the path between '/Workflows' and the type name. So, if the path is an empty string, the workflow path will be '/Workflows/ClassName'; in the example above the path will be '/Workflows/FlowerTutorials/MyFirstWorkflow'; to get a path '/Workflows/CompanyName/SystemName/ClassName' the class ClassName must have Path value 'CompanyName/SystemName' etc.

Variables and services

Instance of a workflow class is a process state. To persist the state, Flower serializes the instance using data contract serializer. So, any field or property marked with DataMember attribute will be preserved during the lifetime of a process while other, not serialized, fields may lost their value suddenly between any of the activities.

Besides the state, fields of a workflow class can keep service references. These fields (or properties) must be marked with Flower.Workflow.ServiceAttribute and must not be serializable. Services are searched by the paths specified in the arguments of the Service attributes and injected by Flower every time a process state is restored (for more information on services see "Services invocation").

It makes sense to declare process variables and services public to allow their initialization in tests.

Process initialization

When the Flower client starts a process, it instantiates the corresponding workflow class using default constructor and calls Initialize method. Then the client persists the instance in the directory. So, in the Initialize method a process has opportunity to construct its initial state.

The following should be kept in mind:
  • Injected services are available during initialization.
  • The initialization is executed in the application domain of the caller.
  • The initialization is called synchronously.

Process definition

The process flow is defined as a chain of Flower.Workflow.IProcessBuilder method calls, but it can be thought off as a program in a special programming language. The following example demonstrates how the bubble sort algorithm could be implemented as a workflow:

[DataMember] public int i;
[DataMember] public int tmp;
[DataMember] public bool swapped;
[DataMember] public int[] array;

...

public void DefineProcess(IProcessBuilder bld)
{
    bld
        .While(_ => true)
            .Exec(_ => swapped = false)

            .For(_ => i = 0, _ => i < array.Length, _ => i++)
                .If(_ => array[i] > array[i + 1])
                    .Exec(_ => tmp = array[i])
                    .Exec(_ => array[i] = array[i + 1])
                    .Exec(_ => array[i + 1] = tmp)

                    .Exec(_ => swapped = true)
                .End()
            .End()

            .If(_ => !swapped)
                .Break()
            .End()
        .End();
}

In the Flower workflow "programming language" each line starts with a dot and contain a keyword followed by a list of expressions (probably empty) in parentheses. An expression is a C# lambda expression the first argument of which is always Flower.Workflow.IContext (in this example and hereinafter the context variable in lambdas is named '_' if it is not used in the expression). Some statements open a block. Each block is terminated by End statement.

The control flow operators (if, while, for) work the same as in C#. The only difference is that instead of an expression you pass lambda.

Conditional statement allows alternative and conditional-alternative paths:

.If(<condition>)
    ...
.Else()
    ...
.End()

.If(<condition>)
    ...
.ElseIf(<condition>)
    ...
.End()

The statements Break and Continue, as in C#, allow to jump out of a loop and skip iteration accordingly.

The Finish statement allows to finish a process immediately.

The statement Exec executes an arbitrary code. It has alternative version ExecAndSave which, unlike Exec, is transactional and persists the process state as finished. You should use ExecAndSave for the actions that you don't want to be repeated in case of system crash or restart after the action but before the next savepoint.

Named activities and breakpoints

Activities are identified by names unique within a workflow. Each activity is named automatically by default, but you can give it a descriptive name by using Name statement.

...
.Name("Some calculations")
.Exec(_ => { ... })
...
.Name("Other calculations")
.Exec(_ => { ... })
...

There is a special type of activities, a brakpoint, the only purpose of which is to mark a place in the code with a named label. You can place a breakpoint by using Breakpoint statement:

.Breakpoint("My breakpoint")

Breakpoints are not emitted by a processor, only by the testing API, so they bring no overhead in runtime. Breakpoints are very useful for demarcating code sections for unit-tests.

...
.Breakpoint("Test start") //Here you initialize the process state.

... //The code to be tested.

.Breakpoint("Test end") // Here you perform assertions.
...

More on workflows

Next

Using these basic constructs you can write a simple workflow and move to the next section: "Testing a workflow".

Last edited Oct 26, 2013 at 11:58 AM by dbratus, version 1

Comments

No comments yet.