오늘은 V3의 SSE를 통한 실시간 알림을 구현했다!
오늘은 우리 서비스에서 사용할 실시간 알림을 구현하기 위해 SSE를 도입해보았다!
SSE는 사용자가 요청하지 않아도 서버가 사용자에게 데이터를 전송할 수 있도록 도와주는데, 알림 기능의 경우 사용자가 별도로 요청하지 않아도 알림이 사용자에게 전달되어야 하기에 이걸 도입하였다.
스프링에서는 SSE Emitter라는 구현체를 제공해서 매우 간단하게 SSE를 사용할 수 있었다.
사용자가 알림 구독을 요청하면, 사용자와의 SSE Emitter 객체를 생성하고, 이 객체를 통해 사용자에게 데이터를 보낼 수 있게 된다.
이때 SSE Emitter는 한번 사용하고 끝나는 것이 아니라 지속적으로 사용해야 하기 때문에, Map<String, SseEmitter> 이렇게 사용자를 판별할 수 있는 key와 SSE Emitter 객체를 저장해두었다.
private void sendNotification(SseEmitter emitter, String emitterId, Object data) {
try {
emitter.send(SseEmitter.event()
.name("sse")
.data(data));
} catch (IOException exception) {
emitterRepository.deleteById(emitterId);
}
}
그리고 저장된 Emitter 객체에서 send() 메서드를 사용하면 매우 간단하게 알림을 전송할 수 있게 된다.
이때 SSE Emitter 내부적으로 ResponseBodyEmitterReturnValueHandler 클래스의 handleReturnValue 메소드에서 비동기 요청을 처리해주는 DeferredResult를 생성하고 다른 쓰레드로 메시지를 전송한다고 한다.
한마디로,, 내부적으로 비동기로 알림 전송을 처리한다고 한다.
다른 사람들이 구현한 내용들을 보니 SSE 통신 연결 시에 마지막으로 받은 알림의 ID를 기록해두고, 그 이후의 알림들부터 다시 받을 수 있도록 구현하더라.
SSE 통신이 끊겨있는 동안에 발생한 알림들을 SSE 통신을 연결하고 다시 받을 수 있도록 하기 위함이었다.
나도 이렇게 구현해보려고 했지만.. 왜인지 자꾸 제대로 되지 않았고..
우리 서비스에서는 푸시 알림 보다는 알림의 개수가 제대로 증가하는 것이 목적이었기 때문에 이 부분이 필요하지 않다고 판단되었다.
그래서 나는 따로 알림에 대한 이벤트 캐시를 저장해두지 않았다.
사실 SSE에 대해서는 인터넷 자료들이 굉장히 많아서 구현하는 데에 큰 어려움은 없었다.
하지만, 우리는 배포 시에 부하 방지를 위해 다중 서버를 사용하려고 하는데, 이때 SSE Emitter 객체들을 어떻게 관리할 것인지에 대한 방법도 필요하다.
그리고 프론트엔드와 연결할 때 문제가 굉장히 많이 생긴다고 해서 이 부분은 이후에 해결해보아야 할 것 같다.
그래도 처음 SSE를 사용해보는데 굉장히 간단하게 잘 해결할 수 있었던 것 같다!
우리 팀이 작성한 코드는 깃허브를 통해 업로드해두었다.
GitHub 보러가기
위치 조회를 진행하면서 생각보다 너무 어렵고, 너무 오래 걸려서 SSE는 더 어려울 것이라 생각하고 걱정했었다.
그런데, 스프링에서 SSE를 너무 잘 지원해주고 있었고, 인터넷에도 관련 자료들이 너무 많아서 생각보다 되게 간단하게 구현할 수 있었다.
이제 또 다른 기능들을 개발하고, 시간이 된다면 엘라스틱 서치까지 시도해볼 예정인데, 너무 재미있을 것 같다.