Dapr Workflow構建塊的.NET Demo

張善友發表於2023-02-18

Dapr 1.10版本中帶來了最有亮點的特性就是工作流構建塊的的釋出,雖然是Alpha 階段,可以讓我們儘早在應用系統中規劃工作流, 在使用Dapr的系統中更好的編寫負責的分散式應用系統。Dapr 工作流使你能夠生成跨多個應用的長時間執行的持久程式或資料流。 Dapr 工作流可以與其他 Dapr API 構建基塊結合使用。例如,工作流可以透過服務呼叫呼叫另一個服務、觸發繫結或檢索機密,從而使您能夠編排和構建複雜的應用程式方案。

Dapr 工作流構建塊的提案早在2022年的5月份提出,計劃在 Dapr Runtime 中構建工作流引擎,社群反映非常積極。這個提案(https://github.com/dapr/dapr/issues/4576)內容比較長 ,敖小劍 有一個翻譯的中文版:https://cn.dapr.io/post/202206-dapr-workflow-proposal/。在Alpha版本里完成了以下內容:

  • 內建工作流引擎將作為初始預覽功能提供 ,這個內建工作流引擎是Azure 上的 持久任務框架 (DTF),在在Azure 基礎設施有大量的應用,它是用.NET 編寫的。Dapr團隊正在把這個實踐抽象成工作流構建塊,基於Dapr Actor構建塊來實現。 Beta 版本會實現 Logic Apps 作為工作流引擎,當然還有更多的工作流引擎可以整合進入Dapr Workflow構建塊裡。
  • API 將支援 3 種方法(啟動、停止、獲取狀態)的工作流管理
  • API 將作為 alpha 版釋出
  • 內建引擎的 API 支援
  • 創作 SDK 將支援:.NET
  • 管理 API SDK 將支援:.NET
  • 文件,詳細的介紹了Dapr Workflow構建塊的工作原理等,推薦大家詳細的看一看:https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-overview/
  • 程式碼示例(.NET ),快速入門文件裡包含了。

Dapr 工作流構建塊目前是alpha版本,自然是有很多限制的,不推薦應用於生產環境中,至少等到beta版本時再考慮應用於生產,通常應用於生產環境的元件都是stable 版本的。Dapr Workflow 構建塊讓我們看到了他的潛力,Dapr Actor 模組基本上.NET圈子用的多,在其他java,go等用的很少,工作流模組組合上 Actor 構建塊會讓Dapr 上一個臺階,和其他類似框架的領先度進一步加大。我們常說“ 技術總是在短期內被高估,但是在長期又被低估。” 再來看看這句話,短期內被高估是因為在短期內的需求所迫,長期內被低估是因為技術的隱形需求要比實際上的業務務求多得多。

作為開發者,你需要考慮你將要投身的技術領域長期的隱形成本與試錯成本。再者,投入到某個技術領域中其實不能走馬觀花,而要長期以往的進行深入研究與學習,才能獲取更高的技術視野。Dapr 作為雲原生時代的開發框架值得投入進去深入研究和學習。

在Dapr的 components-contrib 倉庫裡 也有一個 https://github.com/dapr/components-contrib/tree/master/workflows ,其中包含了一個分散式排程框架

Temporal https://github.com/temporalio/temporal一個元件實現。

現在 可以使用 .NET SDK 體驗 Dapr 工作流,在Dapr 工作流的快速入門文件裡包含了.NET 的示例,建立一個簡單的控制檯應用程式來演示 Dapr 的工作流程式設計模型和工作流管理 API。image

控制檯應用order-processor 啟動並管理在狀態儲存中儲存和檢索資料的工作流的生命週期。工作流OrderProcessingWorkflow 由四個工作流活動或任務組成:

  • NotifyActivity:利用記錄器在整個工作流程中列印出訊息
  • ReserveInventoryActivity:檢查狀態儲存以確保有足夠的庫存供購買
  • ProcessPaymentActivity:處理和授權付款
  • UpdateInventoryActivity:從狀態儲存中刪除請求的物料,並使用新的剩餘庫存值更新儲存

快速入門儲存庫中提供的示例程式碼。倉庫地址: https://github.com/dapr/quickstarts.git ,我們把它克隆到本地,進入到示例程式碼目錄 order-processor:

cd workflows/csharp/sdk/order-processor

在終端中,與 Dapr Sidecar 一起啟動訂單處理器應用:dapr run --app-id order-processor dotnet run ,下面是一次完整執行的輸出:

❯❯ order-processor git:(master)[docker-desktop] 15:05 dapr run --app-id order-processor dotnet run
Starting Dapr with id order-processor. HTTP Port: 1599. gRPC Port: 1600
Checking if Dapr sidecar is listening on HTTP port 1599
time="2023-02-18T15:05:30.4699685+08:00" level=info msg="starting Dapr Runtime -- version 1.10.0 -- commit 029ec8cb7a1c88ec5d222bc2b0d1d53541217f19" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.470968+08:00" level=info msg="log level set to: info" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.4739685+08:00" level=info msg="metrics server started on :1601/" app_id=order-processor instance=geffzhang-laptop scope=dapr.metrics type=log ver=1.10.0
time="2023-02-18T15:05:30.4950736+08:00" level=info msg="Resiliency configuration loaded." app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.5000719+08:00" level=info msg="standalone mode configured" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.5000719+08:00" level=info msg="app id: order-processor" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.5010762+08:00" level=info msg="mTLS is disabled. Skipping certificate request and tls validation" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.5030734+08:00" level=info msg="Dapr trace sampler initialized: DaprTraceSampler(P=1.000000)" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.5862994+08:00" level=info msg="local service entry announced: order-processor -> 192.168.1.6:1602" app_id=order-processor component="mdns (nameResolution/v1)" instance=geffzhang-laptop scope=dapr.contrib type=log ver=1.10.0
time="2023-02-18T15:05:30.5878674+08:00" level=info msg="Initialized name resolution to mdns" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.5892504+08:00" level=info msg="loading components" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.6287701+08:00" level=info msg="component loaded. name: pubsub, type: pubsub.redis/v1" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.6291486+08:00" level=info msg="waiting for all outstanding components to be processed" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.6400323+08:00" level=info msg="detected actor state store: statestore" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.6400323+08:00" level=info msg="component loaded. name: statestore, type: state.redis/v1" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.6400323+08:00" level=info msg="all outstanding components processed" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.6405641+08:00" level=info msg="gRPC proxy enabled" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.6426739+08:00" level=info msg="gRPC server listening on TCP address: :1600" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.grpc.api type=log ver=1.10.0
time="2023-02-18T15:05:30.6431919+08:00" level=info msg="Enabled gRPC tracing middleware" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.grpc.api type=log ver=1.10.0
time="2023-02-18T15:05:30.6437283+08:00" level=info msg="Enabled gRPC metrics middleware" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.grpc.api type=log ver=1.10.0
time="2023-02-18T15:05:30.6491391+08:00" level=info msg="configuring workflow engine gRPC endpoint" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.wfengine type=log ver=1.10.0
time="2023-02-18T15:05:30.6506026+08:00" level=info msg="API gRPC server is running on port 1600" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.6570537+08:00" level=info msg="enabled metrics http middleware" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.http type=log ver=1.10.0
time="2023-02-18T15:05:30.6570537+08:00" level=info msg="enabled tracing http middleware" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.http type=log ver=1.10.0
time="2023-02-18T15:05:30.6575707+08:00" level=info msg="HTTP server listening on TCP address: :1599" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.http type=log ver=1.10.0
time="2023-02-18T15:05:30.6580969+08:00" level=info msg="http server is running on port 1599" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.6586808+08:00" level=info msg="The request body size parameter is: 4" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.6592257+08:00" level=info msg="gRPC server listening on TCP address: :1602" app_id=order-processor instance=geffzhang-lapance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.6921456+08:00" level=info msg="dapr initialized. Status: Running. Init Elapsed 192ms" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:30.7694178+08:00" level=info msg="placement tables updated, version: 0" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor.internal.placement type=log ver=1.10.0
Checking if Dapr sidecar is listening on GRPC port 1600Dapr sidecar is up and running.
Updating metadata for app command: dotnet runYou're up and running! Both Dapr and your app logs will appear here.

== APP == info: Microsoft.DurableTask[1]
== APP ==       Durable Task worker is connecting to sidecar at localhost:1600.
== APP == info: Microsoft.Hosting.Lifetime[0]
== APP ==       Application started. Press Ctrl+C to shut down.
== APP == info: Microsoft.Hosting.Lifetime[0]
== APP ==       Hosting environment: Production
== APP == info: Microsoft.Hosting.Lifetime[0]
== APP ==       Content root path: C:\Users\zsygz\Documents\GitHub\daprquickstarts\workflows\csharp\sdk\order-processor
== APP == Starting workflow 49caa2d7 purchasing 10 Cars
== APP == info: Microsoft.DurableTask.Client.Grpc.GrpcDurableTaskClient[40]
== APP ==       Scheduling new OrderProcessingWorkflow orchestration with instance ID '49caa2d7' and 47 bytes of input data.
time="2023-02-18T15:05:44.2802197+08:00" level=info msg="Error processing operation DaprBuiltInActorNotFoundRetries. Retrying in 1s…" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
== APP == info: Microsoft.DurableTask[4]
== APP ==       Sidecar work-item streaming connection established.
time="2023-02-18T15:05:44.3005331+08:00" level=info msg="work item stream established by user-agent: [grpc-dotnet/2.50.0 (.NET 6.0.13; CLR 6.0.13; net6.0; windows; x64)]" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.wfengine type=log ver=1.10.0
time="2023-02-18T15:05:44.3005331+08:00" level=info msg="worker started with backend dapr.actors/v1-alpha" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.wfengine type=log ver=1.10.0
time="2023-02-18T15:05:44.3010653+08:00" level=info msg="workflow engine started" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.wfengine type=log ver=1.10.0
time="2023-02-18T15:05:47.0119252+08:00" level=info msg="placement tables updated, version: 1" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor.internal.placement type=log ver=1.10.0
time="2023-02-18T15:05:47.2813299+08:00" level=info msg="Recovered processing operation DaprBuiltInActorNotFoundRetries." app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime type=log ver=1.10.0
time="2023-02-18T15:05:47.3016215+08:00" level=info msg="49caa2d7: starting new 'OrderProcessingWorkflow' instance." app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.wfengine type=log ver=1.10.0
time="2023-02-18T15:05:47.3980173+08:00" level=info msg="49caa2d7#0: loading activity state" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.wfengine type=log ver=1.10.0
time="2023-02-18T15:05:47.4054132+08:00" level=info msg="reminder \"start-6af4e78b\" was canceled by the actor" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:05:47.4055923+08:00" level=info msg="Found reminder with key: dapr.internal.wfengine.workflow||49caa2d7||start-6af4e78b. Deleting reminder" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
== APP == info: WorkflowConsoleApp.Activities.NotifyActivity[0]
== APP ==       Received order 49caa2d7 for 10 Cars at $15000
time="2023-02-18T15:05:47.4244248+08:00" level=info msg="reminder \"run-activity\" was canceled by the actor" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:05:47.4249645+08:00" level=info msg="Found reminder with key: dapr.internal.wfengine.activity||49caa2d7#0||run-activity. Deleting reminder" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:05:47.4401806+08:00" level=info msg="49caa2d7#1: loading activity state" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.wfengine type=log ver=1.10.0
time="2023-02-18T15:05:47.4493286+08:00" level=info msg="reminder \"new-event-21a81b86\" was canceled by the actor" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:05:47.4496796+08:00" level=info msg="Found reminder with key: dapr.internal.wfengine.workflow||49caa2d7||new-event-21a81b86. Deleting reminder" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
== APP == info: WorkflowConsoleApp.Activities.ReserveInventoryActivity[0]
== APP ==       Reserving inventory for order 49caa2d7 of 10 Cars
== APP == info: WorkflowConsoleApp.Activities.ReserveInventoryActivity[0]
== APP ==       There are: 100, Cars available for purchase
== APP == Your workflow has started. Here is the status of the workflow: Running
time="2023-02-18T15:05:49.4896663+08:00" level=info msg="reminder \"run-activity\" was canceled by the actor" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:05:49.491597+08:00" level=info msg="Found reminder with key: dapr.internal.wfengine.activity||49caa2d7#1||run-activity. Deleting reminder" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:05:49.5078761+08:00" level=info msg="49caa2d7#2: loading activity state" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.wfengine type=log ver=1.10.0
time="2023-02-18T15:05:49.5208857+08:00" level=info msg="reminder \"new-event-b34a7d49\" was canceled by the actor" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:05:49.5276731+08:00" level=info msg="Found reminder with key: dapr.internal.wfengine.workflow||49caa2d7||new-event-b34a7d49. Deleting reminder" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
== APP == info: WorkflowConsoleApp.Activities.ProcessPaymentActivity[0]
== APP ==       Processing payment: 49caa2d7 for 10 Cars at $15000
== APP == info: WorkflowConsoleApp.Activities.ProcessPaymentActivity[0]
== APP ==       Payment for request ID '49caa2d7' processed successfully
time="2023-02-18T15:05:56.5412886+08:00" level=info msg="reminder \"run-activity\" was canceled by the actor" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:05:56.5418687+08:00" level=info msg="49caa2d7#3: loading activity state" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.wfengine type=log ver=1.10.0
time="2023-02-18T15:05:56.5423941+08:00" level=info msg="Found reminder with key: dapr.internal.wfengine.activity||49caa2d7#2||run-activity. Deleting reminder" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:05:56.551159+08:00" level=info msg="reminder \"new-event-ef938587\" was canceled by the actor" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:05:56.5518012+08:00" level=info msg="Found reminder with key: dapr.internal.wfengine.workflow||49caa2d7||new-event-ef938587. Deleting reminder" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
== APP == info: WorkflowConsoleApp.Activities.UpdateInventoryActivity[0]
== APP ==       Checking Inventory for: Order# 49caa2d7 for 10 Cars
== APP == info: WorkflowConsoleApp.Activities.UpdateInventoryActivity[0]
== APP ==       There are now: 90 Cars left in stock
time="2023-02-18T15:06:01.5721469+08:00" level=info msg="reminder \"run-activity\" was canceled by the actor" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:06:01.5727943+08:00" level=info msg="Found reminder with key: dapr.internal.wfengine.activity||49caa2d7#3||run-activity. Deleting reminder" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:06:01.5741849+08:00" level=info msg="49caa2d7#4: loading activity state" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.wfengine type=log ver=1.10.0
== APP == info: WorkflowConsoleApp.Activities.NotifyActivity[0]
== APP ==       Order 49caa2d7 has completed!
time="2023-02-18T15:06:01.5835833+08:00" level=info msg="reminder \"new-event-d423d421\" was canceled by the actor" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:06:01.5896839+08:00" level=info msg="Found reminder with key: dapr.internal.wfengine.workflow||49caa2d7||new-event-d423d421. Deleting reminder" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:06:01.5919168+08:00" level=info msg="reminder \"run-activity\" was canceled by the actor" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:06:01.597233+08:00" level=info msg="Found reminder with key: dapr.internal.wfengine.activity||49caa2d7#4||run-activity. Deleting reminder" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:06:01.6004245+08:00" level=info msg="49caa2d7: 'OrderProcessingWorkflow' completed with a COMPLETED status." app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.wfengine type=log ver=1.10.0
time="2023-02-18T15:06:01.6048974+08:00" level=info msg="reminder \"new-event-61f53bff\" was canceled by the actor" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
time="2023-02-18T15:06:01.6054844+08:00" level=info msg="Found reminder with key: dapr.internal.wfengine.workflow||49caa2d7||new-event-61f53bff. Deleting reminder" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.actor type=log ver=1.10.0
== APP == Workflow Status: Completed
time="2023-02-18T15:06:03.434236+08:00" level=info msg="work item stream closed" app_id=order-processor instance=geffzhang-laptop scope=dapr.runtime.wfengine type=log ver=1.10.0
Exited App successfully

terminated signal received: shutting down
Exited Dapr successfully

這裡面發生的事情大概如下:

  1. 為工作流生成唯一的訂單 ID(在上面的示例中為49caa2d7 ),並啟動工作流。
  2. 工作流活動NotifyActivity傳送一條通知,指出已收到 10 輛汽車的訂單。
  3. 工作流活動ReserveInventoryActivity檢查庫存資料,確定您是否可以提供訂購的物料,並使用庫存中的汽車數量進行響應。
  4. 您的工作流將啟動並通知您其狀態。
  5. 工作流活動ProcessPaymentActivity開始處理訂單49caa2d7 付款並確認是否成功。
  6. 處理訂單後,工作流活動UpdateInventoryActivity使用當前可用汽車更新庫存。
  7. 工作流活動NotifyActivity 傳送一條通知,指出訂單 49caa2d7 已完成。
  8. 工作流在完成時終止。

相關文章