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 ๋ฅผ ๋ถ์ฌ์ฃผ๋ฉด ๋๋ค.
์คํ๋ง ์ด๋ฒคํธ๋ ์๊ฐ๋ณด๋ค ์ ์ฉํ ๊ธฐ์ ์ด๋ค.
ํ์ ๊ตฌํ์ ํธ๋์ญ์ ์ด ๋ง๋ ๋จ์๋ก ๊ตฌ์ฑ๋์ด ์์๋์ง, ์ปดํฌ๋ํธ๋ค ๊ฐ์ ๋๋ฌด ๊ฐํ ์์กด์ฑ์ ๊ฐ์ง๊ณ ์์ง ์์๋์ง,
๋จ์ผ ์ฑ ์ ์์น์ ์ค์ํ๋์ง ๋ค์ ์๊ฐํด๋ณด๊ฒ ๋๋ค.