SSE를 이용한 알림기능

산기슭곰발자·2024년 2월 14일
post-thumbnail

커뮤니티에서 항상 나오는 알림 기능은 어떤 로직으로 구성이 되어있을까? 개발자 Q&A 커뮤니티라는 프로젝트를 진행하며, 누구의 질문에 댓글이 달렸는지, 해당 댓글에 대댓글이 달렸을 때 인지 할 수 있으려면 알림기능이 필수불가결한 요소임을 깨닫고 알림에 대해 공부를 시작했다.

SSE의 사전적의미?

  • Server - Sent - Events 는 클라이언트가 HTTP 연결을 통해 서버로부터 자동 업데이트를 받을 수 있도록 하는 서버 push 기능이다. 라고 공식문서에 나와있다.
@Sse('sse')
sse(): Observable<MessageEvent> {
  return interval(1000).pipe(map((_) => ({ data: { hello: 'world' } })));
}

예제 코드를 봤는데 그냥 해당 예제 코드만 본다면

return interval(1000).pipe(map((_) => )({data:{hello:'world'}})
- 1000ms / 1초마다 해당 데이터를 pipe를 통하여 삽입한다.

의 개념이다. 어려워지기 시작했다. SSE로 알림기능을 구현해야하는데 그럼 이런 로직을 통해서 어떤 방식으로 알림을 구현해야하는지 막막해지기 시작했다. SSE를 검색하며 공부하다가 [RxJs]라는 것을 알게되어 같이 검색해 보게 되었다. RxJs 공식문서

RxJs는?

RxJS는 관찰 가능한 시퀀스를 사용하여 비동기 및 이벤트 기반 프로그램을 구성하기 위한 라이브러리입니다. 하나의 핵심 유형인 Observable , 위성 유형(Observer, Schedulers, Subjects) 및 Array메서드( map, filter, reduce, every등)에서 영감을 받은 연산자를 제공하여 비동기 이벤트를 컬렉션으로 처리할 수 있습니다.

란다. 이해했을까? 나는 이해 못했다. 미쳐가기 시작했다.
1을 이해하기위해 2를 이해를 하고 와야한단다. 쉬운말로 풀어보자.

event or aasynchronous, time을 Array(배열) 처럼 다룰수 있게 만들어주는 라이브러리

정도면 이해가 쉬울 것 같다.

import { Injectable, MessageEvent } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Observable, Subject, filter, map } from 'rxjs';
import { Alarm } from './entities/alarm.entity';
import { Repository } from 'typeorm';

@Injectable()
export class AlarmService {
    constructor(
        @InjectRepository(Alarm)
        private readonly alarmRepository: Repository<Alarm>,
    ) {}

        private alarms$: Subject<any> = new Subject();

        private observer = this.alarms$.asObservable();

    // 댓글 추가 이벤트 발생 함수
    async emitAlarmAddedEvent(userId: number, title: string, description: string) {
        this.alarms$.next({ userId, title, description });
    }

    // 댓글 추가 이벤트를 구독하는 클라이언트에게 SSE 전송
    sendAlarmAddedEvent(userId: number): Observable<any> {
        return this.observer.pipe(
            // 특정 게시물의 댓글만 필터링
            filter((event) => event.userId === userId),
            // 데이터를 SSE 형식으로 변환
            map((event) => {
                return {
                    data: {
                        title: `${event.title}`,
                        description: `${event.description}`,
                    },
                } as MessageEvent;
            }),
        );
    }

    async createAlarm(userId: number, title: string, description: string) {
        // 알람을 여기서 저장하는 로직 작성
        const alarm = await this.alarmRepository.save({
            title, 
            description,
            userId,
        });
        this.emitAlarmAddedEvent(userId, alarm.title, description)
    }
}

(실제로 팀원이 작성한 코드이다 하나하나 분리해서 알아보자)

      private alarms$: Subject<any> = new Subject();

      private observer = this.alarms$.asObservable();
  • [RxJs]에서의 SubJect 선언 타입은 any타입이다.
  • 그 뒤 선언한 Subject를 observable(관찰 가능)한 객체로 선언
 async emitAlarmAddedEvent(userId: number, title: string, description: string) {
        this.alarms$.next({ userId, title, description });
  • [Rxjs] alarms 추가 이벤트를 발생시키는 함수.
    'userId, title, description 세개의 매개변수를 받는다'
    다음 alarms$라는 Subject 객체의 next method 호출 이벤트 발생. 새로운 값을 발행하도록 명령

@ 현재는 { userId, title, desciption } 객체를 발행하고 있으며 알림 추가 이벤트에 대한 정보를 포함하고 있을것이다.

발행을 했으니 수신하는 로직도 작성해야 할 것이다.

sendAlarmAddedEvent(userId: number): Observable<any> {
        return this.observer.pipe(
            // 특정 게시물의 댓글만 필터링
            filter((event) => event.userId === userId),
            // 데이터를 SSE 형식으로 변환
            map((event) => {
                return {
                    data: {
                        title: `${event.title}`,
                        description: `${event.description}`,
                    },
                } as MessageEvent;
            }),
        );
    }
  1. filter를 통하여 event.userId와 userId가 같은 이벤트만 통과. ( 특정 사용자의 알람 추가 이벤트만 선택)
  2. map을 이용해서 이벤트 객체를 SSE형식으로 변환
    변환된 객체는 data 프로퍼티를 가지고 있음. title및 description 프로퍼티는 이벤트 객체의 값으로 설정되며 messageEvent 타입으로 캐스팅되게 된다. (as MessageEvent
  3. 마지막 변환된 옵져버블을 반환.

을 통하여 알람추가 이벤트를 생성하며 특정 사용자의 알림을 필터링하여 SSE형식으로 변환하여 반환하게끔 처리가 되어 언제든지 해당 사용자에게 알림 추가 이벤트를 전송할 수가있다.

 async createAlarm(userId: number, title: string, description: string) {
        // 알람을 여기서 저장하는 로직 작성
        const alarm = await this.alarmRepository.save({
            title, 
            description,
            userId,
        });
        this.emitAlarmAddedEvent(userId, alarm.title, description)
    }
  1. const alarms을 통하여 알람 리파지토리(저장소)에 title, description userId를 저장한다.

  2. this.emitAlarmAddedEvent(userId, alarm.title, description)

알람생성 함수 호출하여 추가 이벤트 발생하여 받은 매개변수를 전달하여 발생한며, 알람추가 이벤트를 구독하고 있는 다른 부분에서 해당 이벤트를 수신할 수 있다.

간단하지만 간단하지않게 SSE 알림구현에 대해서 알아봤다 RxJs에 대한 부분도 공부를 해야하며, 생각보다 해당 개념은 난이도가 있는부분이어 추후에 디깅을 해봐야 할 것 같다. SSE란 단방향 데이터베이스 전송으로만 이해하고 있었으나 이용을 하는부분에서 쉬운 부분은 아닌것같다.

profile
곰처럼 개발해보자.

0개의 댓글