后端应用中会有很多业务模块,这些业务模块之间会有互相调用的关系。
但是把一个业务模块作为依赖注入的别的业务模块也不大好。
比如下单送优惠券的活动,订单模块在订单完成后调用优惠券模块下发优惠券。
这种如果直接把优惠券模块注入到订单模块里就不大好,因为是两个独立的业务模块。
有没有别的通信方式呢?
有,比如通过 event emitter 通信。
我们试一下:
nest new event-emitter-test
安装用到的包:
npm i --save @nestjs/event-emitter
在 AppModule 引入下 EventEmitterModule:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { EventEmitterModule } from '@nestjs/event-emitter';
@Module({
imports: [
EventEmitterModule.forRoot(),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
然后创建两个 module:
把服务跑起来:
npm run start:dev
访问下 aaa 和 bbb 的接口:
没啥问题。
然后我们想在 aaa 模块的查询触发的时候,调用 bbb 模块记录一条日志呢?
这时候就可以用 Event Emitter 来做。
@Inject(EventEmitter2)
private eventEmitter: EventEmitter2;
findAll() {
this.eventEmitter.emit('aaa.find',{
data: 'xxxx'
})
return `This action returns all aaa`;
}
在 AaaService 里注入 EventEmitter2,然后调用它的 emit 方法发送一个事件。
然后在 BbbService 里监听下:
@OnEvent('aaa.find')
handleAaaFind(data) {
console.log('aaa find 调用', data)
this.create(new CreateBbbDto());
}
试一下:
可以看到 AaaService 的 findAll 调用的时候,自动触发了 BbbService 里的方法调用。
是不是很方便?
如果你没感觉出来,那想一下不通过事件怎么做呢?
是不是需要在 BbbModule 里把 BbbService 放到 exports 里声明,然后在 AaaModule 里引入之后 BbbModule 之后,注入它的 BbbService 来用呢?
或者通过全局模块,把 BbbModule 通过 @Global 声明为全局模块,然后在 AaaService 里注入 BbbService 来调用呢?
不管哪种都很麻烦。
而通过事件的方式就简单太多了。
此外,EventEmitterModule 还支持一些配置:
wildcard 是允许通配符 *。
delimiter 是 namespace 和事件名的分隔符。
配置之后就可以这样用了:
findAll() {
this.eventEmitter.emit('aaa.find',{
data: 'xxxx'
})
this.eventEmitter.emit('aaa.find2',{
data: 'xxxx2'
})
return `This action returns all aaa`;
}
BbbService 里可以用 aaa.* 通配符匹配:
测试下:
event emitter 用起来很简单,但却很有用,比直接引入模块注入依赖的方式方便太多了。
我们来做个具体案例,用户注册成功之后,通知模块里发送欢迎邮件:
nest g resource user --no-spec
nest g resource notification --no-spec
nest g module email
nest g service email --no-spec
创建 user 用户模块、notification 通知模块,email 邮件模块。
先来写下邮件模块:
安装 nodemailer 包:
npm install --save nodemailer
写下 EmailService:
import { Injectable } from '@nestjs/common';
import { createTransport, Transporter} from 'nodemailer';
@Injectable()
export class EmailService {
transporter: Transporter
constructor() {
this.transporter = createTransport({
host: "smtp.qq.com",
port: 587,
secure: false,
auth: {
user: "你的用户名",
pass: "你的授权码"
},
});
}
async sendMail({ to, subject, html }) {
await this.transporter.sendMail({
from: {
name: '系统邮件',
address: "你的邮箱地址"
},
to,
subject,
html
});
}
}
如何获取授权码看 node 发邮件那节。
然后把 EmailModule 声明为全局模块:
import { Global, Module } from '@nestjs/common';
import { EmailService } from './email.service';
@Global()
@Module({
providers: [EmailService],
exports: [EmailService]
})
export class EmailModule {}
这样 NotificationService 里就可以直接注入 EmailService 了:
@Inject(EmailService)
private emailService: EmailService
@OnEvent("user.register")
async hanldeUserRegister(data) {
console.log('user.register');
await this.emailService.sendMail({
to: data.email,
subject: '欢迎' + data.username,
html: '欢迎新人'
})
}
然后在 CreateUserDto 添加两个属性:
export class CreateUserDto {
username: string;
email: string;
}
在 create 的时候调用下:
@Inject(EventEmitter2)
private eventEmitter: EventEmitter2;
create(createUserDto: CreateUserDto) {
this.eventEmitter.emit('user.register', {
username: createUserDto.username,
email: createUserDto.email
})
return 'This action adds a new user';
}
在 postman 里调用下 create 接口:
通知成功了!
案例代码上传了小册仓库。
总结
多个业务模块之间可能会有互相调用的关系,但是也不方便直接注入别的业务模块的 Service 进来。
这种就可以通过 EventEmitter 来实现。
在一个 service 里 emit 事件和 data,另一个 service 里 @OnEvent 监听这个事件就可以了。
用起来很简单,但比起注入别的模块的 service 方便太多了。