diff --git a/AGENTS.md b/AGENTS.md index a499975..6cfc998 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -115,6 +115,7 @@ dotnet test DesignPatterns.slnx -c Release | Command Router | `ICommandRouter`、`IStreamCommandHandler`、`[RegisterCommandHandler]` | `RegisterCommandHandlerGenerator` | | State(M1–M2) | `ITransitionTable`、`[StateMachine]`、`[Transition]` | `StateTransitionGenerator` | | Step Builder | `[GenerateBuilder]`、`[BuilderStep]`、`[BuilderAssemble]`、`BuilderStepState` | `GenerateBuilderGenerator` | +| Work Graph | `IWorkGraph`、`IWorkStep`、`WorkGraphBuilder`、`[WorkGraph]`、`[WorkStep]` | `WorkGraphGenerator` | | DI Health Checks | `AddDesignPatternsHealthChecks`、`IHealthCheck` | —(运行时扩展) | 模式文档见 [docs/design/](docs/design/README.md)。横切约定见 [docs/FactoryKeyConventions.md](docs/FactoryKeyConventions.md)。架构决策见 [docs/adr/](docs/adr/README.md)。 diff --git a/CHANGELOG.md b/CHANGELOG.md index f8c71ff..c312d3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **Work Graph Design Doc**: `docs/design/WorkGraph.md` documents dual-path API (`WorkGraphBuilder` / `[WorkGraph]`+`[WorkStep]` / Keys+`Create`), DP087–DP092, wave fail-fast execution, and non-goals vs Channel Pipeline / Composite parallel / Step Builder / TPL Dataflow; design index, ROADMAP F3 Top-3, and AGENTS pattern summary updated ([#312](https://github.com/Skymly/DesignPatterns/issues/312), Spec [#308](https://github.com/Skymly/DesignPatterns/issues/308)). - **Work Graph generator**: `[WorkGraph]` / `[WorkGraph]` + `[WorkStep]` drive `WorkGraphGenerator`, emitting `{Holder}WorkStepKeys` and `{Holder}WorkGraph.Create(resolver|dictionary)` that fills `WorkGraphBuilder`. Reports **DP087–DP092** (cycle, unknown DependsOn, duplicate id, self-dependency, unreachable Warning, contract mismatch); no MVP DI/Autofac emission ([#311](https://github.com/Skymly/DesignPatterns/issues/311), Spec [#308](https://github.com/Skymly/DesignPatterns/issues/308)). -- **Work Graph diagnostic IDs**: reserved **DP087–DP092** for the upcoming Work Graph generator (dependency cycle, unknown DependsOn, duplicate step id, self-dependency, unreachable step Warning, contract/TContext mismatch); no unregistered-`IWorkStep` Analyzer in MVP; DP067–DP071 remain ADR-008-only ([#310](https://github.com/Skymly/DesignPatterns/issues/310), Spec [#308](https://github.com/Skymly/DesignPatterns/issues/308)). +- **Work Graph diagnostic IDs**: **DP087–DP092** for the Work Graph generator (dependency cycle, unknown DependsOn, duplicate step id, self-dependency, unreachable step Warning, contract/TContext mismatch); no unregistered-`IWorkStep` Analyzer in MVP; DP067–DP071 remain ADR-008-only ([#310](https://github.com/Skymly/DesignPatterns/issues/310), Spec [#308](https://github.com/Skymly/DesignPatterns/issues/308)). +- **Work Graph runtime**: `IWorkStep` / `IWorkGraph` / `WorkGraphBuilder` with topological wave execution and fail-fast cancellation; `[WorkGraph]` / `[WorkGraph]` / `[WorkStep]` attributes; empty/cycle/duplicate/self/unknown DAGs throw `InvalidWorkGraphException` at `Build` ([#309](https://github.com/Skymly/DesignPatterns/issues/309), Spec [#308](https://github.com/Skymly/DesignPatterns/issues/308)). ### Changed diff --git a/CONTEXT.md b/CONTEXT.md index 51477d4..147c02d 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -23,3 +23,11 @@ _Avoid_: IServiceCollection snapshot, container dump, RegisterDi argument-pair c **Peer-presence registration**: A compile-time rule that only requires an unannotated implementation to register when a peer (event, command, or handler context) is already registered elsewhere in the compilation. _Avoid_: contract-peer unregistered check (Strategy/Factory: implements a registered contract but lacks the attribute) + +**Fork–Join Work Graph**: +An in-process async DAG of work steps that share one `TContext`, where edges are readiness dependencies only (not typed payload channels), executed in topological waves with fail-fast cancellation. +_Avoid_: Channel Pipeline, Composite parallel traversal, TPL Dataflow, Step Builder (construction completeness) + +**Work step**: +A type implementing `IWorkStep` with an explicit string id and optional `DependsOn` readiness edges on a named work-graph holder. +_Avoid_: command handler, pipeline stage, channel block diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 6a5c3b6..983f5e5 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -107,7 +107,7 @@ |------|------|------|------| | 1 | **Command Router** | MVP + 域内增强已落地:1:1 `SendAsync`/`TrySendAsync`/`SendStreamAsync`/`TrySendStreamAsync` + `[RegisterCommandHandler]`(含 `IStreamCommandHandler`)+ DP072–077 + pipeline(ADR-009)+ `AddCommandRouter` / Autofac `RegisterCommandRouter` + [Design Doc](design/CommandRouter.md)(#257–#265、#275–#277、#282–#283)。Samples #266;探索与 MediatR 的差异点见 Design Doc | [~] | | 2 | **Builder**(Step Builder) | **MVP 完成**:`[GenerateBuilder]` / `[BuilderStep]` / `[BuilderAssemble]` + 泛型 type-state `{Holder}Builder`([ADR-010](adr/ADR-010-step-builder-type-state-markers.md))+ DP078–DP086 + [Design Doc](design/StepBuilder.md)(#288–#291)+ Samples [DesignPatterns.Samples#23](https://github.com/Skymly/DesignPatterns.Samples/issues/23) / [PR#24](https://github.com/Skymly/DesignPatterns.Samples/pull/24)。Spec [#287](https://github.com/Skymly/DesignPatterns/issues/287)。Phase 2+:async / DI / step validation | [x] | -| 3 | **Fork–Join Work Graph** | 属性声明的工作 DAG;生成器校验环 / 孤儿依赖并生成拓扑波次编排 | [ ] | +| 3 | **Fork–Join Work Graph** | **双路径已落地**:`IWorkStep` / `IWorkGraph` / `WorkGraphBuilder` + `[WorkGraph]` / `[WorkStep]` + `WorkGraphGenerator`(`{Holder}WorkStepKeys` + `Create`)+ DP087–DP092 + [Design Doc](design/WorkGraph.md)(#309–#312)。Spec [#308](https://github.com/Skymly/DesignPatterns/issues/308)。Samples(sibling)与 Phase 2+(Dop / 追踪 / DI / 未注册 Analyzer)待办 | [~] | #### 观望(已过硬门槛,未进 Top-3) @@ -245,3 +245,4 @@ State 转换表 v1 已于 0.1.0-preview4 发布;v2(guard 委托、DI 集成 - F5+ Composite 树 schema 校验:`[CompositeSchema(MaxDepth, MaxNodes)]` 契约级约束 + `[CompositePart(AllowedChildTypes)]` 节点级约束 + DP063(max depth exceeded Warning)/ DP064(child type not allowed Error)/ DP065(node count exceeded Warning)。见 [ADR-007](adr/ADR-007-composite-tree-schema-validation.md)。 - 文档体系精简:移除 RFC / Spec / Plan / Review 仓内类型;Spec 合入 Design Doc;任务与审查改用 GitHub Issue / PR。见 [DOCUMENTATION.md](DOCUMENTATION.md)。 - F3 Step Builder MVP:`[GenerateBuilder]` / `[BuilderStep]` / `[BuilderAssemble]` + type-state `{Holder}Builder`(ADR-010)+ DP078–DP086 + Design Doc(#288–#291)+ Samples [DesignPatterns.Samples#23](https://github.com/Skymly/DesignPatterns.Samples/issues/23);Spec [#287](https://github.com/Skymly/DesignPatterns/issues/287)。 +- F3 Fork–Join Work Graph 双路径:Runtime builder + 波次 fail-fast(#309)+ DP087–DP092(#310)+ Keys/`Create` 生成器(#311)+ Design Doc(#312);Spec [#308](https://github.com/Skymly/DesignPatterns/issues/308)。 diff --git a/docs/design/README.md b/docs/design/README.md index aea075a..8352b0f 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -18,3 +18,4 @@ | Command Router | [CommandRouter.md](CommandRouter.md) | — | | State Transition Table | [StateTransitionTable.md](StateTransitionTable.md) | [ADR-005](../adr/ADR-005-state-transition-table.md) | | Step Builder | [StepBuilder.md](StepBuilder.md) | [ADR-010](../adr/ADR-010-step-builder-type-state-markers.md) | +| Fork–Join Work Graph | [WorkGraph.md](WorkGraph.md) | —(对照 [ADR-006](../adr/ADR-006-composite-parallel-traversal.md)) | diff --git a/docs/design/WorkGraph.md b/docs/design/WorkGraph.md new file mode 100644 index 0000000..8c2b2d3 --- /dev/null +++ b/docs/design/WorkGraph.md @@ -0,0 +1,253 @@ +# Design Doc: Fork–Join Work Graph + +> **版本**:v0.2.4-preview1 +> **关联 ADR**:—(执行波次借鉴 [ADR-006](../adr/ADR-006-composite-parallel-traversal.md) 同层并行思路,**不**扩展 Composite API) +> **关联 Issue**:[Spec #308](https://github.com/Skymly/DesignPatterns/issues/308);落地 #309–#311;本 Design Doc [#312](https://github.com/Skymly/DesignPatterns/issues/312) + +## 概述 + +**Fork–Join Work Graph**(API 前缀 `Work*`)提供**进程内异步 DAG**:步骤共享同一 `TContext`,边仅表示**就绪依赖**(非类型化 payload channel)。双路径装配: + +- **手动**:`WorkGraphBuilder` → `IWorkGraph.RunAsync` +- **属性 + 生成器**:`[WorkGraph]` / `[WorkGraph]` + `[WorkStep]` → `{Holder}WorkStepKeys` + `{Holder}WorkGraph.Create(resolver|dictionary)` + +执行语义为拓扑**波次**:同波可并发;失败则 cancel 同波 in-flight peers 并 **fail-fast** 抛出。库不隔离 / 合并 context——同波对 `TContext` 的重叠无同步写入由调用方禁止。 + +## 设计目标 + +1. Holder 固定图名与 `TContext`;步骤类型独立可测 +2. `[WorkStep(typeof(Holder), Id, DependsOn)]` 显式归属,允许多图共享同一 context 类型而不静默聚合 +3. 边 = readiness only;输出经共享 context 突变,不经 `TIn`/`TOut` channel +4. 运行时 `Build` 与生成器共享合法性矩阵(环 / 未知依赖 / 重复 id / 自依赖 / 契约) +5. 多根合法;不可达步骤仅 Warning(DP091) +6. MVP:async-only、无 Dop / 追踪 / DI / sync `Execute`、双 TFM + +## API 面 + +命名空间:`DesignPatterns.Behavioral`。 + +### 运行时 + +| 类型 | 职责 | +|------|------| +| `IWorkStep` | `ValueTask ExecuteAsync(TContext, CancellationToken)` | +| `IWorkGraph` | `ValueTask RunAsync(TContext, CancellationToken)` | +| `WorkGraphBuilder` | `Add(id, step, params dependsOn)` → `Build()` | +| `InvalidWorkGraphException` | 空图 / 重复 id / 自依赖 / 未知依赖 / 环 | +| `WorkGraphAttribute` / `WorkGraphAttribute` | 标记 holder(后者需 generic attributes,C# 11+ / net7+) | +| `WorkStepAttribute` | `Graph`、`Id`、`DependsOn`;`AllowMultiple = true` | + +```csharp +public interface IWorkStep +{ + ValueTask ExecuteAsync(TContext context, CancellationToken cancellationToken = default); +} + +public interface IWorkGraph +{ + ValueTask RunAsync(TContext context, CancellationToken cancellationToken = default); +} + +public sealed class WorkGraphBuilder +{ + public WorkGraphBuilder Add(string id, IWorkStep step, params string[] dependsOn); + public IWorkGraph Build(); // empty / illegal DAG → InvalidWorkGraphException +} +``` + +### 属性契约 + +- Holder:推荐 `static class`;`[WorkGraph(typeof(TContext))]` 或 `[WorkGraph]` +- Step:实现 `IWorkStep`;`[WorkStep(typeof(Holder), Id = "…", DependsOn = new[] { "…" })]` +- Id:图内唯一非空白字符串;`DependsOn` 引用同 holder 下已声明 id + +### 生成器产出 + +`WorkGraphGenerator` 发出 `{Holder}WorkGraph.g.cs`(同 holder 命名空间): + +| 生成符号 | 说明 | +|----------|------| +| `{Holder}WorkStepKeys` | `public const string` 步 id(Strategy/Factory Keys 先例) | +| `{Holder}WorkGraph.Create(Func>)` | 填 `WorkGraphBuilder` 后 `Build()` | +| `{Holder}WorkGraph.Create(IReadOnlyDictionary>)` | 字典重载,委托到 resolver 路径 | + +空 catalog 仍发出 `Create`,使运行时 `Build()` 抛 `InvalidWorkGraphException`(与手动空图一致)。无 MVP DI / Autofac 发射。 + +### 调用示例(示意) + +```csharp +[WorkGraph] +public static class RequestPrep { } + +[WorkStep(typeof(RequestPrep), Id = "auth")] +sealed class AuthStep : IWorkStep { /* … */ } + +[WorkStep(typeof(RequestPrep), Id = "load-config")] +sealed class LoadConfigStep : IWorkStep { /* … */ } + +[WorkStep(typeof(RequestPrep), Id = "build-principal", DependsOn = new[] { "auth", "load-config" })] +sealed class BuildPrincipalStep : IWorkStep { /* … */ } + +[WorkStep(typeof(RequestPrep), Id = "authorize", DependsOn = new[] { "build-principal" })] +sealed class AuthorizeStep : IWorkStep { /* … */ } + +// 手动 +var manual = new WorkGraphBuilder() + .Add(RequestPrepWorkStepKeys.Auth, new AuthStep()) + .Add(RequestPrepWorkStepKeys.LoadConfig, new LoadConfigStep()) + .Add(RequestPrepWorkStepKeys.BuildPrincipal, new BuildPrincipalStep(), + RequestPrepWorkStepKeys.Auth, RequestPrepWorkStepKeys.LoadConfig) + .Add(RequestPrepWorkStepKeys.Authorize, new AuthorizeStep(), + RequestPrepWorkStepKeys.BuildPrincipal) + .Build(); + +// 生成 +var generated = RequestPrepWorkGraph.Create(id => id switch +{ + RequestPrepWorkStepKeys.Auth => new AuthStep(), + RequestPrepWorkStepKeys.LoadConfig => new LoadConfigStep(), + RequestPrepWorkStepKeys.BuildPrincipal => new BuildPrincipalStep(), + RequestPrepWorkStepKeys.Authorize => new AuthorizeStep(), + _ => throw new ArgumentOutOfRangeException(nameof(id)), +}); + +await generated.RunAsync(new PrepContext(), CancellationToken.None); +``` + +## 执行模型 + +| 规则 | 行为 | +|------|------| +| 拓扑波次 | Kahn 风格 indegree;indegree 0 组成一波 | +| 同波 | 可并发(`Task.WhenAll`);单步波串行 `await` | +| Fail-fast | 第一步失败 → linked CTS cancel 同波 peers → 重抛失败(过滤取消噪声) | +| Context | 共享同一实例;无 isolate/merge;同波无同步写禁止 | +| 空图 | 非法 | +| 单节点 / 菱形 / 多根 | 合法 | + +## 诊断 + +全部为**生成器**诊断(MVP 无 Analyzer / CodeFix)。常量见 `DiagnosticIds`;描述符见 `DesignPatternsDiagnosticDescriptors`。运行时 `Build` 对空图 / 环 / 重复 / 自依赖 / 未知依赖抛 `InvalidWorkGraphException`(不复用 DP 号)。 + +| ID | 严重性 | 触发条件 | 建议动作(摘要) | +|----|--------|----------|------------------| +| **DP087** | Error | `DependsOn` 成环 | 移除或改派环边 | +| **DP088** | Error | `DependsOn` 引用未声明 id | 注册该 id 的 `[WorkStep]` 或删除依赖 | +| **DP089** | Error | 同 holder 下 id 重复 | 重命名使 id 唯一 | +| **DP090** | Error | 步依赖自身(与 DP087 分开) | 从 `DependsOn` 去掉自身 | +| **DP091** | Warning | 从任一根(无 `DependsOn`)不可达 | 接边或删除孤立步;多根本身合法 | +| **DP092** | Error | `[WorkStep]` 未实现 holder 的 `IWorkStep` | 实现契约或修正 holder / 特性 | + +未注册 `IWorkStep` Analyzer:**非** MVP(Phase 2+),以免强制手动 builder 走属性路径。 + +## 不变量 / 兼容基线 + +- 双 TFM:`netstandard2.0` + `net8.0`;generic attribute 形态仅在支持平台编译 +- Core **不**引用 MSDI([ADR-004](../adr/ADR-004-core-does-not-reference-msdi.md)) +- 无 AppDomain 反射扫描注册 +- Roslyn 4.8.0 增量生成器(`ForAttributeWithMetadataName`) +- 公开 API XML 文档;nullable enable;`TreatWarningsAsErrors` +- DP067–DP071 仍专属 [ADR-008](../adr/ADR-008-singleton-lifecycle-diagnostics.md),不得改派 + +## 实现概览 + +### 运行时 + +- `WorkGraphBuilder` 校验后计算波次,冻结为内部 `WorkGraph` +- `RunAsync` 逐波执行;多步波用 linked CTS + fail-fast + +### 源生成器 + +`WorkGraphGenerator`(`IIncrementalGenerator`): + +1. 收集 `[WorkGraph]` / `[WorkGraph]` holders 与 `[WorkStep]` 成员 +2. 按 holder 聚合 catalog → 诊断 DP087–DP092 +3. 成功则发出 Keys + `Create` facade(空 catalog 亦发 `Create`) + +### 诊断检测逻辑 + +见上表;归属均为 Generator。编译期图合法性可对照既有矩阵:Composite 环 / 孤儿(DP011/012 + schema)、State hierarchy(DP056–059)、Step Builder 偏序 / 未知引用(DP082/084)——本域对应 DP087–DP090 / DP091 / DP092,语义为 readiness DAG 而非树 schema 或构造偏序。 + +### 测试主缝 + +1. `DesignPatterns.Tests` — `Build` 校验与 `RunAsync` 波次 / fail-fast +2. `DesignPatterns.SourceGenerators.Tests` — Verify Keys/facade + 诊断快照 + +## 设计权衡 + +### 为何 readiness 边而非 typed channel + +与观望域 **Channel Pipeline** 划界:本域边只表达「何时可跑」,数据经共享 `TContext`。避免把 BCL `Channel` / TPL Dataflow 消息网拉进 Core。 + +### 为何 holder + 显式 `Graph` 归属 + +同 `TContext` 可有多图;若按 context 类型静默聚合会串图。显式 `typeof(Holder)` 与 Step Builder holder、Strategy Keys 先例一致。 + +### 为何自依赖单独 DP090 + +自依赖是局部、可立即修复的错误;与多节点环(DP087)分开便于消息与测试断言。 + +### 为何不可达仅 Warning + +多根(如 `Auth` ∥ `LoadConfig`)合法;真正孤立步应可见但不阻塞编译成功路径。 + +### 为何 MVP 无 DI + +属性 catalog 的步骤实例化留给 `Create(resolver)`;容器生命周期与 captive 分析留 Phase 2,避免 Core 碰 MSDI。 + +## 与生态的边界 + +### vs Channel Pipeline(观望) + +| | Work Graph | Channel Pipeline(未准入) | +|---|---|---| +| 边 | readiness id | 类型化 `TIn`/`TOut` / `Channel` | +| 数据 | 共享 `TContext` | 阶段间消息 | + +### vs Composite parallel(ADR-006) + +| | Work Graph | Composite `TraverseParallel*` | +|---|---|---| +| 结构 | 多前驱 DAG | 树 / 森林 | +| 并行单位 | 拓扑同波 | 同层 BFS / 子节点 | +| API | **不**扩展 `CompositeTraverser` | 树遍历专用 | + +### vs Step Builder + +| | Work Graph | Step Builder | +|---|---|---| +| 证明对象 | 执行就绪 DAG | 构造步完备(type-state) | +| 时间 | `RunAsync` 运行时波次 | `Build()` 前编译期门闩 | + +### vs Command Router + +1:1 CLR 命令分发,不是 fork–join 图。 + +### vs TPL Dataflow + +不是消息块网络 / actor mailbox;可消费 `Task`/`ValueTask`/`WhenAll`,但不包装 Dataflow。 + +## 已知局限(非目标 / Phase 2+) + +- **无** MVP `MaxDegreeOfParallelism` / run options +- **无** MVP `RunAsync` 追踪 / observer +- **无** MVP sync `Execute` +- **无** MVP aggregate / continue-on-error +- **无** MVP MSDI / Autofac 注册 +- **无** MVP 未注册 `IWorkStep` Analyzer +- **无** `TContext` isolate/merge 框架 +- **无** 类型化 payload 边 + +Samples(request-prep)落在 sibling [DesignPatterns.Samples](https://github.com/Skymly/DesignPatterns.Samples),不在本仓 Docs PR。 + +## 参考 + +- Spec:[Fork–Join Work Graph (#308)](https://github.com/Skymly/DesignPatterns/issues/308) +- 落地:#309 Runtime → #310 Diagnostics(DP087–DP092)→ #311 SourceGenerators → #312 Docs → Samples(sibling) +- Wayfinder:[#300](https://github.com/Skymly/DesignPatterns/issues/300) / 准入地图 [#244](https://github.com/Skymly/DesignPatterns/issues/244) +- [ADR-006](../adr/ADR-006-composite-parallel-traversal.md) — Composite 同层并行(对照,非依赖) +- [docs/ROADMAP.md](../ROADMAP.md) F3 Top-3 +- [AGENTS.md](../../AGENTS.md) — 模式摘要与诊断表 +- [StepBuilder.md](StepBuilder.md) — 构造完备 vs 执行 DAG +- [Composite.md](Composite.md) — 树并行遍历对照 +- [CommandRouter.md](CommandRouter.md) — 1:1 分发对照