Table of Contents

工作流引擎

多步骤业务流程的顺序编排:步骤实现类自动发现、推进即持久化、失败重试 + Saga 补偿、重启恢复。

适用场景:订单履约(下单→扣款→发货→通知)、对账流程、KYC 审核、数据迁移管线—— 任何"必须按顺序执行多步、中间失败要把已做的步骤撤掉"的业务。

接入

builder.Services.AddLedonWorkflow();   // 步骤/定义自动发现 + 恢复服务

DbContext 继承 WorkflowDbContext 即获得实例表(随 AddLedonData() 前置建库)。

定义工作流

public class OrderWorkflow : IWorkflowDefinition
{
    public string Name => "order";
    public string InitialStep => "reserve";
    public IReadOnlyDictionary<string, string[]> Transitions => new()
    {
        ["reserve"] = ["charge"],
        ["charge"] = ["ship", "cancel"],   // 多出口:由步骤写 Data["next"] 选择
    };
}

实现步骤

public class ChargeStep(IpaymentGateway pay) : IWorkflowStep, ICompensableStep
{
    public string Name => "charge";

    public async Task<StepOutcome> ExecuteAsync(StepContext ctx, CancellationToken ct = default)
    {
        var orderId = (int)ctx.Data["orderId"]!;
        await pay.ChargeAsync(orderId, ct);
        return StepOutcome.Success;
    }

    // 失败回滚时被调用(应对重复调用幂等)
    public async Task CompensateAsync(StepContext ctx, CancellationToken ct = default) =>
        await pay.RefundAsync((int)ctx.Data["orderId"]!, ct);
}
  • 实现 IWorkflowStep 即被自动发现(引用框架 = 选择加入);
  • 跨步骤数据放 ctx.Data(随实例持久化);
  • 动态出口:ctx.Data["next"] = "cancel" 覆盖转移表默认出口。

运行

var engine = provider.GetRequiredService<WorkflowEngine>();
var instance = await engine.CreateAsync("order", new Dictionary<string, object?> { ["orderId"] = 42 });
try
{
    var done = await engine.RunAsync(instance.Id, ct);   // 推进到终止
    // done.Status == Completed
}
catch (WorkflowExecutionException ex)
{
    // 重试耗尽(默认 3 次/步)并补偿后抛出;ex.Instance 带完整现场
    // ex.Instance.Status == Compensated(有可补偿步骤)或 Failed
}

语义与边界

主题 行为
持久化 每次推进后保存实例(含数据与已完成步骤)——进程崩溃后不丢进度
重启恢复 托管服务启动即把 Running 实例继续推进,之后每 30s 轮询
重试 单步骤失败重试至 WorkflowOptions.MaxRetriesPerStep(默认 3)
补偿 重试耗尽后逆序补偿已执行步骤(Saga);补偿失败只记录不阻断其余
并发 引擎是 Scoped:同一实例不要并发 RunAsync(持久化非行级锁保护)
多实例部署 恢复前先原子认领实例(LeasedBy/LeasedUntil),只有一个节点会恢复同一个实例
并发写保护 实例带乐观并发令牌:两个节点同时保存时后提交的抛 DbUpdateConcurrencyException,不静默覆盖
跨调用补偿 RunAsync 从实例的 CompletedSteps 播种已执行集:恢复后再失败,上一轮的步骤也一起补偿
长时任务 步骤内长时间等待建议改为"写状态 → 外部事件驱动再推进",别占着执行线程

与其它能力的关系

  • 发件箱:步骤里产生的跨服务事件走 AddLedonOutbox()(与业务数据同事务);
  • 定时任务:周期性地推进/补偿批量实例用 AddLedonScheduling();
  • 后台队列:把 RunAsync 丢进队列做异步执行(AddLedonQueues())。

相关主题