๐ŸŒฑ Spring @EventListener๋กœ ์‹œ์ž‘ํ•˜๋Š” ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ์„ธ๊ณ„ - ์ฝ”๋“œ๋ฅผ ๋” ์šฐ์•„ํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ• ๐ŸŒฑ

์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐ŸŒฑ Spring @EventListener๋กœ ์‹œ์ž‘ํ•˜๋Š” ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ์„ธ๊ณ„ - ์ฝ”๋“œ๋ฅผ ๋” ์šฐ์•„ํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ• ๐ŸŒฑ

 

 

2025๋…„ 3์›” 8์ผ ๊ธฐ์ค€ ์ตœ์‹  Spring ์ •๋ณด ๋ฐ˜์˜

์•ˆ๋…•, ๊ฐœ๋ฐœ์ž ์นœ๊ตฌ๋“ค! ์˜ค๋Š˜์€ Spring ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์ •๋ง ์œ ์šฉํ•˜์ง€๋งŒ ์˜์™ธ๋กœ ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ์ œ๋Œ€๋กœ ํ™œ์šฉํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ธฐ๋Šฅ์ธ @EventListener์— ๋Œ€ํ•ด ํ•จ๊ป˜ ์•Œ์•„๋ณผ ๊ฑฐ์•ผ. ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ด ์–ด๋–ป๊ฒŒ ๋„ˆ์˜ ์ฝ”๋“œ๋ฅผ ๋” ๊น”๋”ํ•˜๊ณ , ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์‰ฝ๊ณ , ํ™•์žฅ์„ฑ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๋Š”์ง€ ์žฌ๋ฏธ์žˆ๊ฒŒ ์„ค๋ช…ํ•ด ์ค„๊ฒŒ! ๐Ÿš€

๐Ÿ“‹ ๋ชฉ์ฐจ

  1. ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ด ๋ญ์•ผ? ๐Ÿค”
  2. Spring์˜ ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ ์†Œ๊ฐœ ๐ŸŒŸ
  3. @EventListener ์–ด๋…ธํ…Œ์ด์…˜ ์™„์ „ ์ •๋ณต ๐Ÿ’ช
  4. ์‹ค์ „ ์˜ˆ์ œ๋กœ ๋ฐฐ์šฐ๋Š” ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๐Ÿ› ๏ธ
  5. ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋กœ ์„ฑ๋Šฅ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ โšก
  6. ํŠธ๋žœ์žญ์…˜๊ณผ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๐Ÿ’ผ
  7. ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜์˜ ์žฅ๋‹จ์  โš–๏ธ
  8. Spring Boot 3.x์—์„œ์˜ ์ƒˆ๋กœ์šด ์ด๋ฒคํŠธ ๊ธฐ๋Šฅ๋“ค ๐Ÿ†•
  9. ์‹ค๋ฌด์—์„œ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์ด๋ฒคํŠธ ํŒจํ„ด ๐Ÿ‘จโ€๐Ÿ’ป
  10. ๋งˆ๋ฌด๋ฆฌ ๋ฐ ์ถ”๊ฐ€ ํ•™์Šต ์ž๋ฃŒ ๐Ÿ“š

1. ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ด ๋ญ์•ผ? ๐Ÿค”

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ(Event-Driven Programming)์€ ํ”„๋กœ๊ทธ๋žจ์˜ ํ๋ฆ„์ด ์ด๋ฒคํŠธ์˜ ๋ฐœ์ƒ๊ณผ ์ฒ˜๋ฆฌ์— ์˜ํ•ด ๊ฒฐ์ •๋˜๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ํŒจ๋Ÿฌ๋‹ค์ž„์ด์•ผ. ์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด, "์–ด๋–ค ์ผ์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ(์ด๋ฒคํŠธ), ๊ทธ์— ๋งž๋Š” ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ž"๋ผ๋Š” ๊ฐœ๋…์ด์ง€.

์ผ์ƒ์ƒํ™œ์—์„œ ์˜ˆ๋ฅผ ๋“ค๋ฉด ์ด๋ ‡๊ฒŒ ์ƒ๊ฐํ•ด๋ณผ ์ˆ˜ ์žˆ์–ด:

  1. ์นœ๊ตฌ๊ฐ€ ์ƒ์ผ ํŒŒํ‹ฐ์— ์ดˆ๋Œ€์žฅ์„ ๋ณด๋ƒˆ์–ด (์ด๋ฒคํŠธ ๋ฐœ์ƒ)
  2. ๋„ˆ๋Š” ๊ทธ ์ดˆ๋Œ€์žฅ์„ ๋ฐ›๊ณ  ์ผ์ •์„ ํ™•์ธํ•ด (์ด๋ฒคํŠธ ์ˆ˜์‹ )
  3. ํŒŒํ‹ฐ ๋‚ ์งœ์— ์„ ๋ฌผ์„ ์‚ฌ์„œ ์ฐธ์„ํ•ด (์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ)

ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์•ผ. ์‚ฌ์šฉ์ž๊ฐ€ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ฑฐ๋‚˜, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ƒˆ ๋ฐ์ดํ„ฐ๊ฐ€ ์ถ”๊ฐ€๋˜๊ฑฐ๋‚˜, ํŠน์ • ์กฐ๊ฑด์ด ์ถฉ์กฑ๋˜๋ฉด ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ , ์ด์— ๋Œ€์‘ํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋ผ.

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ํ๋ฆ„ ์ด๋ฒคํŠธ ๋ฐœ์ƒ Event Published ์ด๋ฒคํŠธ ๋ฒ„์Šค ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ Event Handled

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ํ•ต์‹ฌ ์žฅ์ ์€ ๋ฐ”๋กœ ๋Š์Šจํ•œ ๊ฒฐํ•ฉ(Loose Coupling)์ด์•ผ. ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์ฝ”๋“œ์™€ ์ฒ˜๋ฆฌํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์„œ๋กœ ์ง์ ‘์ ์ธ ์˜์กด์„ฑ ์—†์ด ๋…๋ฆฝ์ ์œผ๋กœ ์กด์žฌํ•  ์ˆ˜ ์žˆ์ง€. ์ด๊ฑด ๋งˆ์น˜ ๋„ˆ์™€ ์นœ๊ตฌ๊ฐ€ ์„œ๋กœ ์ง์ ‘ ๋งŒ๋‚˜์ง€ ์•Š๊ณ ๋„ SNS๋ฅผ ํ†ตํ•ด ์†Œํ†ตํ•˜๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•ด. ์„œ๋กœ ์‹œ๊ฐ„๊ณผ ๊ณต๊ฐ„์— ๊ตฌ์• ๋ฐ›์ง€ ์•Š๊ณ  ๋ฉ”์‹œ์ง€๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ์ž–์•„? ๐Ÿ‘

2. Spring์˜ ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ ์†Œ๊ฐœ ๐ŸŒŸ

Spring์€ ์ฒ˜์Œ๋ถ€ํ„ฐ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์ง€์›ํ–ˆ์–ด. Spring์˜ ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ์€ ์˜ต์ €๋ฒ„ ํŒจํ„ด(Observer Pattern)์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ์ง€.

