🌱 Spring Events로 애플리케이션 내 통신 마스터하기! 🚀
안녕, 친구들! 오늘은 정말 재미있고 유용한 주제를 가지고 왔어. 바로 Spring Events를 활용한 애플리케이션 내 통신에 대해 깊이 파헤쳐볼 거야. 😎 이 주제는 Java 개발자들에게 특히 중요하니까, 우리 함께 즐겁게 배워보자고!
먼저, 우리가 왜 이런 걸 배워야 하는지 알아볼까? 🤔 현대의 애플리케이션들은 점점 더 복잡해지고 있어. 여러 컴포넌트들이 서로 소통하면서 일을 처리해야 하거든. 그런데 이 소통을 어떻게 하면 가장 효율적으로 할 수 있을까? 바로 여기서 Spring Events가 등장하는 거야!
Spring Events를 사용하면 애플리케이션의 다른 부분들이 서로 느슨하게 결합되면서도 효과적으로 통신할 수 있어. 마치 재능넷에서 다양한 재능을 가진 사람들이 서로 연결되는 것처럼 말이야. 😉
자, 이제 본격적으로 Spring Events의 세계로 뛰어들어볼 준비 됐어? 우리는 이 여정을 통해 다음과 같은 내용들을 배우게 될 거야:
- Spring Events의 기본 개념 💡
- 이벤트 발행자(Publisher)와 구독자(Listener) 만들기 🎭
- 동기식 vs 비동기식 이벤트 처리 ⚡
- 트랜잭션 바운드 이벤트 다루기 💼
- Spring Events의 고급 기능들 🚀
- 실제 프로젝트에 적용하는 방법 🛠️
흥미진진하지 않아? 그럼 이제 하나씩 자세히 알아보자고!
🌟 Spring Events의 기본 개념
자, 친구들! 이제 Spring Events의 기본 개념에 대해 알아볼 차례야. 🤓 Spring Events는 말 그대로 Spring 프레임워크에서 제공하는 이벤트 메커니즘이야. 이걸 이용하면 애플리케이션의 여러 부분들이 서로 메시지를 주고받을 수 있지.
이벤트 기반 프로그래밍이라고 들어봤어? 이건 프로그램의 흐름이 이벤트의 발생과 처리에 의해 결정되는 프로그래밍 패러다임이야. Spring Events도 이 개념을 따르고 있어.
Spring Events의 핵심 구성 요소들을 살펴볼까?
- 이벤트(Event): 발생한 사건을 나타내는 객체야. 예를 들면, "사용자가 가입했다" 같은 정보를 담고 있지.
- 이벤트 발행자(Event Publisher): 이벤트를 만들어서 시스템에 알리는 역할을 해. 마치 파티에서 "케이크 자르자!"라고 외치는 사람이야.
- 이벤트 리스너(Event Listener): 특정 이벤트가 발생했을 때 반응하는 객체야. 케이크를 먹으러 모여든 사람들이라고 생각하면 돼.
이 세 가지만 있으면 우리는 Spring Events를 사용할 수 있어! 😃
그런데 말이야, Spring Events를 사용하면 어떤 장점이 있을까? 🤔
- 느슨한 결합(Loose Coupling): 이벤트를 사용하면 애플리케이션의 여러 부분들이 서로 직접적으로 의존하지 않아도 돼. 이건 마치 재능넷에서 재능 제공자와 구매자가 직접 만나지 않아도 서비스를 주고받을 수 있는 것과 비슷해.
- 확장성(Scalability): 새로운 기능을 추가하고 싶을 때, 기존 코드를 크게 수정하지 않고도 새로운 리스너를 추가할 수 있어.
- 관심사의 분리(Separation of Concerns): 각 컴포넌트는 자신의 주요 기능에만 집중할 수 있어. 다른 부분에 어떤 영향을 미칠지 걱정할 필요가 없지.
- 테스트 용이성(Testability): 이벤트 기반 시스템은 각 부분을 독립적으로 테스트하기 쉬워.
와! 벌써부터 Spring Events가 얼마나 강력한 도구인지 느껴지지 않아? 😲
이 그림을 보면 Spring Events의 기본 구조를 한눈에 이해할 수 있어. Event Publisher가 이벤트를 발생시키면, 그 이벤트는 Spring Application 내에서 전달되어 Event Listener에게 도착해. 그러면 Listener는 그 이벤트에 반응하는 거지.
자, 이제 우리는 Spring Events의 기본 개념을 알게 됐어. 👏 다음 섹션에서는 이 개념을 실제로 어떻게 코드로 구현하는지 알아볼 거야. 준비됐지? 계속 가보자고! 🚀
🎭 이벤트 발행자(Publisher)와 구독자(Listener) 만들기
안녕, 친구들! 이제 우리가 배운 Spring Events의 개념을 실제 코드로 구현해볼 시간이야. 😃 우리는 이벤트 발행자(Publisher)와 구독자(Listener)를 만들어볼 거야. 마치 재능넷에서 재능을 제공하는 사람과 그 재능을 필요로 하는 사람을 연결하는 것처럼 말이야!
1. 이벤트(Event) 클래스 만들기
먼저, 우리의 이벤트를 정의하는 클래스를 만들어야 해. 이 클래스는 이벤트와 관련된 정보를 담고 있을 거야.
import org.springframework.context.ApplicationEvent;
public class UserRegisteredEvent extends ApplicationEvent {
private final String username;
public UserRegisteredEvent(Object source, String username) {
super(source);
this.username = username;
}
public String getUsername() {
return username;
}
}
여기서 UserRegisteredEvent는 사용자가 등록됐을 때 발생하는 이벤트를 나타내. 이 이벤트는 등록된 사용자의 이름을 담고 있어.
2. 이벤트 발행자(Publisher) 만들기
다음으로, 이벤트를 발행하는 클래스를 만들어볼 거야. 이 클래스는 ApplicationEventPublisher를 사용해서 이벤트를 발행해.
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
@Component
public class UserService {
private final ApplicationEventPublisher eventPublisher;
public UserService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void registerUser(String username) {
// 사용자 등록 로직...
System.out.println("User registered: " + username);
// 이벤트 발행
eventPublisher.publishEvent(new UserRegisteredEvent(this, username));
}
}
이 UserService 클래스는 사용자를 등록하는 메서드를 가지고 있어. 사용자가 등록되면, 이 메서드는 UserRegisteredEvent를 발행해. 마치 재능넷에서 새로운 재능이 등록됐다고 알리는 것과 비슷해!
3. 이벤트 리스너(Listener) 만들기
마지막으로, 우리의 이벤트를 듣고 반응할 리스너를 만들어볼 거야.
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class UserRegistrationListener {
@EventListener
public void handleUserRegisteredEvent(UserRegisteredEvent event) {
System.out.println("Received spring event - user registered: " + event.getUsername());
// 여기에 추가적인 로직을 넣을 수 있어. 예를 들면:
// - 환영 이메일 보내기
// - 사용자 프로필 초기화하기
// - 통계 업데이트하기 등등
}
}
이 UserRegistrationListener 클래스는 @EventListener 어노테이션을 사용해서 UserRegisteredEvent를 듣고 있어. 이벤트가 발생하면, handleUserRegisteredEvent 메서드가 호출돼.
자, 이제 우리가 만든 구조를 시각화해볼까?
이 다이어그램을 보면, Spring Events의 흐름을 쉽게 이해할 수 있어:
- UserService가 UserRegisteredEvent를 발행해.
- UserRegisteredEvent는 Spring Application Context를 통해 전달돼.
- UserRegistrationListener가 이 이벤트를 받아서 처리해.
이렇게 하면 UserService는 누가 이 이벤트를 듣고 있는지 알 필요가 없어. 그저 이벤트를 발행하기만 하면 돼. 마찬가지로 UserRegistrationListener도 이 이벤트가 어디서 왔는지 신경 쓸 필요가 없어. 그저 이벤트를 받아서 처리하기만 하면 돼.
이런 구조는 애플리케이션을 더 유연하고 확장 가능하게 만들어줘. 예를 들어, 나중에 사용자 등록 시 추가적인 작업(예: 환영 이메일 보내기)을 하고 싶다면, UserService를 수정할 필요 없이 새로운 리스너를 추가하기만 하면 돼!
자, 이제 우리는 Spring Events의 기본적인 구현 방법을 알게 됐어. 👏 다음 섹션에서는 동기식과 비동기식 이벤트 처리에 대해 알아볼 거야. 계속 따라와! 🚀
⚡ 동기식 vs 비동기식 이벤트 처리
안녕, 친구들! 이번에는 Spring Events의 아주 중요한 주제인 동기식과 비동기식 이벤트 처리에 대해 알아볼 거야. 😎 이 개념을 이해하면 너의 애플리케이션을 더욱 효율적으로 만들 수 있을 거야!
동기식 이벤트 처리
먼저 동기식 이벤트 처리부터 살펴볼까? 동기식 처리는 기본적으로 Spring Events가 작동하는 방식이야.
동기식 처리의 장단점을 살펴볼까?
- 장점:
- 순서가 보장돼. 모든 리스너가 순차적으로 실행되니까.
- 트랜잭션 관리가 쉬워. 모든 처리가 같은 트랜잭션 내에서 이루어지니까.
- 에러 처리가 간단해. 어떤 리스너에서 에러가 발생하면 바로 알 수 있으니까.
- 단점:
- 성능 이슈가 있을 수 있어. 모든 리스너가 처리를 마칠 때까지 기다려야 하니까.
- 블로킹이 발생해. 이벤트 처리 중에는 다른 작업을 할 수 없으니까.
자, 이제 동기식 처리의 코드를 한번 볼까?
@Component
public class SynchronousEventListener {
@EventListener
public void handleUserRegisteredEvent(UserRegisteredEvent event) {
System.out.println("Synchronously handling user registration: " + event.getUsername());
// 여기서 시간이 오래 걸리는 작업을 한다고 가정해보자.
try {
Thread.sleep(2000); // 2초 동안 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Finished handling user registration for: " + event.getUsername());
}
}
이 코드에서는 @EventListener 어노테이션만 사용했어. 이렇게 하면 기본적으로 동기식으로 처리돼. 이벤트 처리에 2초가 걸린다고 가정했는데, 이 동안 다른 작업은 블로킹돼.
비동기식 이벤트 처리
이번엔 비동기식 이벤트 처리를 알아볼 차례야! 😃
비동기식 처리의 장단점도 살펴볼까?
- 장점:
- 성능이 향상돼. 이벤트 처리와 다른 작업이 병렬로 실행되니까.
- 응답성이 좋아져. 이벤트 발행 후 바로 다음 작업으로 넘어갈 수 있으니까.
- 확장성이 좋아. 더 많은 리스너를 추가해도 전체 성능에 큰 영향을 주지 않아.
- 단점:
- 순서가 보장되지 않아. 어떤 리스너가 먼저 실행될지 알 수 없어.
- 에러 처리가 복잡해. 비동기로 실행되는 리스너에서 발생한 에러를 어떻게 처리할지 고민해야 해.
- 트랜잭션 관리가 어려워. 각 리스너가 별도의 트랜잭션에서 실행되니까.
자, 이제 비동기식 처리의 코드를 볼까?
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("Async-");
executor.initialize();
return executor;
}
}
@Component
public class AsynchronousEventListener {
@Async
@EventListener
public void handleUserRegisteredEvent(UserRegisteredEvent event) {
System.out.println("Asynchronously handling user registration: " + event.getUsername());
// 여기서 시간이 오래 걸리는 작업을 한다고 가정해보자.
try {
Thread.sleep(2000); // 2초 동안 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Finished asynchronously handling user registration for: " + event.getUsername());
}
}
여기서는 @Async 어노테이션을 추가했어. 이렇게 하면 이 리스너는 별도의 스레드에서 비동기적으로 실행돼. 그리고 @EnableAsync로 비동기 처리를 활성화하고, TaskExecutor를 설정해서 스레드 풀을 관리하고 있어.
동기식 vs 비동기식: 언제 무엇을 사용해야 할까?
자, 이제 두 방식을 다 배웠으니 언제 어떤 방식을 사용해야 할지 알아보자!
- 이벤트 처리 순서가 중요할 때
- 이벤트 처리 결과가 즉시 필요할 때
- 트랜잭션 일관성이 중요할 때
- 에러를 즉시 처리해야 할 때
- 이벤트 처리에 시간이 오래 걸릴 때
- 여러 이벤트를 병렬로 처리하고 싶을 때
- 이벤트 발행자의 응답성을 높이고 싶을 때
- 이벤트 처리 결과가 즉시 필요하지 않을 때
예를 들어, 재능넷에서 결제 처리는 동기식으로, 알림 발송은 비동기식으로 처리하는 게 좋을 거야. 결제는 즉시 처리되고 확인되어야 하지만, 알림은 조금 늦게 발송되어도 큰 문제가 없으니까!
이 그림을 보면 동기식과 비동기식의 차이를 한눈에 알 수 있어. 동기식에서는 모든 처리가 순차적으로 이루어지지만, 비동기식에서는 이벤트 발행 후 모든 리스너가 동시에 처리를 시작하는 걸 볼 수 있지.
자, 이제 우리는 동기식과 비동기식 이벤트 처리의 차이와 각각의 장단점, 그리고 언제 어떤 방식을 사용해야 하는지 알게 됐어. 👏 이 지식을 활용하면 너의 애플리케이션을 더욱 효율적으로 만들 수 있을 거야!
다음 섹션에서는 트랜잭션 바운드 이벤트에 대해 알아볼 거야. 이건 정말 재미있고 유용한 주제니까 기대해도 좋아! 계속 따라와! 🚀
💼 트랜잭션 바운드 이벤트 다루기
안녕, 친구들! 이번에는 Spring Events의 아주 강력한 기능인 트랜잭션 바운드 이벤트에 대해 알아볼 거야. 😎 이 개념을 이해하면 너의 애플리케이션의 데이터 일관성을 더욱 잘 유지할 수 있을 거야!
트랜잭션 바운드 이벤트란?
이 기능이 왜 중요할까? 🤔 예를 들어, 사용자 등록 과정에서 데이터베이스 저장이 실패하면 환영 이메일을 보내지 않아야 해. 트랜잭션 바운드 이벤트를 사용하면 이런 상황을 쉽게 처리할 수 있어!
트랜잭션 바운드 이벤트 구현하기
자, 이제 코드로 어떻게 구현하는지 볼까?
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
@Component
public class UserService {
private final ApplicationEventPublisher eventPublisher;
public UserService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
@Transactional
public void registerUser(String username) {
// 사용자 등록 로직...
System.out.println("User registered: " + username);
// 이벤트 발행
eventPublisher.publishEvent(new UserRegisteredEvent(this, username));
}
}
@Component
public class TransactionalEventListenerExample {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleUserRegisteredEvent(UserRegisteredEvent event) {
System.out.println("Handling user registration after transaction commit: " + event.getUsername());
// 여기서 환영 이메일을 보내거나 다른 후속 작업을 수행할 수 있어.
}
}
이 코드에서 주목해야 할 점은 @TransactionalEventListener 어노테이션이야. 이 어노테이션은 트랜잭션의 특정 단계에서 이벤트를 처리하도록 지정해. 여기서는 AFTER_COMMIT을 사용했는데, 이는 트랜잭션이 성공적으로 커밋된 후에 이벤트를 처리한다는 뜻이야.
트랜잭션 단계 (TransactionPhase)
@TransactionalEventListener는 다음과 같은 트랜잭션 단계를 지원해:
- AFTER_COMMIT (기본값): 트랜잭션이 성공적으로 커밋된 후
- AFTER_ROLLBACK: 트랜잭션이 롤백된 후
- AFTER_COMPLETION: 트랜잭션이 완료된 후 (커밋 또는 롤백)
- BEFORE_COMMIT: 트랜잭션이 커밋되기 직전
트랜잭션 바운드 이벤트의 장점
- 데이터 일관성 유지: 트랜잭션이 성공했을 때만 이벤트가 처리되므로, 데이터의 일관성을 유지할 수 있어.
- 에러 처리 간소화: 트랜잭션 실패 시 자동으로 이벤트 처리가 취소되므로, 별도의 에러 처리 로직이 필요 없어.
- 비즈니스 로직 분리: 주요 비즈니스 로직과 부가적인 작업(예: 알림 발송)을 깔끔하게 분리할 수 있어.
- 성능 최적화: AFTER_COMMIT을 사용하면 트랜잭션 완료 후 비동기적으로 이벤트를 처리할 수 있어, 전체 처리 시간을 줄일 수 있어.
자, 이제 트랜잭션 바운드 이벤트의 동작 방식을 시각화해볼까?
이 다이어그램에서 볼 수 있듯이, 이벤트는 트랜잭션 내에서 발행되지만, 실제 처리는 트랜잭션이 커밋된 후에 이루어져. 이렇게 하면 트랜잭션이 실패했을 때 이벤트 처리도 자동으로 취소되는 거지!
- 거래 완료 후 판매자에게 정산 처리 이벤트 발행
- 새로운 재능 등록 후 관심 있는 사용자들에게 알림 이벤트 발행
- 사용자 리뷰 작성 후 판매자 평점 업데이트 이벤트 발행
자, 이제 우리는 트랜잭션 바운드 이벤트의 개념과 사용법, 그리고 그 장점에 대해 알아봤어. 👏 이 기능을 잘 활용하면 너의 애플리케이션의 안정성과 확장성을 한층 더 높일 수 있을 거야!
다음 섹션에서는 Spring Events의 고급 기능들에 대해 더 자세히 알아볼 거야. 계속 따라와! 🚀
🚀 Spring Events의 고급 기능들
안녕, 친구들! 지금까지 우리는 Spring Events의 기본적인 사용법과 트랜잭션 바운드 이벤트에 대해 알아봤어. 이제 더 깊이 들어가서, Spring Events의 고급 기능들을 살펴볼 거야. 이 기능들을 마스터하면 너의 애플리케이션은 한층 더 강력해질 거야! 😎
1. 조건부 이벤트 처리
때로는 특정 조건에서만 이벤트를 처리하고 싶을 때가 있어. Spring은 이를 위해 @EventListener의 condition 속성을 제공해.
@EventListener(condition = "#event.username.startsWith('admin')")
public void handleAdminUserRegisteredEvent(UserRegisteredEvent event) {
System.out.println("Admin user registered: " + event.getUsername());
}
이 코드는 사용자 이름이 'admin'으로 시작할 때만 이벤트를 처리해. 이런 기능은 재능넷에서 특별한 유형의 사용자나 재능에 대해서만 특정 작업을 수행하고 싶을 때 유용할 거야.
2. 이벤트 순서 지정
여러 리스너가 같은 이벤트를 처리할 때, 처리 순서를 지정하고 싶을 수 있어. 이때는 @Order 어노테이션을 사용할 수 있어.
@EventListener
@Order(1)
public void handleUserRegisteredEventFirst(UserRegisteredEvent event) {
System.out.println("First listener: " + event.getUsername());
}
@EventListener
@Order(2)
public void handleUserRegisteredEventSecond(UserRegisteredEvent event) {
System.out.println("Second listener: " + event.getUsername());
}
이렇게 하면 첫 번째 리스너가 항상 두 번째 리스너보다 먼저 실행돼. 재능넷에서 예를 들면, 사용자 등록 후 먼저 환영 이메일을 보내고, 그 다음에 추천 재능 목록을 생성하는 식으로 순서를 정할 수 있겠지?
3. 제네릭 이벤트
때로는 여러 유형의 이벤트를 비슷한 방식으로 처리하고 싶을 때가 있어. 이럴 때 제네릭 이벤트를 사용하면 코드 중복을 줄일 수 있어.
public class EntityCreatedEvent<t> {
private final T entity;
public EntityCreatedEvent(T entity) {
this.entity = entity;
}
public T getEntity() {
return entity;
}
}
@EventListener
public void handleEntityCreatedEvent(EntityCreatedEvent> event) {
System.out.println("Entity created: " + event.getEntity());
}
</t>
이 방식을 사용하면 사용자 생성, 재능 등록, 거래 완료 등 다양한 "생성" 이벤트를 하나의 리스너로 처리할 수 있어. 코드가 훨씬 깔끔해지겠지?
4. 이벤트 발행 결과 반환
때로는 이벤트 처리 결과를 발행자에게 돌려주고 싶을 때가 있어. Spring 4.2부터는 이벤트 리스너가 값을 반환할 수 있게 됐어.
@EventListener
public String handleUserRegisteredEvent(UserRegisteredEvent event) {
String welcomeMessage = "Welcome, " + event.getUsername() + "!";
System.out.println(welcomeMessage);
return welcomeMessage;
}
// 이벤트 발행
List<string> results = (List<string>) eventPublisher.publishEvent(new UserRegisteredEvent(this, "newuser"));
System.out.println("Event processing results: " + results);
</string></string>
이 기능을 사용하면 이벤트 처리 결과를 즉시 확인하고 활용할 수 있어. 재능넷에서 예를 들면, 새 재능 등록 시 관련 태그를 자동으로 생성하고 그 결과를 바로 화면에 표시하는 데 사용할 수 있겠지?
5. 에러 핸들링
이벤트 처리 중 발생하는 에러를 우아하게 처리하는 것도 중요해. @EventListener에 예외 처리를 추가할 수 있어.
@EventListener
public void handleUserRegisteredEvent(UserRegisteredEvent event) {
try {
// 이벤트 처리 로직
} catch (Exception e) {
System.err.println("Error processing event for user: " + event.getUsername());
// 에러 로깅, 알림 발송 등의 추가 처리
}
}
또는 전역 에러 핸들러를 만들어 모든 이벤트 처리 에러를 한 곳에서 관리할 수도 있어:
@Component
public class GlobalEventErrorHandler {
@EventListener
public void handleException(Exception exception) {
System.err.println("An error occurred during event processing: " + exception.getMessage());
// 추가적인 에러 처리 로직
}
}
이렇게 하면 예기치 못한 에러로 인해 애플리케이션이 중단되는 것을 방지할 수 있어. 재능넷 같은 복잡한 시스템에서는 이런 에러 처리가 정말 중요하지!
@EventListener(condition = "#event.user.isVIP()")
@Order(Ordered.HIGHEST_PRECEDENCE)
public void handleVIPTalentRegistration(TalentRegisteredEvent event) {
// VIP 사용자의 재능 등록 처리
}
자, 이제 우리는 Spring Events의 고급 기능들에 대해 알아봤어. 👏 이 기능들을 잘 활용하면 너의 애플리케이션은 더욱 유연하고, 확장 가능하며, 관리하기 쉬워질 거야!
다음 섹션에서는 이 모든 개념들을 종합해서 실제 프로젝트에 어떻게 적용할 수 있는지 알아볼 거야. 기대되지 않아? 계속 따라와! 🚀
🛠️ 실제 프로젝트에 적용하기
안녕, 친구들! 드디어 우리가 배운 모든 것을 종합해서 실제 프로젝트에 적용해볼 시간이 왔어. 😃 우리가 지금까지 배운 Spring Events의 모든 개념을 재능넷 같은 플랫폼에 어떻게 적용할 수 있는지 살펴보자!
1. 프로젝트 구조 설계
먼저, 이벤트 기반의 아키텍처를 적용한 프로젝트 구조를 설계해볼게:
com.talentnet
├── config
│ └── AsyncConfig.java
├── event
│ ├── TalentRegisteredEvent.java
│ ├── UserRegisteredEvent.java
│ └── TransactionCompletedEvent.java
├── listener
│ ├── TalentEventListener.java
│ ├── UserEventListener.java
│ └── TransactionEventListener.java
├── service
│ ├── TalentService.java
│ ├── UserService.java
│ └── TransactionService.java
└── controller
├── TalentController.java
├── UserController.java
└── TransactionController.java
이런 구조로 설계하면 각 도메인(재능, 사용자, 거래)별로 이벤트와 리스너를 깔끔하게 분리할 수 있어.
2. 이벤트 정의
각 도메인에 대한 이벤트를 정의해보자:
public class TalentRegisteredEvent extends ApplicationEvent {
private final String talentId;
private final String userId;
public TalentRegisteredEvent(Object source, String talentId, String userId) {
super(source);
this.talentId = talentId;
this.userId = userId;
}
// getters...
}
public class UserRegisteredEvent extends ApplicationEvent {
private final String userId;
private final String email;
public UserRegisteredEvent(Object source, String userId, String email) {
super(source);
this.userId = userId;
this.email = email;
}
// getters...
}
public class TransactionCompletedEvent extends ApplicationEvent {
private final String transactionId;
private final String buyerId;
private final String sellerId;
private final BigDecimal amount;
public TransactionCompletedEvent(Object source, String transactionId, String buyerId, String sellerId, BigDecimal amount) {
super(source);
this.transactionId = transactionId;
this.buyerId = buyerId;
this.sellerId = sellerId;
this.amount = amount;
}
// getters...
}
3. 서비스 구현
이제 각 서비스에서 이벤트를 발행하는 로직을 구현해보자:
@Service
public class TalentService {
private final ApplicationEventPublisher eventPublisher;
public TalentService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
@Transactional
public void registerTalent(String talentId, String userId) {
// 재능 등록 로직...
// 이벤트 발행
eventPublisher.publishEvent(new TalentRegisteredEvent(this, talentId, userId));
}
}
@Service
public class UserService {
private final ApplicationEventPublisher eventPublisher;
public UserService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
@Transactional
public void registerUser(String userId, String email) {
// 사용자 등록 로직...
// 이벤트 발행
eventPublisher.publishEvent(new UserRegisteredEvent(this, userId, email));
}
}
@Service
public class TransactionService {
private final ApplicationEventPublisher eventPublisher;
public TransactionService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
@Transactional
public void completeTransaction(String transactionId, String buyerId, String sellerId, BigDecimal amount) {
// 거래 완료 로직...
// 이벤트 발행
eventPublisher.publishEvent(new TransactionCompletedEvent(this, transactionId, buyerId, sellerId, amount));
}
}
4. 리스너 구현
이제 각 이벤트에 대한 리스너를 구현해보자:
@Component
public class TalentEventListener {
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleTalentRegistered(TalentRegisteredEvent event) {
System.out.println("새로운 재능이 등록되었습니다: " + event.getTalentId());
// 추천 알고리즘 업데이트
// 관심 있는 사용자에게 알림 발송
}
}
@Component
public class UserEventListener {
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleUserRegistered(UserRegisteredEvent event) {
System.out.println("새로운 사용자가 등록되었습니다: " + event.getUserId());
// 환영 이메일 발송
// 초기 추천 재능 목록 생성
}
}
@Component
public class TransactionEventListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleTransactionCompleted(TransactionCompletedEvent event) {
System.out.println("거래가 완료되었습니다: " + event.getTransactionId());
// 판매자에게 정산 처리
// 구매자에게 리뷰 요청 발송
// 거래 통계 업데이트
}
}
5. 비동기 설정
비동기 처리를 위한 설정을 추가해보자:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("TalentNet-");
executor.initialize();
return executor;
}
}
6. 컨트롤러 구현
마지막으로, 이 모든 것을 사용하는 컨트롤러를 구현해보자:
@RestController
@RequestMapping("/talents")
public class TalentController {
private final TalentService talentService;
public TalentController(TalentService talentService) {
this.talentService = talentService;
}
@PostMapping
public ResponseEntity> registerTalent(@RequestBody TalentRegistrationRequest request) {
talentService.registerTalent(request.getTalentId(), request.getUserId());
return ResponseEntity.ok().build();
}
}
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public ResponseEntity> registerUser(@RequestBody UserRegistrationRequest request) {
userService.registerUser(request.getUserId(), request.getEmail());
return ResponseEntity.ok().build();
}
}
@RestController
@RequestMapping("/transactions")
public class TransactionController {
private final TransactionService transactionService;
public TransactionController(TransactionService transactionService) {
this.transactionService = transactionService;
}
@PostMapping("/complete")
public ResponseEntity> completeTransaction(@RequestBody TransactionCompletionRequest request) {
transactionService.completeTransaction(request.getTransactionId(), request.getBuyerId(), request.getSellerId(), request.getAmount());
return ResponseEntity.ok().build();
}
}
- 각 도메인(재능, 사용자, 거래)이 느슨하게 결합되어 있어 유지보수가 쉬워.
- 비동기 처리를 통해 성능이 향상되고 확장성이 좋아져.
- 트랜잭션 바운드 이벤트를 사용해 데이터 일관성을 유지할 수 있어.
- 새로운 기능 추가가 쉬워. 예를 들어, 새로운 알림 시스템을 추가하고 싶다면 새로운 리스너만 만들면 돼!
이 구조를 바탕으로 재능넷 같은 복잡한 플랫폼을 효과적으로 구현할 수 있어. 예를 들어:
- 새로운 재능이 등록되면, 관심 있는 사용자에게 자동으로 알림을 보낼 수 있어.
- 사용자가 가입하면, 환영 이메일을 보내고 초기 추천 목록을 생성할 수 있어.
- 거래가 완료되면, 판매자에게 정산을 처리하고, 구매자에게 리뷰 요청을 보낼 수 있어.
- 모든 이벤트를 로깅해서 플랫폼 사용 통계를 실시간으로 업데이트할 수 있어.
이렇게 Spring Events를 활용하면, 복잡한 비즈니스 로직을 깔끔하게 분리하고 효율적으로 처리할 수 있어. 너의 애플리케이션이 점점 더 강력해지는 걸 느낄 수 있을 거야! 👏
자, 이제 우리는 Spring Events의 모든 것을 배우고 실제 프로젝트에 적용해봤어. 이 지식을 바탕으로 너만의 멋진 애플리케이션을 만들어보는 건 어때? 화이팅! 🚀