工作流引擎之Elsa入門系列教學之一 初始化專案並建立第一個工作流

2022-06-15 12:01:26

引子

工作流(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": "https://localhost:5001"
    }
  }
  

修改launchSettings.json

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

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "https://localhost:5001",
      "sslPort": 5001
    }
  },
  "profiles": {
    "ElsaCore.Server": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "https://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>

啟動專案

執行該專案,開啟瀏覽器存取https://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可以看到我們剛剛建立的流程

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

啟動流程

存取https://localhost:5001/code/hello-world即可。

小結

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