Spring ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ์˜ ์ฃผ์š” ๊ตฌ์„ฑ์š”์†Œ

  1. ApplicationEvent: ๋ชจ๋“  ์ด๋ฒคํŠธ์˜ ๊ธฐ๋ณธ ํด๋ž˜์Šค
  2. ApplicationEventPublisher: ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค
  3. ApplicationListener: ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค
  4. @EventListener: ๋ฉ”์†Œ๋“œ์— ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๊ธฐ๋Šฅ์„ ๋ถ€์—ฌํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜

Spring 4.2 ์ด์ „์—๋Š” ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ApplicationListener ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ–ˆ์–ด. ๊ทธ๋Ÿฐ๋ฐ ์ด ๋ฐฉ์‹์€ ์ข€ ๋ฒˆ๊ฑฐ๋กœ์› ์ง€. ํด๋ž˜์Šค๊ฐ€ ํŠน์ • ์ธํ„ฐํŽ˜์ด์Šค์— ์ข…์†๋˜๊ณ , ์ฝ”๋“œ๋„ ์žฅํ™ฉํ•ด์กŒ๊ฑฐ๋“ .

Spring 4.2 ์ด์ „์˜ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๊ตฌํ˜„ ๋ฐฉ์‹:

public class UserRegistrationListener implements ApplicationListener<UserRegisteredEvent> {
    
    @Override
    public void onApplicationEvent(UserRegisteredEvent event) {
        // ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋กœ์ง
        User user = event.getUser();
        sendWelcomeEmail(user);
    }
    
    private void sendWelcomeEmail(User user) {
        // ์ด๋ฉ”์ผ ๋ฐœ์†ก ๋กœ์ง
    }
}

Spring 4.2๋ถ€ํ„ฐ๋Š” @EventListener ์–ด๋…ธํ…Œ์ด์…˜์ด ๋„์ž…๋˜๋ฉด์„œ ํ›จ์”ฌ ๊ฐ„๊ฒฐํ•˜๊ณ  ์œ ์—ฐํ•˜๊ฒŒ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋์–ด. ์ด์ œ ์–ด๋–ค ๋นˆ์˜ ๋ฉ”์†Œ๋“œ์—๋„ ์ด ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ด๊ธฐ๋งŒ ํ•˜๋ฉด ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๊ฐ€ ๋˜๋Š” ๊ฑฐ์ง€! ๐Ÿ‘

Spring 4.2 ์ดํ›„์˜ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๊ตฌํ˜„ ๋ฐฉ์‹:

@Component
public class UserService {
    
    @EventListener
    public void handleUserRegistered(UserRegisteredEvent event) {
        // ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋กœ์ง
        User user = event.getUser();
        sendWelcomeEmail(user);
    }
    
    private void sendWelcomeEmail(User user) {
        // ์ด๋ฉ”์ผ ๋ฐœ์†ก ๋กœ์ง
    }
}

ํ›จ์”ฌ ๊น”๋”ํ•ด์กŒ์ง€? ์ด์ œ ํŠน๋ณ„ํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•  ํ•„์š” ์—†์ด, ํ‰๋ฒ”ํ•œ Spring ๋นˆ์˜ ๋ฉ”์†Œ๋“œ์— ์–ด๋…ธํ…Œ์ด์…˜๋งŒ ๋ถ™์ด๋ฉด ๋ผ. ์ด๊ฒŒ ๋ฐ”๋กœ Spring์ด ์ถ”๊ตฌํ•˜๋Š” POJO(Plain Old Java Object) ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ์ฒ ํ•™์ด์•ผ. ๐Ÿง™โ€โ™‚๏ธ

3. @EventListener ์–ด๋…ธํ…Œ์ด์…˜ ์™„์ „ ์ •๋ณต ๐Ÿ’ช

์ด์ œ @EventListener ์–ด๋…ธํ…Œ์ด์…˜์— ๋Œ€ํ•ด ๋” ์ž์„ธํžˆ ์•Œ์•„๋ณด์ž. ์ด ์ž‘์€ ์–ด๋…ธํ…Œ์ด์…˜ ํ•˜๋‚˜๋กœ ํ•  ์ˆ˜ ์žˆ๋Š” ์ผ์ด ์ •๋ง ๋งŽ์•„!

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ์‚ฌ์šฉ๋ฒ•์€ ์ด๋ฒคํŠธ ํƒ€์ž…์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š” ๋ฉ”์†Œ๋“œ์— @EventListener๋ฅผ ๋ถ™์ด๋Š” ๊ฑฐ์•ผ.

@Component
public class MyEventHandler {
    
    @EventListener
    public void handleMyEvent(MyCustomEvent event) {
        System.out.println("์ด๋ฒคํŠธ ์ˆ˜์‹ : " + event.getMessage());
    }
}

์กฐ๊ฑด๋ถ€ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ

@EventListener๋Š” SpEL(Spring Expression Language)์„ ์‚ฌ์šฉํ•œ ์กฐ๊ฑด์‹์„ ์ง€์›ํ•ด. ์ด๋ฅผ ํ†ตํ•ด ํŠน์ • ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š” ์ด๋ฒคํŠธ๋งŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด.

@EventListener(condition = "#event.success == true")
public void handleSuccessfulEvent(MyCustomEvent event) {
    System.out.println("์„ฑ๊ณตํ•œ ์ด๋ฒคํŠธ๋งŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค!");
}

์œ„ ์ฝ”๋“œ๋Š” event.success ๊ฐ’์ด true์ธ ์ด๋ฒคํŠธ๋งŒ ์ฒ˜๋ฆฌํ•ด. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ถˆํ•„์š”ํ•œ if ๋ฌธ์„ ์ค„์ด๊ณ  ์„ ์–ธ์ ์œผ๋กœ ์กฐ๊ฑด์„ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์ง€.

์—ฌ๋Ÿฌ ์ด๋ฒคํŠธ ํƒ€์ž… ์ฒ˜๋ฆฌํ•˜๊ธฐ

ํ•˜๋‚˜์˜ ๋ฆฌ์Šค๋„ˆ๋กœ ์—ฌ๋Ÿฌ ํƒ€์ž…์˜ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹ถ์„ ๋•Œ๋Š” classes ์†์„ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

@EventListener(classes = { UserCreatedEvent.class, UserUpdatedEvent.class })
public void handleUserEvents(ApplicationEvent event) {
    if (event instanceof UserCreatedEvent) {
        UserCreatedEvent userCreatedEvent = (UserCreatedEvent) event;
        // ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
    } else if (event instanceof UserUpdatedEvent) {
        UserUpdatedEvent userUpdatedEvent = (UserUpdatedEvent) event;
        // ์‚ฌ์šฉ์ž ์—…๋ฐ์ดํŠธ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
    }
}

์ด๋ฒคํŠธ ๋ฐœํ–‰ํ•˜๊ธฐ

์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰(publish)ํ•˜๋ ค๋ฉด ApplicationEventPublisher๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ด. Spring์—์„œ๋Š” ์ด๋ฅผ ์‰ฝ๊ฒŒ ์ฃผ์ž…๋ฐ›์„ ์ˆ˜ ์žˆ์–ด.

@Service
public class UserService {
    
    private final ApplicationEventPublisher eventPublisher;
    
