본문 바로가기
Back-end/벡엔드

도메인 주도 개발 - 이벤트

by javapp 자바앱 2024. 5. 15.
728x90

 

 

이벤트

과거에 벌어진 어떤 것을 의미

 

 

 

시스템 간 강결합 문제

public void 최소() {
    주문취소
    
    환불 (외부 환불 처리 서비스 이용)
}

외부 서비스의 성능에 영향을 받는 문제

 

도메인 객체에 서로 다른 도메인 로직이 섞이는 문제

 

강한 결합을 없앨 수 있는 방법은 이벤트를 사용하는 것

특히 비동기 이벤트를 사용하면 두 시스템 간의 결합을 크게 낮출 수 있다.

 

 

이벤트 구성요소

이벤트 생성 주체 -> 이벤트 퍼블리셔 -> 이벤트 핸들러

 

이벤트 용도

1. 트리거: 도메인의 상태가 바뀔 때 다른 후처리가 필요하면 후처리를 실행하기 위한 트리거로 이벤트를 사용할 수 있다.

2. 서로 다른 시스템 간의 데이터 동기화

 

 

이벤트 장점

1. 서로 다른 도메인 로직이 섞이는 것을 방지

2. 기능 확장에 용이

 

 

 

구현

이벤트 자체를 위한 상위 타입은 존재하지 않는다.

이름을 결정할 때에는 과거 시제를 사용해야 한다.

 

이벤트를 위한 공통 추상 클래스 사용 가능

public abstract class Event

 

 

 

비동기 이벤트 처리

'A 하면 일정 시간 안에 B 하라' --> 이벤트를 비동기로 처리하는 방식으로 구현

별도 스레드로 B를 수행하는 방식으로 구현

 

이벤트를 비동기로 구현하는 방법 4가지

  • 로컬 핸들러를 비동기로 실행
  • 메시지 큐를 사용
  • 이벤트 저장소와 이벤트 포워더 사용
  • 이벤트 저장소와 이벤트 제공 API 사용

 

로컬 핸들러를 비동기로 실행

@EnableAsync
@Configuration
public class AsyncConfig {
    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        int processorSize = Runtime.getRuntime().availableProcessors();
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(processorSize);
        executor.setMaxPoolSize(processorSize * 2);
        executor.setQueueCapacity(processorSize * 10);
        executor.initialize();
        return executor;
    }
}

 

핸들러

@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleDiaryTagNotification(DiaryTagNotificationEvent event) {
    String message = "";
    switch (event.getMessageCode()) {
        case DIARY_TAG:
            for (String taggedId : event.getTaggedIds()) {
                message = String.format("%s의 일기에 %s님이 회원님을 태그했습니다.", event.getPet().getName(), event.getActor().getNickname());
                notificationService.createNotification(Type.DIARY, event.getActor().getId(), taggedId, event.getActor().getThumbnailPath(), message);
            }
            break;
    }
}

이벤트 핸들러를 실행했는데 트랜잭션이 롤백되는 상황은 발생하지 않는다.

그래서 이벤트 발생 코드와 이벤트 저장처리를 한 트랜잭션으로 처리하면 된다.

이벤트 처리 실패만 고민하면 된다. 이벤트 특성에 따라 재처리 방식을 결정하면 된다.

 

 

 

 

 

 

메시지 큐를 사용

카프카나 레빗MQ 등

 

 

 

이벤트 저장소와 이벤트 포워더 사용

로컬 핸들러 --> 저장소 <-- 포워더 --> 이벤트 핸들러

 

포워더 : 이벤트를 주기적으로 읽어와 전달, 어디까지 전달했는지 추적

 

 

 

이벤트 저장소와 이벤트 제공 API 사용

로컬 핸들러 --> 저장소 <-- 제공 API  --> 이벤트 핸들러

 

 

 

이벤트 적용시 추가 고려사항

1. 이벤트 소스를 EventEntry(Entity) 에 추가할 지 여부 

2. 포워더에서 전송 실패를 얼마나 허용할 것인지

3. 이벤트 손실

4. 이벤트 순서

5. 이벤트 재처리

    가장 쉬운 방법은 마지막으로 처리한 이벤트의 순번을 기억해 두었다가 이미 처리한 순번의 이벤트가 도착하면 해당 이벤트를 처리하지 않고 무시하는 것

 

 

이벤트 처리와 DB 트랜잭션 고려

"이벤트를 처리할 때는 DB 트랜잭션을 함께 고려해야 한다."

 

 

 

 

 

 

 

 

 

 

참고: 도메인 주도 개발 시작하기, 한빛미디어

 

댓글