๐Ÿ’ SSE + Spring Event์˜ ๋งŒ๋‚จ

์œ ๊ฑด์šฐยท2025๋…„ 1์›” 7์ผ

ํ”„๋กœ์ ํŠธ

๋ชฉ๋ก ๋ณด๊ธฐ
7/9
post-thumbnail

โ“๊ฐœ์š”

ํŒŒ์ด๋„ ํ”„๋กœ์ ํŠธ ์š”๊ตฌ์‚ฌํ•ญ์œผ๋กœ ์•Œ๋ฆผ ๊ธฐ๋Šฅ์ด ์กด์žฌํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์•Œ๋ฆผ ๊ธฐ๋Šฅ์„ ๋„ฃ๊ธฐ์— ์•ž์„œ ์–ด๋–ค์‹์œผ๋กœ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ์„ ๋ณด๋‚ผ์ง€ ๊ธฐ์ˆ ์ ์œผ๋กœ ์„ ํƒํ•ด์•ผํ•˜๋Š” ์ƒํ™ฉ์ด์—ˆ์Šต๋‹ˆ๋‹ค. FCM๊ณผ SSE์— ๋Œ€ํ•ด 2๊ฐ€์ง€ ์„ ํƒ์‚ฌํ•ญ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. FCM ๊ฐ™์€ ๊ฒฝ์šฐ ์„œ๋ฒ„ ๋ฆฌ์†Œ์Šค๋ฅผ ์“ฐ์ง€ ์•Š๊ณ  ์™ธ๋ถ€ ์„œ๋ฒ„๋ฅผ ์ด์šฉํ•ด ์•Œ๋ฆผ ๊ธฐ๋Šฅ์— ์ฑ…์ž„์„ ๋ถ€์—ฌํ•˜๊ณ  ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ €ํฌ ํ”„๋กœ์ ํŠธ๊ฐ™์€ ๊ฒฝ์šฐ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—†๊ธฐ๋•Œ๋ฌธ์— ๋””๋ฐ”์ด์Šค ํ† ํฐ ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์—†๊ณ  ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋งŒ์œผ๋กœ๋Š” ํ…Œ์ŠคํŠธํ•˜๊ธฐ์—๋Š” ๋ฌด๋ฆฌ๊ฐ€ ์žˆ๋‹ค๊ณ  ํŒ๋‹จํ•˜์—ฌ SSE ๋ฅผ ํ†ตํ•ด ์•Œ๋ฆผ๊ธฐ๋Šฅ์„ ๋ณด๋‚ด๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์‹ค์‹œ๊ฐ„์„ฑ์€ FCM ์— ๋น„ํ•ด ๋–จ์–ด์ง€์ง€๋งŒ ๊ธฐ์กด HTTP ์ธํ”„๋ผ๋ฅผ ๊ทธ๋Œ€๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ด๋ฒคํŠธ ํ˜•์‹์œผ๋กœ ์ฑ…์ž„์„ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์กด์žฌํ•˜๊ธฐ์— SSE ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์ง€๊ธˆ ๋ถ€ํ„ฐ Event + SSE ๋ฅผ ํ†ตํ•ด ๊ตฌํ˜„๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.





โš™๏ธ SSE ๋™์ž‘ ์›๋ฆฌ

SSE ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์— ์„ค๋ช…ํ•˜๊ธฐ์— ์•ž์„œ ๋จผ์ € SSE ๋™์ž‘ ์›๋ฆฌ์— ๋Œ€ํ•ด์„œ ์งš๊ณ  ๋„˜์–ด๊ฐ€๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์š”์ฒญ ํ๋ฆ„

  • ์ดˆ๊ธฐ ์—ฐ๊ฒฐ
    • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์— GET ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.
    • ์„œ๋ฒ„๋Š” text/event-stream ์ปจํ…์ธ  ํƒ€์ž…์œผ๋กœ ์‘๋‹ตํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
    • ์ดˆ๊ธฐ ์—ฐ๊ฒฐ ์‹œ ๋”๋ฏธ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด์•ผ 5xx ์—๋Ÿฌ๋ฅผ ํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆฌ๋ฐ
    • ์ดˆ๊ธฐ ์—ฐ๊ฒฐ์ด ํ›„ ์„œ๋ฒ„๊ฐ€ ์ง€์†์ ์œผ๋กœ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์‘๋‹ตํ•˜๊ฒŒ๋ฉ๋‹ˆ๋‹ค.
    • ๊ฐ ์ด๋ฒคํŠธ๋Š” data ์ ‘๋‘์‚ฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  • ์—ฐ๊ฒฐ ๋Š๊น€ ๋ฐ ์žฌ์—ฐ๊ฒฐ
    • ์—ฐ๊ฒฐ์ด ๋Š์–ด์งˆ ๊ฒฝ์šฐ ํด๋ผ์ด์–ธํŠธ๋Š” ์ž๋™์œผ๋กœ ์žฌ์—ฐ๊ฒฐ์„ ์‹œ๋„ํ•˜๊ฒŒ๋ฉ๋‹ˆ๋‹ค.
    • Last-Event-Id๋ฅผ ํ†ตํ•ด ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฐ›์€ ์ด๋ฒคํŠธ๋ถ€ํ„ฐ ๋‹ค์‹œ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ฒŒ๋ฉ๋‹ˆ๋‹ค.
  • ์ŠคํŠธ๋ฆฌ๋ฐ ์ง€์†
    • ๋ช…์‹œ์ ์œผ๋กœ ์ข…๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€ ์—ฐ๊ฒฐ์ด ์œ ์ง€๋˜๋ฉฐ ๊ณ„์†ํ•ด์„œ ์ด๋ฒคํŠธ๋ฅผ ์‘๋‹ตํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

