๐Ÿงฉ Spring Application Event๋กœ ์ฃผ๋ฌธ ์ƒ์„ฑ ํŠธ๋žœ์žญ์…˜ ๋ถ„๋ฆฌํ•˜๊ธฐ

zionยท2์ผ ์ „

TL;DR

์ด ๊ธ€์—์„œ๋Š” ๊ธฐ์กด์— ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ๋™์ž‘ํ•˜๋˜ ์ฃผ๋ฌธ ์ƒ์„ฑ ๋กœ์ง์„ Spring Application Event ๊ธฐ๋ฐ˜์œผ๋กœ ๋ถ„๋ฆฌํ•˜๋ฉฐ, ํŠธ๋žœ์žญ์…˜ ์•ˆ์ •์„ฑ๊ณผ ์ฝ”๋“œ ๊ตฌ์กฐ ๊ฐœ์„ ์„ ๋™์‹œ์— ๋‹ฌ์„ฑํ•œ ๊ณผ์ •์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.


โš™๏ธ Spring Application Event๋ž€ ๋ฌด์—‡์ธ๊ฐ€

Spring Application Event๋Š” JVM ๋‚ด๋ถ€ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ๋™์ž‘ํ•˜๋Š” in-memory ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค.
์„œ๋น„์Šค ๊ฐ„ ๊ฐ•ํ•œ ๊ฒฐํ•ฉ์„ ์ค„์ด๊ณ  ํ›„์† ๋กœ์ง์„ ๋ณ„๋„์˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๋ฐ ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.
๋‹จ์ผ ์„œ๋ฒ„ ํ™˜๊ฒฝ์—์„œ๋Š” ๊ตฌํ˜„์ด ๊ฐ„๋‹จํ•˜๊ณ  ์„ฑ๋Šฅ๋„ ์ข‹์•„ ๋„๋ฆฌ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

๋‹ค๋งŒ ๊ตฌ์กฐ์ ์œผ๋กœ JVM ๋‹จ์ผ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ช‡ ๊ฐ€์ง€ ๋ช…ํ™•ํ•œ ํ•œ๊ณ„๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.


๐Ÿšซ Spring Application Event์˜ ๊ตฌ์กฐ์  ํ•œ๊ณ„

Spring Event๋Š” JVM ์•ˆ์—์„œ๋งŒ ์ฒ˜๋ฆฌ๋˜๋Š” ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜ ์ด๋ฒคํŠธ์ด๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ž˜์™€ ๊ฐ™์€ ์ œ์•ฝ์„ ๊ฐ–์Šต๋‹ˆ๋‹ค.

๐Ÿ” ๋ฉ€ํ‹ฐ ์ธ์Šคํ„ด์Šค ํ™˜๊ฒฝ์—์„œ ์ด๋ฒคํŠธ๊ฐ€ ์—ฌ๋Ÿฌ ๋ฒˆ ์‹คํ–‰๋˜๋Š” ๋ฌธ์ œ

Spring Event๋Š” ์ธ์Šคํ„ด์Šค๋งˆ๋‹ค ๋…๋ฆฝ์ ์œผ๋กœ ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์—
๋™์ผํ•œ ์ด๋ฒคํŠธ๊ฐ€ ์—ฌ๋Ÿฌ ์ธ์Šคํ„ด์Šค์—์„œ ๊ฐ๊ฐ ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
โ€œํ›„์† ์ฒ˜๋ฆฌ๊ฐ€ ๋‹จ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋ผ์•ผ ํ•˜๋Š” ๋กœ์งโ€์—๋Š” ์ ํ•ฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๐Ÿ’ฅ ์žฅ์•  ์‹œ ์ด๋ฒคํŠธ ์œ ์‹ค

์ด๋ฒคํŠธ๊ฐ€ ์ €์žฅ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—
์ฒ˜๋ฆฌ๋˜๊ธฐ ์ „์— ์„œ๋ฒ„๊ฐ€ ์ข…๋ฃŒ๋˜๋ฉด ํ•ด๋‹น ์ด๋ฒคํŠธ๋Š” ๋ณต๊ตฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
๊ฒฐ์ œยท์ •์‚ฐยทํฌ์ธํŠธ ๋“ฑ ์‹ ๋ขฐ์„ฑ์ด ์ค‘์š”ํ•œ ์˜์—ญ์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์šฐ์„  ํŠธ๋žœ์žญ์…˜ ๋ถ„๋ฆฌ๋ฅผ ๋ชฉ์ ์— ๋‘๊ณ  Spring Event๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ› ๏ธ ์ฃผ๋ฌธ ์ƒ์„ฑ ํŠธ๋žœ์žญ์…˜ ๋ถ„๋ฆฌ ๊ณผ์ •