    @Autowired
    public UserService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }
    
    public void registerUser(User user) {
        // ์‚ฌ์šฉ์ž ๋“ฑ๋ก ๋กœ์ง
        userRepository.save(user);
        
        // ์ด๋ฒคํŠธ ๋ฐœํ–‰
        eventPublisher.publishEvent(new UserRegisteredEvent(this, user));
    }
}

Spring 4.2๋ถ€ํ„ฐ๋Š” ApplicationEventPublisher๋ฅผ ํ†ตํ•ด ApplicationEvent๋ฅผ ์ƒ์†ํ•˜์ง€ ์•Š๋Š” POJO ๊ฐ์ฒด๋„ ์ง์ ‘ ์ด๋ฒคํŠธ๋กœ ๋ฐœํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋์–ด. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ด๋ฒคํŠธ ํด๋ž˜์Šค๊ฐ€ Spring์— ์˜์กดํ•˜์ง€ ์•Š๊ฒŒ ๋˜์ง€!

// POJO ์ด๋ฒคํŠธ ํด๋ž˜์Šค
public class UserRegisteredEvent {
    
    private final User user;
    
    public UserRegisteredEvent(User user) {
        this.user = user;
    }
    
    public User getUser() {
        return user;
    }
}

// ์ด๋ฒคํŠธ ๋ฐœํ–‰
eventPublisher.publishEvent(new UserRegisteredEvent(user));
@EventListener์˜ ๋™์ž‘ ๋ฐฉ์‹ Publisher Event ๋ฐ์ดํ„ฐ + ๋ฉ”ํƒ€์ •๋ณด Listener 1 Listener 2 Listener 3 publishEvent() @EventListener

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ๋‹ค์–‘ํ•œ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ํ”Œ๋žซํผ์—์„œ๋„ ๋งค์šฐ ์œ ์šฉํ•ด. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž๊ฐ€ ์ƒˆ๋กœ์šด ์žฌ๋Šฅ์„ ๋“ฑ๋กํ–ˆ์„ ๋•Œ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ , ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฆฌ์Šค๋„ˆ์—์„œ ๊ฒ€์ƒ‰ ์ธ๋ฑ์Šค ์—…๋ฐ์ดํŠธ, ์•Œ๋ฆผ ๋ฐœ์†ก, ํ†ต๊ณ„ ์ง‘๊ณ„ ๋“ฑ์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์ง€. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์„ ๊น”๋”ํ•˜๊ฒŒ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด! ๐ŸŽฏ

4. ์‹ค์ „ ์˜ˆ์ œ๋กœ ๋ฐฐ์šฐ๋Š” ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๐Ÿ› ๏ธ

์ด์ œ ์‹ค์ œ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ์•„๋ณด์ž. ์˜จ๋ผ์ธ ์‡ผํ•‘๋ชฐ์—์„œ ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณผ๊ฒŒ.

์ฃผ๋ฌธ ์ด๋ฒคํŠธ ์ •์˜ํ•˜๊ธฐ

๋จผ์ €, ์ฃผ๋ฌธ๊ณผ ๊ด€๋ จ๋œ ์ด๋ฒคํŠธ๋“ค์„ ์ •์˜ํ•ด๋ณด์ž.

// ์ฃผ๋ฌธ ์ƒ์„ฑ ์ด๋ฒคํŠธ
public class OrderCreatedEvent {
    private final Order order;
    
    public OrderCreatedEvent(Order order) {
        this.order = order;
    }
    
    public Order getOrder() {
        return order;
    }
}

// ์ฃผ๋ฌธ ๊ฒฐ์ œ ์™„๋ฃŒ ์ด๋ฒคํŠธ
public class OrderPaidEvent {
    private final Order order;
    private final Payment payment;
    
    public OrderPaidEvent(Order order, Payment payment) {
        this.order = order;
        this.payment = payment;
    }
    
    public Order getOrder() {
        return order;
    }
    
    public Payment getPayment() {
        return payment;
    }
}

์ฃผ๋ฌธ ์„œ๋น„์Šค ๊ตฌํ˜„ํ•˜๊ธฐ

์ด์ œ ์ฃผ๋ฌธ ์„œ๋น„์Šค์—์„œ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๋Š” ๋ถ€๋ถ„์„ ๊ตฌํ˜„ํ•ด๋ณด์ž.

@Service
public class OrderService {
    
    private final OrderRepository orderRepository;
    private final ApplicationEventPublisher eventPublisher;
    
    @Autowired
    public OrderService(OrderRepository orderRepository, ApplicationEventPublisher eventPublisher) {
        this.orderRepository = orderRepository;
        this.eventPublisher = eventPublisher;
    }
    
    @Transactional
    public Order createOrder(OrderRequest request) {
        // ์ฃผ๋ฌธ ์ƒ์„ฑ ๋กœ์ง
        Order order = new Order();
        order.setCustomerId(request.getCustomerId());
        order.setItems(request.getItems());
        order.setTotalAmount(calculateTotalAmount(request.getItems()));
        order.setStatus(OrderStatus.CREATED);
        
        Order savedOrder = orderRepository.save(order);
        
        // ์ฃผ๋ฌธ ์ƒ์„ฑ ์ด๋ฒคํŠธ ๋ฐœํ–‰
        eventPublisher.publishEvent(new OrderCreatedEvent(savedOrder));
        
        return savedOrder;
    }
    
    @Transactional
    public Order processPayment(Long orderId, PaymentRequest paymentRequest) {
        Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new OrderNotFoundException(orderId));
        
        // ๊ฒฐ์ œ ์ฒ˜๋ฆฌ ๋กœ์ง
        Payment payment = paymentService.processPayment(paymentRequest);
        
        order.setStatus(OrderStatus.PAID);
        Order updatedOrder = orderRepository.save(order);
        
        // ๊ฒฐ์ œ ์™„๋ฃŒ ์ด๋ฒคํŠธ ๋ฐœํ–‰
        eventPublisher.publishEvent(new OrderPaidEvent(updatedOrder, payment));
        
        return updatedOrder;
    }
    
    private BigDecimal calculateTotalAmount(List<orderitem> items) {
        // ์ฃผ๋ฌธ ์ด์•ก ๊ณ„์‚ฐ ๋กœ์ง
        return items.stream()
                .map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}</orderitem>

์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๊ตฌํ˜„ํ•˜๊ธฐ

์ด์ œ ๋ฐœํ–‰๋œ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋ฆฌ์Šค๋„ˆ๋“ค์„ ๊ตฌํ˜„ํ•ด๋ณด์ž.

@Component
public class OrderEventListeners {
    
    private final EmailService emailService;
    private final InventoryService inventoryService;
    private final AnalyticsService analyticsService;
    
    @Autowired
    public OrderEventListeners(EmailService emailService, 
                              InventoryService inventoryService,
                              AnalyticsService analyticsService) {
        this.emailService = emailService;
        this.inventoryService = inventoryService;
        this.analyticsService = analyticsService;
    }
    
