🚀 ABP VNext + Dapr Workflows:轻量级分布式工作流


📚 目录

  • 🚀 ABP VNext + Dapr Workflows:轻量级分布式工作流
    • 一、引言 ✨
      • TL;DR 🔥
    • 二、环境与依赖 🛠️
    • 三、系统架构与流程图 🏗️
    • 四、在 ABP 模块中注册 Dapr Workflows 📦
    • 五、定义 Workflow 与 Activities 🎯
      • 5.1 定义活动(Activity)
      • 5.2 定义工作流(Workflow)
    • 六、触发与查询工作流 🔍
      • 6.1 启动 ABP 应用
      • 6.2 发起工作流
      • 6.3 暴露查询端点
    • 七、示例演示 🎬
    • 八、最佳实践与优化 💡


一、引言 ✨

TL;DR 🔥

  • 在 ABP VNext 应用中,只需一行 services.AddDaprWorkflow(...) 即可无侵入集成 Dapr Workflow SDK,开启长运行分布式工作流编排 🎉 (Dapr Docs)
  • 通过 state.redis 或 CosmosDB 等可插拔 State Store 实现跨服务状态持久化与恢复,支持 Saga 补偿模式 🔄 (Dapr Docs)
  • 定义继承自 Workflow<TInput, TOutput> 的工作流类与 WorkflowActivity<TArg, TResult> 的活动类,使用 context.CallActivityAsync 保证确定性重放 🛠️ (Diagrid)
  • 演示“下单—保留库存—扣款—失败补偿”全流程,涵盖高性能、高可用、易复现实践 ✅

背景
在微服务架构中,分布式事务难以扩展,“最终一致性”与 Saga 模式已成主流。Dapr Workflows 提供代码化工作流,基于 DurableTask 引擎在 State Store 中持久化状态,结合补偿与定时器,简化复杂业务的可靠编排。


二、环境与依赖 🛠️

  • .NET 平台:.NET 9,ABP vNext v9.x

  • Dapr 运行时:Dapr CLI ≥1.10;Workflow Runtime v1.15.4

  • NuGet 包

    dotnet add package Dapr.Workflow --version 1.15.4
    
  • State Store 组件 (components/statestore.yaml):

    apiVersion: dapr.io/v1alpha1
    kind: Component
    metadata:name: statestore
    spec:type: state.redisversion: v1metadata:- name: redisHostvalue: "localhost:6379"# 生产环境推荐使用支持事务的后端,如 Azure Cosmos DB 或 SQL Server
    

    (Dapr Docs)

  • 基础设施:Redis / Azure Cosmos DB;Dapr Sidecar


三、系统架构与流程图 🏗️

State_Store
Workflow_Runtime
ABP_App
ScheduleNewWorkflowAsync
Redis/CosmosDB
OrderWorkflow 实例
ReserveInventoryActivity
ChargePaymentActivity
RefundPaymentActivity
ReleaseInventoryActivity
Dapr Sidecar
OrderService API
  • OrderService API 通过 Dapr Sidecar 调用 Workflow 管理 API
  • Workflow Runtime 调度活动并将状态写入 State Store,支持断点重放
  • Saga 补偿:在失败场景通过补偿活动保证最终一致性

四、在 ABP 模块中注册 Dapr Workflows 📦

using Dapr.Client;
using Dapr.Workflow;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;public class MyAppModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){// 可选:显式注册 DaprClientcontext.Services.AddDaprClient();// 一行集成 Dapr Workflows,自动注册 Client 与 Workercontext.Services.AddDaprWorkflow(options =>{options.RegisterWorkflow<OrderWorkflow>();options.RegisterActivity<ReserveInventoryActivity>();options.RegisterActivity<ChargePaymentActivity>();options.RegisterActivity<RefundPaymentActivity>();options.RegisterActivity<ReleaseInventoryActivity>();});}
}

AddDaprWorkflow 会自动注册 DaprWorkflowClientDaprClient(若未注册)及后台 HostedService,无需额外中间件调用 (Dapr Docs)


五、定义 Workflow 与 Activities 🎯

using Dapr.Workflow;
using Dapr.Workflow.Models;

5.1 定义活动(Activity)

继承自 WorkflowActivity<TArg, TResult> 并重写 RunAsync,实现幂等逻辑:

