DDD 도메인주도설계

도메인주도개발 시작하기 10장 이벤트

MIN우 2024. 3. 10. 13:42
728x90

## 시스템 간 강결합 문제

쇼핑몰에서 구매를 취소하면 환불처리를 해야한다.

이때 보통 결제 시스템은 외부에 있으므로 Order 도메인에서 구매 취소에 관련된 서비스를 다음과 같이 파라미터로 주입할 것이다.

 

위처럼 외부 시스템을 도메인에서 호출 시 3가지 문제가 발생할 수 있다.

 

1. 외부 서비스가 비정상일 경우 트랜젝션 처리를 어떻게 할까?

- 롤백을 해야할까? 일단 커밋을 해야할까?, 아니면 상태만 변경한 후에 나중에 다시 시도를 해야할까?

2. 외부 시스템의 응답 시간이 길어지면 어떻게 할까?

- 대기 시간만큼 응답시간이 길어져서 성능에 악영향을 주지 않을까?

3. 도메인 객체에 서비스를 전달하면 설계상 문제가 발생하지 않을까?

- 도메인 로직과 외부 로직이 뒤섞이지 않을까?

 

바운디드 컨텍스트 간 강결합이 생기고 하나의 마이크로가 문제가 생길 시 해당 시스템은 작동하지않게되고

의존도가 높아진다.

-> 해결방안: 비동기 이벤트를 사용하겨 시스템 간 결합도는 크게 낮춘다.

 

 

### 이벤트 용도

이벤트는 크게 두 용도로 사용한다.

1.. 트리거 : 도메인 상태 변경 후 후처리를 실행하기 위한 트리거로 이벤트를 사용

- 예) 현재 flowbit에서 후처리를 하는 경우 -> 이메일전송하는 것을 따로 이벤트 driven으로 처리하면

될 것같다. 그럼 동기식이 아닌 비동기로 처리되니 성능 및 결합도도 낮출 수 있다.

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

 

이벤트를 통해 서로 다른 도메인 로직을 섞이는 것을 방지할 수 있다. 

 

또한 기능 확장에도 용이하다. 해당 이벤트 구독하는 헨들러만 추가하면 된다.

 

 

 

## 이벤트, 핸들러, 디스패처 구현

### 디스패처 : ApplicationEventPublisher 이용

 

### Events : 이벤트 발행, ApplicationEventPublisher 이용

raise를 이용하여 이벤트 발생

 

### 이벤트 핸들러 : 이벤트 수신 처리

이벤트 처리시에는 @EventListener 사용

하지만 이 방식에는 분산환경에서는 통하지 않는다. spring boot내장에 메모리에 적재되어있는 것이라

분산된 서버에서는 동기화되지않는 문제가 발생할 수 있다. 따라서 kafka처럼 외부에 따로 빼서 사용할 수 있는 것을 사용해야한다.

 

## 동기 이벤트 처리 문제

이벤트 처리를 할 때, 외부 서비스가 느리거나 예외가 발생하면 어떻게 할 것인가?

이벤트를 비동기로 처리하거나 이벤트와 트랜젝션을 연계하여 외부 시스템과 연동할 수 있다.

 

## 비동기 이벤트 처리

우리가 구현하는 것 중에서는 "A하면 이어서 B 하라"는  A하면 최대 언제까지 B를 해라" 라는 경우가 많다.

즉, 일정 시간 안에만 후속 조치를 처리하면 되는 경우가 많다.

B를 수행하는데 실패하면 일정 간격으로 재시도하거나, 수동으로 처리해도 되는 경우가 있다.

여기서 "A하면"은 이벤트로 볼 수 있다.

위와 같은 요구사항은 이벤트를 비동기로 처리하는 방식으로 구현할 수 있다.

** 여기서 비동기로 처리했다 하더라도 다른 마이크로서비스에서 오류가 발생했다면 ?

-> 해결방안: saga패턴 보상트랜잭션을 통해 commit 한 데이터나 이미 반영한 데이터를 보정하는 트랜잭션으로

커밋된 데이터를 다시 이전으로 변경해주면 된다.

 

ex)

@KafkaListener(topics = "register-flowbit-saga", groupId = "register-flowbit-saga-1", containerFactory = "eventRegisterFlowbitSagaPatternListener")
    protected void consumeRegisterFlowbitSagaPattern(EventAccount.RegisterEnterpriseSaga event) throws IOException {
        if(!event.isSuccess()) {
            // 등록 실패 시 보상 트랜잭션
            accountService.deleteRegisteredAccount(event.getAccountId());
            logger.info(event.getAccountId() + " Saga 보상 트랜잭션 발행");
        } else {
            logger.info(event.getAccountId() + " 등록 성공(Saga)");
        }
    }

 

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

이벤트 핸들러를 별도 스레드로 실행한다

@Async를 사용하면 된다.

@EnableAsync 에너테이션을 사용해서 비동기 기능을 활성화한다

이 방법 또한 분산된 환경에서는 불필요하다. 또한 하나하나 어노테이션 다 붙여줘야해서 비효율적이라고 생각한다.

 

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

1. 이벤트 소스를 엔트리 안에 추가할 지 여부. 경우에 따라 이벤트 발생 주체에 대한 정보가 필요할 수도 있다.

2. 포워더에서 전송 실패를 얼마나 허용할 것인가? 재전송 횟수 제한이 필요한가? 계속 실패한다면 생략 가능한가?

3. 이벤트 손실되면 어떻게 하는가? 한 트랜젝션으로 처리해야 하는가?

4. 이벤트 순서를 보장해야 하는가? 순서를 보장해야 한다면 이벤트 저장소를 사용하는 것이 좋다

5. 이벤트 재처리가 가능한가? 멱등성을 제공할 것인가?(같은 이벤트를 중복 실행해도 결과가 같은가?)

728x90