Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)。
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<TContext>]` + `[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<TContext>` / `IWorkGraph<TContext>` / `WorkGraphBuilder<TContext>` with topological wave execution and fail-fast cancellation; `[WorkGraph]` / `[WorkGraph<TContext>]` / `[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

Expand Down
8 changes: 8 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<TContext>` with an explicit string id and optional `DependsOn` readiness edges on a named work-graph holder.
_Avoid_: command handler, pipeline stage, channel block
3 changes: 2 additions & 1 deletion docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)。
1 change: 1 addition & 0 deletions docs/design/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)) |
253 changes: 253 additions & 0 deletions docs/design/WorkGraph.md
Original file line number Diff line number Diff line change
@@ -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<TContext>` → `IWorkGraph<TContext>.RunAsync`
- **属性 + 生成器**:`[WorkGraph]` / `[WorkGraph<TContext>]` + `[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<TContext>` | `ValueTask ExecuteAsync(TContext, CancellationToken)` |
| `IWorkGraph<TContext>` | `ValueTask RunAsync(TContext, CancellationToken)` |
| `WorkGraphBuilder<TContext>` | `Add(id, step, params dependsOn)` → `Build()` |
| `InvalidWorkGraphException` | 空图 / 重复 id / 自依赖 / 未知依赖 / 环 |
| `WorkGraphAttribute` / `WorkGraphAttribute<TContext>` | 标记 holder(后者需 generic attributes,C# 11+ / net7+) |
| `WorkStepAttribute` | `Graph`、`Id`、`DependsOn`;`AllowMultiple = true` |

```csharp
public interface IWorkStep<TContext>
{
ValueTask ExecuteAsync(TContext context, CancellationToken cancellationToken = default);
}

public interface IWorkGraph<TContext>
{
ValueTask RunAsync(TContext context, CancellationToken cancellationToken = default);
}

public sealed class WorkGraphBuilder<TContext>
{
public WorkGraphBuilder<TContext> Add(string id, IWorkStep<TContext> step, params string[] dependsOn);
public IWorkGraph<TContext> Build(); // empty / illegal DAG → InvalidWorkGraphException
}
```

### 属性契约

- Holder:推荐 `static class`;`[WorkGraph(typeof(TContext))]` 或 `[WorkGraph<TContext>]`
- Step:实现 `IWorkStep<TContext>`;`[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<string, IWorkStep<TContext>>)` | 填 `WorkGraphBuilder` 后 `Build()` |
| `{Holder}WorkGraph.Create(IReadOnlyDictionary<string, IWorkStep<TContext>>)` | 字典重载,委托到 resolver 路径 |

空 catalog 仍发出 `Create`,使运行时 `Build()` 抛 `InvalidWorkGraphException`(与手动空图一致)。无 MVP DI / Autofac 发射。

### 调用示例(示意)

```csharp
[WorkGraph<PrepContext>]
public static class RequestPrep { }

[WorkStep(typeof(RequestPrep), Id = "auth")]
sealed class AuthStep : IWorkStep<PrepContext> { /* … */ }

[WorkStep(typeof(RequestPrep), Id = "load-config")]
sealed class LoadConfigStep : IWorkStep<PrepContext> { /* … */ }

[WorkStep(typeof(RequestPrep), Id = "build-principal", DependsOn = new[] { "auth", "load-config" })]
sealed class BuildPrincipalStep : IWorkStep<PrepContext> { /* … */ }

[WorkStep(typeof(RequestPrep), Id = "authorize", DependsOn = new[] { "build-principal" })]
sealed class AuthorizeStep : IWorkStep<PrepContext> { /* … */ }

// 手动
var manual = new WorkGraphBuilder<PrepContext>()
.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<TContext>` | 实现契约或修正 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<TContext>`
- `RunAsync` 逐波执行;多步波用 linked CTS + fail-fast

### 源生成器

`WorkGraphGenerator`(`IIncrementalGenerator`):

1. 收集 `[WorkGraph]` / `[WorkGraph<T>]` 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<T>` / 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<T>` |
| 数据 | 共享 `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 分发对照
Loading