실시간 알림기능 SSE로 구현하기

Sol's·2023년 2월 6일
0

팀프로젝트

목록 보기
21/25

앞서 실시간 알림기능을 SSE로 구현하기로 하였습니다.

이번글에서는 실시간 알림 기능을 구현해 보겠습니다.

서버단의 로직은 생각보다 복잡하여 Front먼저 살펴보겠습니다.

자바스크립트를 통한 SSE통신 연결 요청

클라이언트에서 서버로 연결이 필요합니다.
이때 자바스크립트의 EventSource를 사용하여 연결하면 됩니다.

<script type="application/javascript" th:fragment="sse">
    let userId = document.getElementById("myName").innerText;
    console.log(userId);
    if (userId.length > 0) {
        const eventSource = new EventSource("/sse" + "?userId="+userId)
        eventSource.addEventListener("alarm", function(event) {
            let message = event.data;
            Swal.fire({
                // toast:true,
                position: 'top-end',
                icon: 'success',
                title: message,
                showConfirmButton: false,
                timer: 1500
            });

        })
</script>

로그인이 되어있는 유저를 sse연결을 하여 알림을 요청할 것이기 떄문에 Controller에 userId를 같이 넘겨줍니다.

addEventListener를 사용하여 "alarm"라는 이벤트가 발생되면 서버에서 통신을 받을 수 있게 작성하였습니다.

위의 자바스크립트를 navbar에 넣어 공통적으로 적용되게 하였습니다.

<script type="text/javascript" src="/js/sse.js"></script>

Controller

이제 서버단의 로직입니다.

클라이언트에서 전달받은 userId를 키값으로 SseEmitter를 밸류로 설정하여 Map을 선언합니다.

@RestController
@RequestMapping("/sse")
@Slf4j
public class SseController {

    public static Map<String, SseEmitter> sseEmitters = new ConcurrentHashMap<>();

    @GetMapping(value = "", consumes = MediaType.ALL_VALUE)
    public SseEmitter streamSseMvc(@RequestParam String userId) {
        log.info("userId = {}", userId);

        // 현재 클라이언트를 위한 SseEmitter 생성
        SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
        try {
            // 연결!!
            emitter.send(SseEmitter.event().name("connect"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        // userId를 key값으로 해서 SseEmitter를 저장
        sseEmitters.put(userId, emitter);

        emitter.onCompletion(() -> sseEmitters.remove(userId));
        emitter.onTimeout(() -> sseEmitters.remove(userId));
        emitter.onError((e) -> sseEmitters.remove(userId));

        return emitter;
    }
}

Map을 통해 사용자별로 SseEmitter를 식별하여 이벤트를 보낼 수 있습니다.

Service

//sse 로직
if (sseEmitters.containsKey(crew.getUser().getUsername())) {
	SseEmitter sseEmitter = sseEmitters.get(crew.getUser().getUsername());
    try {
    	sseEmitter.send(SseEmitter.event().name("alarm").data(
        userName + "님이 \"" + crew.getTitle() + "\" 모임에 좋아요를 눌렀습니다."));
        } catch (Exception e) {
        	sseEmitters.remove(crew.getUser().getUsername());
        }
    }

service에서는 Sse연결이된 User가 존재한다면 alarm이벤트를 발생시킵니다.

정리

Sse 알림기능을 구현하면서 정말많은 검색과 시도를 해보았습니다.
실제로 구현한 결과를 보니, 고생한 만큼의 보람이 있어 너무 기분이 좋았습니다.

서버에 트래픽을 신경써서 어떤기능을 넣을지 고민하는것부터
정해진 기능을 구현하는것까지 전부 너무 재미있는 과정이였습니다.

실제로 로직이 동작하는것을 보니 얼른 실무에서도 직접 사용자 경험을 높일 수 있는 방법을 고민하고 그것을 개발하여
사용자에게 선보이고 싶어졌습니다

참고자료

알림 기능을 구현해보자 - SSE(Server-Sent-Events)!

🔥 TIL - Day 64 SSE를 이용한 실시간 알림

[Spring + SSE] Server-Sent Events를 이용한 실시간 알림

Server-Sent Events 알아보기 (feat. Spring) - sse시간출력

profile
배우고, 생각하고, 행동해라

0개의 댓글