Files
FlowScope/docs/specs/architecture-scope-container-implementation-spec.md
2026-06-18 12:20:20 +08:00

779 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FlowScope ArchitectureScope / Container 实现规格
## 背景
本文基于 `docs/requirements/scope-feature-architecture-requirements.md`,用于约束下一版 FlowScope 核心架构实现。
本轮设计不再沿用当前 `RootContext + FeatureScope` 的试验模型,也不继续在 `IFeatureScope``ICommandContext``IQueryContext` 上叠加规则。新方向是吸收 ft_3 Container 的核心能力和 API 手感,自研 FlowScope Container / Scope Tree。
第一阶段只解决一件事:
```text
用统一的 ArchitectureScope 建立 Root -> Feature -> Child Scope 的容器树、注册规则、解析规则和生命周期语义。
```
暂时不进入:
- Command / Query / Event。
- 完整 MVC / MVVM。
- UI Panel 生命周期统一接管。
- 自动扫描所有 Feature。
- 复杂诊断面板。
## 设计目标
1. Root、Feature、Child Scope 都是同一种 `ArchitectureScope`
2. Root 只是 `Parent == null` 的 Scope不拥有另一套接口模型。
3. Feature 是业务模块安装定义,不是另一种容器。
4. `CreateFeature<TFeature>()` 默认使用 `typeof(TFeature).Name` 创建 Scope并自动调用 `Feature.Install(scope)`
5. 任意 Scope 可以随时 `Register`,但只能写入自己。
6. `Resolve` 从当前 Scope 开始,找不到再向父 Scope 查找。
7. 子 Scope 可以覆盖父 Scope 的注册。
8. 默认业务边界是 `Root -> Feature`Child Scope 只作为复杂生命周期能力保留。
9. 实现必须避免“外层说 Scope-local Register内层只有 Root Register”的双重语义。
## 核心类型清单
### FlowScope
静态入口类型,负责创建根 Scope。
建议职责:
- `Create()` 创建一个没有 Parent 的 `ArchitectureScope`
- 不持有全局单例。
- 不提供静态 `Resolve<T>()`
建议 API
```csharp
public static class FlowScope
{
public static IArchitectureScope Create();
public static IArchitectureScope Create(string rootName);
}
```
### ArchitectureScope
统一 Scope 节点的内部实现类型。
建议职责:
- 保存 Scope 名称、父节点、子节点。
- 保存当前 Scope 的注册表。
- 保存当前 Scope 持有的实例和释放栈。
- 执行 Register / Resolve / CreateScope / CreateFeature / Dispose。
- 对外通过 `IArchitectureScope` 暴露。
Root、Feature、Child Scope 不应分别实现不同运行时类型。它们的差异来自创建位置和安装内容,而不是来自继承层级。
### IArchitectureScope
业务侧默认接触的 Scope 接口。
它既是容器访问入口,也是规则约束入口。
```csharp
public interface IArchitectureScope : IScopeInfo, IDisposable
{
IArchitectureScope CreateScope(string name);
IArchitectureScope CreateFeature<TFeature>()
where TFeature : IFeature, new();
IArchitectureScope CreateFeature<TFeature>(string name)
where TFeature : IFeature, new();
IRegistrationBuilder Register<TImplementation>();
IRegistrationBuilder Register<TImplementation>(string name);
IRegistrationBuilder Register<TService, TImplementation>()
where TImplementation : TService;
IRegistrationBuilder Register<TService, TImplementation>(string name)
where TImplementation : TService;
void RegisterInstance<TService>(TService instance);
void RegisterInstance<TService>(TService instance, string name);
TService Resolve<TService>();
TService Resolve<TService>(string name);
bool TryResolve<TService>(out TService value);
bool TryResolve<TService>(out TService value, string name);
}
```
### IScopeInfo
只读 Scope 元信息,避免业务代码通过 Parent 写父级。
```csharp
public interface IScopeInfo
{
string Name { get; }
IScopeInfo Parent { get; }
string Path { get; }
bool IsDisposed { get; }
}
```
`Parent` 第一阶段建议只暴露为 `IScopeInfo`,而不是 `IArchitectureScope`。如果后续需要高级管理能力,应通过单独的内部接口或显式管理 API 暴露。
### IFeature
Feature 是业务模块安装定义。
```csharp
public interface IFeature
{
void Install(IArchitectureScope scope);
}
```
第一阶段不要求 `IFeature : IDisposable`。Feature 自身不是生命周期容器Feature 相关对象的生命周期应由安装后的 Scope 管理。
如果某个 Feature 安装对象确实需要释放,应把它注册进 Scope或由它在 `Install` 中注册需要释放的服务。
### ServiceKey
注册和解析使用的键。
```csharp
public readonly struct ServiceKey : IEquatable<ServiceKey>
{
public Type Type { get; }
public string Name { get; }
}
```
语义:
- `Type` 必须非空。
- `Name` 可以为空或空字符串,但内部应归一化为同一种无名状态。
- `(Type, Name)` 完全相同才视为同一个注册。
### Registration
注册记录。
建议字段:
```csharp
internal sealed class Registration
{
public ServiceKey Key { get; }
public Type ImplementationType { get; }
public object Instance { get; }
public ServiceLifetime Lifetime { get; }
public bool OwnsInstance { get; }
}
```
`Registration` 只描述“如何创建”,不应把所有 PerScope 缓存都放在注册所在 Scope 上。
### ServiceLifetime
第一阶段支持四种生命周期。
```csharp
public enum ServiceLifetime
{
Singleton,
PerScope,
Transient,
Instance
}
```
### IRegistrationBuilder
声明生命周期的构建器。
```csharp
public interface IRegistrationBuilder
{
void AsSingleton();
void PerScope();
void AsTransient();
}
```
第一阶段建议每次 `Register` 返回一个待完成 builder只有调用生命周期方法后才写入注册表或者默认 `PerScope`,但必须在 spec 与测试中固定。为了避免隐式行为,推荐第一阶段要求显式调用生命周期方法。
## 接口设计
第一阶段接口应围绕“统一 Scope 节点”组织,而不是围绕 Root / Feature 两种上下文组织。
公开接口分三层:
```text
FlowScope
创建 Root Scope。
IArchitectureScope
默认业务接口,负责创建子 Scope、创建 Feature、注册、解析和释放。
IScopeInfo
只读诊断接口,暴露 Name / Parent / Path / IsDisposed。
```
Feature 接口只负责安装:
```text
IFeature.Install(IArchitectureScope scope)
```
不在第一阶段公开:
- `IRootContext`
- `IFeatureScope`
- `ICommandContext`
- `IQueryContext`
- `IEventContext`
原因是这些接口会把核心重新拉回“Root 特殊、Feature 特殊、通信模型提前进入核心”的旧方向。第一阶段所有业务和测试都应以 `IArchitectureScope` 为主入口。
## 容器生命周期语义
### Singleton
`Singleton` 表示注册所在 Scope 持有唯一实例。
解析规则:
- 在某个 Scope 找到 Singleton 注册后,实例缓存到该注册所在 Scope。
- 子 Scope Resolve 到父 Scope 的 Singleton 注册时,拿到父 Scope 的同一个实例。
- 子 Scope Dispose 不释放父 Scope Singleton。
- 注册所在 Scope Dispose 时释放该 Singleton 实例。
适用:
- Root 注册全局服务。
- Feature 注册该 Feature 内唯一共享服务。
### PerScope
`PerScope` 表示每个发起 Resolve 的 Scope 持有一个实例。
解析规则:
- 从当前 Scope 开始查找注册。
- 找到注册后,实例缓存到“发起 Resolve 的 Scope”不是注册所在 Scope。
- 同一个发起 Scope 多次 Resolve 返回同一个实例。
- 不同子 Scope Resolve 同一个父级 PerScope 注册时,各自得到不同实例。
- 发起 Scope Dispose 时释放自己缓存的 PerScope 实例。
这是吸收 ft_3 Container 后最重要的生命周期能力,用于“同一个注册规则,在不同 Scope 下得到不同运行实例”。
### Transient
`Transient` 表示每次 Resolve 都创建新实例。
解析规则:
- 不缓存。
- 如果创建出的实例实现 `IDisposable`,第一阶段建议由发起 Scope 跟踪并在 Dispose 时释放。
- 如果不跟踪 Transient Disposable必须在接口文档里明确调用方负责释放。为了减少 Unity 项目泄漏风险,推荐跟踪释放。
### Instance
`Instance` 表示注册外部传入实例。
解析规则:
- 每次 Resolve 返回同一个传入对象。
- 默认 `OwnsInstance = false`Scope Dispose 不释放外部传入实例。
- 如后续需要 Scope 接管实例释放,可以增加显式 API例如 `RegisterOwnedInstance<T>()`,第一阶段不做。
### Dispose 顺序
Scope Dispose 必须满足:
1. 幂等,多次 Dispose 不重复释放。
2. 先 Dispose 子 Scope再 Dispose 当前 Scope 自己持有的实例。
3. 子 Scope Dispose 不影响父 Scope。
4. 父 Scope Dispose 会递归 Dispose 所有子 Scope。
5. 当前 Scope Dispose 后不能继续 Register / Resolve / CreateScope / CreateFeature。
6. Dispose 过程中出现多个异常时,第一阶段可以选择:
- 记录第一个异常并继续释放剩余对象,最后抛出聚合异常。
- 或使用 `AggregateException`
建议使用 LIFO 顺序释放当前 Scope 持有的实例,使后创建对象先释放。
## Register 算法
### 规则
1. 任意 Scope 可以随时 Register。
2. Register 永远只写当前 Scope。
3. Register 不会向父 Scope 写入。
4. Register 不会向子 Scope 广播。
5. 当前 Scope 已 Dispose 时 Register 失败。
6. 同一 Scope 内相同 `ServiceKey` 重复注册,第一阶段建议直接失败,避免隐式覆盖。
子 Scope 覆盖父 Scope 不是通过修改父注册实现,而是子 Scope 自己注册相同 `ServiceKey`
### 算法
```text
Register(scope, key, registration):
1. 如果 scope 已 Dispose抛 ObjectDisposedException。
2. 校验 key.Type 非空。
3. 归一化 key.Name。
4. 如果 scope._registrations 已存在 key抛 DuplicateRegistrationException。
5. 把 registration 写入 scope._registrations。
6. 返回或完成 builder。
```
### 命名注册
命名注册必须和无名注册共存。
```text
Register<IRewardService, DefaultRewardService>()
Register<IRewardService, BossRewardService>("boss")
```
以上是两个不同 `ServiceKey`
## Resolve 算法
### 规则
1. Resolve 从当前 Scope 开始。
2. 当前 Scope 找不到时,再向 Parent 查找。
3. 查找路径直到 Root。
4. 子 Scope 的相同 `ServiceKey` 会覆盖父 Scope。
5. Resolve 不允许绕过当前 Scope 直接查 Root。
6. 当前 Scope 已 Dispose 时 Resolve 失败。
### 查找算法
```text
FindRegistration(scope, key):
current = scope
while current != null:
if current._registrations.TryGetValue(key, out registration):
return (registrationOwner: current, registration)
current = current.Parent
return null
```
### 创建算法
```text
Resolve(scope, key):
1. 如果 scope 已 Dispose抛 ObjectDisposedException。
2. result = FindRegistration(scope, key)。
3. 如果找不到,抛 ServiceNotFoundException。
4. 根据 registration.Lifetime 创建或读取实例。
5. 返回实例。
```
生命周期分支:
```text
Singleton:
缓存位置 = registrationOwner
若缓存不存在,在 registrationOwner 中创建并记录释放
返回 registrationOwner 缓存
PerScope:
缓存位置 = resolveStartScope
若缓存不存在,在 resolveStartScope 中创建并记录释放
返回 resolveStartScope 缓存
Transient:
每次创建新实例
若实例需要 Scope 接管释放,记录到 resolveStartScope 释放栈
返回新实例
Instance:
返回 registration.Instance
```
### TryResolve 算法
```text
TryResolve(scope, key, out value):
1. 如果 scope 已 Dispose抛 ObjectDisposedException。
2. 如果找不到注册value = default返回 false。
3. 如果找到注册,走 Resolve 创建流程。
4. 创建成功返回 true。
5. 创建过程中发生构造异常,不吞异常。
```
`TryResolve` 只表示“是否存在注册”,不表示“构造失败也返回 false”。
## CreateScope 算法
### 规则
1. 任意未 Dispose Scope 都可以创建子 Scope。
2. 子 Scope 的 Parent 指向当前 Scope。
3. 子 Scope 加入当前 Scope 的 Children 集合。
4. 子 Scope 名称由调用方传入。
5. 子 Scope 名称用于诊断和 Path不作为服务注册 key。
6. 同一父级下是否允许重名第一阶段建议禁止,便于诊断。
### 算法
```text
CreateScope(parent, name):
1. 如果 parent 已 Dispose抛 ObjectDisposedException。
2. 校验 name 非空。
3. 如果 parent.Children 已存在同名未释放 Scope抛 DuplicateScopeNameException。
4. 创建 child = new ArchitectureScope(name, parent)。
5. parent.Children.Add(child)。
6. 返回 child。
```
`Path` 示例:
```text
Root
Root/BossFightFeature
Root/BossFightFeature/RevivePanel
```
## CreateFeature 算法
### 规则
1. `CreateFeature<TFeature>()` 默认使用 `typeof(TFeature).Name`
2. `CreateFeature<TFeature>(name)` 创建当前 Scope 的子 Scope。
3. 创建成功后实例化 `TFeature`
4. 自动调用 `feature.Install(featureScope)`
5. 如果 Install 失败,必须 Dispose 已创建的 featureScope并从父级 Children 中移除。
6. 调用者不应手写 Scope 创建和 Feature 安装流程。
### 第一阶段接口
```csharp
IArchitectureScope CreateFeature<TFeature>()
where TFeature : IFeature, new();
IArchitectureScope CreateFeature<TFeature>(string name)
where TFeature : IFeature, new();
```
### 算法
```text
CreateFeature<TFeature>(parent):
return CreateFeature<TFeature>(parent, typeof(TFeature).Name)
CreateFeature<TFeature>(parent, name):
1. featureScope = parent.CreateScope(name)。
2. try:
feature = new TFeature()
feature.Install(featureScope)
return featureScope
catch:
featureScope.Dispose()
从 parent.Children 移除 featureScope
rethrow
```
### 后续扩展点
第一阶段先使用 `new()` 约束,避免把构造注入、工厂注册和 Feature 安装绑在一起。
后续如需 Feature 构造依赖注入,可增加:
```csharp
IArchitectureScope CreateFeature<TFeature>(string name, Func<IArchitectureScope, TFeature> factory)
where TFeature : IFeature;
```
不要在第一阶段默认扫描或反射构造 Feature。
## Dispose 算法
### Scope 状态
建议内部状态:
```text
Active
Disposing
Disposed
```
### 算法
```text
Dispose(scope):
1. 如果 scope.State == Disposed直接返回。
2. 如果 scope.State == Disposing直接返回或抛 InvalidOperationException推荐直接返回保证幂等。
3. scope.State = Disposing。
4. 复制 Children 列表。
5. 按创建顺序反向 Dispose 子 Scope。
6. 清空 Children。
7. 按 LIFO Dispose 当前 Scope 持有的实例。
8. 清空当前 Scope 的 PerScope / Singleton / Transient 跟踪缓存。
9. 从 Parent.Children 中移除自己。
10. scope.State = Disposed。
11. 如有释放异常,抛出聚合异常。
```
### 持有关系
当前 Scope 只释放自己持有的对象:
- 当前 Scope 创建的 Singleton。
- 当前 Scope 发起 Resolve 后缓存的 PerScope。
- 当前 Scope 接管的 Transient Disposable。
- 当前 Scope 创建的子 Scope。
当前 Scope 不释放:
- 父 Scope 持有的 Singleton。
- 父 Scope 持有的 PerScope 实例。
- 默认 RegisterInstance 传入的外部对象。
## 文件组织建议
第一阶段建议把核心集中在 Runtime Core不引入 Command / Query / Event 目录。
```text
My project/Assets/FlowScope/Runtime/
Core/
FlowScope.cs
IArchitectureScope.cs
IScopeInfo.cs
IFeature.cs
Core/Container/
ArchitectureScope.cs
ServiceKey.cs
Registration.cs
RegistrationBuilder.cs
ServiceLifetime.cs
ScopeExceptions.cs
```
测试建议:
```text
My project/Assets/FlowScope/Tests/EditMode/
Core/
ArchitectureScopeRegistrationTests.cs
ArchitectureScopeResolveTests.cs
ArchitectureScopeFeatureTests.cs
ArchitectureScopeDisposeTests.cs
```
如果后续迁移到 package 结构,应保持相同命名空间和目录职责:
```text
Packages/com.flowscope.kernel/Runtime/Core/
Packages/com.flowscope.kernel/Runtime/Core/Container/
Packages/com.flowscope.kernel/Tests/EditMode/Core/
```
命名空间建议:
```text
FlowScope
FlowScope.Container
```
不要继续把新核心放到 `FlowScope.Composition`,避免和旧 `RootContext + FeatureScope + Command/Query` 试验模型混在一起。
## 测试用例清单
### Root / Scope 统一模型
- `Create_ReturnsRootArchitectureScope`
- `RootScope_HasNoParent`
- `ChildScope_UsesSameArchitectureScopeContractAsRoot`
- `ScopePath_BuildsFromRootToChild`
### Register 规则
- `Register_WritesOnlyCurrentScope`
- `Register_OnDisposedScopeThrows`
- `Register_DuplicateKeyInSameScopeThrows`
- `Register_SameKeyInChildOverridesParentWithoutChangingParent`
- `Register_NamedAndUnnamedKeysAreIndependent`
- `RegisterInstance_DefaultDoesNotDisposeExternalInstance`
### Resolve 规则
- `Resolve_FindsCurrentScopeRegistrationFirst`
- `Resolve_FallsBackToParentWhenCurrentMissing`
- `Resolve_ChildRegistrationOverridesParent`
- `Resolve_MissingRegistrationThrows`
- `TryResolve_MissingRegistrationReturnsFalse`
- `TryResolve_ConstructorFailureThrows`
- `Resolve_OnDisposedScopeThrows`
### 生命周期
- `Singleton_IsCachedInRegistrationOwnerScope`
- `Singleton_FromParentIsSharedByChildren`
- `PerScope_IsCachedInResolveStartScope`
- `PerScope_FromParentCreatesDifferentInstancesPerChildScope`
- `Transient_CreatesNewInstanceEveryResolve`
- `Transient_DisposableIsReleasedWhenOwningScopeDisposes`
- `ChildDispose_DoesNotDisposeParentOwnedInstances`
- `ParentDispose_DisposesChildrenAndOwnedInstances`
- `Dispose_IsIdempotent`
- `Dispose_UsesLifoForOwnedInstances`
### CreateScope
- `CreateScope_CreatesChildWithParent`
- `CreateScope_OnDisposedParentThrows`
- `CreateScope_EmptyNameThrows`
- `CreateScope_DuplicateSiblingNameThrows`
- `ChildDispose_RemovesChildFromParent`
### CreateFeature
- `CreateFeature_UsesFeatureTypeNameAsDefaultScopeName`
- `CreateFeature_WithNameUsesExplicitName`
- `CreateFeature_CallsInstallWithCreatedScope`
- `CreateFeature_ReturnsInstalledScope`
- `CreateFeature_InstallCanRegisterServicesToFeatureScope`
- `CreateFeature_InstallFailureDisposesCreatedScope`
- `CreateFeature_InstallFailureRemovesScopeFromParent`
### 默认业务边界
- `FeatureScope_CanResolveRootServices`
- `FeatureScope_CanOwnFeatureServices`
- `TwoFeatureScopes_DoNotSharePerScopeFeatureServices`
- `ChildScope_CanOverrideFeatureServiceForLocalLifetime`
- `FeatureWithoutChildScope_IsEnoughForNormalBusinessServices`
## 当前旧实现迁移 / 替换策略
### 要替换的旧模型
当前 `My project/Assets/FlowScope/Runtime/Composition` 中的试验模型应作为替换对象,而不是继续扩展对象。
旧模型特征:
- `IRootContext``IFeatureScope` 是不同接口。
- `RootContext``FeatureScope` 是不同实现。
- `IFeatureScope` 同时混入 `IArchitectureScope``ICommandContext``IQueryContext`
- `CreateFeatureScope(name)` 只创建 Feature Scope没有统一 Child Scope 模型。
- Command / Query / Architecture roles 与 Scope 基础能力耦合过早。
新模型应改为:
```text
FlowScope.Create()
-> IArchitectureScope Root
Root.CreateFeature<TFeature>()
-> IArchitectureScope Feature
Feature.CreateScope("LocalLifetime")
-> IArchitectureScope Child
```
### 迁移步骤建议
第一阶段迁移只做核心替换,不迁移 Command / Query / Event。
1. 新增 `Core` / `Core.Container` 下的统一 Scope 类型和接口。
2. 新增测试覆盖 Register / Resolve / CreateFeature / Dispose。
3. 将旧 Composition 测试中仍有价值的用例迁移到新 Core 测试:
- Root 服务可被 Feature Resolve。
- Feature 级服务彼此隔离。
- 命名注册可解析。
4. 删除或降级旧 `RootContext` / `FeatureScope` 作为历史试验实现。
5. 暂时不把旧 `ICommandContext` / `IQueryContext` 接回新 Scope。
6. 后续如果要恢复 Command / Query / Event应作为 Scope 上层扩展模块,而不是第一阶段核心职责。
### 兼容策略
第一阶段不建议保留旧 API 兼容层。
原因:
- `CreateFeatureScope(name)` 会把心智继续拉回“Root 特殊、Feature 特殊”的两层模型。
- `IFeatureScope` 会让调用侧继续依赖旧接口名。
- Command / Query 混入旧 FeatureScope 会污染新核心边界。
如果必须短期兼容样例,可用单独 adapter 文件临时桥接,但不得作为新核心主入口:
```text
LegacyCompositionAdapter
CreateFeatureScope(name) -> root.CreateScope(name)
```
该 adapter 应标记为迁移期代码,并在第一阶段验收后移除。
## 第一阶段验收标准
### 文档与边界
1. 新核心 spec 明确以 `ArchitectureScope` 为唯一 Scope 模型。
2. 文档明确旧 `RootContext + FeatureScope` 是替换对象。
3. 文档明确第一阶段不进入 Command / Query / Event、完整 MVC/MVVM、UI Panel 生命周期统一接管。
### API
1. 存在 `FlowScope.Create()` 根入口。
2. 存在统一 `IArchitectureScope`
3. `Root``Feature``Child` 都通过 `IArchitectureScope` 表达。
4. 存在 `IFeature.Install(IArchitectureScope scope)`
5. 存在 `CreateFeature<TFeature>()``CreateFeature<TFeature>(string name)`
6. `CreateFeature<TFeature>()` 默认 Scope 名称为 `typeof(TFeature).Name`
### Register / Resolve
1. 任意 Scope 可以 Register。
2. Register 只写当前 Scope。
3. Resolve 从当前 Scope 开始。
4. Resolve 找不到时向 Parent 查找。
5. 子 Scope 相同 key 覆盖父 Scope。
6. 子 Scope 不能修改父 Scope 注册。
### 生命周期
1. 支持 `Singleton``PerScope``Transient``Instance`
2. 父级 Singleton 被子级共享。
3. 父级 PerScope 注册在不同子 Scope 下生成不同实例。
4. 子 Scope Dispose 不释放父 Scope 对象。
5. 父 Scope Dispose 会释放全部子 Scope。
6. Dispose 幂等。
### 测试
1. EditMode 测试覆盖 Root / Feature / Child 统一模型。
2. EditMode 测试覆盖 Register 只写当前 Scope。
3. EditMode 测试覆盖 Resolve 向父级查找与子级覆盖。
4. EditMode 测试覆盖 CreateFeature 默认名称、自动 Install、失败清理。
5. EditMode 测试覆盖 Dispose 递归释放、子不释放父、幂等。
### 负向验收
第一阶段完成时,不应出现:
- 新核心依赖旧 `RootContext`
- 新核心依赖旧 `FeatureScope`
- 新核心把 Command / Query / Event 作为必需接口。
- 新核心要求每个 UI Panel 都创建 Scope。
- 新核心要求 Feature 内部必须使用 MVC 或 MVVM。
## 后续阶段预留
后续可以在 Scope Tree 稳定后再讨论:
- Command / Query / Event 作为扩展模块如何挂到 Scope。
- Feature 内部 MVC / MVVM 推荐层。
- UI Panel 生命周期是否需要独立模块接管。
- Scope 诊断树、注册来源、Resolve Trace。
- Feature 工厂的依赖注入构造。
- 自动扫描 Feature 的 marker-based 方案。
这些能力不应反向改变第一阶段核心事实:
```text
FlowScope 的第一主角是统一 ArchitectureScope。
```