사이드 프로젝트에서 푸시 알림 기능을 도입하게 되었다.
기술 스택 채택과 적용 과정에 대해서 기록하고자 한다.
알림 기능 요구사항
다른 유저가 참여한 경우다른 유저가 인증글을 작성한 경우미제출 상태인 경우3가지 기능 모두 공통적으로 클라이언트에서 알림을 발행하는 케이스 없이,서버에서 단방향으로 데이터를 내려준다. 또한, 실시간 이벤트 스트리밍이 요구된다.
Polling
클라이언트가 주기적으로 서버로 요청을 보내는 방법이다. 일정 시간마다 서버에 요청을 보내 데이터가 갱신되었는지 확인하고, 갱신되었다면 데이터를 응답 받는 방법이다.

연결을 끊고, 다시 연결하기를 반복하기 때문에 불필요한 트래픽이 많이 발생한다는 단점이 있다.Long Polling
기존의 Polling 방식에서 개선되어, 요청을 보내고 서버에서 변경이 일어날 때까지 대기하는 방법이다.

변경 사항이 있을 때까지, 커넥션을 유지하다가 응답을 받으면 끊고, 다시 연결 요청을 한다.
즉, 상태가 빈번하게 바뀐다면 polling에서의 단점을 그대로 가진다.
SSE
서버와 연결을 맺으면 일정 시간동안 서버에서 변경이 일어날 때마다 데이터를 전달받는 방법이다.

Long Polling 방식과 유사하지만, 데이터의 갱신이 발생하더라도 연결을 끊는 것이 아니라 일정 시간동안 연결을 유지하는 차이점을 가지고 있다.
Web Socket
대표적인 양방향 통신 방법으로, 주로 채팅 서비스에 많이 이용된다.
클라이언트와 서버가 연결을 유지하면서 데이터를 주고 받는 통신 방식이다.
우리 서비스는 클라이언트가 서버로 데이터를 전송하지 않기 때문에, 단방향 통신 방식을 고려하게 되었다. Polling, Long Polling 방식은 요청과 연결이 빈번하기 때문에 리소스 낭비를 초래할 수 있을 뿐더러, SSE 방식은 별도의 의존성 추가가 필요 없고, 러닝 커브가 가볍다고 판단해서 sse로 결정하게 되었다.
SSE는 지속적인 연결을 유지하기 때문에, 네트워크 리소스를 소모하고, 연결을 유지하기 때문에 서버의 처리 부하를 증가시킬 수 있기 때문에, 이러한 부분을 경계해야한다.
Notification Entity

EmitterRepository

기술 블로그를 찾아보니, 동시성 이슈때문에 ConcurrentHashMap을 많이 사용하더라. 이번 기회에 몰랐던 자료구조를 활용해보게 되었다.
EmitterRepository는 JPARepository를 사용하지 않고, 메모리 상에서 map을 관리한다. 그렇기 때문에 생성, 삭제, 조회 메서드를 직접 구현했다.
NotifyRepository
인스타그램에서 알림을 확인하는 기능에서 착안하여, 알림을 DB에서 관리하기 위해 JPARepository를 상속받았다.
NotifyController

클라이언트가 {domain}/api/v1/notify/subscribe로 접근하면, 서버와 연결을 요청하는 API다.
비즈니스 로직 - emitter 생성

비즈니스 로직 - subscribe

위 create 메서드를 호출하여 emitter 객체를 생성하고 커넥션을 맺는 로직이다.
처음 SSE 커넥션을 맺을 때, 아무런 이벤트도 보내지 않으면 재연결 요청을 보낼 때나, 연결 요청 자체에서 오류가 발생한다고 한다. 이런 이유로, 처음 emitter를 생성하면서 더미데이터를 담아서 전송하도록 구현했다.
send

다른 도메인 서비스에서 send 메서드를 호출한다. 인증글이 새로 작성된 시점에, 서비스를 이용중이지 않은 사용자들이 있을 수 있다. 그렇기 때문에 NPE를 예방하기 위해, 조건문에서 DB에 저장하고 return하도록 작성했다.
subscribe api 호출로 커넥션 수립

인증글 새로 작성 시, 해당 미션 참여자들에게 알림

인코딩 이슈가 있긴하지만, DB 저장은 정상적으로 이루어졌다.

postService에서 notify를 호출하는 결합도가 높은 코드를 리팩토링하고, 비동기로 메시지큐를 통해 성능을 개선하고자 한다.
https://tecoble.techcourse.co.kr/post/2022-10-11-server-sent-events/