# workflow-poc **Repository Path**: ymjake/workflow-poc ## Basic Information - **Project Name**: workflow-poc - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-22 - **Last Updated**: 2026-09-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: bpmn, workflow, dmn, feel ## README # Workflow > ⚠️ **已结项(2026-09-22)**:这是一个**个人验证项目** —— 把 BPMN 2.0 + DMN 1.5 的**执行语义** > 实现在一个纯 .NET 类库里。**验证成功,但没有做成通用嵌入式库**(原因与验证结论见 > [`docs/README.md`](docs/README.md))。下面的说明是"如果要用,该怎么用"。 嵌入式 **BPMN 2.0 + DMN 1.5** 工作流引擎,跑在你的进程里、用**你自己的 `DbContext`**。 不是 Zeebe 那种独立集群:没有 broker、没有独立部署、没有 gateway。引擎是一个普通的 .NET 库, 流程表挂在宿主 `AppDbContext` 上(OpenIddict 那种路子)。 **引擎也不提供 HTTP 端点** —— 它就是一组类库,接什么协议由你的应用决定。 > ⚠️ **事务:引擎与宿主共用同一个 `DbContext` 实例 ⇒ 天然一笔事务,零胶水。** > `EfCoreProcessStore` 是 **Scoped**,构造注入的是**基类** `DbContext`(`WithDbAsync` 塌缩成 `action(db)`), > 与宿主业务代码同 scope ⇒ 同实例;而 EF Core 的事务绑定在**上下文实例**上 ⇒ 实例相同就看得见。 > 所以宿主自己开事务,就能把引擎的落盘和业务写包成一笔: > > ```csharp > await using var tx = await db.Database.BeginTransactionAsync(ct); > await workflow.StartAsync(...); // 引擎的落盘自动并进 tx > await tx.CommitAsync(ct); > ``` > > **代价**:tracker 共用 ⇒ 引擎每次 `SaveChangesAsync` 会顺手把宿主**还没保存**的改动一起刷下去 > (一次推进 4~6 次 flush)⇒ 别把"半成品"实体留在 tracker 里跨过 `StartAsync` / `CompleteWorkAsync`。 > **反过来:宿主不开事务时仍会留半截写入**(最典型"有任务、没实例"的孤儿任务行)—— > 这是"引擎不替宿主管事务"的代价,**不是 bug**。详见 [`docs/usage.md`](docs/usage.md) 与 > [`docs/architecture.md`](docs/architecture.md)。 --- ## 安装 引擎**不发布 NuGet 包**(见「状态」),用 `ProjectReference` 引入: ```xml ``` ## 包清单 | 包 | 作用 | 什么时候直接引 | |---|---|---| | `Workflow.Abstractions` | 接口与模型(`IProcessStore` / `IWorkflowRunner` / `ProcessSnapshot` / 审计记录) | 你自己实现持久化时 | | `Workflow.Runtime` | 执行器核心 + 宿主门面 `WorkflowHost`:drain 循环、scope 树、Incident/Retry、事务边界、DMN 求值 | 绕开 DI 手工装配时 | | `Workflow.Mediator` | 进程内中介者(Elsa.Mediator 源码,零依赖) | 基本不用单独引 | | `Workflow.Persistence.EntityFrameworkCore` | EF Core 持久化(JSON 快照,单实例一行) | 自己接 DI 时 | | `Workflow.Extensions.DependencyInjection` | `AddWorkflow()` / `UseEntityFrameworkCore()` | **默认就引这个** | 底层的 BPMN/DMN 能力来自 `bpmn/` 与 `dmn/` 两个**独立的主机无关库**(解释器、模型、XML/JSON 互换、FEEL), 它们在这个仓库里以子目录形式存在,**通过 `ProjectReference` 被 `Workflow.*` 直接引用**(不是 NuGet 依赖;我们不发包,见「状态」一节)。 ## 快速开始 ```csharp using Bpmn.Interchange; using Microsoft.EntityFrameworkCore; using Workflow.Extensions.DependencyInjection; using Workflow.Persistence.EntityFrameworkCore; using Workflow.Runtime; var builder = WebApplication.CreateBuilder(args); // 1. 你自己的 DbContext —— 引擎的表就建在它上面 builder.Services.AddDbContext(o => o.UseSqlServer(conn)); // 2. 接入引擎 —— 工作流表建在哪个 DbContext 上,就在这里点名 builder.Services.AddWorkflow() .UseEntityFrameworkCore(); // 3. 注册 ServiceTask 的委托实现 builder.Services.AddBpmnDelegate("charge-card"); var app = builder.Build(); // 4. 建表 + 启动恢复(宿主重启后必须做) using (var scope = app.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); var host = scope.ServiceProvider.GetRequiredService(); await db.Database.MigrateAsync(); foreach (var def in await db.Set() .Where(x => x.IsPublished).ToListAsync()) host.RegisterDefinitions(new BpmnXmlReader().Read(def.BpmnXml).Definitions); await host.RecoverTimersAsync(); } // 5. 你自己的 API / 你的业务入口 app.MapPost("/orders/{id}/approve", async (string id, WorkflowHost host, OrderDb db) => { /* … */ }); app.Run(); ``` 要"运行时才定得了用哪个库"(比如按租户挑库),用普通 DI 覆盖基类即可 —— 引擎和存储都只认基类 `DbContext`: ```csharp builder.Services.AddScoped(sp => sp.GetRequiredService()); ``` 驱动流程(注入 `WorkflowHost` 或 `IWorkflowRunner`): ```csharp var snapshot = await runner.StartAsync(definitions, variables); // 启动 await runner.CompleteWorkAsync(instanceId, handle, "Approved"); // 完成一个 UserTask await runner.RetryWorkAsync(instanceId, handle); // 重试停在 Incident 上的 work await runner.PublishMessageAsync("PaymentReceived", correlationKey); ``` `IWorkflowRunner` 返回 `ProcessSnapshot`(宿主视图:状态 / 变量 / token / 任务), 不泄漏解释器内部结构。要细粒度控制可以注入 `WorkflowHost`,拿 `WorkflowInstance`。 **完整接入说明见 [`docs/usage.md`](docs/usage.md)**(含自建 REST API 的清单与踩坑记录)。 ## 特性 - **BPMN 2.0**:顺序流、并行/包容/排他网关、边界事件(Error/Timer/Message/Signal)、 事件子流程(中断与非中断)、子流程与嵌套 scope、多实例、Timer、消息关联、Signal 广播 - **DMN 1.5**:决策表(七种 hit policy)、字面量表达式、FEEL 表达式、决策依赖图拓扑求值, 含**决策审计**(记录每张表的输入/输出)。**未做**:BKM 与其余非表格决策逻辑 (Context / List / Relation / Invocation / FunctionDefinition) - **宿主回调**:ServiceTask / SendTask 通过 delegate 回调宿主,抛异常自动记 Incident - **UserTask**:Camunda 7 / Activiti / Flowable / Zeebe 四种分配方言 - **可靠性**:一次推进 = 一个事务单元;Incident 是独立记录、不进状态机(对标 Zeebe); 快照式持久化,实例状态一行 JSON - **审计**:活动历史(任务生命周期转移)+ 决策历史(DMN 输入输出) ## 状态:已结项(2026-09-22) **阶段 0–5 功能已封口**(没有已知的功能缺失),**阶段 6(HTTP 层)与阶段 7(NuGet 发布)主动砍掉**。 **但这不是一个通用嵌入式库** —— 两条结构性原因(旧稿还列过第三条"共享事务做不到", **已被推翻**:现在就是**零胶水、共用同一个 `DbContext` 实例**,见上文): ① 引擎**故意没有领域层**(定义就是一张 EF 表)⇒ 宿主被迫 `db.Set()`、 自己解析 XML / 实现查重与版本; ② 通用库要的公共 API 契约 / 多 provider 验证 / 真实宿主验证 / 分发形态**一样都没做**。 **验证结论与已知限制清单见 [`docs/README.md`](docs/README.md)(先读这个)。** | 文档 | 内容 | |---|---| | [`docs/README.md`](docs/README.md) | **结项说明 / 验证了什么 / 没验证什么 / 已知限制**(先读这个) | | [`docs/architecture.md`](docs/architecture.md) | 架构、**生命周期规则**、零胶水事务、持久化形态 | | [`docs/usage.md`](docs/usage.md) | 接入指南(含自建 REST API 的清单与踩坑记录) | | [`docs/design/definition-management.md`](docs/design/definition-management.md) | **未实施**的设计稿:定义管理(补限制 #4) | > 旧的 `roadmap.md` / `reference.md` / `features/*` 已在 2026-09-22 的文档重写中移入 > `workflow/.backup/2026-09-22-before-docs-rewrite/`。 ## 许可 MIT。