好的,我们来探讨一个能让你应用内部各个模块之间实现优雅解耦和通信的强大模式:事件 (Events)。这就像是为你的应用内部建立一个高效、有序的“广播和订阅系统”。
应用内部的“广播站”:精通 NestJS 事件驱动模式
想象你的应用是一家大型的新闻机构。
机构里有多个独立的部门:
- 社会新闻部(
UsersService
):负责报道新用户注册的事件。 - 财经新闻部(
OrdersService
):负责报道新订单创建的事件。 - 市场推广部(
MarketingService
):对新用户注册和新订单创建都很感兴趣,希望能收到通知,以便发送欢迎邮件或优惠券。 - 数据分析部(
AnalyticsService
):也想知道所有这些事件,以便进行数据统计。
没有事件系统的“低效沟通”模式: 当社会新闻部报道了一位新用户注册后,它需要亲自、挨个地打电话通知市场推广部和数据分析部:“嘿,我们这有个新用户!”```typescript // users.service.ts (耦合的设计) export class UsersService { constructor( private readonly marketingService: MarketingService, // 直接依赖 private readonly analyticsService: AnalyticsService, // 直接依赖 ) {}
createUser(dto) { // ...创建用户... const user = { ... };
// 挨个调用,紧密耦合
this.marketingService.sendWelcomeEmail(user);
this.analyticsService.trackUserCreation(user);
return user;
} }
这种方式的问题是**紧密耦合 (Tight Coupling)**。如果未来新增一个“客户关怀部”也需要这个通知,你就必须去修改 `UsersService` 的代码,给它再注入一个新的依赖。这违反了“开闭原则”(对扩展开放,对修改关闭)。
**引入事件的“广播站”模式:**
`UsersService` 的职责被简化了。当一个新用户注册时,它只需要拿起广播话筒,向全公司大喊一声:“**特大新闻!一个名为‘user.created’的事件发生了!这是详细资料!**”
* 它**不关心**谁在听这个广播。
* 市场推广部和数据分析部,都提前在自己的办公室里安装了“收音机”,并调到了“user.created”这个频道。
* 当广播响起时,它们的“收音机”(事件监听器)就会自动收到消息,然后各自执行自己的任务(发邮件、做统计)。
这种模式就是**事件驱动 (Event-Driven)**。发出事件的一方(**Emitter**)和监听事件的一方(**Listener**)互不了解,它们通过一个中立的“广播中心”(**EventEmitter**)进行通信,实现了完美的**解耦**。
NestJS 官方提供了 **`@nestjs/event-emitter`** 模块,它是基于 Node.js 内置的 `EventEmitter` 实现的,非常轻量和高效。
### 1. 第一步:搭建“广播站” (`EventEmitterModule`)
**第一步:安装依赖**
```bash
npm install @nestjs/event-emitter
第二步:在 AppModule
中注册 EventEmitterModule
// app.module.ts
import { Module } from '@nestjs/common';
import { EventEmitterModule } from '@nestjs/event-emitter';
@Module({
imports: [
// 启动广播站
EventEmitterModule.forRoot(),
],
// ...
})
export class AppModule {}
就这么简单,你的应用现在已经拥有了一个功能齐全的内部广播系统。
2. 第二步:播出新闻 (发出事件)
任何一个服务都可以成为“新闻播报员”。它只需要注入 EventEmitter2
这个服务即可。
src/users/users.service.ts
(解耦后的设计)
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UsersService {
// 1. 注入 EventEmitter2
constructor(private readonly eventEmitter: EventEmitter2) {}
createUser(createUserDto: CreateUserDto) {
const user = {
id: 1,
email: createUserDto.email,
name: 'John Doe',
};
console.log('👤 [UsersService] 用户已创建:', user);
// 2. 使用 eventEmitter.emit() 发出一个事件
// 第一个参数是事件名,推荐使用 "名词.动词过去式" 的格式
// 第二个参数是事件负载 (payload),也就是你想传递的数据
this.eventEmitter.emit('user.created', user);
return user;
}
}
注意,现在的 UsersService
变得非常干净,它不再依赖任何其他服务。它的职责就是创建用户,然后“广而告之”。
3. 第三步:收听广播 (监听事件)
任何一个服务都可以成为“忠实听众”。它只需要在某个方法上使用 @OnEvent()
装饰器。
src/marketing/listeners/user-created.listener.ts
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
@Injectable()
export class UserCreatedListener {
// 1. 使用 @OnEvent() 装饰器来订阅 'user.created' 事件
@OnEvent('user.created')
handleUserCreatedEvent(payload: any) {
// payload 就是我们 emit 时传递的 user 对象
console.log('📧 [MarketingDept] 收到新用户注册事件!正在发送欢迎邮件...');
console.log('收件人:', payload.email);
// ... 执行发送邮件的逻辑 ...
}
}
src/analytics/listeners/analytics.listener.ts
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
@Injectable()
export class AnalyticsListener {
@OnEvent('user.created')
trackUserCreation(payload: any) {
console.log('📊 [AnalyticsDept] 收到新用户注册事件!正在记录数据...');
console.log('用户ID:', payload.id);
// ... 执行数据上报的逻辑 ...
}
}
最后,别忘了在相应的模块中注册这些服务和监听器。
运行流程:
- 客户端请求
POST /users
来创建一个新用户。 UsersController
调用UsersService.createUser()
。UsersService
创建用户,然后调用eventEmitter.emit('user.created', user)
。- “广播站”(
EventEmitter
) 立即查找所有订阅了user.created
频道的“收音机”。 - 它发现
UserCreatedListener
和AnalyticsListener
都在收听。 handleUserCreatedEvent
和trackUserCreation
方法被异步地、并行地调用,它们各自执行自己的逻辑。UsersService
的createUser
方法无需等待监听器执行完成,直接返回响应给 Controller。
4. “广播”的高级玩法
通配符监听
你可以使用通配符 *
来监听一类事件。
// analytics.listener.ts
@OnEvent('*.created') // 监听所有以 .created 结尾的事件
handleAnyCreationEvent(payload: any) {
console.log('📊 [AnalyticsDept] 监听到一个创建事件!');
}
现在,无论是 user.created
还是 order.created
,这个监听器都会被触发。
异步监听器
默认情况下,事件监听器是异步的(async
),并且 emit()
方法是**“即发即忘” (fire-and-forget)** 的,它不会等待监听器执行完成。
如果你希望等待所有监听器都执行完毕后再继续,你可以使用 emitAsync()
。
// users.service.ts
async createUser(dto) {
// ...
console.log('正在发出事件,并等待所有监听器完成...');
await this.eventEmitter.emitAsync('user.created.sync', user);
console.log('所有监听器都已执行完毕!');
return user;
}
总结
@nestjs/event-emitter
是实现应用内模块解耦的强大工具,它遵循经典的“发布-订阅”模式。
角色 | 核心 API | 职责 |
---|---|---|
广播站 (中心) | EventEmitterModule.forRoot() | 提供事件通信的基础设施。 |
播报员 (Emitter) | 注入 EventEmitter2 ,调用 emit() | 只负责发出事件,不关心谁来处理。 |
听众 (Listener) | 在方法上使用 @OnEvent() 装饰器 | 只负责响应事件,不关心事件由谁发出。 |
当你发现你的一个服务开始 import
和 inject
越来越多不直接相关的其他服务,仅仅是为了在某个动作完成后“通知”它们一下时,这就是一个强烈的信号:是时候引入事件驱动,建立你的内部“广播站”,让你的模块们各自独立、高效地工作了。