---
title: '[.NET] 流程引擎 Workflow Core 的碰壁過程'
---
## Workflow Core 簡介
Workflow Core 是一個基於 .NET 的輕量級工作流程引擎,用於建構長期執行的工作流程與業務邏輯,支援分散式系統、事件驅動以及持久化, 可用電子商務訂單處理、自動化任務或複雜事件調度等等工作流程。
特點
* 跨平台:可使用 .NET 進行開發,並且與 .NET 8 兼容
* 事件驅動:支援事件等待,事件觸發後繼續執行流程
* 持久化:支援 PostgreSQL、SQL Server、Redis,來儲存 Workflow 的流程和狀態
## TL;DR
* Workflow Core 為輕量版引擎套件,適用於簡單的流程作業
* 不支援 BPMN 2.0 協議,不利於引擎套件的更換
* 可將流程產出 Json 格式,但支援的可視化工具有限
* 官方文件的範例說明不夠齊全,需以原始碼輔助
## 版本資訊
:::info
* .NET 8
* Workflow Core Version 3.13.0
:::
## 開始使用
### 基本範例
先以 Console 專案來實際操作 Workflow Core 的使用,建立好 Console 專案後輸入以下指令安裝 Workflow Core。
* 套件安裝
```bash
dotnet add package Microsoft.Extensions.DependencyInjection
dotnet add package Microsoft.Extensions.Logging
dotnet add package WorkflowCore
```
* 情境模擬