๊ธฐ์กด ์ฃผ๋ฌธ ์ƒ์„ฑ ๋กœ์ง์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

  • ์ฃผ๋ฌธ ์ƒ์„ฑ, ์žฌ๊ณ  ์ฐจ๊ฐ, ํฌ์ธํŠธ ์ฐจ๊ฐ, ์ฟ ํฐ ์ฒ˜๋ฆฌ, PG ์š”์ฒญ์ด ๋ชจ๋‘ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜์— ๋ฌถ์—ฌ ์žˆ์Œ
  • ํ›„์† ๋กœ์ง ์˜ค๋ฅ˜(PG ์š”์ฒญ ๋“ฑ)๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ฃผ๋ฌธ ์ „์ฒด๊ฐ€ ๋กค๋ฐฑ๋จ
  • ํŠธ๋žœ์žญ์…˜ ์‹œ๊ฐ„์ด ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๊ธธ์–ด์ง
  • ๊ธฐ๋Šฅ ํ™•์žฅ ์‹œ ๋ณต์žก๋„ ์ฆ๊ฐ€

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ฃผ๋ฌธ ์ƒ์„ฑ๊ณผ ํ›„์† ์ฒ˜๋ฆฌ(์ฟ ํฐ, PG ์š”์ฒญ)๋ฅผ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.


๐Ÿงญ ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ์ „๋žต ์„ ํƒ ๊ณผ์ •

๋ชจ๋ธ 1 โ€” โ€œ๊ฒ€์ฆยท์ ์œ  ์šฐ์„ โ€ (์ •ํ•ฉ์„ฑ ์šฐ์„ )

ํ•ญ๊ณต๊ถŒ/์˜ˆ์•ฝ ์‹œ์Šคํ…œ ๋“ฑ ์ •ํ™•์„ฑ์ด ์ค‘์š”ํ•œ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

๋ชจ๋ธ 2 โ€” โ€œ์ฃผ๋ฌธ ๋จผ์ €, ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ์€ ๋‚˜์ค‘โ€ (๋งค์ถœ ์šฐ์„ )

ํŠธ๋ž˜ํ”ฝ์ด ๋†’์€ ์ปค๋จธ์Šค์—์„œ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.


๐ŸŽฏ ์„ ํƒํ•œ ๋ฐฉํ–ฅ: ๋ชจ๋ธ 1 ๊ธฐ๋ฐ˜ + ํ›„์† ์ฒ˜๋ฆฌ ์ด๋ฒคํŠธ ๋ถ„๋ฆฌ

์„œ๋น„์Šค ํŠน์„ฑ์ƒ ์•„๋ž˜ ๊ธฐ์ค€์„ ์„ธ์› ์Šต๋‹ˆ๋‹ค.

  • ์žฌ๊ณ ๋Š” ๋™์‹œ์„ฑ ์š”๊ตฌ๊ฐ€ ๋งค์šฐ ๊ฐ•ํ•ด ํŠธ๋žœ์žญ์…˜ ๋‚ด๋ถ€์—์„œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • ์ฟ ํฐ, ํฌ์ธํŠธ, PG ์š”์ฒญ์€ ํ›„์ˆœ์œ„์—ฌ๋„ ๋ฌด๋ฐฉํ•ฉ๋‹ˆ๋‹ค
  • ์ฃผ๋ฌธ ์ƒ์„ฑ ์ž์ฒด๋Š” PG ์„ฑ๊ณต ์—ฌ๋ถ€์™€ ๊ด€๊ณ„์—†์ด ๊ธฐ๋ก๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค

๐Ÿ—๏ธ ์ตœ์ข… ๊ตฌ์กฐ

์ฃผ๋ฌธ ์ƒ์„ฑ์€ ๋ณธ ํŠธ๋žœ์žญ์…˜์—์„œ ์ฒ˜๋ฆฌ

@Transactional
public OrderInfo createOrder(CreateOrderCommand command) {
    List<Product> products = productService.getExistingProducts(
        command.orderItemRequests().stream()
            .map(CreateOrderCommand.OrderItemRequest::productId)
            .toList()
    );

    deductStock(command.orderItemRequests());

    Order order = Order.create(command.userId(), createOrderItems(command.orderItemRequests(), products));
    Order savedOrder = orderService.save(order);

    eventPublisher.publishEvent(new OrderCreatedEvent(
        savedOrder.getId(),
        command.userId(),
        command.couponId(),
        command.cardType(),
        command.cardNo()
    ));

    return OrderInfo.from(savedOrder);
}

ํ•ต์‹ฌ ์˜๋„

  • ์ฃผ๋ฌธ ์ƒ์„ฑ๊ณผ ์žฌ๊ณ  ์ฐจ๊ฐ์€ ์ฆ‰์‹œ DB์— ๋ฐ˜์˜๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • ์ฟ ํฐ/ํฌ์ธํŠธ/PG ์š”์ฒญ์€ ์ฃผ๋ฌธ ์ƒ์„ฑ ์ดํ›„์—๋„ ์ถฉ๋ถ„ํžˆ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค
  • ํ›„์† ๋กœ์ง์€ ์ด๋ฒคํŠธ๋กœ ๋ถ„๋ฆฌํ•ด ํŠธ๋žœ์žญ์…˜ ์˜ํ–ฅ๋„๋ฅผ ์ตœ์†Œํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค

ํ›„์† ๋กœ์ง์€ ๋ณ„๋„ ํŠธ๋žœ์žญ์…˜์—์„œ ์ฒ˜๋ฆฌ

@TransactionalEventListener(phase = AFTER_COMMIT)
@Async
public void handleOrderCreated(OrderCreatedEvent event) {
    Order order = orderService.getOrder(event.orderId());

    Money originalPrice = order.getTotalPrice();
    Money finalPrice = couponService.useCouponById(
        event.couponId(),
        event.userId(),
        originalPrice
    );

    paymentService.requestPayment(event.orderId(), event.cardType(), event.cardNo(), finalPrice);

    eventPublisher.publishEvent(new OrderDataTransferEvent(
        event.orderId(),
        event.userId(),
        order.getStatus(),
        order.getTotalPrice().getAmount(),
        LocalDateTime.now(),
        "ORDER_CREATED"
    ));
}

์™œ AFTER_COMMIT์ธ๊ฐ€?

  • ์ฃผ๋ฌธ ์ƒ์„ฑ ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹๋œ ์ดํ›„ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค
  • ํ›„์† ๋กœ์ง ์‹คํŒจ๊ฐ€ ์ฃผ๋ฌธ ์ƒ์„ฑ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค
  • ํ›„์† ์ž‘์—…๋งŒ ์ž์ฒด์ ์œผ๋กœ ๋กค๋ฐฑ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

โค๏ธ ์ข‹์•„์š” ์ง‘๊ณ„ ๊ตฌ์กฐ ๊ฐœ์„  โ€” ๊ณ ๋นˆ๋„ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์ตœ์ ํ™”

์ข‹์•„์š” ์ด๋ฒคํŠธ๋Š” ๋ฐœ์ƒ ๋นˆ๋„๊ฐ€ ๋†’์•„
๊ฐ ์š”์ฒญ๋งˆ๋‹ค ์ฆ‰์‹œ ์ง‘๊ณ„ ํ…Œ์ด๋ธ”์„ ๊ฐฑ์‹ ํ•˜๋ฉด ์„ฑ๋Šฅ ๋ถ€ํ•˜๊ฐ€ ์ปค์ง‘๋‹ˆ๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์ง‘๊ณ„ + ๋””๋ฐ”์šด์Šค ๋ฐฉ์‹์„ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

Long lastTime = lastProcessedTime.get(event.productId());
long currentTime = System.currentTimeMillis();

if (lastTime != null && (currentTime - lastTime) < debounceInterval) {
    return;
}

long actualLikeCount = likeService.getLikeCount(event.productId());
productListViewService.syncLikeCount(event.productId(), actualLikeCount);

lastProcessedTime.put(event.productId(), currentTime);

๐Ÿ“Œ ์ •๋ฆฌ

๊ตฌ์กฐ ๊ฐœ์„ ์„ ํ†ตํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํšจ๊ณผ๋ฅผ ์–ป์—ˆ์Šต๋‹ˆ๋‹ค.

  • ์ฃผ๋ฌธ ์ƒ์„ฑ ํŠธ๋žœ์žญ์…˜์ด ์งง์•„์กŒ์Šต๋‹ˆ๋‹ค
  • ํ›„์† ์ž‘์—… ์˜ค๋ฅ˜๊ฐ€ ์ฃผ๋ฌธ ์ƒ์„ฑ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค
  • ์ข‹์•„์š”์™€ ๊ฐ™์€ ๊ณ ๋นˆ๋„ ์ด๋ฒคํŠธ์˜ ์ฒ˜๋ฆฌ ํšจ์œจ์ด ํฌ๊ฒŒ ํ–ฅ์ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค

๋‹ค๋งŒ Spring Event๋Š” ๊ทผ๋ณธ์ ์ธ ์ด๋ฒคํŠธ ํ”Œ๋žซํผ์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์—
์ด๋ฒคํŠธ ์œ ์‹คยท์ค‘๋ณต ์ฒ˜๋ฆฌ ๋“ฑ ๊ตฌ์กฐ์  ํ•œ๊ณ„๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.
์ด๋ฒˆ ๊ฐœํŽธ์—์„œ๋Š” ํŠธ๋žœ์žญ์…˜ ๋ถ„๋ฆฌ์™€ ์ฝ”๋“œ ๊ตฌ์กฐ ๊ฐœ์„ ์„ ์ฃผ ๋ชฉ์ ์— ๋‘์—ˆ์œผ๋ฉฐ,
์ถ”ํ›„ Kafka/RabbitMQ ๋“ฑ MQ ๊ธฐ๋ฐ˜ ๊ตฌ์กฐ๋กœ ํ™•์žฅํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

profile
be_zion

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