    @EventListener
    public void handleOrderCreatedEvent(OrderCreatedEvent event) {
        Order order = event.getOrder();
        
        // ์ฃผ๋ฌธ ํ™•์ธ ์ด๋ฉ”์ผ ๋ฐœ์†ก
        emailService.sendOrderConfirmationEmail(order);
        
        // ์žฌ๊ณ  ํ™•์ธ ๋ฐ ์˜ˆ์•ฝ
        inventoryService.reserveItems(order.getItems());
        
        // ์ฃผ๋ฌธ ์ƒ์„ฑ ๋ถ„์„ ๋ฐ์ดํ„ฐ ๊ธฐ๋ก
        analyticsService.trackOrderCreated(order);
        
        System.out.println("์ฃผ๋ฌธ ์ƒ์„ฑ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์™„๋ฃŒ: " + order.getId());
    }
    
    @EventListener
    public void handleOrderPaidEvent(OrderPaidEvent event) {
        Order order = event.getOrder();
        Payment payment = event.getPayment();
        
        // ๊ฒฐ์ œ ์™„๋ฃŒ ์ด๋ฉ”์ผ ๋ฐœ์†ก
        emailService.sendPaymentConfirmationEmail(order, payment);
        
        // ๋ฐฐ์†ก ํ”„๋กœ์„ธ์Šค ์‹œ์ž‘
        shippingService.initiateShipping(order);
        
        // ๊ฒฐ์ œ ์™„๋ฃŒ ๋ถ„์„ ๋ฐ์ดํ„ฐ ๊ธฐ๋ก
        analyticsService.trackOrderPaid(order, payment);
        
        System.out.println("๊ฒฐ์ œ ์™„๋ฃŒ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์™„๋ฃŒ: " + order.getId());
    }
    
    // ํŠน์ • ๊ธˆ์•ก ์ด์ƒ์˜ ์ฃผ๋ฌธ์— ๋Œ€ํ•ด์„œ๋งŒ VIP ๊ณ ๊ฐ ์„œ๋น„์Šค ์•Œ๋ฆผ
    @EventListener(condition = "#event.order.totalAmount.compareTo(new java.math.BigDecimal('100000')) > 0")
    public void handleLargeOrderPaid(OrderPaidEvent event) {
        Order order = event.getOrder();
        
        // VIP ๊ณ ๊ฐ ์„œ๋น„์ŠคํŒ€์— ์•Œ๋ฆผ
        customerServiceNotifier.notifyVipTeam(order);
        
        System.out.println("๋Œ€๊ทœ๋ชจ ์ฃผ๋ฌธ ์•Œ๋ฆผ ์ฒ˜๋ฆฌ ์™„๋ฃŒ: " + order.getId());
    }
}

์œ„ ์˜ˆ์ œ์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋“ฏ์ด, ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์‚ฌ์šฉํ•˜๋ฉด ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ์™€ ๊ด€๋ จ๋œ ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ๋“ค(์ด๋ฉ”์ผ ๋ฐœ์†ก, ์žฌ๊ณ  ๊ด€๋ฆฌ, ๋ถ„์„ ๋“ฑ)์„ ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด OrderService๋Š” ์˜ค์ง ์ฃผ๋ฌธ ์ƒ์„ฑ๊ณผ ๊ฒฐ์ œ ์ฒ˜๋ฆฌ๋ผ๋Š” ํ•ต์‹ฌ ์ฑ…์ž„์—๋งŒ ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ์ง€. ๐ŸŽฏ

๋˜ํ•œ condition ์†์„ฑ์„ ์‚ฌ์šฉํ•ด ํŠน์ • ์กฐ๊ฑด(์˜ˆ: ์ฃผ๋ฌธ ๊ธˆ์•ก์ด 10๋งŒ์› ์ด์ƒ)์„ ๋งŒ์กฑํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•  ์ˆ˜๋„ ์žˆ์–ด. ์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™์„ ์„ ์–ธ์ ์œผ๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์ง€.

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

5. ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋กœ ์„ฑ๋Šฅ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ โšก

์ง€๊ธˆ๊นŒ์ง€ ์‚ดํŽด๋ณธ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋™๊ธฐ(Synchronous) ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•ด. ์ฆ‰, ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœํ–‰๋˜๋ฉด ๋ชจ๋“  ๋ฆฌ์Šค๋„ˆ์˜ ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋ฐœํ–‰ ๋ฉ”์†Œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜๋˜์ง€ ์•Š์•„. ์ด๋Š” ๋ฆฌ์Šค๋„ˆ์—์„œ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๊ฒฝ์šฐ ์ „์ฒด ์‘๋‹ต ์‹œ๊ฐ„์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์ง€.

๋‹คํ–‰ํžˆ Spring์€ @Async ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด. ์ด๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์ด๋ฒคํŠธ ๋ฐœํ–‰ ํ›„ ์ฆ‰์‹œ ๋ฉ”์†Œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜๋˜๊ณ , ๋ฆฌ์Šค๋„ˆ๋Š” ๋ณ„๋„์˜ ์Šค๋ ˆ๋“œ์—์„œ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰๋ผ.

๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์„ค์ •ํ•˜๊ธฐ

๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋จผ์ € Spring ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•ด.

@Configuration
@EnableAsync  // ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ํ™œ์„ฑํ™”
public class AsyncConfig {
    
    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("Async-");
        executor.initialize();
        return executor;
    }
}

๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๊ตฌํ˜„ํ•˜๊ธฐ

์ด์ œ @Async ์–ด๋…ธํ…Œ์ด์…˜์„ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ์— ์ถ”๊ฐ€ํ•ด ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋„๋ก ํ•ด๋ณด์ž.

@Component
public class AsyncOrderEventListeners {
    
    private final EmailService emailService;
    private final AnalyticsService analyticsService;
    
    @Autowired
    public AsyncOrderEventListeners(EmailService emailService, AnalyticsService analyticsService) {
        this.emailService = emailService;
        this.analyticsService = analyticsService;
    }
    
    @Async
    @EventListener
    public void handleOrderCreatedEvent(OrderCreatedEvent event) {
        Order order = event.getOrder();
        
        // ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ๋Š” ์ด๋ฉ”์ผ ๋ฐœ์†ก ์ž‘์—…
        emailService.sendOrderConfirmationEmail(order);
        
        System.out.println("๋น„๋™๊ธฐ ์ฃผ๋ฌธ ์ƒ์„ฑ ์ด๋ฉ”์ผ ๋ฐœ์†ก ์™„๋ฃŒ: " + order.getId() + 
                          " (Thread: " + Thread.currentThread().getName() + ")");
    }
    
    @Async
    @EventListener
    public void trackOrderAnalytics(OrderCreatedEvent event) {
        Order order = event.getOrder();
        
        // ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ๋Š” ๋ถ„์„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
        analyticsService.trackDetailedOrderMetrics(order);
        
        System.out.println("๋น„๋™๊ธฐ ์ฃผ๋ฌธ ๋ถ„์„ ์ฒ˜๋ฆฌ ์™„๋ฃŒ: " + order.getId() + 
                          " (Thread: " + Thread.currentThread().getName() + ")");
    }
}

์œ„ ์˜ˆ์ œ์—์„œ๋Š” ์ด๋ฉ”์ผ ๋ฐœ์†ก๊ณผ ๋ถ„์„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์™€ ๊ฐ™์ด ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ๋Š” ์ž‘์—…๋“ค์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๊ตฌํ˜„ํ–ˆ์–ด. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฃผ๋ฌธ ์ƒ์„ฑ API์˜ ์‘๋‹ต ์‹œ๊ฐ„์ด ํฌ๊ฒŒ ๊ฐœ์„ ๋  ์ˆ˜ ์žˆ์ง€!

