Workflow Core 是一個基於 .NET 的輕量級工作流程引擎,用於建構長期執行的工作流程與業務邏輯,支援分散式系統、事件驅動以及持久化, 可用電子商務訂單處理、自動化任務或複雜事件調度等等工作流程。
特點
先以 Console 專案來實際操作 Workflow Core 的使用,建立好 Console 專案後輸入以下指令安裝 Workflow Core。
dotnet add package Microsoft.Extensions.DependencyInjection
dotnet add package Microsoft.Extensions.Logging
dotnet add package WorkflowCore
以下開始說明程式,拆分成主程式以及各步驟的類別實作,原始碼請參考這裡
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();
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();
}
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();
}
}
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();
}
}
public class ShipItems : StepBody
{
public int Quantity { get; set; }
public override ExecutionResult Run(IStepExecutionContext context)
{
Console.WriteLine($"確定出貨,數量:{Quantity}");
return ExecutionResult.Next();
}
}
開放 WebAPI 給外部程式做存取,以及開啟持久化設定,可以支援外部事件的觸發,範例將實現以下功能,原始碼請參考這裡
// 開啟持久化設定
// 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
來等待簽核事件的觸發,並且需要開啟持久化設定
補充說明
需要開啟持久化設定的原因為工作流程的狀態預設是存在記憶體中,當程式結束後工作狀態會跟著消失。
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();
}
}
踩雷
從原始碼 WaitFor 中可以看到如果執行流程中有事件觸發的等待,必須去判斷事件 context.ExecutionPointer.EventPublished
是否已經觸發
[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);