diff --git "a/chap10/\354\206\241\354\230\201\353\257\274.md" "b/chap10/\354\206\241\354\230\201\353\257\274.md" new file mode 100644 index 0000000..ff25dcc --- /dev/null +++ "b/chap10/\354\206\241\354\230\201\353\257\274.md" @@ -0,0 +1,57 @@ +# DDD 10장 + +## 10-1. 시스템 간 강결합 문제 +- 여러 바운디드 컨텍스트의 도메인 로직이 강결합되어있으면 다음과 같은 문제가 발생할 수 있다. + - 새로운 기능을 추가할 때, 여러 도메인간의 도메인 로직이 뒤섞이게 된다. + - 내가 처리하고자 하는 도메인 로직의 성능이 타 외부 서비스(시스템)에 의해 저하된다. + - 여러 서비스들을 같이 처리할 경우, 서로간의 트랜잭션 롤백 전략 등을 어떻게 가져갈지 고민하게 된다. +- 이를 해결하기 위해 **이벤트**를 사용하여 서비스간의 강결합을 줄일 수 있다. +- 특히 **비동기 이벤트**를 사용하면 이 결합을 크게 낮출 수 있다. + +## 10-2. 이벤트 개요 +- 여기서 이벤트란 "과거에 벌어진 어떤 것"을 의미한다. +- 예를 들어 사용자가 암호를 변경했을 때, "암호를 변경함"이 이벤트이다. +- 이벤트에는 다음과 같은 구성요소가 있다. + - 이벤트 생성 주체 : 이벤트를 발행하는 주체로 엔티티, 벨류, 도메인 서비스 등 도메인 객체이다. + - 이벤트 디스패처 : 이벤트 생성 주체가 발행한 이벤트를 이벤트 핸들러로 전달한다. + - 이벤트 핸들러 : 이벤트 생성 주체가 발행한 이벤트에 구독(반응) 한다. +- 예를 들어, 암호를 변경했을때, User 엔티티의 changePassword함수는 UserPasswordChangedEvent 를 발행한다. +- 이때 UserChangedPasswordEvent를 구독한 이벤트 핸들러에서 메일을 보낸다거나 하는 행위를 할 수 잇다. +- 이벤트는 크게 두가지 용도가 있다. + - 트리거 : 도메인의 상태가 바뀔 때 후처리 용도로 사용할 수 있다. (ex. 주문 취소 시 결제 환불을 실행할때 그 트리거) + - 데이터 동기화 : 서로 다른 시스템간의 데이터를 동기화한다. 주문 도메인이 배송지 변경을 하면, 배송 서비스에게 배송지 변경을 알려주는 이벤트를 발행할 수 있다. + +## 10-3. 이벤트, 핸들러, 디스패쳐 구현 +- 이벤트 클래스: 이벤트를 표현하는 클래스이다. + - 상속을 받아도 된다. 딱히 최상위 클래스가 상속받아야하는 클래스는 없다. (스프링에선) +- 디스패쳐 : 스프링이 ApplicationEventPublisher를 제공한다. + - 빈 주입 없이 사용하기 위해 static 필드로 EventPublisher를 두고, Configuration 클래스에서 주입해줄 수 있다. + - 이 클래스(Event)의 스태틱 함수 raise()를 통해 빈이 아닌 클래스에서 이벤트를 발행할 수 있다. +- 이벤트 핸들러 : 스프링이 제공하는 EventHandler를 사용한다. + +## 10-4. 동기 이벤트 처리 문제 +- 기본적으로 ApplicationEventPublisher는 동기로 동작한다. 즉 EventHandler에서 발생한 오류가 raise를 호출한 시점에서도 전파될 수 있다. +- 또한 이벤트 핸들러가 동작해야 다음으로 넘어가기에, 기존의 성능 문제 역시 그대로 발생한다. +- 따라서 비동기 처리를 하거나, 이벤트와 트랜잭션을 연계할 수 있다. + +## 10-5. 비동기 이벤트 처리 +- 주문을 취소하자마자 결제를 취소할 필요는 없다. 나중에 취소되어도 문제 없다. +- 회원가입 후 메일 발송도 마찬가지이다. 이걸 비동기로 처리해도 되는지 확인하는 방법은, +- 'A하면 일정 시간 안에 B하라'로 바꿀 수 있는 요구사항은 비동기로 구현할 수 있다. +- 방법은 크게 4가지가 있다. + - 로컬 핸들러를 비동기로 실행하기. (@Async 어노테이션) + - 메세지 큐 이용하기 + - 이 경우 트랜잭션으로 묶여야 한다면 글로벌 트랜잭션(혹은 분산 트랜잭션)이 필요하다. + - 이벤트 저장소와 이벤트 포워더 사용하기 + - 일단 이벤트를 디비에 저장하고, 이벤트 포워더라는 프로세스가 주기적으로 쌓인 이벤트를 추적해서 실행한다. + - 이벤트 저장소와 이벤트 제공 API 사용하기. + - 외부 핸들러가 API서버로 이벤트 목록을 가져간다. + - 포워더는 추적을 포워더가 한다면, API 방식에서는 이벤트 핸들러가 어디까지 이벤트 읽었는지 추적해야한다. + +## 10-6. 이벤트 적용시 추가 고려 사항 +- 이벤트를 구현할때 이벤트 소스 (누가 발행햇는지?)를 조회할 수 없다. 필요하다면 추가해야한다. +- 포워더의 전송 실패를 얼마나 허용하느냐? 도 고려해야한다. 계속 하나 실패해서 뒤에게 밀리면 안되기 때문 +- 이벤트가 손실될 때 (처리하지 못했을때)도 고려해야 한다. +- 이벤트 발생 순서대로 실행이 필요하다면, 이벤트 저장소를 사용하거나 메세징큐중 순서 보장되는걸 써야한다. +- 이벤트 재처리가 요청왔을때 어떻게 핸들링하는가 (동일한 순번의 이벤트가 오는경우 등) +- TransactionalEventListener로 트랜잭션이 성공할때만 이벤트가 실행되게 하면 좋다.