Build a workflow¶
Create an activity¶
An activity is a single unit of process that takes inputs and produces outputs. The Activity
interface defines the exact behavior of an activity.
In order to create a new activity, the simplest way is to create a class that extends AbstractActivityWithStateManagement
. This abstract class hides all the complexity related to state, inputs and outputs management as well as error handling. It only requires the sub-class to implement the doRun(Context)
method, which is then called each time the activity is executed.
The following example shows a simple HelloWorld
activity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import fr.kazejiyu.ekumi.core.workflow.Context;
import fr.kazejiyu.ekumi.core.workflow.impl.AbstractActivityWithStateManagement;
/**
* An activity that prints "Hello World!" when run.
*/
public class HelloWorld extends AbstractActivityWithStateManagement {
@Override
public void doRun(Context context) {
System.out.println("Hello World!");
}
}
|
About the execution context¶
As shown in the example above, the doRun
method takes a Context
instance as parameter. This instance represents the context of the execution and can be used, among other things, to:
- set/get global variables
- cancel the execution
The following activity prints different sentence depending on whether the execution has been cancelled:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import fr.kazejiyu.ekumi.core.workflow.Context;
import fr.kazejiyu.ekumi.core.workflow.impl.AbstractActivityWithStateManagement;
/**
* An activity that, when run, prints:
* - "Cancelled" if the execution has been cancelled
* - "Succeeded" otherwise
*/
public class HelloWorld extends AbstractActivityWithStateManagement {
@Override
public void doRun(Context context) {
if (context.execution().isCancelled()) {
System.out.println("Cancelled");
}
else {
System.out.pritnln("Succeeded");
}
}
}
|
Provide inputs and outputs¶
An activity’s inputs and outputs can be accessed through the inputs
and outputs
methods.
The following example shows how to create an AplusB
activity that:
- has two inputs called a and b of type
double
- has one output called c of type
double
- set c to the value of a + b
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import fr.kazejiyu.ekumi.core.workflow.Context;
import fr.kazejiyu.ekumi.core.workflow.impl.AbstractActivityWithStateManagement;
import fr.kazejiyu.ekumi.datatypes.DoubleType;
/**
* An activity that computes its outputs 'c' from the addition of its two inputs 'a' and 'b'.
*/
public class AplusB extends AbstractActivityWithStateManagement {
public AplusB(String id) {
super(id, "a + b = c");
inputs().create("a", new DoubleType());
inputs().create("b", new DoubleType());
outputs().create("c", new DoubleType());
}
@Override
protected void doRun(Context context) throws Exception {
double a = (double) input("a").value();
double b = (double) input("b").value();
output("c").set(a + b);
}
}
|
Inputs and outputs can also be assigned to fields for more convenience:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import fr.kazejiyu.ekumi.core.workflow.Context;
import fr.kazejiyu.ekumi.core.workflow.Input;
import fr.kazejiyu.ekumi.core.workflow.Output;
import fr.kazejiyu.ekumi.core.workflow.impl.AbstractActivityWithStateManagement;
import fr.kazejiyu.ekumi.datatypes.DoubleType;
public class AplusB extends AbstractActivityWithStateManagement {
private Input a;
private Input b;
private Output c;
public AplusB(String id) {
super(id, "AplusB");
a = inputs().create("A", new DoubleType());
b = inputs().create("B", new DoubleType());
c = outputs().create("C", new DoubleType());
}
@Override
protected void doRun(Context context) throws Exception {
double a = (double) this.a.value();
double b = (double) this.b.value();
c.set(a + b);
}
}
|
From lambda expressions¶
Alternatively, the Activity
interface provides factory methods allowing to creates activities from lambda methods:
1 2 3 4 5 6 7 8 9 10 | import fr.kazejiyu.ekumi.core.workflow.Activity;
public class Main {
public static void main(String[] args) {
Activity sayHello = Activity.of(() -> System.out.println("Hello!");
Activity sayIfCancelled = Activity.of(context -> System.out.println(context.execution().isCancelled());
}
}
|
Bind activities¶
Configure execution order¶
The execution order of two activities can be specified through a predecessor/successor relationship. A predecessor is always executed before its successor.
This relationship can be created via the precede
and succeed
methods:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import fr.kazejiyu.ekumi.core.workflow.Activity;
public class Main {
public static void main(String[] args) {
Activity first = Activity.of(() -> System.out.println("First");
Activity second = Activity.of(() -> System.out.println("second");
// 'first' will be executed before 'second'
first.precede(second);
// Can also be written this way:
second.succeed(first);
}
}
|
Bind outputs to inputs¶
Outputs and inputs must be connected in order to share data between activities. An input can be connected to another data by calling the Input.bind(Data) method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import fr.kazejiyu.ekumi.core.workflow.Activity;
public class Main {
public static void main(String[] args) {
Activity add1 = new AplusB("add1");
Activity add2 = new AplusB("add2");
// At runtime, the value of add2's 'b' input
// will be set to the value of add1's 'c' output
add2.input("b").bind(add1.output("c"));
}
}
|
Create a sequence of activities¶
Usually successors and predecessors must be executed sequentially. A Sequence
is a composite activity that owns a root activity and which, when run, executes its root activity then all its successors and the successors of the successors and so on.
One can be created as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import fr.kazejiyu.ekumi.core.workflow.Activity;
import fr.kazejiyu.ekumi.core.workflow.Sequence;
import fr.kazejiyu.ekumi.core.workflow.impl.BasicSequence;
public class Main {
public static void main(String[] args) {
Activity print1 = new Print("print1", "1");
Activity print2 = new Print("print2", "2");
// Create a predecessor/successor relationship,
// Thus ensuring the sequence will execute print1 then print2
print1.precede(print2);
Sequence print1Then2 = new BasicSequence("id", "Print 1 then 2", print1);
}
}
|
Create parallel activities¶
A Parallel Split
is a composite activity that executes its own activities concurrently.
Once can be created as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import fr.kazejiyu.ekumi.core.workflow.Activity;
import fr.kazejiyu.ekumi.core.workflow.ParallelSplit;
import fr.kazejiyu.ekumi.core.workflow.impl.BasicParallelSplit;
public class Main {
public static void main(String[] args) {
List<Activity> concurrentActivities = new ArrayList<>();
for (int i = 0 ; i < 4; ++i) {
Activity concurrentActivity = Activity.of(() -> System.out.println("I run concurrently");
concurrentActivities.add(concurrentActivity);
}
ParallelSplit split = new BasicParallelSplit("id", "Run branches in parallel", concurrentActivities);
}
}
|
Create loop of activities¶
A Structured Loop
is a composite activity that executes another activity until a specified pre or post condition is verified.
One can be created as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import fr.kazejiyu.ekumi.core.workflow.Activity;
import fr.kazejiyu.ekumi.core.workflow.Condition;
import fr.kazejiyu.ekumi.core.workflow.StructuredLoop;
import fr.kazejiyu.ekumi.core.workflow.impl.BasicStructuredLoop;
public class Main {
public static void main(String[] args) {
Condition preCondition = null; // the loop has no pre-condition
Condition postCondition = Condition.of(() -> true); // the loop will end after the first execution
Activity greet = Activity.of(() -> System.out.println("Hello"));
StructuredLoop loop = new BasicStructuredLoop("id", "name", greet, preCondition, postCondition);
}
}
|