Files
FlowScope/docs/requirements/scope-feature-architecture-requirements.md
2026-06-18 12:20:20 +08:00

579 lines
16 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 Scope / Feature 架构需求草案
## 目标
这份文档用于约束下一版 FlowScope 核心架构实现。目标不是复刻 QFramework也不是照搬 ft_3 当前容器用法,而是形成一套能服务真实 Unity 项目的可落地框架:
- 能力向 ft_3 靠拢:支持动态注册、父子 Scope、局部覆盖、运行期灵活组合。
- 规则向 QFramework 靠拢:业务代码默认在当前架构上下文中工作,不鼓励直接拿根容器乱 Resolve。
- 默认使用简单:大多数业务只需要 `Root -> Feature` 两级。
- 保留扩展能力:复杂业务可以继续创建子 Scope但不把每个 UI、流程、函数调用都强制变成 Scope。
## 核心结论
下一版的第一主角应该是统一的 `ArchitectureScope`,不是固定的 `RootContext + FeatureScope` 两层结构。
`RootScope``FeatureScope``ChildScope` 本质都是同一种 Scope只是所在层级和安装内容不同
```text
Root Scope
BossFightFeature Scope
ScratchTicketFeature Scope
```
必要时可以继续细分:
```text
Root Scope
BossFightFeature Scope
BattleRound Scope
RevivePanel Scope
```
但这不是默认推荐路径。默认推荐是每个活动、玩法、业务模块对应一个 Feature ScopeFeature 内部再按 MVC、MVVM、MVP、Controller、ViewModel、Service、State Machine 等方式组织。
## 概念定义
### ArchitectureScope
`ArchitectureScope` 是框架的核心上下文,也是受规则约束的容器节点。
它负责:
- 保存当前 Scope 的注册项。
- 从当前 Scope 开始解析依赖。
- 找不到时向父 Scope 查找。
- 创建子 Scope。
- 创建 Feature Scope。
- 管理当前 Scope 生命周期。
- 允许当前 Scope 覆盖父 Scope 的能力。
它不应该只是一个裸 DI Container而是一个带层级、生命周期和使用规则的运行时上下文。
### RootScope
`RootScope` 是没有父节点的 `ArchitectureScope`
它不应该拥有一套和普通 Scope 不同的能力。它只是树的根:
```text
RootScope = Parent == null 的 ArchitectureScope
```
Root 适合注册跨 Feature 的基础能力,例如:
- 配置服务
- 资源服务
- 存档服务
- 音频服务
- 网络服务
- 全局账号/环境信息
Root 不应该成为业务代码到处直接访问的全局服务定位器。
### Feature
`Feature` 是一个业务模块的安装定义,不是另一种容器。
一个 Feature 代表一个可独立理解的业务边界,例如:
- BossFightFeature
- ScratchTicketFeature
- Event1v1BattleFeature
- MainMenuFeature
创建 Feature 时,框架会自动创建一个新的 `ArchitectureScope`,然后把 Feature 安装到这个 Scope 上。
推荐 API
```csharp
var boss = game.CreateFeature<BossFightFeature>();
```
等价概念:
```csharp
var boss = game.CreateScope("BossFight");
new BossFightFeature().Install(boss);
```
但调用侧不应该手写安装流程。
### Child Scope
Child Scope 是 Feature 内部或任意 Scope 下的局部生命周期工具。
它适合处理:
- 生命周期明显短于 Feature 的对象。
- 需要局部覆盖某个服务的场景。
- 一组临时对象需要一起释放的场景。
- 同一个 Feature 内存在多个并行实例的场景。
- 特殊流程需要隔离依赖解析的场景。
它不适合处理:
- 普通 UI Panel。
- 普通 ViewModel。
- 普通 Controller。
- 普通业务函数调用。
- 只是为了“看起来更分层”的拆分。
默认原则:能放在 Feature 内部 MVC/MVVM 结构里的,不要先拆成 Scope。
## Register / Resolve 规则
这版设计不应该限制只能在安装期 Register。
更准确的规则是:
```text
Register 本身不是问题。
问题是越级注册、越级修改、污染父级、绕开当前 Scope 解析。
```
因此下一版需要遵守:
1. 任意 Scope 都可以随时 Register。
2. Register 永远只写当前 Scope。
3. Resolve 永远从当前 Scope 开始。
4. 当前 Scope 找不到时,才向 Parent 查找。
6. 子 Scope 可以覆盖父 Scope 的注册。
6. 子 Scope 不能修改父 Scope 的注册。
7. Root 只是普通 Scope 的根节点,不是特殊全局服务定位器。
8. Feature 是创建出来并自动安装的 Scope。
示例:
```csharp
var game = FlowScope.Create();
game.Register<IConfigService, ConfigService>().AsSingleton();
var boss = game.CreateFeature<BossFightFeature>();
boss.Register<IRewardService, BossRewardService>().PerScope();
using var revive = boss.CreateScope("RevivePanel");
revive.Register<IRewardService, ReviveRewardService>().PerScope();
var reward = revive.Resolve<IRewardService>();
```
解析顺序:
```text
RevivePanel Scope -> BossFight Scope -> Root Scope
```
此时 `revive.Register` 不会污染 `boss``boss.Register` 不会污染 `root`
## 容器策略
下一版不应该只包装 ft_3 的 `asap.core.Container`,而应该吸收它的核心设计,形成 FlowScope 自己的 Container。
已通过 `asap.core.dll` 元数据确认ft_3 当前底层容器具备这些值得保留的设计:
- `(Type, string)` 作为注册 key。
- `Register<T>().AsSingleton()` 的声明式生命周期 API。
- `Register<TService, TImplementation>().AsSingleton()` 的接口到实现注册。
- `Register<T>(name).PerScope()` 的命名注册。
- `Register(Type serviceType, string name, Type implementationType).PerScope()` 的运行时类型注册。
- `RegisterInstance(instance)` 的现有实例注册。
- `Resolve<T>()` / `Container.IScope.GetInstance(...)` 的解析入口。
- `CreateScope()` 的 Scope 生命周期入口。
- `PerScope()` 通过 Scope lifetime 在具体 Scope 解析时生成/缓存实例。
同时也确认了 ft_3 当前公开接口的限制:
- `Container.IScope` 公开接口只有 `GetInstance((Type, string))`
- `Container.IScope` 没有公开 `Parent` 属性。
- `Container.IScope` 没有公开 `Register` 方法。
- 底层注册入口主要在根 `Container` 上,不是每个 Scope 都能注册到自己。
- 底层只确认有 `AsSingleton()``PerScope()`,没有确认 `AsTransient()`
- 底层没有确认存在 `TryResolve<T>()`
因此下一版不采用“薄包装 asap.core.Container”的方案。
原因是 FlowScope 当前目标和 ft_3 底层公开模型不完全一致:
```text
ft_3 当前模型Root Container 注册Scope 负责按 Scope 生命周期解析/缓存。
FlowScope 目标模型:每个 ArchitectureScope 都可以注册到自己,并从自己开始 Resolve再向父级查找。
```
如果只包装,会出现两套语义:外层说 Scope-local Register内层却只有 Root Register。长期看会让 `ArchitectureScope` 变成补丁层,代码容易拧巴。
下一版应该选择:
```text
借鉴 ft_3 Container 的算法和 API 手感
但实现 FlowScope 自己的 Container / Scope Tree
```
## FlowScope Container 目标模型
FlowScope 的容器节点应该同时承担三件事:
```text
Container 负责能力
ArchitectureScope 负责规则
Feature 工厂负责业务模块创建
```
但实现上不再拆成“外层 Scope 包内层 Container.IScope”而是统一为 FlowScope 自己的 Scope 节点:
```text
ContainerScope / ArchitectureScope
Name
Parent
Children
Dictionary<ServiceKey, Registration>
CreateScope()
CreateFeature<TFeature>()
Register()
Resolve()
TryResolve()
Dispose()
```
其中 `ServiceKey` 继续沿用 ft_3 的核心思路:
```csharp
public readonly struct ServiceKey
{
public Type Type { get; }
public string Name { get; }
}
```
## 生命周期规则
下一版容器至少支持:
```text
Singleton
注册所在 Scope 持有一个实例。
PerScope
每个 Resolve 发起 Scope 持有一个实例。
Transient
每次 Resolve 都创建新实例。
Instance
注册现有实例。是否 Dispose 需要显式规则,默认不销毁外部传入实例更安全。
```
`PerScope` 是从 ft_3 里最值得保留的能力之一。它适合活动、Feature、局部流程这种“同一个注册不同 Scope 下得到不同实例”的场景。
## Register / Resolve 目标规则
FlowScope 自己的 Container 需要直接支持当前讨论确定的规则:
1. 任意 Scope 都可以随时 Register。
2. Register 永远只写当前 Scope。
3. Resolve 永远从当前 Scope 开始。
4. 当前 Scope 找不到时,才向 Parent 查找。
5. 子 Scope 可以覆盖父 Scope 的注册。
6. 子 Scope 不能修改父 Scope 的注册。
7. Root 只是没有 Parent 的普通 Scope。
8. Parent / Children 是 FlowScope 自己容器模型的一部分,不依赖底层外部容器。
示例:
```csharp
var game = FlowScope.Create();
game.Register<IConfigService, ConfigService>().AsSingleton();
var boss = game.CreateFeature<BossFightFeature>();
boss.Register<IRewardService, BossRewardService>().PerScope();
using var revive = boss.CreateScope("RevivePanel");
revive.Register<IRewardService, ReviveRewardService>().PerScope();
var reward = revive.Resolve<IRewardService>();
```
解析顺序:
```text
RevivePanel Scope -> BossFightFeature Scope -> Root Scope
```
`revive.Register` 不污染 `boss``boss.Register` 不污染 `root`
## CreateFeature 规则
`CreateFeature<TFeature>()` 是 Scope 创建 + Feature 安装的语义封装。
调用者不传 name 时,默认使用 `typeof(TFeature).Name`
```csharp
var boss = game.CreateFeature<BossFightFeature>();
// 等价于 game.CreateFeature<BossFightFeature>(typeof(BossFightFeature).Name)
```
推荐 API
```csharp
IArchitectureScope CreateFeature<TFeature>()
where TFeature : IFeature, new();
IArchitectureScope CreateFeature<TFeature>(string name)
where TFeature : IFeature, new();
```
第一版可以先使用 `where TFeature : IFeature, new()`,保持简单:
```csharp
public IArchitectureScope CreateFeature<TFeature>(string name)
where TFeature : IFeature, new()
{
var child = CreateScope(name);
var feature = new TFeature();
feature.Install(child);
return child;
}
```
后续如果 `TFeature` 需要依赖注入构造,可以改为从当前 Scope 创建:
```csharp
child.Register<TFeature>().PerScope();
var feature = child.Resolve<TFeature>();
feature.Install(child);
```
调用侧不应该手写:
```csharp
boss.InstallFeature(new BossFightFeature());
boss.UseArchitecture(new BossFightArchitecture());
```
Feature 的安装和 Scope 的创建应该被 `CreateFeature<TFeature>()` 收住。
## Feature 默认边界
大多数业务默认只需要:
```text
Root Scope
Feature Scope
```
例如:
```text
Root Scope
BossFightFeature Scope
ScratchTicketFeature Scope
```
BossFight 内部的战斗、复活、奖励、UI不应该默认都拆成 Scope。它们可以优先放在 Feature 内部架构里:
```text
BossFightFeature
Model
Service
Controller
ViewModel
ViewBinder
StateMachine
```
只有当局部生命周期、依赖覆盖、并行实例、独立释放这些需求明确存在时,才创建 Child Scope。
## Feature 与 MVC / MVVM
`Feature` 只定义业务模块边界,不强制决定内部表现层架构。
Feature 内部可以选择:
- MVC
- MVVM
- MVP
- Presenter
- Flow Controller
- State Machine
- 简单 Service + View 组合
框架不应该强制所有 Feature 使用同一种 UI 或业务组织模式。
示例:
```csharp
public sealed class BossFightFeature : IFeature
{
public void Install(IArchitectureScope scope)
{
scope.Register<BossFightModel>().PerScope();
scope.Register<IBossFightService, BossFightService>().PerScope();
scope.UseMvvm<BossFightViewModel>();
scope.UseMvc<BossFightController>();
}
}
```
`UseMvvm``UseMvc` 这类 API 可以作为后续扩展模块,不一定属于第一版核心。
## 与 ft_3 Activity 的关系
Activity 不应该等同于大 Feature 内部的所有业务实现。
更合理的职责划分:
```text
Activity
负责活动开关、入口、可见性、预加载、跳转、生命周期接入。
Feature
负责活动作为业务模块的运行边界。
ArchitectureScope
负责该模块的依赖注册、解析、父子继承、局部覆盖和生命周期。
MVC / MVVM / State
负责 Feature 内部具体业务和 UI 组织。
```
也就是说Activity 更像外部接入器Feature 才是业务模块边界。
## 约束方式
约束重点不应该是禁止 Register而是让代码默认只能操作当前 Scope。
建议通过接口和可见性控制:
```csharp
public interface IArchitectureScope : IDisposable
{
string Name { get; }
IScopeInfo Parent { get; }
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<TService, TImplementation>()
where TImplementation : TService;
T Resolve<T>();
T Resolve<T>(string name);
}
```
`Parent` 可以只暴露只读元信息,避免业务代码通过 Parent 直接写父 Scope。
```csharp
public interface IScopeInfo
{
string Name { get; }
IScopeInfo Parent { get; }
}
```
如果确实需要高级能力,可以后续通过内部接口或显式高级 API 暴露,而不是默认给所有业务代码。
## 推荐最小核心 API
第一版可以先只实现:
```csharp
public interface IFeature
{
void Install(IArchitectureScope scope);
}
public interface IArchitectureScope : IDisposable
{
string Name { get; }
IScopeInfo Parent { get; }
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<T>(T instance);
void RegisterInstance<T>(T instance, string name);
T Resolve<T>();
T Resolve<T>(string name);
bool TryResolve<T>(out T value);
bool TryResolve<T>(out T value, string name);
}
```
工厂方法:
```csharp
var game = FlowScope.Create();
var boss = game.CreateFeature<BossFightFeature>();
```
## 暂不进入的内容
以下能力暂时不作为下一版第一阶段重点:
- Command / Query / Event 通信模型。
- 完整 MVC / MVVM 框架。
- 自动扫描所有 Feature。
- UI Panel 生命周期统一接管。
- 复杂诊断面板。
- 热更新资源接入细节。
原因是当前最核心的问题还在:
```text
Scope / Feature / Container / Architecture 的第一层抽象需要先稳定。
```
通信和表现层规则应该建立在稳定 Scope 模型之上。
## 验收标准
下一版实现完成后,应满足:
1. Root 和普通 Scope 使用同一种 `ArchitectureScope` 模型。
2. `CreateFeature<TFeature>()` 默认使用 `typeof(TFeature).Name` 作为 Scope 名称。
3. `CreateFeature<TFeature>(name)` 会自动创建子 Scope 并调用 `TFeature.Install(scope)`
4. 任意 Scope 可以随时 Register且只能注册到自己。
5. Resolve 从当前 Scope 开始,支持向父 Scope 查找。
6. 子 Scope 可以覆盖父 Scope 注册。
7. 子 Scope Dispose 不会释放父 Scope 对象。
8. 父 Scope Dispose 会释放子 Scope。
9. Feature Scope 默认就是主要业务边界。
10. Child Scope 能用,但文档和测试不鼓励把普通 UI/VM/Controller 都拆成 Scope。
11. 第一阶段不要求实现 Command / Query / Event。
## 当前设计取舍
这套设计选择了:
```text
默认 Root -> Feature复杂时再 Child Scope。
```
而不是:
```text
强制 Root -> Feature -> Flow -> UI -> Task。
```
原因是 FlowScope 应该帮助真实项目收住边界,而不是替业务制造额外层级。框架应提供可递归的 Scope 能力,但默认心智模型必须保持简单。