以下開始說明程式,拆分成主程式以及各步驟的類別實作,原始碼請參考[這裡](https://dev.azure.com/jjaywang1121/Example.Code/_git/Workflow.Core.ConsoleApp)
* Program.cs
```csharp!
using Microsoft.Extensions.DependencyInjection;
using WorkflowCore.Interface;
using WorkflowCore.Models;
var serviceProvider = new ServiceCollection()
.AddLogging()
.AddWorkflow()
.BuildServiceProvider();
var host = serviceProvider.GetService<IWorkflowHost>();
if (host == null) throw new Exception("Fail");
host.RegisterWorkflow<ShipmentWorkflow, ShipmentData>();
host.Start();
Console.Write("輸入數量:");
var quantity = Console.ReadLine();
if (quantity == null || !int.TryParse(quantity, out int parseQuantity))
{
throw new Exception("Fail");
}
var shipData = new ShipmentData();
shipData.Id = Guid.NewGuid();
shipData.Quantity = parseQuantity;
await host.StartWorkflow("ShipmentWorkflow", shipData);
Console.ReadLine();
host.Stop();
```
* 工作流類別 ShipmentWorkflow.cs
```csharp
public class ShipmentWorkflow : IWorkflow<ShipmentData>
{
public string Id => "ShipmentWorkflow";
public int Version => 1;
public void Build(IWorkflowBuilder<ShipmentData> builder)
{
builder.StartWith<CheckInventory>()
.Input(step => step.Quantity, data => data.Quantity)
.Output(data => data.IsInventorySufficient, step => step.IsInventorySufficient)
.If(data => data.IsInventorySufficient).Do(
builder => builder
.If(data => data.Quantity > 100).Do(
builder => builder.StartWith<SupervisorApproval>()
.Output(data => data.IsApproved, step => step.IsApproved)
.If(data => data.IsApproved).Do(
builder => builder.StartWith<ShipItems>()
.Input(step => step.Quantity, data => data.Quantity)
)
)
.If(data => data.Quantity <= 100).Do(
builder => builder.StartWith<ShipItems>()
.Input(step => step.Quantity, data => data.Quantity)
)
)
.Then(ctx => Console.WriteLine("流程結束"))
.EndWorkflow();
}
```
* 官方文件連結
:::success
* [Inputs and Outputs](https://workflow-core.readthedocs.io/en/latest/json-yaml/#inputs-and-outputs)
* [If Conditions](https://workflow-core.readthedocs.io/en/latest/control-structures/#if-conditions)
:::
* 檢查庫存 CheckInventory.cs
```csharp
public class CheckInventory : StepBody
{
public int Quantity { get; set; }
public bool IsInventorySufficient { get; set; }
public override ExecutionResult Run(IStepExecutionContext context)
{
Console.WriteLine($"檢查庫存: {Quantity}");
IsInventorySufficient = Quantity <= 200;
if (!IsInventorySufficient) Console.WriteLine("庫存不足");
return ExecutionResult.Next();
}
}
```
* 主管簽核 SupervisorApproval.cs
```csharp
public class SupervisorApproval : StepBody
{
public bool IsApproved { get; set; }
public override ExecutionResult Run(IStepExecutionContext context)
{
Console.WriteLine("等待主管簽核中...");
IsApproved = true;
var output = IsApproved ? "通過" : "不通過";
Console.WriteLine($"主管簽核:{output}");
return ExecutionResult.Next();
}
}
```
* 出貨 ShipItems.cs
```csharp
public class ShipItems : StepBody
{
public int Quantity { get; set; }
public override ExecutionResult Run(IStepExecutionContext context)
{
Console.WriteLine($"確定出貨,數量:{Quantity}");
return ExecutionResult.Next();
}
}
```
### 搭配 WebAPI 專案
開放 WebAPI 給外部程式做存取,以及開啟持久化設定,可以支援外部事件的觸發,範例將實現以下功能,原始碼請參考[這裡](https://dev.azure.com/jjaywang1121/Example.Code/_git/Workflow.Core.WebAPI)
:::info
* Store workflow in postgres.
* Start or suspend workflow.
* Event trigger in workflow.
* Get specific workflow information.
:::
* Program.cs
```csharp!
// 開啟持久化設定
// UsePostgreSQL(string connectionString, bool canCreateDB, bool canMigrateDB, [string schemaName = "wfc"])
builder.Services.AddWorkflow(x => x.UsePostgreSQL("<connectionstring>", true, true));
// 中間省略...
var host = app.Services.GetService<IWorkflowHost>() ?? throw new Exception("fail");
host.RegisterWorkflow<ShipmentWorkflow, ShipmentData>();
host.Start();
app.Lifetime.ApplicationStopped.Register(() => host.Stop());
app.Run();
```
SupervisorApproval.cs 特別提出簽核流程 API 的處理過程,需要使用 `ExecutionResult.WaitForEvent` 來等待簽核事件的觸發,並且需要開啟持久化設定
> [!Note] 補充說明
> 需要開啟持久化設定的原因為工作流程的狀態預設是存在記憶體中,當程式結束後工作狀態會跟著消失。
* SupervisorApproval.cs
```csharp!
public class SupervisorApproval : StepBody
{
public bool IsApproved { get; set; }
public override ExecutionResult Run(IStepExecutionContext context)
{
if (!context.ExecutionPointer.EventPublished)
{
Console.WriteLine("等待主管簽核中...");
return ExecutionResult.WaitForEvent("Approve", context.Workflow.Id, DateTime.Now);
}
IsApproved = (bool)context.ExecutionPointer.EventData;
var output = IsApproved ? "通過" : "不通過";
Console.WriteLine($"主管簽核:{output}");
return ExecutionResult.Next();
}
}
```
> [!Warning] 踩雷
從原始碼 [WaitFor](https://github.com/danielgerlag/workflow-core/blob/3112482d403226de04caebd27c66842cdca178ec/src/WorkflowCore/Primitives/WaitFor.cs#L7) 中可以看到如果執行流程中有事件觸發的等待,必須去判斷事件 `context.ExecutionPointer.EventPublished` 是否已經觸發
* 觸發簽核事件
```csharp!
[HttpPost("[action]")]
public async Task<IActionResult> Approve(ApproveData approveData)
{
await _workflowHost.PublishEvent("Approve", approveData.WorkflowId, approveData.Approve, DateTime.Now);
return Ok("OK");
}
/// <summary>
/// PublishEvent
/// </summary>
/// <param name="eventName"></param>
/// <param name="eventKey"></param>
/// <param name="eventData"></param>
/// <param name="effectiveDate"></param>
/// <returns></returns>
Task PublishEvent(string eventName, string eventKey, object eventData, DateTime? effectiveDate = null);
```
## 參考資料
* [官方文件](https://workflow-core.readthedocs.io/en/latest/)
* [原始碼](https://github.com/danielgerlag/workflow-core)
## 延伸閱讀
* [Elsa Workflows](https://v3.elsaworkflows.io/)
* [SpiffWorkflow ](https://www.spiffworkflow.org/)
* [WorkflowEngine.NET](https://github.com/optimajet/WorkflowEngine.NET)