# 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。