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

16 KiB
Raw Permalink Blame History

FlowScope Scope / Feature 架构需求草案

目标

这份文档用于约束下一版 FlowScope 核心架构实现。目标不是复刻 QFramework也不是照搬 ft_3 当前容器用法,而是形成一套能服务真实 Unity 项目的可落地框架:

  • 能力向 ft_3 靠拢:支持动态注册、父子 Scope、局部覆盖、运行期灵活组合。
  • 规则向 QFramework 靠拢:业务代码默认在当前架构上下文中工作,不鼓励直接拿根容器乱 Resolve。
  • 默认使用简单:大多数业务只需要 Root -> Feature 两级。
  • 保留扩展能力:复杂业务可以继续创建子 Scope但不把每个 UI、流程、函数调用都强制变成 Scope。

核心结论

下一版的第一主角应该是统一的 ArchitectureScope,不是固定的 RootContext + FeatureScope 两层结构。

RootScopeFeatureScopeChildScope 本质都是同一种 Scope只是所在层级和安装内容不同

Root Scope
  BossFightFeature Scope
  ScratchTicketFeature Scope

必要时可以继续细分:

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 不同的能力。它只是树的根:

RootScope = Parent == null 的 ArchitectureScope

Root 适合注册跨 Feature 的基础能力,例如:

  • 配置服务
  • 资源服务
  • 存档服务
  • 音频服务
  • 网络服务
  • 全局账号/环境信息

Root 不应该成为业务代码到处直接访问的全局服务定位器。

Feature

Feature 是一个业务模块的安装定义,不是另一种容器。

一个 Feature 代表一个可独立理解的业务边界,例如:

  • BossFightFeature
  • ScratchTicketFeature
  • Event1v1BattleFeature
  • MainMenuFeature

创建 Feature 时,框架会自动创建一个新的 ArchitectureScope,然后把 Feature 安装到这个 Scope 上。

推荐 API

var boss = game.CreateFeature<BossFightFeature>();

等价概念:

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。

更准确的规则是:

Register 本身不是问题。
问题是越级注册、越级修改、污染父级、绕开当前 Scope 解析。

因此下一版需要遵守:

  1. 任意 Scope 都可以随时 Register。
  2. Register 永远只写当前 Scope。
  3. Resolve 永远从当前 Scope 开始。
  4. 当前 Scope 找不到时,才向 Parent 查找。
  5. 子 Scope 可以覆盖父 Scope 的注册。
  6. 子 Scope 不能修改父 Scope 的注册。
  7. Root 只是普通 Scope 的根节点,不是特殊全局服务定位器。
  8. Feature 是创建出来并自动安装的 Scope。

示例:

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>();

解析顺序:

RevivePanel Scope -> BossFight Scope -> Root Scope

此时 revive.Register 不会污染 bossboss.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 底层公开模型不完全一致:

ft_3 当前模型Root Container 注册Scope 负责按 Scope 生命周期解析/缓存。
FlowScope 目标模型:每个 ArchitectureScope 都可以注册到自己,并从自己开始 Resolve再向父级查找。

如果只包装,会出现两套语义:外层说 Scope-local Register内层却只有 Root Register。长期看会让 ArchitectureScope 变成补丁层,代码容易拧巴。

下一版应该选择:

借鉴 ft_3 Container 的算法和 API 手感
但实现 FlowScope 自己的 Container / Scope Tree

FlowScope Container 目标模型

FlowScope 的容器节点应该同时承担三件事:

Container 负责能力
ArchitectureScope 负责规则
Feature 工厂负责业务模块创建

但实现上不再拆成“外层 Scope 包内层 Container.IScope”而是统一为 FlowScope 自己的 Scope 节点:

ContainerScope / ArchitectureScope
  Name
  Parent
  Children
  Dictionary<ServiceKey, Registration>
  CreateScope()
  CreateFeature<TFeature>()
  Register()
  Resolve()
  TryResolve()
  Dispose()