SSE ๋Š” ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ์ „์†ก๋ฉ๋‹ˆ๋‹ค. WebSocket๊ณผ ๋‹ฌ๋ฆฌ ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋ฒ„๋กœ ๋ฐ์ดํ„ฐ ์ „์†ก์€ ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ๋ธŒ๋ผ์šฐ์ € ์—ฐ๊ฒฐ ์ˆ˜ ์ œํ•œ์œผ๋กœ ์ธํ•ด ๋™์‹œ ์—ฐ๊ฒฐ ์ˆ˜๊ฐ€ ์ œํ•œ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ WebSocket์— ๋น„ํ•ด ๊ตฌํ˜„์ด ๊ฐ„๋‹จํ•˜๋ฉฐ HTTP๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ์— ๋‹ค๋ฅธ ์„œ๋ฒ„์™€์˜ ํ˜ธํ™˜์„ฑ์ด ์ข‹๊ธฐ ๋•Œ๋ฌธ์— ์•Œ๋ฆผ๊ธฐ๋Šฅ, ์‹ค์‹œ๊ฐ„ ๋กœ๊ทธ ๋ชจ๋‹ˆํ„ฐ๋ง์œผ๋กœ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.





๐Ÿ’ฌ Spring Event ์— ๋Œ€ํ•ด

๊ตฌํ˜„ ์ฝ”๋“œ๋ฅผ ์„ค๋ช…ํ•˜๊ธฐ์— ์•ž์„œ Spring Event ๋ฐœํ–‰ ํ•˜๊ณ  ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ฐ„๋‹จํžˆ ์•Œ์•„ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๋ ค๋ฉด Event ๊ฐ์ฒด๊ฐ€ ํ•„์š”ํ•˜๋ฉฐ ์ด ์ด๋ฒคํŠธ ๊ฐ์ฒด๋ฅผ ๋ณด๋‚ด์ค„ ApplicationEventPublisher ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  EventListener ๋ฅผ ํ†ตํ•ด ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•ด์ค„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์œ„ 3๊ฐœ๋งŒ ์žˆ๋‹ค๋ฉด ๊ฐ„๋‹จํ•˜๊ฒŒ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•ด ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ„๋‹จํ•˜๊ฒŒ ์ฝ”๋“œ๋กœ ํ™•์ธํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Event

public class CustomEvent extends ApplicationEvent {
    private String message;
    
    public CustomEvent(Object source, String message) {
        super(source);
        this.message = message;
    }
}
  • ์ด๋ฒคํŠธ ๊ฐ์ฒด๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  • ํ•ด๋‹น ์ด๋ฒคํŠธ์— ์–ด๋–ค ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ด๊ธฐ๋Š”์ง€๋Š” Customํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ผญ ApplicationEvent ์ƒ์†๋ฐ›์•„ ๊ตฌํ˜„ํ•ด์•ผํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค.

Event Publisher

@Autowired
private ApplicationEventPublisher eventPublisher;

public void publishEvent() {
    CustomEvent event = new CustomEvent(this, "์ด๋ฒคํŠธ ๋ฐœ์ƒ!");
    eventPublisher.publishEvent(event);
}
  • ApplicationEventPublisher ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • publishEvent() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ด๋ฒคํŠธ๊ฐ์ฒด๋ฅผ ๋‹ด์•„ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•ฉ๋‹ˆ๋‹ค.

Event Listener