๋™๊ธฐ vs ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋™๊ธฐ ์ฒ˜๋ฆฌ ์ด๋ฒคํŠธ ๋ฐœํ–‰ ๋ฆฌ์Šค๋„ˆ 1 ์ฒ˜๋ฆฌ (2์ดˆ) ๋ฆฌ์Šค๋„ˆ 2 ์ฒ˜๋ฆฌ (3์ดˆ) ์‘๋‹ต ๋ฐ˜ํ™˜ (์ด 5์ดˆ ์†Œ์š”) ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ์ด๋ฒคํŠธ ๋ฐœํ–‰ ์‘๋‹ต ๋ฐ˜ํ™˜ (์ฆ‰์‹œ) ๋ฆฌ์Šค๋„ˆ 1 (2์ดˆ) ๋ฆฌ์Šค๋„ˆ 2 (3์ดˆ)

๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์‹œ ์ฃผ์˜์‚ฌํ•ญ

  1. ์˜ˆ์™ธ ์ฒ˜๋ฆฌ: ๋น„๋™๊ธฐ ๋ฉ”์†Œ๋“œ์—์„œ ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ๋Š” ํ˜ธ์ถœ์ž์—๊ฒŒ ์ „ํŒŒ๋˜์ง€ ์•Š์•„. ๋”ฐ๋ผ์„œ ์ ์ ˆํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ „๋žต์ด ํ•„์š”ํ•ด.
  2. ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ: ๋น„๋™๊ธฐ ๋ฉ”์†Œ๋“œ๋Š” ์ƒˆ๋กœ์šด ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋˜๋ฏ€๋กœ, ์›๋ž˜ ํŠธ๋žœ์žญ์…˜์ด ์ „ํŒŒ๋˜์ง€ ์•Š์•„. ํ•„์š”ํ•˜๋‹ค๋ฉด ์ƒˆ๋กœ์šด ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•ด์•ผ ํ•ด.
  3. ๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ: ์Šค๋ ˆ๋“œ ํ’€ ํฌ๊ธฐ๋ฅผ ์ ์ ˆํžˆ ์„ค์ •ํ•˜์—ฌ ๋ฆฌ์†Œ์Šค ๊ณ ๊ฐˆ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ด.
  4. ํ…Œ์ŠคํŠธ: ๋น„๋™๊ธฐ ์ฝ”๋“œ๋Š” ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ํ…Œ์ŠคํŠธ ์ „๋žต์„ ์‹ ์ค‘ํ•˜๊ฒŒ ๊ณ„ํšํ•ด์•ผ ํ•ด.
@Async
@EventListener
public void handleOrderCreatedEvent(OrderCreatedEvent event) {
    try {
        // ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๋กœ์ง
        emailService.sendOrderConfirmationEmail(event.getOrder());
    } catch (Exception e) {
        // ์˜ˆ์™ธ ๋กœ๊น…
        log.error("Failed to process order created event", e);
        
        // ์‹คํŒจํ•œ ์ด๋ฒคํŠธ ์ €์žฅ (๋‚˜์ค‘์— ์žฌ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก)
        failedEventRepository.save(new FailedEvent(event, e.getMessage()));
    }
}

๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋Š” ์„ฑ๋Šฅ ํ–ฅ์ƒ์— ํฐ ๋„์›€์ด ๋˜์ง€๋งŒ, ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ ์ฃผ์˜์‚ฌํ•ญ๋“ค์„ ๊ณ ๋ คํ•˜์—ฌ ์‹ ์ค‘ํ•˜๊ฒŒ ์„ค๊ณ„ํ•ด์•ผ ํ•ด. ํŠนํžˆ ์ค‘์š”ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด๋‚˜ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์ด ์ค‘์š”ํ•œ ๊ฒฝ์šฐ์—๋Š” ๋™๊ธฐ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์ด ๋” ์ ํ•ฉํ•  ์ˆ˜ ์žˆ์–ด. ๐Ÿค”

6. ํŠธ๋žœ์žญ์…˜๊ณผ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๐Ÿ’ผ

Spring์—์„œ๋Š” ํŠธ๋žœ์žญ์…˜ ์™„๋ฃŒ ์‹œ์ ์— ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ํŠน๋ณ„ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด. ์ด๋Š” @TransactionalEventListener ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ๊ตฌํ˜„๋ผ.

์ด ๊ธฐ๋Šฅ์ด ์™œ ์ค‘์š”ํ• ๊นŒ? ์˜ˆ๋ฅผ ๋“ค์–ด, ์ฃผ๋ฌธ์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ ํ›„์—๋งŒ ์ด๋ฉ”์ผ์„ ๋ณด๋‚ด๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž. ๋งŒ์•ฝ ์ผ๋ฐ˜์ ์ธ @EventListener๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ํŠธ๋žœ์žญ์…˜์ด ๋กค๋ฐฑ๋˜๋”๋ผ๋„ ์ด๋ฏธ ์ด๋ฉ”์ผ์€ ๋ฐœ์†ก๋˜์—ˆ์„ ์ˆ˜ ์žˆ์–ด. ์ด๋Ÿฐ ์ƒํ™ฉ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด @TransactionalEventListener๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

@TransactionalEventListener ์‚ฌ์šฉํ•˜๊ธฐ

@TransactionalEventListener๋Š” ํŠธ๋žœ์žญ์…˜์˜ ํŠน์ • ๋‹จ๊ณ„(phase)์— ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜.

@Component
public class OrderTransactionalEventListener {
    
    private final EmailService emailService;
    
    @Autowired
    public OrderTransactionalEventListener(EmailService emailService) {
        this.emailService = emailService;
    }
    
    // ํŠธ๋žœ์žญ์…˜์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋œ ํ›„์—๋งŒ ์‹คํ–‰
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleOrderCreatedEvent(OrderCreatedEvent event) {
        Order order = event.getOrder();
        emailService.sendOrderConfirmationEmail(order);
        System.out.println("ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ํ›„ ์ด๋ฉ”์ผ ๋ฐœ์†ก ์™„๋ฃŒ: " + order.getId());
    }
    
    // ํŠธ๋žœ์žญ์…˜์ด ๋กค๋ฐฑ๋œ ํ›„์— ์‹คํ–‰
    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void handleOrderCreationFailed(OrderCreatedEvent event) {
        Order order = event.getOrder();
        System.out.println("์ฃผ๋ฌธ ์ƒ์„ฑ ํŠธ๋žœ์žญ์…˜ ๋กค๋ฐฑ๋จ: " + order.getId());
        // ๋กค๋ฐฑ ๊ด€๋ จ ๋กœ๊น… ๋˜๋Š” ๋ชจ๋‹ˆํ„ฐ๋ง ์•Œ๋ฆผ
    }
    
    // ํŠธ๋žœ์žญ์…˜ ์™„๋ฃŒ(์ปค๋ฐ‹ ๋˜๋Š” ๋กค๋ฐฑ) ํ›„ ์‹คํ–‰
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
    public void handleOrderProcessingCompleted(OrderCreatedEvent event) {
        System.out.println("์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ํŠธ๋žœ์žญ์…˜ ์™„๋ฃŒ๋จ (์ปค๋ฐ‹ ๋˜๋Š” ๋กค๋ฐฑ)");
        // ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ ๋“ฑ
    }
    
    // ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์ „์— ์‹คํ–‰ (์ฃผ์˜: ์ด ์‹œ์ ์—์„œ ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ํŠธ๋žœ์žญ์…˜์ด ๋กค๋ฐฑ๋จ)
    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
    public void validateOrderBeforeCommit(OrderCreatedEvent event) {
        Order order = event.getOrder();
        // ์ตœ์ข… ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋“ฑ
        System.out.println("ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์ „ ์ฃผ๋ฌธ ์ตœ์ข… ๊ฒ€์ฆ: " + order.getId());
    }
}

@TransactionalEventListener๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ phase ์˜ต์…˜์„ ์ œ๊ณตํ•ด:

  1. AFTER_COMMIT (๊ธฐ๋ณธ๊ฐ’): ํŠธ๋žœ์žญ์…˜์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ปค๋ฐ‹๋œ ํ›„ ์‹คํ–‰
  2. AFTER_ROLLBACK: ํŠธ๋žœ์žญ์…˜์ด ๋กค๋ฐฑ๋œ ํ›„ ์‹คํ–‰
  3. AFTER_COMPLETION: ํŠธ๋žœ์žญ์…˜์ด ์™„๋ฃŒ(์ปค๋ฐ‹ ๋˜๋Š” ๋กค๋ฐฑ)๋œ ํ›„ ์‹คํ–‰
  4. BEFORE_COMMIT: ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹๋˜๊ธฐ ์ง์ „์— ์‹คํ–‰

fallbackExecution ์˜ต์…˜

๊ธฐ๋ณธ์ ์œผ๋กœ @TransactionalEventListener๋Š” ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœํ–‰๋œ ๊ฒฝ์šฐ์—๋งŒ ๋™์ž‘ํ•ด. ํ•˜์ง€๋งŒ fallbackExecution = true ์˜ต์…˜์„ ์„ค์ •ํ•˜๋ฉด ํŠธ๋žœ์žญ์…˜์ด ์—†๋Š” ์ƒํ™ฉ์—์„œ๋„ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๊ฐ€ ์‹คํ–‰๋ผ.

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
public void handleOrderCreatedEvent(OrderCreatedEvent event) {
    // ํŠธ๋žœ์žญ์…˜ ์œ ๋ฌด์— ๊ด€๊ณ„์—†์ด ํ•ญ์ƒ ์‹คํ–‰๋จ
    emailService.sendOrderConfirmationEmail(event.getOrder());
}
@TransactionalEventListener์˜ ๋™์ž‘ ๋ฐฉ์‹ ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘ ์ด๋ฒคํŠธ ๋ฐœํ–‰ BEFORE_COMMIT ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ AFTER_COMMIT ํŠธ๋žœ์žญ์…˜ ์™„๋ฃŒ ์ผ๋ฐ˜ @EventListener BEFORE_COMMIT AFTER_COMMIT

@TransactionalEventListener๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๋ฉด์„œ๋„ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜์˜ ์žฅ์ ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ํŠนํžˆ ๊ธˆ์œต ๊ฑฐ๋ž˜๋‚˜ ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ์™€ ๊ฐ™์ด ๋ฐ์ดํ„ฐ ์ •ํ™•์„ฑ์ด ์ค‘์š”ํ•œ ์‹œ์Šคํ…œ์—์„œ ๋งค์šฐ ์œ ์šฉํ•˜์ง€. ๐Ÿ’ฐ

์˜ˆ๋ฅผ ๋“ค์–ด, ์žฌ๋Šฅ๋„ท์—์„œ ์žฌ๋Šฅ ๊ฑฐ๋ž˜๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ํŒ๋งค์ž์—๊ฒŒ ์ˆ˜์ต์„ ์ •์‚ฐํ•˜๋Š” ๊ณผ์ •์„ ์ƒ๊ฐํ•ด๋ณด์ž. ์ •์‚ฐ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์„ฑ๊ณต์ ์œผ๋กœ ์ €์žฅ๋œ ํ›„์—๋งŒ ํŒ๋งค์ž์—๊ฒŒ ์•Œ๋ฆผ์„ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•  ๊ฑฐ์•ผ. ์ด๋Ÿฐ ๊ฒฝ์šฐ @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŠธ๋žœ์žญ์…˜์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋œ ํ›„์—๋งŒ ์•Œ๋ฆผ์ด ๋ฐœ์†ก๋˜๋„๋ก ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์–ด. ๐Ÿ‘

7. ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜์˜ ์žฅ๋‹จ์  โš–๏ธ

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ ๋งŽ์€ ์žฅ์ ์„ ์ œ๊ณตํ•˜์ง€๋งŒ, ๋ชจ๋“  ์ƒํ™ฉ์— ์ ํ•ฉํ•œ ๊ฒƒ์€ ์•„๋‹ˆ์•ผ. ์ด์ œ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜์˜ ์žฅ๋‹จ์ ์„ ์‚ดํŽด๋ณด์ž.

์žฅ์  ๐Ÿ‘

  1. ๋Š์Šจํ•œ ๊ฒฐํ•ฉ(Loose Coupling): ์ด๋ฒคํŠธ ๋ฐœํ–‰์ž์™€ ๊ตฌ๋…์ž ๊ฐ„์˜ ์ง์ ‘์ ์ธ ์˜์กด์„ฑ์ด ์—†์–ด ์ฝ”๋“œ์˜ ์œ ์—ฐ์„ฑ์ด ํ–ฅ์ƒ๋ผ.
  2. ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ(Separation of Concerns): ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์„ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด.
  3. ํ™•์žฅ์„ฑ(Scalability): ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ๋•Œ ๊ธฐ์กด ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ ๋„ ์ƒˆ๋กœ์šด ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์–ด.
  4. ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ: ๊ฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฌ์›Œ์ ธ.
  5. ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ: @Async์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์–ด.
  6. ์ฝ”๋“œ ๊ฐ€๋…์„ฑ: ํ•ต์‹ฌ ๋กœ์ง์ด ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์œผ๋กœ ์˜ค์—ผ๋˜์ง€ ์•Š์•„ ์ฝ”๋“œ๊ฐ€ ๋” ๊น”๋”ํ•ด์ ธ.

