๐Ÿ”” ํ‘ธ์‹œ ์•Œ๋ฆผ ์„œ๋น„์Šค ๊ตฌํ˜„

Sujung Shinยท2024๋…„ 10์›” 13์ผ
0

๋“ค์–ด๊ฐ€๋ฉฐ

ํ”„๋กœํ† ํƒ€์ธ ํ”„๋กœ์ ํŠธ์˜ ๋ฆฌํŒฉํ† ๋ง ์ž‘์—…์—์„œ ์•Œ๋ฆผ ๊ธฐ๋Šฅ์„ ๊ตฌ์กฐํ™”&์„ค๊ณ„ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค.

์šฐ๋ฆฌ ํ”„๋กœํ† ํƒ€์ธ์˜ ์•Œ๋ฆผ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ 2๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ผ ๋•Œ ํŠธ๋ฆฌ๊ฑฐ๋œ๋‹ค:

  1. ๋ณด์œ  ํ‹ฐ์ผ“ ๊ฐœ์ˆ˜์— ์œ ๋™์ ์ธ ๋ณ€ํ™”๊ฐ€ ์ƒ๊ธด๋‹ค.
  2. ๊ธฐ์—…์—๊ฒŒ ํŒจ๋„ํ‹ฐ๋ฅผ ๋ฐ›์„ ์‹œ์—, ํ•ด๋‹น ์‚ฌ์‹ค์„ ๊ณ ์ง€ํ•ด์ค€๋‹ค.
    (๋‚ด์šฉ: ํŒจ๋„ํ‹ฐ๋ฅผ ๋ถ€์—ฌํ•œ ๊ธฐ์—…, ๊ฐ„๋‹จํ•œ ์‚ฌ์œ )

ํ‘ธ์‹œ ์•Œ๋žŒ์˜ ํ•ต์‹ฌ์€ ์‚ฌ์šฉ์ž๊ฐ€ ์šฐ๋ฆฌ ํ”Œ๋žซํผ์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์ง€ ์•Š์•„๋„, ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ์„ ๋ณด๋‚ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.
๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ์˜ ๋ฉ”์ธ ์ด์Šˆ๋Š” HTTP๋Š” connectionlessํ•˜๋‹ค๋Š” ๊ฑฐ๋‹ค. (์—ฐ๊ฒฐํ•œ ์ ์ด ์žˆ์–ด๋„ ๋Š์–ด๋ฒ„๋ฆผ)
๊ธฐ์กด์˜ ์„œ๋ฒ„-ํด๋ผ์ด์–ธํŠธ๋Š” ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์„ ์ฑ„ํƒํ•˜๋ฏ€๋กœ ์ด๋Ÿฐ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋Š” ๊ฒƒ์ด๋‹ค.
๊ทธ๋ ‡๋‹ค๋ฉด ํ‘ธ์‹œ ์•Œ๋žŒ์€ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์„๊นŒ?
๋‚˜์ด๋ธŒํ•˜๊ฒŒ๋Š” ์ผ๋ฐฉ์ ์ธ ๋‹จ๋ฐฉํ–ฅ ํ†ต์‹ ์„ ์ฑ„ํƒํ•˜๋ฉด ๋  ๊ฒƒ๋งŒ ๊ฐ™๋‹ค.

ํ‘ธ์‹œ ์•Œ๋žŒ์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์—๋Š” Server-Sent-Event(SSE) ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹๊ณผ, Firebase Cloud Messaging(FCM) ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด ์žˆ๋‹ค.

ํ˜„์žฌ ์šฐ๋ฆฌ ์š”๊ตฌ์‚ฌํ•ญ์— ๋” ์ ํ•ฉํ•œ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์€ ๋‘˜ ์ค‘ ๋ฌด์—‡์ผ์ง€ ๊ณ ๋ฏผํ–ˆ๋‹ค.
๋จผ์ € Server-Sent-Event(SSE) ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž.

Server-Sent-Event ๋ฐฉ์‹

  1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์˜ ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ๋…ํ•˜๊ธฐ ์œ„ํ•œ ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.
  2. ์„œ๋ฒ„์—์„œ๋Š” ํด๋ผ์ด์–ธํŠธ์™€ ๋งตํ•‘๋˜๋Š” SSE๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  3. ์„œ๋ฒ„๋Š” ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆผ์„ ์ƒ์„ฑํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•œ๋‹ค.

Firebase Cloud Messsaging ๋ฐฉ์‹


