--- 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://hackmd.io/_uploads/Bki-1NR8Je.png) 以下開始說明程式,拆分成主程式以及各步驟的類別實作,原始碼請參考[這裡](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)