BackEnd/Spring Boot

[Spring] Spring Event๋กœ ํŠธ๋žœ์žญ์…˜, ์˜์กด์„ฑ ๋ถ„๋ฆฌํ•˜๊ธฐ

ddonghyeo 2024. 8. 11. 07:58

0. Application Events

์Šคํ”„๋ง์—์„œ ์ง€์›ํ•˜๋Š” ์ด๋ฒคํŠธ๋กœ, ๋‚ด๋ถ€์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜์ด๋‹ค.

 

Spring Event๋ฅผ ํ†ตํ•ด ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜(Event-Driven Architecture)๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

1. ์‚ฌ์šฉ ์ด์œ 

ํ•œ ์˜ˆ์‹œ๋ฅผ ์•Œ์•„๋ณด์ž.

@Service
public class UserService {

    private final UserRepository userRepository;
    private final EmailService emailService;

    @Transactional
    public void registerUser(User user) {

        userRepository.save(user); //์‚ฌ์šฉ์ž ์ €์žฅ

        emailService.sendEmail(user.getEmail());

    }
}

 

ํ•œ ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ€์ž…ํ•˜๋ฉด, ์‚ฌ์šฉ์žํ•œํ…Œ ๊ฐ€์ž… ์ถ•ํ•˜ ๋ฉ”์ผ์„ ์ „์†กํ•˜๋Š” ๋กœ์ง์ด ์žˆ๋‹ค๊ณ  ํ•ด๋ณด์ž.

 

 

์˜์กด์„ฑ ๋ถ„๋ฆฌ

์ƒˆ๋กœ์šด UserService์™€ EmailService๋Š” ์„œ๋กœ ์˜์กดํ•˜๊ณ  ์žˆ๋‹ค.

 

์œ„์˜ ์˜ˆ์‹œ๊ฐ€ ์•„๋‹ˆ๋”๋ผ๋„, ๋‹ค๋ฅธ ์„œ๋น„์Šค์™€์˜ ๋กœ์ง๊ณผ ๊ฒน์น˜๊ฒŒ ๋˜๋ฉด ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์™€์˜ ๊ฒฐํ•ฉ๋„๊ฐ€ ๊ฐ•ํ•ด์ง„๋‹ค.

 

์ด๋Š” ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋–จ์–ดํŠธ๋ฆฐ๋‹ค.

 

ํ•ด๋‹น ๋กœ์ง์„ ์ด๋ฒคํŠธ ๋ฐœํ–‰ ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค๋ฉด ์˜์กด์„ฑ์„ ์™„์ „ํžˆ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

๋‹จ์ผ์ฑ…์ž„์›์น™(SRP; Single Responsibility Principle)

 

ํ•ด๋‹น ํด๋ž˜์Šค๋Š” ์‚ฌ์šฉ์ž ๊ด€๋ จ ์กฐ์ž‘์— ๋Œ€ํ•œ ์ฑ…์ž„์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

 

์ด๋ฉ”์ผ ์ „์†ก๊ณผ ํ•œ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ฌถ์—ฌ ๊ฐ™์€ ์ฑ…์ž„์œผ๋กœ ๋™์ž‘ํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ์ด๋Š” ๋‹จ์ผ์ฑ…์ž„์›์น™์„ ์œ„๋ฐฐํ•˜๊ฒŒ ๋œ๋‹ค.

 

 

 

ํŠธ๋žœ์žญ์…˜ ๋ถ„๋ฆฌ

 

์ด๋ฉ”์ผ ์ „์†ก ๋กœ์ง๊ณผ ์‚ฌ์šฉ์ž ์ €์žฅ์€ ํ•œ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ฌถ์ด๊ฒŒ ๋œ๋‹ค.

 

๋งŒ์•ฝ ์ด๋ฉ”์ผ ์ „์†ก์—์„œ ์˜ˆ์™ธ๊ฐ€ ์ผ์–ด๋‚˜๋ฉด, ํŠธ๋žœ์žญ์…˜ ์ „์ฒด๊ฐ€ ์‹คํŒจํ•˜๊ฒŒ ๋œ๋‹ค.

 

์ด๋ฉ”์ผ ์ „์†ก์— ์‹คํŒจํ•˜๋”๋ผ๋„, ์‚ฌ์šฉ์ž ์ €์žฅ์€ ์„ฑ๊ณตํ•ด์•ผ ํ•œ๋‹ค.

 

 

2. ์ ์šฉ

์ด์ œ Spring Event๋ฅผ ์ด์šฉํ•ด์„œ ์ ์šฉํ•˜์—ฌ Pub/Sub ๊ตฌ์กฐ๋กœ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ๋‹ค.

 

 

 

Event

๋จผ์ € ์ด๋ฒคํŠธ ์ „๋‹ฌ์šฉ ํด๋ž˜์Šค๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

