acts
is a fast, tiny, extensiable workflow engine, which provides the abilities to execute workflow based on yml model.
The yml workflow model is not as same as the tranditional workflow flow. such as bpmn. The yml format is inspired by Github actions. The main point of this workflow is to create a top abstraction to run the workflow logic and interact with the client via act
node.
Every user's action can be regarded as a abstract act. these acts can be generated by some rules. such as for
or catches
.
This workflow engine focus on the workflow logics itself and message distributions. the complex business logic will be completed by act
via the act message.
Uses rust to create the lib, there is no virtual machine, no db dependencies. The feature local_store uses the rocksdb to make sure the store performance.
Running benches\workflow.rs start_workflow time: [842.58 µs 876.99 µs 912.59 µs]
The lib size is only 3.5mb (no local_store), you can use Adapter to create external store.
Supports for extending the plugin Supports for creating external store
The easiest way to get the latest version of acts
is to install it via cargo
cargo add acts
- Start the workflow engine by
engine.start
. - Load a yaml model to create a
workflow
. - Deploy the model in step 2 by
engine.manager()
. - Config events by
engine.emitter()
. - Start the workflow by
engine.executor()
.
use acts::{Engine, Vars, Workflow};
#[tokio::main]
async fn main() {
let engine = Engine::new();
engine.start();
let text = include_str!("../examples/simple/model.yml");
let mut workflow = Workflow::from_yml(text).unwrap();
let executor = engine.executor();
engine.manager().deploy(&workflow).expect("fail to deploy workflow");
let mut vars = Vars::new();
vars.insert("input".into(), 3.into());
vars.insert("pid".to_string(), "w1".into());
executor.start(&workflow.id, &vars);
let emitter = engine.emitter();
emitter.on_start(|e| {
println!("start: {}", e.start_time);
});
emitter.on_message(|e| {
println!("message: {:?}", e);
});
emitter.on_complete(|e| {
println!("outputs: {:?} end_time: {}", e.outputs(), e.end_time);
});
emitter.on_error(|e| {
println!("error on proc id: {} model id: {}", e.pid, e.mid);
});
}
Please see examples
The model uses the yaml file to create, there are different type of node, which is constructed by [Workflow
], [Branch
], [Step
] and [Act
]. Every workflow can have more more steps, a step can have more branches and a branch can have if
property to judge the condition.
The env
property can be set the initialzed vars in workflow
, in the step's run
scripts, you can use env
moudle to get(env.get
) or set(env.set
) the value
The run
property is the script based on rhai script
name: model name
env:
value: 0
steps:
- name: step 1
run: |
print("step 1")
- name: step 2
branches:
- name: branch 1
if: ${ env.get("value") > 100 }
run: |
print("branch 1");
- name: branch 2
if: ${ env.get("value") <= 100 }
steps:
- name: step 3
run: |
print("branch 2")
In the [Workflow
], you can set the outputs
to output the env to use.
name: model name
outputs:
output_key:
steps:
- name: step1
run: |
env.set("output_key", "output value");
Add workflow actions
to create custom event with client
name: model name
actions:
- name: fn1
id: fn1
on:
- state: created
nkind: workflow
- state: completed
nkind: workflow
- name: fn2
id: fn2
on:
- state: completed
nid: step2
- name: fn3
id: fn3
on:
- state: completed
nid: step3
inputs:
a: ${ env.get("value") }
steps:
- name: step1
- name: step2
- name: step3
Use steps
to add step to the workflow
name: model name
steps:
- id: step1
name: step 1
- id: step2
name: step 2
Use branches
to add branch to the step
name: model name
steps:
- id: step1
name: step 1
branches:
- id: b1
if: env.get("v") > 0
steps:
- name: step a
- name: step b
- id: b2
else: true
steps:
- name: step c
- name: step d
- id: step2
name: step 2
Use acts
to create act to interact with client
name: model name
outputs:
output_key:
steps:
- name: step1
acts:
- id: init
name: my act init
inputs:
a: 6
outputs:
c:
There is a example to use for
to generate acts, which can wait util calling the action to complete.
name: model name
steps:
- name: step1
acts:
- for:
by: any
in: |
let a = ["u1"];
let b = ["u2"];
a.union(b)
It will generate the user act and send message automationly according to the in
collection.
The by
tells the workflow how to pass the act. There are several by
rules.
- by
-
all to match all of the acts to complete
-
any to match any of the acts to complete
-
some(rule) to match some acts by giving rule name. If there is some rule, it can also generate a some act to ask the client to pass or not.
-
ord or ord(rule) to generate the act one by one. If there is order rule, it can also generate a rule act to sort the collection.
- in A collection to generate the acts.
The code act.role("test_role")
uses the role rule to get the users through the role test_role
in: |
let users = act.role("test_role");
users
The following code uses the relate
rule to find the user's owner of the department (d.owner
).
users: |
let users = act.relate("user(test_user).d.owner");
users
Use the catches
to capture the act
error and start a new act to run.
name: a example to catch act error
id: catches
steps:
- name: prepare
id: prepare
acts:
- id: init
- name: step1
id: step1
acts:
- id: act1
catches:
- id: catch1
err: err1
- id: catch2
err: err2
- id: catch_others
- name: final
id: final