@EventListener
public void handleCustomEvent(CustomEvent event) {
    // ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋กœ์ง
}
  • @EventListener ์• ๋…ธํ…Œ์ด์…˜์„ ์ด์šฉํ•ด Event ๋ฅผ ์ˆ˜์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋น„๋™๊ธฐ๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด @Async ์• ๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

Spring Event ๋ฅผ ์ด์šฉํ–”๋‹ค๋ฉด ๊ฐ„๋‹จํ•˜๊ฒŒ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜์—ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ์˜ ์ฝ”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•ด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ํŠธ๋žœ์žญ์…˜์„ ๋ถ„๋ฆฌํ•˜์—ฌ ์•Œ๋ฆผ๊ธฐ๋Šฅ์ด ์‹คํŒจํ•˜๋”๋ผ๋„ ๋น„์ฆˆ๋‹ˆ์Šค๋กœ์ง์ด ๋กค๋ฐฑ๋˜๋Š” ๊ฒฝ์šฐ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ Spring Event + SSE ๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•œ ์ฝ”๋“œ์— ๋Œ€ํ•ด ์„ค๋ช…ํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.





๐Ÿง‘โ€๐Ÿ’ป ์ „์ฒด ์ฝ”๋“œ

Event

public record NotificationEvent<T extends Notification>(NotificationType type, T data) {
}
  • Event ๋ฅผ ํ†ตํ•ด ๋ณด๋‚ด์ค„ ๋ฐ์ดํ„ฐ๋ฅผ ์ •ํ•ด์ค๋‹ˆ๋‹ค.
  • ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๊ธฐ ๋•Œ๋ฌธ์— Notification ์„ ์ƒ์†๋ฐ›์€ ์—”ํ‹ฐํ‹ฐ์— ํ•œํ•ด์„œ Event ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค.



EventPublisherService

@Slf4j
@Service
@RequiredArgsConstructor
public class NotificationPublisherService {
    private final ApplicationEventPublisher applicationEventPublisher;
    private final ChatNotificationRepository chatNotificationRepository;
    private final DonationNotificationRepository donationNotificationRepository;
    private final ExchangeNotificationRepository exchangeNotificationRepository;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createExchangeNotification(Long userId, String exchangeItemName, String requesterNickname, TradeStatus status) {
        ExchangeNotification exchangeNotification = ExchangeNotification.builder()
                .userId(userId)
                .exchangeItemName(exchangeItemName)
                .requestUserNickname(requesterNickname)
                .tradeStatus(status)
                .build();

        ExchangeNotification savedNotification;

        try {
            savedNotification = exchangeNotificationRepository.save(exchangeNotification);
            applicationEventPublisher.publishEvent(new NotificationEvent<>(NotificationType.EXCHANGE, savedNotification));
        } catch (Exception e) {
            log.warn("์•Œ๋ฆผ ๋ฐœํ–‰ ์‹คํŒจ : {}", e.getMessage());
        }
    }
}
  • NotificationPublisherService ๋Š” ์ด๋ฒคํŠธ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ  ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๋Š”๋ฐ์— ๊ด€์‹ฌ์ด ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค.
  • propagation์„ ํ†ตํ•ด ํŠธ๋žœ์žญ์…˜์„ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ํ›„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์— ์—๋Ÿฌ๋ฅผ ์žก์•„์ฃผ์ง€ ์•Š๋Š”๋‹ค๋ฉด ํŠธ๋žœ์žญ์…˜์„ ๋ถ„๋ฆฌํ–ˆ๋‹ค๊ณ  ํ•˜๋”๋ผ๋„ ์•Œ๋ฆผ ๋ฐœํ–‰ ์—๋Ÿฌ๊ฐ€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋กค๋ฐฑ์‹œํ‚ฌ ์ˆ˜ ์žˆ๊ธฐ๋•Œ๋ฌธ์— ๋ฐœ์ƒ๋  ์ˆ˜ ์žˆ๋Š” ์—๋Ÿฌ๋Š” ์žก์•„์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.



EventListener

@Slf4j
@Component
@RequiredArgsConstructor
public class NotificationEventListener {

    private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>();