@Getter
@RequiredArgsConstructor
public class UserRegisteredEvent {
    private final String email;
    
}

 

ํ•„๋“œ๊ฐ’ ๋‚ด์šฉ์ด ๋ฐ”๋€” ์ผ์€ ์—†๊ธฐ ๋•Œ๋ฌธ์—, final๋กœ ์ง€์ •ํ•˜๋ฉด ๋˜๊ฒ ๋‹ค.

 

 

 

Publish

@RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepository userRepository;
    
    private final ApplicationEventPublisher eventPublisher;

    public void registerUser(User user) {

        userRepository.save(User user); //์‚ฌ์šฉ์ž ์ €์žฅ

        // ์ด๋ฒคํŠธ ๋ฐœํ–‰
        eventPublisher.publishEvent(new UserRegisteredEvent(user.getEmail()));
    }
}

 

ApplicationEventListner๋Š” ๊ฒฐ๊ตญ ApplicationContext๊ฐ€ ๊ตฌํ˜„ํ•˜์ง€๋งŒ, ์ธํ„ฐํŽ˜์ด์Šค ๋ถ„๋ฆฌ ์›์น™(ISP)์— ๋งž๊ฒŒ

 

ApplicationEventPublisher  ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ฃผ์ž…๋ฐ›์•„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

 

 

 

Subscribe

@Service
public class EmailEventHandler {

    @EventListener
    public void handleUserRegisteredEvent(UserRegisteredEvent event) {
        //์ด๋ฒคํŠธ ๋ฐœ์ƒ ๋กœ์ง
    }
}

 

๊ตฌ๋…์€ ApplicationListener ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ฑฐ๋‚˜ @EventListener๋ฅผ ํ†ตํ•ด ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

 

ApplicationListener๋ฅผ ์ƒ์†๋ฐ›์•„ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋ฒˆ๊ฑฐ๋กญ๊ธฐ ๋•Œ๋ฌธ์— @EventListner๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ํ›จ์”ฌ ๋‚ซ๋‹ค.

 

 

์†์„ฑ

 

- ํด๋ž˜์Šค ์ง€์ •

@Service
public class EmailEventHandler {

    @EventListener(classes = UserRegisteredEvent.class) 
    public void handleUserRegisteredEvent(UserRegisteredEvent event) {
        //์ด๋ฒคํŠธ ๋ฐœ์ƒ ๋กœ์ง
    }
}

 

- ์—ฌ๋Ÿฌ ํด๋ž˜์Šค ์ง€์ •

@EventListener(classes = {UserRegisteredEvent.class, UserDeletedEvent.class})
public void handleUserEvents(ApplicationEvent event) {
    if (event instanceof UserRegisteredEvent) {
    	// ์‚ฌ์šฉ์ž ๊ฐ€์ž… ๋กœ์ง
    } else if (event instanceof UserDeletedEvent) {
        // ์‚ฌ์šฉ์ž ์‚ญ์ œ ๋กœ์ง
    }
}

 

- ํŠน์ • ์กฐ๊ฑด ์ •์˜

@EventListener(condition = "#event.userEmail.endsWith('@gmail.com')")
public void handleGmailUserRegistration(UserRegisteredEvent event) {
        // Gmail ์‚ฌ์šฉ์ž ํŠน๋ณ„ ์ฒ˜๋ฆฌ ๋กœ์ง
}

 

 

- ์‹๋ณ„์ž ์ •์˜

@EventListener(id = "premiumUserRegistrationListener", 
                   condition = "#event.userType == 'PREMIUM'")
public void handlePremiumUserRegistration(UserRegisteredEvent event) {    
	// ํ”„๋ฆฌ๋ฏธ์—„ ์‚ฌ์šฉ์ž ํŠน๋ณ„ ์ฒ˜๋ฆฌ ๋กœ์ง
}

 

์ง์ ‘ ํด๋ž˜์Šค๋ฅผ ์ง€์ •ํ•˜๊ฑฐ๋‚˜, ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์กฐ๊ฑด (condition), ์‹๋ณ„์ž(id)๋ฅผ ์ง์ ‘ ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

 

 

3. TransactionEventListener

 

์Šคํ”„๋ง 4.2๋ถ€ํ„ฐ ์ด๋ฒคํŠธ ๊ตฌ๋…์— @TransactionalEventListener๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

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

 

์ด๋Ÿฐ ๊ธฐ๋ณธ ๋™์ž‘์„ ๋ณ€๊ฒฝํ•˜๊ณ  ํŠธ๋žœ์žญ์…˜ ์ƒํƒœ์— ๋”ฐ๋ผ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•ด @TransactionalEventListener๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

Phase

@Service
public class EmailEventHandler {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 
    public void handleUserRegisteredEvent(UserRegisteredEvent event) {
        //์ด๋ฒคํŠธ ๋ฐœ์ƒ ๋กœ์ง
    }
}

 