其中 ServiceKey 继续沿用 ft_3 的核心思路:

public readonly struct ServiceKey
{
    public Type Type { get; }
    public string Name { get; }
}

生命周期规则

下一版容器至少支持:

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 自己容器模型的一部分,不依赖底层外部容器。

示例:

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>();

解析顺序:

RevivePanel Scope -> BossFightFeature Scope -> Root Scope

revive.Register 不污染 bossboss.Register 不污染 root

CreateFeature 规则

CreateFeature<TFeature>() 是 Scope 创建 + Feature 安装的语义封装。

调用者不传 name 时,默认使用 typeof(TFeature).Name

var boss = game.CreateFeature<BossFightFeature>();
// 等价于 game.CreateFeature<BossFightFeature>(typeof(BossFightFeature).Name)

推荐 API

IArchitectureScope CreateFeature<TFeature>()
    where TFeature : IFeature, new();

IArchitectureScope CreateFeature<TFeature>(string name)
    where TFeature : IFeature, new();

第一版可以先使用 where TFeature : IFeature, new(),保持简单:

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 创建:

child.Register<TFeature>().PerScope();
var feature = child.Resolve<TFeature>();
feature.Install(child);

调用侧不应该手写:

boss.InstallFeature(new BossFightFeature());
boss.UseArchitecture(new BossFightArchitecture());

Feature 的安装和 Scope 的创建应该被 CreateFeature<TFeature>() 收住。

Feature 默认边界

大多数业务默认只需要:

Root Scope
  Feature Scope

例如:

Root Scope
  BossFightFeature Scope
  ScratchTicketFeature Scope

BossFight 内部的战斗、复活、奖励、UI不应该默认都拆成 Scope。它们可以优先放在 Feature 内部架构里:

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 或业务组织模式。

示例:

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>();
    }
}

UseMvvmUseMvc 这类 API 可以作为后续扩展模块,不一定属于第一版核心。

与 ft_3 Activity 的关系

Activity 不应该等同于大 Feature 内部的所有业务实现。

更合理的职责划分:

Activity
  负责活动开关、入口、可见性、预加载、跳转、生命周期接入。

Feature
  负责活动作为业务模块的运行边界。

ArchitectureScope
  负责该模块的依赖注册、解析、父子继承、局部覆盖和生命周期。

MVC / MVVM / State
  负责 Feature 内部具体业务和 UI 组织。

也就是说Activity 更像外部接入器Feature 才是业务模块边界。

约束方式

约束重点不应该是禁止 Register而是让代码默认只能操作当前 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<TService, TImplementation>()
        where TImplementation : TService;

    T Resolve<T>();
    T Resolve<T>(string name);
}

Parent 可以只暴露只读元信息,避免业务代码通过 Parent 直接写父 Scope。

public interface IScopeInfo
{
    string Name { get; }
    IScopeInfo Parent { get; }
}

如果确实需要高级能力,可以后续通过内部接口或显式高级 API 暴露,而不是默认给所有业务代码。

推荐最小核心 API

第一版可以先只实现:

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);
}

工厂方法:

var game = FlowScope.Create();
var boss = game.CreateFeature<BossFightFeature>();

暂不进入的内容

以下能力暂时不作为下一版第一阶段重点:

  • Command / Query / Event 通信模型。
  • 完整 MVC / MVVM 框架。
  • 自动扫描所有 Feature。
  • UI Panel 生命周期统一接管。
  • 复杂诊断面板。
  • 热更新资源接入细节。

原因是当前最核心的问题还在:

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。

当前设计取舍

这套设计选择了:

默认 Root -> Feature复杂时再 Child Scope。

而不是:

强制 Root -> Feature -> Flow -> UI -> Task。

原因是 FlowScope 应该帮助真实项目收住边界,而不是替业务制造额外层级。框架应提供可递归的 Scope 能力,但默认心智模型必须保持简单。