public record PaymentInput(Guid OrderId, decimal Amount);public class ReserveInventoryActivity : WorkflowActivity<Guid, bool>
{public override Task<bool> RunAsync(WorkflowActivityContext context,Guid orderId){// 调用库存服务,保证幂等return Task.FromResult(true);}
}public class ChargePaymentActivity : WorkflowActivity<PaymentInput, bool>
{public override Task<bool> RunAsync(WorkflowActivityContext context,PaymentInput input){// 调用支付服务,保证幂等return Task.FromResult(true);}
}

(Diagrid)

5.2 定义工作流(Workflow)

继承自 Workflow<OrderDto, object>,在 RunAsync 中编排活动并处理补偿:

public class OrderWorkflow : Workflow<OrderDto, object>
{public override async Task<object> RunAsync(WorkflowContext context,OrderDto order){var logger = context.CreateReplaySafeLogger<OrderWorkflow>();logger.LogInformation("Order {OrderId} 开始", order.Id);try{await context.CallActivityAsync<bool>(nameof(ReserveInventoryActivity),order.Id);await context.CallActivityAsync<bool>(nameof(ChargePaymentActivity),new PaymentInput(order.Id, order.Amount));}catch (Exception ex){logger.LogWarning(ex, "执行失败,开始补偿");await context.CallActivityAsync<bool>(nameof(RefundPaymentActivity),order.Id);await context.CallActivityAsync<bool>(nameof(ReleaseInventoryActivity),order.Id);throw;}logger.LogInformation("Order {OrderId} 完成", order.Id);return null!;}
}

(Diagrid)


六、触发与查询工作流 🔍

6.1 启动 ABP 应用

dapr run \--app-id order-api \--app-port 5000 \--dapr-http-port 3500 \--components-path ./components \dotnet run

6.2 发起工作流

using Dapr.Workflow;public class OrderAppService : ApplicationService
{public async Task<string> CreateOrderAsync(CreateOrderDto dto){var client = ServiceProvider.GetRequiredService<DaprWorkflowClient>();string instanceId = Guid.NewGuid().ToString();await client.ScheduleNewWorkflowAsync(workflowName: nameof(OrderWorkflow),instanceId: instanceId,input: dto);return instanceId;}// 新增:查询工作流状态public async Task<WorkflowState> GetWorkflowStateAsync(string instanceId){var client = ServiceProvider.GetRequiredService<DaprWorkflowClient>();return await client.GetWorkflowStateAsync(instanceId, includeInputsAndOutputs: true);}
}

使用 ScheduleNewWorkflowAsync 启动实例 (Dapr Docs)

6.3 暴露查询端点

using Dapr.Workflow;
using Microsoft.AspNetCore.Mvc;[ApiController]
[Route("api/workflows")]
public class WorkflowController : ControllerBase
{private readonly DaprWorkflowClient _client;public WorkflowController(DaprWorkflowClient client) => _client = client;[HttpGet("{instanceId}")]public async Task<IActionResult> Get(string instanceId){var state = await _client.GetWorkflowStateAsync(instanceId, includeInputsAndOutputs: true);return Ok(state);}
}
curl http://localhost:5000/api/workflows/{instanceId}
  • 返回 JSON 包含 RuntimeStatus、输入输出、历史事件等信息。

七、示例演示 🎬

  1. 基础设施

    docker run -d --name redis -p 6379:6379 redis
    dapr init --runtime-version v1.10
    
  2. 运行应用并发起订单

    curl -X POST http://localhost:5000/api/orders \-H "Content-Type: application/json" \-d '{"productId":"123","quantity":1,"amount":100}'
    
  3. 查询状态

    curl http://localhost:5000/api/workflows/{instanceId}
    
  4. 模拟失败:在 ChargePaymentActivity 抛出异常,验证补偿活动自动执行 💥


八、最佳实践与优化 💡

  • 幂等性:活动内部调用尽量幂等,防止重试产生副作用。
  • 超时与重试:结合 Durable Timers 及 RetryOptions 控制超时与重试。
  • 并行与分支:可在工作流中使用 Task.WhenAll(...) 或动态 CallActivityAsync 实现并行。
  • 版本兼容:升级工作流时,通过前缀或迁移逻辑兼容老实例。
  • 生产环境:推荐使用支持事务回滚的 State Store(Cosmos DB、SQL Server)替代 Redis (Dapr Docs)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/91884.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/91884.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/91884.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

⭐ Unity 实现UI视差滚动效果(Parallax)鼠标控制、可拓展陀螺仪与脚本控制

✨ 效果如下在许多游戏、APP 或动效页面中&#xff0c;我们常见的一种视觉效果是 视差滚动&#xff08;Parallax Scrolling&#xff09;&#xff1a;前景、中景、背景在鼠标或设备移动时以不同速率轻微移动&#xff0c;从而营造出一种空间感和深度感。目前遇到这样一个需求 所以…

【05】VM二次开发——模块参数配置--带渲染/不带渲染(WinForm界面调用 模块参数配置)

文章目录1 Winform 窗口界面 &#xff08;带渲染的参数配置控件&#xff09;2 配置代码3 运行测试4 不带渲染的参数配置控件 对比4.1 添加控件4.2 代码及演示效果模块参数配置本教程介绍如何在VM二次开发中对模块参数进行配置 1 Winform 窗口界面 &#xff08;带渲染的参数配置…

Android 之 蓝牙通信(2.0 经典)

​​一、环境配置​​1. ​​添加依赖​​在 build.gradle 中添加库依赖&#xff1a;dependencies {implementation com.github.akexorcist:bluetoothspp:1.0.0 }2. ​​权限声明&#xff08;AndroidManifest.xml&#xff09;​<uses-permission android:name"androi…

使用 Scikit-LLM 进行零样本和少样本分类

使用 Scikit-LLM 进行零样本和少样本分类 使用 Scikit-LLM 进行零样本和少样本分类 在本文中&#xff0c;您将学习&#xff1a; Scikit-LLM如何将OpenAI的GPT等大型语言模型与Scikit-learn框架集成以进行文本分析。零样本和少样本分类之间的区别以及如何使用Scikit-LLM实现它…

android内存作假通杀补丁(4GB作假8GB)

可过如下app检测&#xff1a; 安兔兔、鲁大师、白眼、AIDA64、CPU X、CPU-Z、DevCheck、DeviceInfoHW lyw235yk235:~/Extend/lyw235/V/sprdroid1_v_4/sprdroid1_v$ git diff vnd/bsp/kernel5.15/kernel5.15/mm/page_alloc.c diff --git a/vnd/bsp/kernel5.15/kernel5.15/mm/pag…

Android 之 MVC架构

介绍1. MVC架构分工​​​​Model层​​&#xff1a;处理数据验证、网络请求等业务逻辑。​​View层​​&#xff1a;XML布局定义界面&#xff0c;Activity处理用户输入和显示结果。​​Controller层​​&#xff1a;Activity作为控制器&#xff0c;协调Model和View的交互对于登…

Centos Docker 安装手册(可用)

Centos 安装 Docker # 卸载旧版 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine \docker-selinux # 安装依赖工具 yum install -y yum-utils device-mapper-persistent-d…

烽火HG680-KX-海思MV320芯片-2+8G-安卓9.0-强刷卡刷固件包

烽火HG680-KX-海思MV320芯片-28G-安卓9.0-强刷卡刷固件包U盘强刷刷机步骤&#xff1a;1、强刷刷机&#xff0c;用一个usb2.0的8G以下U盘&#xff0c;fat32&#xff0c;2048块单分区格式化&#xff08;强刷对&#xff35;盘非常非常挑剔&#xff0c;usb2.0的4G U盘兼容的多&…

Python爬虫实战:研究pycares技术构建DNS解析系统

1. 引言 1.1 研究背景 随着互联网的飞速发展,网络上的数据量呈现爆炸式增长。网络爬虫作为一种高效的数据采集工具,被广泛应用于数据分析、市场调研、学术研究等领域。传统的爬虫在进行大规模数据采集时,往往会受到 DNS 解析效率的制约,成为影响爬取性能的瓶颈之一。 DNS…

从 0 到 1 认识 Spring MVC:核心思想与基本用法(下)

文章目录&#x1f4d5;4. 响应✏️4.1 返回静态页面✏️4.2 返回数据ResponseBody​✏️4.3 返回HTML代码片段​✏️4.4 返回JSON✏️4.5 设置状态码✏️4.6 设置Header&#xff08;了解&#xff09;&#x1f4d5;5. 案例练习✏️5.1 加法计算器✏️5.2 用户登录✏️5.3 留言板…

Python-初学openCV——图像预处理(五)——梯度处理、边缘检测、图像轮廓

目录 一、图像梯度处理 1、垂直边缘提取 2、Sobel算子 3、Laplacian算子 二、图像边缘检测 1、高斯滤波 2、计算图像的梯度、方向 3、非极大值抑制 4、双阈值筛选 三、绘制图像轮廓 1、概念 2、寻找轮廓 3、绘制轮廓 一、图像梯度处理 还记得高数中的一阶导数求极值…

【Redis】安装Redis,通用命令,常用数据结构,单线程模型

目录 一.在Ubuntu系统安装Redis 二. redis客户端介绍 三. 全局命令 3.1.GET和SET命令 3.2.KEYS&#xff08;生产环境禁止使用&#xff09; 3.3.EXISTS 3.4.DEL 3.5.EXPIRE 3.6.TTL 3.6.1.Redis的过期策略 3.6.2.基于优先级队列/堆的实现去实现定时器 3.6.3.定时器&a…

ubuntu22.04系统实践 linux基础入门命令(三) 用户管理命令

以下有免费的4090云主机提供ubuntu22.04系统的其他入门实践操作 地址&#xff1a;星宇科技 | GPU服务器 高性能云主机 云服务器-登录 相关兑换码星宇社区---4090算力卡免费体验、共享开发社区-CSDN博客 之所以推荐给大家使用&#xff0c;是因为上面的云主机目前是免费使用的…

DPDK中的TCP头部处理

1. TCP头部结构 TCP头部通常为20字节&#xff08;不含可选字段&#xff09;&#xff0c;每个字段占据固定的字节位置。以下是TCP头部的结构&#xff0c;按字节位置逐一说明&#xff1a;0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 …

开源在线客服系统Chatwoot配置文件

参考&#xff1a; https://developers.chatwoot.com/self-hosted/deployment/dockerhttps://developers.chatwoot.com/self-hosted/deployment/docker 1、.env 配置文件 # Learn about the various environment variables at # https://www.chatwoot.com/docs/self-hosted/co…

PHP进阶语法详解:命名空间、类型转换与文件操作

掌握了PHP面向对象编程的基础后&#xff0c;就可以深入学习命名空间、类型转换、文档注释、序列化以及文件操作等重要概念。 1、命名空间&#xff08;Namespace&#xff09; 命名空间是PHP 5.3引入的重要特性&#xff0c;它解决了类名、函数名和常量名冲突的问题&#xff0c;使…

Webpack 搭建 Vue3 脚手架详细步骤

创建一个新的 Vue 项目 1&#xff09;初始化项目目录 新建一个文件夹&#xff0c;或者使用以下指令 mkdir webpack-vue_demo cd webpack-vue_demo2&#xff09;初始化 npm 项目 npm init -y3&#xff09;安装 vue 和 webpack 相关依赖 npm install vue vue-loader vue-template…

【Git 误操作恢复指南】

Git 误操作恢复指南 适用场景&#xff1a;git reset --hard 误操作后的紧急恢复 风险等级&#xff1a;&#x1f534; 高风险 - 可能导致代码丢失 恢复成功率&#xff1a;95%&#xff08;CI/CD 环境下&#xff09; &#x1f6a8; 紧急情况概述 问题描述 在项目开发过程中&am…

Go语言 逃 逸 分 析

逃逸分析是什么 逃逸分析是编译器用于决定变量分配到堆上还是栈上的一种行为。一个变量是在堆上分配&#xff0c;还是在栈上分配&#xff0c;是经过编译器的逃逸分析之后得出的“结论”。Go 语言里编译器的逃逸分析&#xff1a;它是编译器执行静态代码分析后&#xff0c…

LeetCode算法日记 - Day 1: 移动零、复写零

目录 1. 移动零 1.1 思路解析 1.2 代码实现 2. 复写零 2.1 思路解析 2.2 代码实现 1. 移动零 283. 移动零 - 力扣&#xff08;LeetCode&#xff09; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请…