@TransactionalEventListener์—๋Š” Phase๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์†์„ฑ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

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

์ด๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๊ณ , ์œ ์—ฐํ•œ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

 

 

FallbackExecution

@Service
public class EmailEventHandler {

    @TransactionalEventListener(fallbackExecution = true) 
    public void handleUserRegisteredEvent(UserRegisteredEvent event) {
        // ํŠธ๋žœ์žญ์…˜ ์™ธ๋ถ€์—์„œ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋Š” ์ด๋ฒคํŠธ ๋ฐœ์ƒ ๋กœ์ง
    }
}

 

fallbackExecution ์†์„ฑ์€ ํŠธ๋žœ์žญ์…˜ ์™ธ๋ถ€์—์„œ๋„ ํ•ด๋‹น ์ด๋ฒคํŠธ ๋กœ์ง์ด ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Œ์„ ๋œปํ•œ๋‹ค.

 

๊ธฐ๋ณธ๊ฐ’์€ false์ด๋ฉฐ, false์ผ ๊ฒฝ์šฐ ํŠธ๋žœ์žญ์…˜ ์™ธ๋ถ€์—์„œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒ๋˜๋ฉด ํ•ด๋‹น Listener๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค.

 

true๋กœ ์„ค์ •ํ•˜๋ฉด ํŠธ๋žœ์žญ์…˜ ์™ธ๋ถ€(ํŠธ๋žœ์žญ์…˜์ด ์•„๋‹ˆ๋”๋ผ๋„)์—์„œ๋„ ์‹คํ–‰๋œ๋‹ค.

 

 

 

4. ๋น„๋™๊ธฐ์ฒ˜๋ฆฌ

 

์ด๋ฒคํŠธ ๋ฐœ์ƒ ์ชฝ์˜ ๋กœ์ง๊ณผ ์ด๋ฒคํŠธ ์‹คํ–‰ ๋กœ์ง์€ ์ฒ ์ €ํ•˜๊ฒŒ ๋ถ„๋ฆฌ๋˜์–ด์•ผ ํ•œ๋‹ค.

 

์„ฑ๋Šฅ ํ–ฅ์ƒ / ๋กœ์ง ๋ถ„๋ฆฌ ๋“ฑ์˜ ์ด์œ ๋กœ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ด๋Š” ํŠธ๋žœ์žญ์…˜์ด ์ฒ ์ €ํ•˜๊ฒŒ ๋ถ„๋ฆฌ๋จ์„ ๋œปํ•œ๋‹ค.

 

@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2); //์Šค๋ ˆ๋“œ ํ’€ ๊ธฐ๋ณธ ์Šค๋ ˆ๋“œ ์ˆ˜
        executor.setMaxPoolSize(5); // ์Šค๋ ˆ๋“œ ํ’€ ์ตœ๋Œ€ ์Šค๋ ˆ๋“œ ์ˆ˜
        executor.setQueueCapacity(500); // ์ž‘์—… ๋Œ€๊ธฐ์—ด ํฌ๊ธฐ
        executor.setThreadNamePrefix("AsyncThread-"); //์Šค๋ ˆ๋“œ ์ ‘๋‘์‚ฌ
        executor.initialize();
        return executor;
    }
}

 

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ @EnableAsync ๋ฅผ ํ†ตํ•ด ๋น„๋™๊ธฐ ์ž‘์—…์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค.

 

@Service
public class EmailEventHandler {

    @Async
    @EventListener(classes = UserRegisteredEvent.class) 
    public void handleUserRegisteredEvent(UserRegisteredEvent event) {
        //์ด๋ฒคํŠธ ๋ฐœ์ƒ ๋กœ์ง
    }
}

 

๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋Š” ๋‹จ์ˆœํ•˜๊ฒŒ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ์— @Async ๋ฅผ ๋ถ™์—ฌ์ฃผ๋ฉด ๋œ๋‹ค.

 

 

 

 

 

 


 

์Šคํ”„๋ง ์ด๋ฒคํŠธ๋Š” ์ƒ๊ฐ๋ณด๋‹ค ์œ ์šฉํ•œ ๊ธฐ์ˆ ์ด๋‹ค.

ํ‰์†Œ ๊ตฌํ˜„์˜ ํŠธ๋žœ์žญ์…˜์ด ๋งž๋Š” ๋‹จ์œ„๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์—ˆ๋Š”์ง€, ์ปดํฌ๋„ŒํŠธ๋“ค ๊ฐ„์— ๋„ˆ๋ฌด ๊ฐ•ํ•œ ์˜์กด์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š์•˜๋Š”์ง€, 

๋‹จ์ผ ์ฑ…์ž„ ์›์น™์„ ์ค€์ˆ˜ํ–ˆ๋Š”์ง€ ๋‹ค์‹œ ์ƒ๊ฐํ•ด๋ณด๊ฒŒ ๋๋‹ค.