Server โ†” FCM โ†” Client
1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ FCM ์„œ๋ฒ„์—์„œ ์œ ์ €๋ณ„๋กœ ๊ณ ์œ ํ•œ FCM ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›๋Š”๋‹ค.
2. ์„œ๋ฒ„๋Š” ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ํ•ด๋‹น ํ† ํฐ์„ ์ „๋‹ฌ๋ฐ›๊ณ  ์ €์žฅํ•œ๋‹ค.
3. ํŠน์ • ์ƒํ™ฉ์— ๋ฉ”์‹œ์ง€์— ํ† ํฐ์„ ๋‹ด์•„์„œ FCM ์„œ๋ฒ„์— ์ „์†ก ์‹œ, FCM์ด ํ•ด๋‹น ํ† ํฐ์˜ ์œ ์ €์—๊ฒŒ ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•œ๋‹ค.

๊ฒฐ๊ณผ: SSE ์ฑ„ํƒ

๐Ÿค” ๋‚˜์˜ ๊ธฐ์ˆ ์  ๊ณ ๋ฏผ & ์†”๋ฃจ์…˜

โš ๏ธ FCM ์„œ๋ฒ„์— ์˜์กดํ•œ๋‹ค๋ฉด, FCM ์„œ๋ฒ„๊ฐ€ ๋‹ค์šด๋˜์—ˆ์„ ๋•Œ ์šฐ๋ฆฌ ํ”Œ๋žซํผ ์ „์ฒด๊ฐ€ ๋‹ค์šด๋  ์œ„ํ—˜์ด ์žˆ์—ˆ๋‹ค.

๐ŸŒŸ ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์•Œ๋ฆผ ์ „์†ก ๊ตฌํ˜„ ์‹œ์—, ๋‚˜๋Š” SSE๋ฅผ ์‚ฌ์šฉํ•˜๊ฒ ๋‹ค. ํŠธ๋žœ์žญ์…˜ ๋‹จ์œ„๋ฅผ ๋ถ„๋ฆฌํ•œ ํ›„์—, ์•Œ๋ฆผ ์ „์†ก ๋‚ด์—ญ์„ SQS Queue์— ๋„ฃ๊ณ  ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋ ค๊ณ  ํ•œ๋‹ค.

๐Ÿงช Unit Test Code ์ž‘์„ฑ- JUnit5, Mockito ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ด์šฉ

package com.prototyne;

import com.prototyne.Users.service.AlarmService.AlarmService;
import com.prototyne.Users.web.controller.AlarmController;
import com.prototyne.config.JwtManager;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

@ExtendWith(MockitoExtension.class)
public class AlarmControllerTest {

    @InjectMocks
    private AlarmController alarmController;

    @Mock
    private AlarmService alarmService;

    @Mock
    private JwtManager jwtManager;

    @Test
    void subscribe_shouldReturnSseEmitter() {
        String accessToken = "mockAccessToekn";
        SseEmitter mockEmitter = new SseEmitter();
        HttpServletRequest mockRequest = mock(HttpServletRequest.class);

        given(jwtManager.getToken(any(HttpServletRequest.class))).willReturn(accessToken);
        given(alarmService.subscribe(accessToken)).willReturn(mockEmitter);

        SseEmitter res= alarmController.subscribe(mockRequest);

        assertNotNull(res);
        verify(jwtManager).getToken(any(HttpServletRequest.class));
        verify(alarmService).subscribe(accessToken);
    }



}

๋” ์ž์„ธํ•œ ํ…Œ์ŠคํŠธ ์‹คํ–‰๊ฒฐ๊ณผ๋ฅผ ํŠธ๋ž˜ํ‚นํ•˜๊ธฐ ์œ„ํ•ด์„œ,
ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜๋Š” ๋ช…๋ น์–ด๋ฅผ ํ„ฐ๋ฏธ๋„์— ์ž…๋ ฅํ•œ๋‹ค.

gradlew test --info

ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜จ๋‹ค.
build/reports/tests/test/index.html ๊ฒฝ๋กœ์— ์žˆ๋Š” htmlํŒŒ์ผ์„ ๋ธŒ๋ผ์šฐ์ €๋กœ ์—ด์–ด๋ณธ๋‹ค.

์ด๋ ‡๊ฒŒ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜จ๋‹ค.
ํ…Œ์ŠคํŠธ ์ปจํ…์ŠคํŠธ๋Š” 2๊ฐœ(์•Œ๋ฆผ subscribe api & ์ „์ฒด @SpringBootTest) ์—, ์™„๋ฒฝํ•˜๊ฒŒ ์„ฑ๊ณตํ–ˆ๋‹ค๊ณ  ๋‚˜์˜จ๋‹ค.
๊ฐ ์ปจํ…์ŠคํŠธ ๋ณ„ ์‹คํ–‰์‹œ๊ฐ„์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด, ํด๋ž˜์Šค ๋ณ„ ๋ฉ”ํŠธ๋ฆญ์„ ํด๋ฆญํ–ˆ๋‹ค.

profile
๋ฐฑ๋ฌธ์ด๋ถˆ์—ฌ์ผํƒ€

0๊ฐœ์˜ ๋Œ“๊ธ€

๊ด€๋ จ ์ฑ„์šฉ ์ •๋ณด