๋‹จ์  ๐Ÿ‘Ž

  1. ๋””๋ฒ„๊น… ์–ด๋ ค์›€: ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ๋ฆ„์€ ์ถ”์ ํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์–ด. ํŠนํžˆ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๋”์šฑ ๊ทธ๋ž˜.
  2. ๋ณต์žก์„ฑ ์ฆ๊ฐ€: ์‹œ์Šคํ…œ์ด ์ปค์งˆ์ˆ˜๋ก ์ด๋ฒคํŠธ ํ๋ฆ„์„ ์ดํ•ดํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๊ธฐ ์–ด๋ ค์›Œ์งˆ ์ˆ˜ ์žˆ์–ด.
  3. ์˜ค๋ฒ„ํ—ค๋“œ: ์ด๋ฒคํŠธ ๋ฐœํ–‰๊ณผ ์ฒ˜๋ฆฌ์— ์•ฝ๊ฐ„์˜ ์„ฑ๋Šฅ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด.
  4. ์ˆœ์„œ ๋ณด์žฅ์˜ ์–ด๋ ค์›€: ์—ฌ๋Ÿฌ ๋ฆฌ์Šค๋„ˆ ๊ฐ„์˜ ์‹คํ–‰ ์ˆœ์„œ๋ฅผ ์ œ์–ดํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์–ด.
  5. ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ ๋ณต์žก์„ฑ: ํŠนํžˆ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๋•Œ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ๊ฐ€ ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ์–ด.

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜๊ฐ€ ์ ํ•ฉํ•œ ์ƒํ™ฉ

  1. ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์ด ๋งŽ์€ ๊ฒฝ์šฐ: ํ•ต์‹ฌ ๊ธฐ๋Šฅ ์™ธ์— ์•Œ๋ฆผ, ๋กœ๊น…, ๋ถ„์„ ๋“ฑ ๋‹ค์–‘ํ•œ ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ
  2. ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ: ์‚ฌ์šฉ์ž ์‘๋‹ต ์‹œ๊ฐ„์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๊ณ  ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์ž‘์—…์ด ๋งŽ์€ ๊ฒฝ์šฐ
  3. ํ™•์žฅ์„ฑ์ด ์ค‘์š”ํ•œ ๊ฒฝ์šฐ: ์‹œ์Šคํ…œ์ด ์ง€์†์ ์œผ๋กœ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋ฉฐ ํ™•์žฅ๋  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋˜๋Š” ๊ฒฝ์šฐ
  4. ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜: ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ ์ด ๋งŽ์€ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ํ™˜๊ฒฝ
์ „ํ†ต์  ์•„ํ‚คํ…์ฒ˜ vs ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜ ์ „ํ†ต์  ์•„ํ‚คํ…์ฒ˜ OrderService ์ด๋ฉ”์ผ ์žฌ๊ณ  ๋ถ„์„ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜ OrderService ์ด๋ฒคํŠธ ์ด๋ฉ”์ผ ์žฌ๊ณ  ๋ถ„์„

์œ„ ๋‹ค์ด์–ด๊ทธ๋žจ์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋“ฏ์ด, ์ „ํ†ต์ ์ธ ์•„ํ‚คํ…์ฒ˜์—์„œ๋Š” OrderService๊ฐ€ ์ด๋ฉ”์ผ ๋ฐœ์†ก, ์žฌ๊ณ  ๊ด€๋ฆฌ, ๋ถ„์„ ๋“ฑ์˜ ๋ชจ๋“  ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์„ ์ง์ ‘ ํ˜ธ์ถœํ•ด. ๋ฐ˜๋ฉด ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜์—์„œ๋Š” OrderService๋Š” ๋‹จ์ˆœํžˆ ์ด๋ฒคํŠธ๋งŒ ๋ฐœํ–‰ํ•˜๊ณ , ๊ฐ ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์€ ์ด ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ๋…ํ•˜์—ฌ ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ด.

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜๋Š” ํ™•์žฅ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์–ด. ์˜ˆ๋ฅผ ๋“ค์–ด, ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ(์˜ˆ: ๊ฑฐ๋ž˜ ์™„๋ฃŒ ์‹œ ๋ฆฌ์›Œ๋“œ ํฌ์ธํŠธ ์ง€๊ธ‰)์„ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด, ๊ธฐ์กด ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ  ์ƒˆ๋กœ์šด ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ผ. ์ด๋Š” ๊ธฐ์กด ์‹œ์Šคํ…œ์˜ ์•ˆ์ •์„ฑ์„ ํ•ด์น˜์ง€ ์•Š์œผ๋ฉด์„œ๋„ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ์ง€. ๐Ÿš€

8. Spring Boot 3.x์—์„œ์˜ ์ƒˆ๋กœ์šด ์ด๋ฒคํŠธ ๊ธฐ๋Šฅ๋“ค ๐Ÿ†•

Spring Boot 3.x ๋ฒ„์ „์—์„œ๋Š” ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์™€ ๊ด€๋ จ๋œ ๋ช‡ ๊ฐ€์ง€ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ๊ณผ ๊ฐœ์„ ์‚ฌํ•ญ์ด ์ถ”๊ฐ€๋์–ด. 2025๋…„ 3์›” ๊ธฐ์ค€์œผ๋กœ ์ตœ์‹  Spring Boot ๋ฒ„์ „์—์„œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ด๋ฒคํŠธ ๊ด€๋ จ ๊ธฐ๋Šฅ๋“ค์„ ์‚ดํŽด๋ณด์ž.

ObservationRegistry์™€ ํ†ตํ•ฉ๋œ ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ

Spring Boot 3.x์—์„œ๋Š” Micrometer์˜ Observation API์™€ ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ์ด ํ†ตํ•ฉ๋˜์–ด, ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ์ถ”์ ์ด ๋”์šฑ ๊ฐ•ํ™”๋์–ด.

@Configuration
public class EventObservationConfig {
    
    @Bean
    public ApplicationEventObservationConvention applicationEventObservationConvention() {
        return new CustomApplicationEventObservationConvention();
    }
}

public class CustomApplicationEventObservationConvention implements ApplicationEventObservationConvention {
    @Override
    public String getName() {
        return "events";
    }
    
    @Override
    public String getContextualName(ApplicationEventObservationContext context) {
        return "event-" + context.getApplicationEvent().getClass().getSimpleName();
    }
    
    @Override
    public KeyValues getLowCardinalityKeyValues(ApplicationEventObservationContext context) {
        return KeyValues.of("event.type", context.getApplicationEvent().getClass().getSimpleName());
    }
}

์ด๋ฅผ ํ†ตํ•ด ์ด๋ฒคํŠธ ๋ฐœํ–‰๊ณผ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ์ง€ํ‘œ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ , ๋ถ„์‚ฐ ์ถ”์  ์‹œ์Šคํ…œ๊ณผ ์—ฐ๋™ํ•˜์—ฌ ์ด๋ฒคํŠธ ํ๋ฆ„์„ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์–ด.

ApplicationEventMulticaster์˜ ๊ฐœ์„ 

Spring 6.0๋ถ€ํ„ฐ๋Š” ApplicationEventMulticaster์˜ ์„ฑ๋Šฅ๊ณผ ํ™•์žฅ์„ฑ์ด ๊ฐœ์„ ๋์–ด. ํŠนํžˆ ๋Œ€๋Ÿ‰์˜ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ ๋” ํšจ์œจ์ ์œผ๋กœ ๋™์ž‘ํ•ด.

@Configuration
public class EventMulticasterConfig {
    
    @Bean
    public ApplicationEventMulticaster applicationEventMulticaster() {
        SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
        
        // ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ์ „์šฉ ์Šค๋ ˆ๋“œ ํ’€ ์„ค์ •
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(10);
        taskExecutor.setMaxPoolSize(50);
        taskExecutor.setQueueCapacity(100);
        taskExecutor.setThreadNamePrefix("event-");
        taskExecutor.initialize();
        eventMulticaster.setTaskExecutor(taskExecutor);
        
        // ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ์ฒ˜๋ฆฌํ•  ํ•ธ๋“ค๋Ÿฌ ์„ค์ •
        eventMulticaster.setErrorHandler(throwable -> {
            log.error("์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ", throwable);
            // ์ถ”๊ฐ€ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ๋กœ์ง
        });
        
        return eventMulticaster;
    }
}

