현재 프로젝트를 진행하면서 회원가입 성공시 혹은 리소스 생성시 구현되어야 할 기능들이 추가 및 변경되는 일이 잦았다.
그래서 기존의 로직은 영향을 받지 않으면서 변경 및 추가되는 기능에 대해 대응하기 위해 이벤트 드리븐으로 개발해야 겠다는 생각이 들었다.
예를 들어, 기존에는 회원가입 성공시에 추가 로직이 없었지만, 현재는 인증 메일 전송 및 가입 축하 알림톡 기능이 추가 되어야 하고,
리소스 생성시(예를 들면 선생님이 신규 클래스를 등록하는 등) 알림 db에 insert 하여 해당 그룹의 유저들이 확인 하고, 그 알림이 실시간으로 전송되어야 한다는 요구가 있었다.
예시에서는 신규 클래스 생성 완료 되면 이벤트를 발생시켜서 알림을 등록하는 부분을 작성하겠다.
async create(createArtClassDto: CreateArtClassDto, currentAcademy: Academy): Promise<ArtClass> {
// 신규 클래스 등록 로직 생략...
await this.artClassRepository.save(newArtClass);
// 신규 클래스 등록시 이벤트 발생
this.eventEmitter.emit('art-class.created', newArtClass, currentAcademy);
return newArtClass;
}
위 코드에서 보는 것 처럼 newArtClass를 save하고, 그 후에 이벤트를 발생시킨다.
1. eventEmitter의 emit함수의 첫번째 인자로 이벤트 명을 작성하고(이벤트의 이름은 실제로는 enum값으로 정의하였으나 편의를 위해 평문으로 대체합니다.)
2. 그 뒤에 이벤트 리스너에게 전달될 인자를 작성한다.
@Injectable()
export class ArtClassEventListener {
constructor(
private teacherRepository: TeacherRepository,
private notificationToTeacherRepository: NotificationToTeacherRepository,
private notificationRepository: NotificationRepository
) {}
/**
*클래스 생성 이벤트
* @param artClass
*/
@OnEvent('art-class.created', { async: true })
async handleArtClassCreatedEvent(artClass: ArtClass, academy: Academy) {
// 코드 생략 및 로직 주석으로 대체
// 1. notification 테이블 insert
// 2. 현재 학원에 소속된 teacher 조회
// 2. notification과 teacher 테이블 연결
}
}
다음으로 이벤트가 발생되면, 발생을 감지할 리스너가 필요하다.
@OnEvent 데코레이터를 함수위에 작성해주고, 데코레이터의 옵션으로 이벤트명을 적어주면 해당 이벤트가 발생했을 때
다음의 함수가 실행된다.
여기서 async 옵션을 true로 주면 이벤트가 발생하고 처리하는 로직을 비동기로 실행시킬 수 있다.
(필수적으로 동작을 보장해야하는 로직이 아니라면 사실상 이게 false일 때, 추가 개발에 용이하도록 코드를 분리하는 의미 외에는 기능적으로는 의미를 가지지 않기 때문에, 특수한 경우가 아니라면 true로 줄 것 같다.)
추가로 하나의 이벤트에서 여러가지 동작이 필요한 경우라면 예를 들어 회원가입 후에 인증메일도 전송해야하고, 알림톡도 보내야 한다면
아래와 같이 같은 이벤트명을 가진 이벤트리스너를 각각의 함수위에 달아서 사용하면 코드를 분리하기도 용이하다.
@OnEvent('user.created')
async sendAuthEmail(){
// 인증 메일 전송
}
@OnEvent('user.created')
async sendKakao(){
// 알림톡 발송
}
또한 이벤트명에 와일드 카드를 지원하기 때문에 domain.event 식으로 이벤트명을 짓는것이 좋아보인다.
예를 들어 도메인의 모든 이벤트에서 처리해야하는 로직이 있을 경우 이벤트 명을 domain.* 식으로 하면 되기 때문이다.
Event Emitter를 사용해본 결과, 굉장히 편리했으나 OnEvent는 실행을 보장하지 않고, 응답과 관계없이 비동기로 처리 되기 때문에 http exexption filter에서도 걸리지 않아 에러 발생 유무를 알수 없는 문제가 있었다.
그래서 아래 코드와 같이 프로덕션모드 일 때, 이벤트 리스너에서 에러가 나면, 슬랙으로 알림이 오게 추가적으로 작업해 주었다.
(슬랙과 연동하는 법은 다음에 기회가 되면 추가로 포스팅하겠습니다ㅎㅎ)
@OnEvent('art-class.created', { async: true })
async handleArtClassCreatedEvent(artClass: ArtClass, academy: Academy) {
try {
// 1. notification 테이블 insert
// 2. 현재 학원에 소속된 teacher 조회
// 2. notification과 teacher 테이블 연결
throw new Error('이벤트 에러 테스트')
} catch (exception) {
if (isProductionMode) {
SlackUtils.send(`error: ${exception.stack}`, SlackChanel.NOTI_ERROR).catch((e) => e);
}
}
}
이외에 지적사항이나 좋은 방법이 있다면 댓글 달아주시면 감사하겠습니다 :)
'JavaScript & TypeScript' 카테고리의 다른 글
[NestJS] iterable 객체 병렬처리 하기(feat.Bluebird JS) (1) | 2022.10.09 |
---|---|
[NestJS] class-validator를 사용한 필수값 체크(feat.다음 우편번호 API) (0) | 2022.10.08 |
[NestJS] DTO의 사용범위에 관한 고찰 및 리팩토링(feat.Layered Architecture) (4) | 2022.09.23 |
[NestJS] 쿼리빌더를 사용한 코드 리팩토링 (feat.TypeORM) (0) | 2022.08.23 |
[NestJS] Many-to-Many 생성시에 할당하는 코드 리팩토링 (feat.TypeORM) (0) | 2022.07.12 |