    public SseEmitter subscribe(Long userId) {
        SseEmitter emitter = new SseEmitter(Duration.ofHours(1).toMillis());

        emitter.onCompletion(() -> emitters.remove(userId));
        emitter.onTimeout(() -> emitters.remove(userId));
        emitter.onError((ex) -> {
            log.info("์•Œ๋ฆผ ๊ธฐ๋Šฅ ์—๋Ÿฌ ๋ฐœ์ƒ User Id : {}", userId);
            emitters.remove(userId);
        });

        try {
            emitter.send(SseEmitter.event() // ์ดˆ๊ธฐ ์—ฐ๊ฒฐ ์‹œ ๋”๋ฏธ๋ฐ์ดํ„ฐ ์ „์†ก 
                    .name("init")
                    .data("connect!!!!!!!"));
        } catch (IOException e) {
            log.info("์•Œ๋žŒ ๊ตฌ๋… ๊ธฐ๋Šฅ ์—๋Ÿฌ ๋ฐœ์ƒ User Id : {}", userId);
            throw new RuntimeException(e);
        }

        emitters.put(userId, emitter);
        return emitter;
    }

    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void sendNotification(NotificationEvent<? extends Notification> event) {
        SseEmitter emitter = emitters.get(event.data().getUserId());

        if (emitter != null) {
            try {
                emitter.send(SseEmitter.event() // ์•Œ๋ฆผ ์ „์†ก 
                        .id(event.data().getUserId().toString())
                        .name(event.type().name())
                        .data(event.data())
                        .reconnectTime(Duration.ofHours(1).toMillis()) // ์žฌ์—ฐ๊ฒฐ ์‹œ๊ฐ„ ์„ค์ ˆ 
                );
            } catch (IOException e) {
                log.info("์•Œ๋ฆผ ๋ฐœ์†ก ์‹คํŒจ User Id : {}", event.data().getUserId());
                emitters.remove(event.data().getUserId());
            }
        }
    }
}
  • NotificationEventListener ์˜ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์—ญํ• ์€ ์ดˆ๊ธฐ ์—ฐ๊ฒฐ๊ณผ ์•Œ๋ฆผ์„ ๋ฐœํ–‰ํ•˜๋Š” ์—ญํ• ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • SSE์— emitter ๋ฅผ ์ด์šฉํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค.
  • ์ดˆ๊ธฐ ์—ฐ๊ฒฐ ์‹œ ๋”๋ฏธ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด์ฃผ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.
  • @EventListener ๋ฅผ ํ†ตํ•ด ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜๊ณ  ๋น„๋™๊ธฐ์ ์œผ๋กœ ์•Œ๋ฆผ์„ ์ „์†กํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.



์ „์ฒด์ ์ธ ํ๋ฆ„๋„

  • Notification์ด๋ผ๋Š” ์ถ”์ƒ ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด ์•Œ๋ฆผ ๊ธฐ๋Šฅ์„ ์ถ”์ƒํ™”ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ฒŒ๋ฉ๋‹ˆ๋‹ค.
  • NotificationPublisherService ๋Š” ์•Œ๋ฆผ์„ ๋ฐœํ–‰ํ•˜๋Š” ๊ด€์‹ฌ๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • NotificationEventListener ๋Š” ์•Œ๋ฆผ์„ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์‘๋‹ตํ•˜๋Š”๋ฐ์— ๊ด€์‹ฌ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.







๐Ÿ“– ํ†บ์•„๋ณด๊ธฐ

  • Spring Event ๋ฅผ ํ†ตํ•ด ์•Œ๋ฆผ ๋ฐœํ–‰ ๊ธฐ๋Šฅ์„ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
  • ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์€ ํŠธ๋žœ์žญ์…˜์„ ๋ถ„๋ฆฌํ•˜์—ฌ ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์ด ์‹คํŒจํ•˜๋”๋ผ๋„ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์‹คํŒจํ•˜์ง€ ์•Š๋„๋ก ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
  • @TransactionalEventListener ๋ฅผ ํ†ตํ•ด ๋น„์ฆˆ๋‹ˆ์Šค๋กœ์ง์ด ์ปค๋ฐ‹๋˜๊ณ  ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋„๋ก ํ•จ์œผ๋กœ์จ ๋‚ฎ์€ ๊ฒฐํ•ฉ๋„๋ฅผ ๊ฐ€์ง€๋Š” ์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
  • SSE ๋ฅผ ํ†ตํ•ด ์•Œ๋ฆผ ๋ฐœํ–‰์ด ์•„๋‹ˆ๋”๋ผ๋„ ์กฐํšŒ ์ˆ˜ ์นด์šดํŒ…/์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณ€ํ•˜๋Š” ํŽ˜์ด์ง€๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๊นจ๋‹ฌ์•˜์Šต๋‹ˆ๋‹ค.
profile
โœ…ย ์ ๋‹นํ•œ ์ถ”์ƒํ™”๋ฅผ ์ฐพ์•„๊ฐ€๋Š” ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค.

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