Skip to main content
下面是我对 tegg 为什么设计成现在这种架构的系统性分析,结合 tegg 仓库的结构来回答“动机-分层-机制-取舍”四个维度,帮助你从“为什么这样设计”的角度理解它。

为什么这样设计

一、目标与约束(Why)

  • 核心目标
    • 面向复杂业务的“可组合能力”:将控制器、AOP、DAL、事件总线、调度等能力模块化、按需装配,避免“一个包做所有事”的内聚膨胀。
    • 与 Egg.js 生态的“解耦集成”:核心能力要独立于 Web/框架,但又能通过插件自然接入 Egg 的生命周期、配置和中间件体系。
    • 强类型与装饰器的开发体验:以 TypeScript + 装饰器为入口,提升领域建模的表达力,把设计“显式化”“可扫描化”。
    • 生命周期与上下文的正确性:异步调用链下的上下文传递与隔离,用于解决“异步堆栈里传参散落”的顽疾。
    • 单测与演化友好:以 monorepo 管理多包,使能力可以独立演进、单独发布与回滚,降低全局耦合风险。
  • 外部约束
    • Node.js 平台语义(模块系统、异步模型)与 Egg 插件机制。
    • 工程化约束(多包发布、类型共享、构建一致性)。
    • 对既有项目渐进迁移的现实需求(CJS 到 ESM、全局上下文到 ALS)。

二、分层与包的职责(How it’s structured)

  • 核心(Core)层:抽象出“运行时/元数据/装载器”三件套
    • 元数据:装饰器标注 → 可被扫描/序列化的元数据
    • 装载器:扫描工程、解析元数据、构建原型/装配单元(LoadUnit/Prototype)
    • 运行时容器:依赖注入、实例生命周期、上下文、作用域等
    • 类型与跨包契约:抽离公共类型,降低编译耦合
    • 能力入口/外观(Facade):聚合核心能力(为上层提供简化入口)
  • 能力(Decorator/Runtime)层:将横切/领域能力按“装饰器 + 运行时”解耦
    • 例如 AOP、DAL、ORM、事件总线、调度等均以“装饰器声明 + 对应运行时”组合出现
  • 集成(Plugin)层:把核心能力与 Egg 的应用框架做“边界清晰”的桥接
    • HTTP/Controller 插件:把控制器声明/路由/参数校验等与核心容器打通
    • 与 Egg 的集成外壳(入口/初始化/配置),将 tegg 功能以插件方式启用
    • 其它插件(orm/schedule/ajv 等)将具体能力接入 Egg
  • 运行形态(Standalone)层:除了在 Egg 中运行,也支持独立运行模式
这套分层遵循“能力内聚、耦合外移”的原则:核心保持与框架无关,插件负责落地集成,增强了运行时形态的可移植性与渐进引入能力。

三、关键机制与设计取舍(How it works)

  • Decorator → Metadata → Loader → Runtime 的流水线
    • 为什么要“先装饰器再元数据”:装饰器让“意图”显式化、可静态扫描;元数据是稳定的中间层,屏蔽语言/框架差异。
    • 为什么要“装载器”:将分散在项目各处的声明统一装配为“原型/LoadUnit”,把“扫描/解析/组装”的复杂度从业务代码挪走。
    • 为什么要“容器/运行时”:解决依赖注入、生命周期、作用域、实例化策略、上下文传播等通用问题。
  • 上下文管理采用 AsyncLocalStorage(演进点)
    • 解决异步调用链中的上下文丢失问题,替代手写传参或全局变量,提升一致性与可靠性。
    • 与 Egg 的请求生命周期天然契合,插件层可以围绕请求/任务创建异步上下文作用域。
  • ESM only 与类型抽离(工程化演进)
    • ESM only:与现代 Node/工具链对齐,简化双模块形态带来的复杂性与坑点。
    • 类型抽离到 types:将跨包契约集中管理,降低“隐式类型耦合”,便于重构与发布。
  • 与 Egg 的解耦集成
    • 为什么把“HTTP 控制器能力”做成插件而不是放在核心:保持核心对框架无感,支持多运行形态;同时让“控制器如何映射到 Egg 的路由/中间件”由插件层决定,避免强耦合。
  • Monorepo 的组织理由
    • 每个能力独立发版、独立回滚;变更影响范围可控;同时共享构建/规范工具链,降低总体维护成本。

四、相对其它方案的取舍(Why not X)

  • 不做“单一大包(one big package)”
    • 优点:降低初期心智负担;缺点:后期耦合过高,发布与回滚风险极大,难以扩展新能力或裁剪。
    • tegg 选择“多包/可插拔”,以长期演进/大型团队协同为优先。
  • 不把 HTTP/框架语义塞进核心
    • 核心一旦耦合框架语义,将限制运行形态与扩展;插件可以自由演化适配不同的框架/协议(HTTP、RPC、消息)。
  • 不继续使用全局变量/手写传参做上下文
    • 在 Node 异步场景下不可靠、不可维护;AsyncLocalStorage 是更符合异步模型的“正交能力”。
  • 不维持双模块(CJS+ESM)长期并存
    • 工程复杂度高、边界 case 多;ESM only 让生态与工具链更清晰一致。

五、这个架构带来的收益与成本

  • 收益
    • 解耦:核心可独立复用,插件可平行演进。
    • 可测试:每个能力包可单测/集成测隔离执行。
    • 可演化:装饰器/元数据/容器作为“稳定三件套”,外部能力以“模块化”扩展。
    • 类型安全:跨包契约集中在 types,重构风险更可控。
  • 成本
    • 学习曲线:理解装饰器→元数据→装载器→运行时流水线需要一定门槛。
    • 工程复杂度:多包构建/发布/版本管理需要规范与工具链保障。
    • 性能权衡:启动期的扫描/装载有开销,需要通过缓存/按需加载来权衡。

架构总览

装饰器→元数据→装载器→运行时→插件 下图展示 tegg 的关键流转与边界:装饰器将“意图”转化为元数据;装载器扫描并构建 LoadUnit/Prototype;运行时负责 DI/生命周期/上下文(基于 AsyncLocalStorage);插件层与 Egg 集成。

Tegg 架构时序/组件图

关键数据结构(示例清单):
  • Metadata:ClassMetadata、PropertyMetadata、ParamMetadata
  • Loader:LoadUnit、EggPrototype/EggObject、Qualifier/Scope
  • Runtime:Context(ALS)、Container、LifecycleHook
  • Plugin:HTTPController、路由注册/参数解析

数据结构索引

以下索引帮助你从“架构图/生命周期图”跳到具体源码
  • Controller 元数据:plugin/controller/lib/ControllerMetadata.ts
  • 方法元数据接口:core/types/MethodMeta.ts
  • 原型接口:core/types/EggPrototype.ts
  • 加载单元:core/types/LoadUnit.ts
  • 对象包装:core/types/EggObject.ts
  • 请求上下文:core/types/EggContext.ts
  • 生命周期钩子:core/types/LifecycleHook.ts
  • 控制器注册工厂:plugin/controller/lib/ControllerRegisterFactory.ts
  • HTTP 方法注册器:plugin/controller/lib/HTTPMethodRegister.ts
  • 容器对象工厂:core/lib/EggContainerFactory.ts(或所在实现位置)