工作流引擎之Elsa入門系列教程之一 初始化項目並創建第一個工作流

引子

工作流(Workflow)是對工作流程及其各操作步驟之間業務規則的抽象、概括描述。
為了實現某個業務目標,需要多方參與、按預定規則提交數據時,就可以用到工作流。
通過流程引擎,我們按照流程圖,編排一系列的步驟,讓數據可以按照一定的規則,一定的順序,提交給一定的負責人進行處理,實現帶有時間軸的數據協作。

目前dotnet平台主流工作流引擎有兩個:

輕量級嵌入式工作流引擎。它支援多種持久化方式和並發提供程式,以允許多節點群集,可以編碼或者使用json、xml編排工作流。
這個引擎功能比較簡單,但不適合處理長期工作流(定時任務類型的),隨著執行的次數越來越多,處理速度會越來越慢。
Workflow slow when the count of the execution point more and more #1028

PersistedWorkflow ExecutionPointers exponentially increase in workflow loop. #1030

而且它是非同步的,通過webapi啟動流程後不能實時返回此次流程中step返回的數據,官方更新速度也不太理想,所以不選擇此工作流引擎。


Elsa Core 是一個工作流庫,可在任何 .NET Core 應用程式中執行工作流。可以使用程式碼和可視化工作流設計器來定義工作流。(功能更加全面,附帶可視化流程設計器與流程監控頁面)

本系列文章選擇使用Elsa作為流程引擎,準備介紹此流程引擎的使用與擴展,如何與Abp框架一起使用,集成swagger,一步一步實現一個Demo。

快速開始

我們用vs2022創建一個空的ASP.NET Core Web應用,作為工作流核心服務,包含儀錶盤與流程API。
一步一步添加依賴與配置,並啟動。後續慢慢改造。

初始化項目

創建一個名為ElsaCore.Server的新項目

 dotnet new web -n "ElsaCore.Server"

進入項目文件夾中為項目安裝包

cd ElsaCore.Server
dotnet add package Elsa
dotnet add package Elsa.Activities.Http
dotnet add package Elsa.Activities.Timers
dotnet add package Elsa.Activities.UserTask
dotnet add package Elsa.Activities.Temporal.Quartz
dotnet add package Elsa.Persistence.EntityFramework.SqlServer
dotnet add package Elsa.Server.Api
dotnet add package Elsa.Designer.Components.Web

dotnet add package Microsoft.EntityFrameworkCore.Tools

添加ef tools用於初始化資料庫

Elsa.Activities.Temporal.Quartz可以換成Elsa.Activities.Temporal.Hangfire,後續會講解集成Hangfire和儀錶盤。

上面的Activities是Elsa提供的幾個活動實現,Http就是通過webapi介面形式的、Timers提供定時任務功能、UserTask提供了用戶審批的功能,後續會詳細解釋,並且還有好多其他的Activities,我們還可以自己實現一個新的。

修改Program.cs

using Elsa;
using Elsa.Persistence.EntityFramework.Core.Extensions;
using Elsa.Activities.UserTask.Extensions;
using Elsa.Persistence.EntityFramework.SqlServer;

var builder = WebApplication.CreateBuilder(args);

// Elsa services.
var elsaSection = builder.Configuration.GetSection("Elsa");
builder.Services.AddElsa(elsa => elsa
                    .UseEntityFrameworkPersistence(ef => ef.UseSqlServer(builder.Configuration.GetConnectionString("Default"), typeof(Program)))
                    .AddConsoleActivities()
                    .AddJavaScriptActivities()
                    .AddUserTaskActivities()
                    .AddHttpActivities(elsaSection.GetSection("Server").Bind)
                    .AddQuartzTemporalActivities()
                    .AddWorkflowsFrom<Program>()
                )
                // Elsa API endpoints.
                .AddElsaApiEndpoints()

                // For Dashboard.
                .AddRazorPages();
var app = builder.Build();

app.UseStaticFiles()// For Dashboard.
    .UseHttpActivities()
    .UseRouting()
    .UseEndpoints(endpoints =>
    {
        // Elsa API Endpoints are implemented as regular ASP.NET Core API controllers.
        endpoints.MapControllers();
        // For Dashboard
        endpoints.MapFallbackToPage("/_Host");
    });
app.Run();

添加appsettings.json配置

BaseUrl的埠號要和launchSettings.json中的一致

  "ConnectionStrings": {
    "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=ElsaServer;Trusted_Connection=True"
  },
  "Elsa": {
    "Server": {
      "BaseUrl": "//localhost:5001"
    }
  }
  

修改launchSettings.json

