최근 팀에서 기술공유 목적의 세미나(?)를 진행했다.
사실 간단히 내용 공유 목적이었지만, 준비하다보니 할 이야기들이 많아서 발표자료까지 만들게 되버렸다!
그때 준비하면서 고민했던 내용과 실제로 적용한 내용을 회고 해보려 한다.
문제 상황
우리는 Spring에서 제공하는 ApplicationEventPublisher와 Spring Data 에서 제공해주는 Domain Event를 사용하고 있다. 서비스간의 의존성을 줄이기 위해서 사용하기 위해서 꽤나 많은 로직에서 사용중인 모습을 볼 수 있었는데, 여기서 문제가 되었던 건, AbstractAggregateRoot의 registerEvent() 였다.
현재 업무로 운영/유지보수를 하고 있는 나로써는 계속해서 이와 같은 반복적으로 발생하는 이슈들을 분석해보기 시작했고, 서비스와의 연동이 필요한 부분에서 이벤트가 하나도 (빈번히도 아닌) 발생하지 않고 있던 부분을 확인할 수 있었다. 매번 수동으로 처리하는게 귀찮고 시간을 빼앗아 가기도 했고, 결국 문제를 그냥 두는 꼴이 되어버려서 이참에 뜯어보기로 했다. 인터럽트가 걸리는 일이 줄어든다는 건, 하고 있는 업무에 온전히 집중할 수 있도록 만들기 때문! (반복 이슈들을 많이 해결해 놓은 지금, 아주 많이 느끼고 있는 중)
극약 처방(?)
여러가지 게시글에서 save 해주면 된다더라! 해서 일단 안되는 곳에 save() 처리를 해줌으로써 동작이 가능하도록 해주었다. 어떻게 보면 극약 처방이 아닌, 이게 맞는 것일 수도 있다. save라는 명시적 행위가 타당하다면?.... 근데 동기화와 같은 save 액션이 아닌 행위에도 명시적으로 save를 해주는 것이 불편했고, 다음 포스트에는 이를 해결하고자 시도했던 내용들, 그리고 최종적으로 반영된 내용을 알아볼 예정이다. 이번엔 저 save를 어떻게 잡아서 이벤트를 처리하는지, 동작과정을 알아보자.
AbstractAggregateRoot의 동작 과정
AbstractAggregateRoot의 registerEvent로 등록된 이벤트들이 save 액션이 일어나야 실제로 이벤트가 발행된다고 해서, 그부분이 어딘지 찾아보니 다음과 같이 확인할 수 있었다.
위 두 코드는 모두 Spring Data의 EventPublishingRepositoryProxyPostProcessor 에서 확인할 수 있다. 조건에는 메서드 네이밍이 save뿐만 아니라 delete~~, 그리고 인자가 1개일 경우에만 이벤트를 발생시키려 하고 있다. 극약처방으로 진행한 save 호출 뿐만아니라 다른 조건들이 존재했던 것이다. 여기까지 오는데 어떤과정이 있을지 한번 알아보자.
Entity 마다 공통 처리가 필요한 것들을 상속받을 객체로 만들어서 관리중인데, 여기에 저 Spring Data에 있는 AbstractAggregateRoot도 포함이 되어있다. 그렇기 때문에 Entity 마다 registerEvent() 를 사용해서 이벤트를 등록할 수 있었다. registerEvent() 를 하게되면, domainEvents에 이벤트를 추가하게 된다. 여기만 봐도 registerEvent를 한다고 이벤트가 발생되지는 않겠구나 싶었다. @AfterDomainEventPublication이 붙은 메서드는 등록된 domainEvents들을 clear 해주고, @DomainEvents가 붙은 메서드는 등록된 이벤트들을 반환해주는 역할을 수행한다. 두 어노테이션이 붙은 메서드는 실제로 Spring이 올라가면서 어딘가에 가지고 있게 된다.
위처럼 RepositoryFactorySupport에서 repository 관련 여러가지 processor들을 등록해주고, 그중에 EventPublishingRepositoryProxyPostProcessor가 있으며, postProcess() 를 통해서
위와 같이 엔티티마다의 @DomainEvents, @AfterDomainEventPublication 를 찾아서 등록해준다. 네이밍에서 알 수 있듯이, 발행할 이벤트 리스트를 반환하기 위한 메서드와 반환 후에 초기화 해줄 메서드 정보를 가지고 있는 모습을 볼 수 있다.
이렇게 등록된 후에는 앞에서 봤던 것 처럼 JPA 메서드 네이밍을 확인해서 이벤트를 발행할지 처리를 진행하게 된다.
조건에 맞다면, publishEventsFrom() 이 수행되게 된다. 인자에 arguments[0]는 하나의 인자만 받아올 조건이기에 하나의 Entity 객체가 될 것이고, publisher는 Spring 에서 제공해주는 ApplicationEventPublisher가 된다. 내부는 이렇게 생겼다.
내부에서는 Entity를 aggregateRoot라는 네이밍으로 가져와서 처음에 등록한 이벤트 리스트 반환 메서드를 호출 시켜서 발행할 이벤트를 모두 가져온다. 그런 다음, 해당 이벤트 리스트를 순회해서 결국 ApplicationEventPublisher의 publishEvent를 통해 이벤트를 발행하게 된다. 이벤트를 발행하는 기술은 결국 Spring 에서 제공해주는 Event를 활용하게 된다. 이벤트 발행이 모두 끝났다면, 또 다시 처음에 등록한 이벤트 리스트 clear 메서드를 호출해서 이벤트 리스트를 초기화 해주는 것으로 동작은 마무리 된다.
결국 Domain Event도 Spring Event를 사용한다
Entity에서 발생시키는 Domain Event도 결국 Spring Event 를 사용하는 것을 볼 수 있었다. ApplicationEventPublisher가 즉시 발행하기 위해서 publishEvent()를 사용한다면 Domain Event를 사용하는 AbstractAggregateRoot의 registerEvent() 는 대기중인 이벤트 리스트의 개념에 등록시켜 놓고, JPA 메서드 중에서도 해당하는 메서드에 걸리게 되면 ApplicationEventPublisher의 publishEvent() 를 통해서 실제로 발행하게 된다.
원리만 알았을 뿐, 부자연스러운 save 액션을 통해서 이벤트를 발행하는것은 아직 해결되지 않았다. 다음 포스트에서는 고민 끝에 내린 결론과 구현과정을 다뤄볼 예정이다.
'Tech' 카테고리의 다른 글
Event가 동작할 것만 같지? - 잔여 이벤트 처리하기 (1) | 2023.05.01 |
---|---|
Dead Code 분석도구 - Scavenger (Feat. DEVIEW 2023) (0) | 2023.03.27 |
SnowFlake 적용기 - JavaScript의 자료형 문제 (0) | 2023.01.10 |
SnowFlake 적용기 - 구현 (3) | 2023.01.07 |
SnowFlake 적용기 - Unique한 값 (0) | 2023.01.07 |