Reactive ์ด๋ฒคํŠธ ์ง€์› ๊ฐ•ํ™”

Spring Boot 3.x์—์„œ๋Š” Reactive ์Šคํƒ์—์„œ์˜ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์ง€์›์ด ๊ฐ•ํ™”๋์–ด. WebFlux์™€ ๊ฐ™์€ Reactive ํ™˜๊ฒฝ์—์„œ๋„ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ํšจ๊ณผ์ ์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

@Component
public class ReactiveEventListener {
    
    private final Sinks.Many<ordercreatedevent> orderCreatedSink = Sinks.many().multicast().onBackpressureBuffer();
    private final Flux<ordercreatedevent> orderCreatedFlux = orderCreatedSink.asFlux();
    
    @EventListener
    public void handleOrderCreatedEvent(OrderCreatedEvent event) {
        orderCreatedSink.tryEmitNext(event);
    }
    
    public Flux<ordercreatedevent> getOrderCreatedEvents() {
        return orderCreatedFlux;
    }
}</ordercreatedevent></ordercreatedevent></ordercreatedevent>

์ด๋ฅผ ํ†ตํ•ด ์ด๋ฒคํŠธ๋ฅผ Reactive ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ณ , ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด.

AOT(Ahead-of-Time) ์ปดํŒŒ์ผ ์ง€์›

Spring Boot 3.x์—์„œ ๋„์ž…๋œ AOT ์ปดํŒŒ์ผ์€ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ์˜ ์ฒ˜๋ฆฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œ์ผœ. ํŠนํžˆ GraalVM Native Image๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ์ด ํšจ์œจ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋„๋ก ์ตœ์ ํ™”๋์–ด.

@EventListener
public void handleOrderCreatedEvent(OrderCreatedEvent event) {
    // AOT ์ปดํŒŒ์ผ ์‹œ ์ตœ์ ํ™”๋˜๋Š” ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋กœ์ง
    // ๋ฆฌํ”Œ๋ ‰์…˜์„ ์ตœ์†Œํ™”ํ•˜๊ณ  ์ง์ ‘ ๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ๋กœ ๋ณ€ํ™˜๋จ
}

๊ตฌ์กฐํ™”๋œ ๋กœ๊น…๊ณผ ์ด๋ฒคํŠธ ํ†ตํ•ฉ

Spring Boot 3.x์—์„œ๋Š” ๊ตฌ์กฐํ™”๋œ ๋กœ๊น…(Structured Logging)๊ณผ ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ์˜ ํ†ตํ•ฉ์ด ๊ฐ•ํ™”๋์–ด. ์ด๋ฅผ ํ†ตํ•ด ์ด๋ฒคํŠธ ๋ฐœํ–‰๊ณผ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ๋กœ๊ทธ๋ฅผ ๋” ์ฒด๊ณ„์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด.

@EventListener
public void handleOrderCreatedEvent(OrderCreatedEvent event) {
    // ๊ตฌ์กฐํ™”๋œ ๋กœ๊น…
    log.atInfo()
       .addKeyValue("eventType", "OrderCreated")
       .addKeyValue("orderId", event.getOrder().getId())
       .addKeyValue("customerId", event.getOrder().getCustomerId())
       .addKeyValue("amount", event.getOrder().getTotalAmount())
       .log("Order created event processed");
    
    // ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋กœ์ง
}

์ด๋Ÿฌํ•œ Spring Boot 3.x์˜ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ๋“ค์€ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์‹œ์Šคํ…œ์˜ ์„ฑ๋Šฅ, ๋ชจ๋‹ˆํ„ฐ๋ง, ๋””๋ฒ„๊น…์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œ์ผœ. ํŠนํžˆ ๋Œ€๊ทœ๋ชจ ์‹œ์Šคํ…œ์—์„œ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์˜ ํšจ์œจ์„ฑ๊ณผ ์•ˆ์ •์„ฑ์„ ๋†’์ด๋Š” ๋ฐ ๋„์›€์ด ๋ผ. ๐Ÿš€

9. ์‹ค๋ฌด์—์„œ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์ด๋ฒคํŠธ ํŒจํ„ด ๐Ÿ‘จโ€๐Ÿ’ป

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์‹ค๋ฌด์—์„œ ํšจ๊ณผ์ ์œผ๋กœ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋ช‡ ๊ฐ€์ง€ ํŒจํ„ด๊ณผ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค๋ฅผ ์•Œ์•„๋ณด์ž.

๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ํŒจํ„ด

๋„๋ฉ”์ธ ์ด๋ฒคํŠธ(Domain Event)๋Š” ๋„๋ฉ”์ธ ๋ชจ๋ธ์—์„œ ์ค‘์š”ํ•œ ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์ด๋ฅผ ํ‘œํ˜„ํ•˜๋Š” ์ด๋ฒคํŠธ์•ผ. ์ด ํŒจํ„ด์€ DDD(Domain-Driven Design)์—์„œ ๋งŽ์ด ์‚ฌ์šฉ๋ผ.

@Entity
public class Order {
    @Id
    @GeneratedValue
    private Long id;
    
    private String customerEmail;
    private OrderStatus status;
    
    @Transient
    private List<domainevent> domainEvents = new ArrayList<>();
    
    public void markAsPaid() {
        this.status = OrderStatus.PAID;
        // ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ๋“ฑ๋ก
        this.domainEvents.add(new OrderPaidEvent(this));
    }
    
    public List<domainevent> getDomainEvents() {
        return Collections.unmodifiableList(domainEvents);
    }
    
    public void clearDomainEvents() {
        this.domainEvents.clear();
    }
}

@Service
public class OrderService {
    
    private final OrderRepository orderRepository;
    private final ApplicationEventPublisher eventPublisher;
    
    @Transactional
    public void payOrder(Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow();
        order.markAsPaid();
        orderRepository.save(order);
        
        // ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ๋ฐœํ–‰
        order.getDomainEvents().forEach(eventPublisher::publishEvent);
        order.clearDomainEvents();
    }
}</domainevent></domainevent>

์ด ํŒจํ„ด์˜ ์žฅ์ ์€ ๋„๋ฉ”์ธ ๋ชจ๋ธ์ด ์ž์‹ ์˜ ์ƒํƒœ ๋ณ€ํ™”์— ๋Œ€ํ•œ ์ด๋ฒคํŠธ๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ•จ์œผ๋กœ์จ, ๋„๋ฉ”์ธ ๋กœ์ง๊ณผ ์ด๋ฒคํŠธ ๋ฐœํ–‰์ด ๋ฐ€์ ‘ํ•˜๊ฒŒ ๊ฒฐํ•ฉ๋œ๋‹ค๋Š” ๊ฑฐ์•ผ. ์ด๋ฅผ ํ†ตํ•ด ๋„๋ฉ”์ธ ๋ชจ๋ธ์˜ ๋ณ€๊ฒฝ์ด ๋ˆ„๋ฝ๋˜์ง€ ์•Š๊ณ  ํ•ญ์ƒ ์ด๋ฒคํŠธ๋กœ ํ‘œํ˜„๋  ์ˆ˜ ์žˆ์–ด.

์ด๋ฒคํŠธ ์†Œ์‹ฑ ํŒจํ„ด