把launchSettings中的iis profiles刪除,埠號改為5001

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "//localhost:5001",
      "sslPort": 5001
    }
  },
  "profiles": {
    "ElsaCore.Server": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "//localhost:5001",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

初始化資料庫

首先生成一次項目,然後執行

dotnet ef migrations add init

會自動創建Migrations目錄。

然後更新資料庫,執行

dotnet ef database update

此時打開SQL Server對象資源管理器可以看到資料庫已經初始化完畢。

創建頁面

新建目錄Pages,創建在該目錄下創建一個_Host.cshtml。

@page "/"
@{
    var serverUrl = $"{Request.Scheme}://{Request.Host}";
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Elsa Workflows</title>
    <link rel="icon" type="image/png" sizes="32x32" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/images/favicon-32x32.png">
    <link rel="icon" type="image/png" sizes="16x16" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/images/favicon-16x16.png">
    <link rel="stylesheet" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/assets/fonts/inter/inter.css">
    <link rel="stylesheet" href="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/elsa-workflows-studio.css">
    <script src="/_content/Elsa.Designer.Components.Web/monaco-editor/min/vs/loader.js"></script>
    <script type="module" src="/_content/Elsa.Designer.Components.Web/elsa-workflows-studio/elsa-workflows-studio.esm.js"></script>
</head>
<body>
<elsa-studio-root server-url="@serverUrl" monaco-lib-path="_content/Elsa.Designer.Components.Web/monaco-editor/min">
    <elsa-studio-dashboard></elsa-studio-dashboard>
</elsa-studio-root>
</body>
</html>

啟動項目

運行該項目,打開瀏覽器訪問//localhost:5001/,頁面如下所示:

第一個HTTP Endpoint工作流

我們先定義一個簡單的工作流,後續會實現啟動參數與返回特定格式數據的流程。

定義工作流的方式有兩種,使用設計器和程式碼。設計器定義的好處是可以在運行時動態添加與修改流程,並且是直接在流程圖上進行修改,但是只能使用已註冊的Activity,如果業務需要自定義Activity,則還是需要先寫一些程式碼。

通過流程設計器定義

新建流程

選擇菜單中的Workflow Definitions,進入工作流定義頁,點擊Create Workflow創建一個新的工作流。

點擊Start,然後選擇Http裡面的HTTP Endpoint創建一個介面用來做為流程的入口。

設置參數並保存

  • Path: /design/hello-world
  • Methods: GET

接下來設置該介面的返回值。在流程的Done節點下點加號,選擇HTTP裡面的HTTPResponse,設置參數並保存:

  • Content: <h1>Hello World! </h1><p>這是通過設計器實現的流程</p>
  • Content Type: text/html
  • Status Code: OK

設置流程名稱,點擊右上角的設置按鈕,設置Name為hello-world-design,Display Name為hello-world by design

點擊右下角的publish發布流程。此時返回到Workflow Definitions中可以看到剛剛定義好的流程。

啟動流程

因為hello-world-design這個流程是由HTTP Endpoint作為起點,所以我們可以通過介面來啟動該流程。
訪問hello-world-design可以看到如下效果

此時我們點擊Workflow Instances
可以看到剛剛執行的工作流實例,點擊進入可以看到流程執行的詳細過程。

使用程式碼定義

我們通過程式碼的方式實現上述流程。

新建流程

新建一個Workflows目錄用於存放工作流。

創建一個類名為:HelloWorldWorkflow,並實現IWorkflow介面。具體程式碼如下:

using Elsa.Builders;
using Elsa.Activities.Http;

namespace ElsaCore.Server.Workflows
{
    public class HelloWorldWorkflow : IWorkflow
    {
        public void Build(IWorkflowBuilder builder)
        {
            builder.HttpEndpoint(setup =>
            {
                setup.WithMethod(HttpMethod.Get.Method).WithPath("/code/hello-world");
            })
           
                .Then<WriteHttpResponse>(setup =>
                {
                    setup.WithContentType("text/html")
                    .WithContent("<h1>Hello World! </h1><p>這是通過程式碼實現的流程</p>")
                    .WithStatusCode(System.Net.HttpStatusCode.OK);
                });
        }
    }
}

因為我們在Program.cs中配置Elsa的時候使用了AddWorkflowsFrom<Program>(),所以會自動掃描目標類所在的程式集下所有實現IWorkflow介面的工作流自動註冊。
否則需要調用AddWorkflow<HelloWorldWorkflow>()手動註冊流程。

查看流程

啟動項目並點擊Workflow Registry可以看到我們剛剛創建的流程

點進去可以看到流程圖,但因為是程式碼實現的所以是只讀。

啟動流程

訪問//localhost:5001/code/hello-world即可。

小結

本次我們創建了一個新項目,引入了一些Elsa相關的包,完成了工作流服務+圖形化工作流儀錶盤。創建了一個簡單的工作流,但是這樣是遠遠不夠的,我們需要更加複雜的工作流,比如自定義參數、不同參數返回不同結果,模擬一些真實的業務場景,慢慢熟悉此框架,應用到真實的業務場景中,將在後續文章中體現,未完待續…