elsa 程序化工作流与设计工作流
本质上,Elsa
执行的是 IActivity
对象。这些对象通过编程方式创建或使用设计器。当使用设计器创建时,工作流定义以 JSON
形式存储,并在运行时用于重建实际的 Workflow
对象(实现 IActivity
)。
让我们看看这两种方法之间一些更重要的差异和相似之处。
程序化工作流
程序化工作流通过实例化 IActivity
对象并设置它们的属性来创建。例如,以下代码创建了一个提示用户输入姓名并打印到控制台的工作流:
// 定义一个工作流变量来捕获ReadLine活动的输出。
var nameVariable = new Variable<string>();
// 定义一个简单的顺序工作流:
var workflow = new Sequence
{
// 注册名称变量。
Variables = { nameVariable },
// 设置要运行的活动序列。
Activities =
{
new WriteLine("请输入您的名字:"),
new ReadLine(nameVariable),
new WriteLine(context => $"很高兴认识你,{nameVariable.Get(context)}!")
}
};
输出:
请输入您的名字:
Elsa
很高兴认识你,Elsa!
值得注意的是,我们使用了 Sequence
活动来定义一个顺序工作流。除了 Sequence
之外,Elsa 还支持 Flowchart
活动,允许您通过声明活动之间的连接来将工作流定义为活动图。以下是使用 Flowchart
重新实现先前工作流的示例:
// 定义一个工作流变量来捕获ReadLine活动的输出。
var nameVariable = new Variable<string>();
// 定义要放入流程图的活动:
var writeLine1 = new WriteLine("请输入您的名字:");
var writeLine2 = new ReadLine(nameVariable);
var writeLine3 = new WriteLine(context => $"很高兴认识你,{nameVariable.Get(context)}!");
// 定义一个流程图工作流:
var workflow = new Flowchart
{
// 注册名称变量。
Variables = { nameVariable },
// 添加活动。
Activities =
{
writeLine1,
writeLine2,
writeLine3
},
// 设置活动之间的连接。
Connections =
{
new Connection(writeLine1, writeLine2),
new Connection(writeLine2, writeLine3)
}
};
尽管实现不同,但输出将相同:
请输入您的名字:
Elsa
很高兴认识你,Elsa!
设计器工作流
使用设计器时,通过将活动拖放到画布上并连接它们来定义工作流。底层数据模型与程序化工作流使用的模型相同,并使用 Flowchart
活动。换句话说,使用设计器创建工作流时,您创建的工作流其 Root
属性设置为 Flowchart
活动。
使用设计器创建工作流时,输入值使用脚本表达式定义,而不是 C# Lambda
语句。虽然您可以在设计器中使用 C#
脚本表达式,但不同之处在于 C#
脚本表达式在运行时评估,而程序化工作流中的 Lambda
表达式在构建时编译。
让我们看一个使用 JavaScript
表达式的例子。
以下工作流是使用设计器创建的:
接下来,我们逐一查看每个活动:
活动 1
这是一个 WriteLine
活动。其 Text
属性设置为 请输入您的名字:
。
活动 2
这是一个 ReadLine
活动。该活动没有输入,但其 Output
属性设置为名为 Name
的工作流变量。
此变量在工作流的 Variables
设置中定义:
活动 3
这是一个 WriteLine
活动。其 Text
属性设置为 JavaScript 表达式:Nice to meet you, ${getName()}!\
。
从 JavaScript 访问工作流变量
要从 JavaScript 访问工作流变量,您可以使用以下命名约定:
get{nameOfVariable}()
。例如,要访问Name
变量,您可以使用getName()
。
JSON 表示
让我们从设计器导出工作流并查看其 JSON 结构(已格式化并简化以便阅读):
{
"name": "很高兴认识你",
"variables": [
{
"id": "d6d10d5433e646b9bc02f1d05efeb584",
"name": "Name",
"typeName": "String"
}
],
"root": {
"type": "Elsa.Flowchart",
"id": "Flowchart1",
"start": "WriteLine1",
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "请输入您的名字:"
},
"memoryReference": {
"id": "WriteLine1:input-1"
}
},
"id": "WriteLine1",
"type": "Elsa.WriteLine"
},
{
"result": {
"typeName": "String",
"memoryReference": {
"id": "d6d10d5433e646b9bc02f1d05efeb584"
}
},
"id": "ReadLine1",
"type": "Elsa.ReadLine"
},
{
"text": {
"typeName": "String",
"expression": {
"type": "JavaScript",
"value": "`很高兴认识你,${getName()}!`"
},
"memoryReference": {
"id": "WriteLine2:input-1"
}
},
"id": "WriteLine2",
"type": "Elsa.WriteLine"
}
],
"connections": [
{
"source": "WriteLine1",
"target": "ReadLine1",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "ReadLine1",
"target": "WriteLine2",
"sourcePort": "Done",
"targetPort": "In"
}
]
}
}
几个关键点需要注意:
variables
属性包含了工作流变量。root
属性包含了工作流的根活动,它是一个Flowchart
活动。activities
属性包含了构成工作流的活动。connections
属性包含了活动之间的连接关系。memoryReference
属性用于引用工作流变量。expression
属性用于定义 JavaScript 表达式。typeName
属性用于定义属性的类型。type
属性用于定义活动的类型。id
属性用于唯一标识一个活动或连接。source
和target
属性用于定义连接的源活动和目标活动。sourcePort
和targetPort
属性用于定义连接的源端口和目标端口。
JSON 结构与 C# 数据模型非常相似。主要的区别在于我们使用了 JavaScript 表达式而非 C# 的 Lambda 表达式,并且设计器创建了一个使用其 Root
属性包裹 Flowchart
对象的 Workflow
对象。
总结
在本章中,我们了解了 Elsa 工作流的核心概念。我们已经看到工作流是由活动组成的,这些活动可以相互连接。同时,我们也了解到工作流可以通过编程方式或设计器来定义。
在接下来的章节中,我们将探索更多概念。每当你看到工作流或活动的 C# 表示时,请记住通过设计器创建的 JSON 表示形式非常相似。主要不同之处在于设计器使用 JavaScript 表达式而不是 C# 的 Lambda 表达式,并且设计器会创建一个使用 Root
属性包裹 Flowchart
对象的 Workflow
对象。