site logo

Marico's space

Wyrly DI:现代 TypeScript 的类型安全依赖注入

前端技术 2026-06-02 11:28:50 6

最近折腾了 Wyrly DI,一个 TypeScript 的依赖注入(DI,Dependency Injection)工具库,顺手把踩坑经历整理一下。

Wyrly DI 面向现代 TypeScript 应用,主打的是不依赖 reflect-metadata、不依赖 emitDecoratorMetadata、不用老式装饰器、不用参数装饰器这几个特性。

核心特点:

  • 使用标准装饰器(TC39 装饰器,TypeScript 5.0+ 支持)
  • 类型安全的 Token 设计
  • 显式声明依赖关系
  • 支持 Web 应用的请求作用域
  • 依赖图可检查、可验证

说白了,"标准装饰器"指的是 TC39 提案的装饰器,TypeScript 5.0 之后默认支持那种。跟之前 experimentalDecorators 那套玩法不一样,很多现有的 DI 库都是基于老式装饰器写的。

相关链接

  • GitHub 仓库
  • @wyrly/core on JSR
  • @wyrly/core on npm
  • 示例代码

为什么再造一个 DI 轮子?

很多 TypeScript DI 库依赖运行时元数据来推断构造函数依赖。

这样确实方便,但问题在于——重要的依赖信息被藏到了运行时元数据和装饰器行为后面,代码审查、静态分析、CI、AI 辅助工具都很难看到。

Wyrly DI 走了另一条路:显式声明。

import { Injectable, token } from "@wyrly/core"; type User = { id: string; name: string;
}; interface UserRepository { findById(id: string): Promise<User | null>;
} const UserRepositoryToken = token<UserRepository>("UserRepository"); @Injectable({ deps: [UserRepositoryToken], lifetime: "scoped",
})
class GetUserUseCase { constructor(private readonly users: UserRepository) {} execute(id: string) { return this.users.findById(id); }
}

通过 deps 声明依赖关系,代码审查、静态分析、CI、AI 辅助开发工具都能直接看到这些信息。

因为用的是标准装饰器,Wyrly DI 不会去读构造函数参数类型。构造函数照常写没问题(比如 constructor(private readonly users: UserRepository)),但依赖映射必须通过 deps 显式声明。

上面例子里用 UserRepositoryToken 是因为 TypeScript 接口在运行时不存在。如果依赖是类本身,类也可以直接当 Token 用。也就是说,接口用类型化 Token,类用类本身当 Token。

类型安全的 Token

TypeScript 接口在运行时消失,所以基于接口的依赖需要运行时 Token。

Wyrly DI 提供了类型化的 Token 来解决这个问题。

const UserRepositoryToken = token<UserRepository>("UserRepository"); const users = scope.resolve(UserRepositoryToken);
// users: UserRepository

解析出来的值会被推导为 UserRepository 类型,所以基于接口的依赖依然能类型安全地使用。

请求作用域

Web 应用里,每个请求往往需要不同的依赖实例。

常见场景:

  • 当前登录用户
  • 请求对象
  • 响应对象
  • 工作单元(Unit of Work)
  • DataLoader
  • 请求级缓存

Wyrly DI 的 Web 适配器就是围绕这个模型设计的:

1 HTTP 请求 = 1 个 DI 作用域
1 GraphQL 请求 = 1 个 DI 作用域

请求作用域不是唯一支持的声明周期。核心包支持 singleton(单例)、scoped(作用域级)、transient(每次解析新建实例),可以覆盖应用级依赖、请求级依赖、需要每次创建新实例的依赖。

比如 Hono 适配器在中间件里创建每个请求的 Scope,然后在处理器里解析依赖。

import { Hono } from "hono";
import { createContainer } from "@wyrly/core";
import { di, getDI, type HonoDIVariables } from "@wyrly/hono"; const app = new Hono<{ Variables: HonoDIVariables }>();
const container = createContainer(); app.use(di(container)); app.get("/users/:id", async (c) => { const scope = getDI(c); const usecase = scope.resolve(GetUserUseCase); return c.json(await usecase.execute(c.req.param("id")));
});

检查和验证依赖图

Wyrly DI 支持检查和验证依赖图。

const graph = container.inspect(); const result = container.validate();

比如验证能检测出危险的声明周期组合:单例依赖作用域级依赖。

singleton -> scoped dependency

这种关系会埋下隐蔽的 bug,比如请求级的用户数据被单例持有,导致数据串场。

还可以在测试里加依赖图验证,CI 失败时直接报警。

import { assertEquals } from "@std/assert"; Deno.test("DI graph is valid", () => { const result = container.validate(); assertEquals( result.ok, true, result.issues.map((issue) => issue.message).join("\n"), );
});

小型应用可能觉得没必要。

但对于在意边界设计的项目,比如 DDD(领域驱动设计)或清洁架构风格,依赖问题发现得越晚,修复成本越高。

典型场景:

  • 单例服务不小心依赖了请求级的 CurrentUser
  • 应用用例(Use Case)跟基础设施细节耦合太紧
  • 组合根(Composition Root)膨胀,没人注意到依赖关系已经出问题

在 CI 里验证依赖图,能在这些问题变成"正常代码"之前抓住它们。

已发布的包

目前发布了这些包:

@wyrly/core
@wyrly/next
@wyrly/express
@wyrly/hono
@wyrly/fresh
@wyrly/graphql

Deno 用户用 JSR:

deno add jsr:@wyrly/core

Node.js 和 Bun 用户用 npm:

npm install @wyrly/core

按需安装适配器:

npm install @wyrly/hono
npm install @wyrly/next
npm install @wyrly/graphql

什么时候适合用 Wyrly DI

如果你的项目符合这些需求,Wyrly DI 可能是合适的选择:

  • 不想用 reflect-metadata
  • 想用 TypeScript 标准装饰器做 DI
  • 需要在 DDD 或清洁架构里显式管理组合根
  • 需要在 Next.js、Hono、Express 或 GraphQL 中使用请求作用域
  • 想让依赖图在代码审查和 CI 里可见

什么时候可能不适合

如果你的需求是这些,可能有别的工具更合适:

  • 想要全栈框架自带控制器、模块、认证、配置管理
  • 主要工作流是自动扫描注册,不需要手动配置
  • 习惯用参数装饰器注入
  • 现有项目重度依赖 reflect-metadata

Wyrly DI 有意把一个轻量的显式 DI 核心和框架适配器分开设计。

示例

仓库里包含了可运行的示例:

  • DDD 组合根
  • Hono API
  • Express API
  • Next.js App Router
  • GraphQL 请求作用域
  • DataLoader 模式
  • 依赖图验证

示例地址在 GitHub 上。

写在最后

Wyrly DI 是一个让 TypeScript 依赖注入变得显式、可分析、适合 Web 请求作用域应用的工具库。

设计原则:

  • 显式依赖声明 > 隐式自动解析
  • 结构可检查 > 行为藏背后
  • 标准装饰器 > 老式装饰器
  • 类型安全 Token > 纯字符串 Token
  • 组合根 > 自动扫描

如果觉得对你的项目有用,从 @wyrly/core 开始,找最接近你使用框架的示例上手。

  • GitHub
  • JSR
  • npm

原文链接:https://dev.to/yoonesk/wyrly-di-type-safe-dependency-injection-for-modern-typescript-4f9i