이벤트 기반 아키텍쳐

김재연·2025년 6월 29일
post-thumbnail

갑자기 이벤트 아키텍쳐 ?

제가 다니고 있는 회사는 선박의 위치 및 다양한 정보를 나타내는 AIS 데이터를 통해 많은 기능들을 기업들에게 제공하고 있습니다.

많은 기능 중 하나인, 선박의 정보들과 화물들의 정보를 잘 묶어 화물의 현재 위치등 다양한 정보들을 제공하는 기능도 존재합니다.

이번에 이 화물에 특정 이벤트가 발생했을 때, 이 화물에 대한 알림을 받고자 하는 인원들에게 메일을 발송하는 기능을 만드는 것을 맡게되었습니다.

저희 회사는 많은 기능들이 있었지만 현재까지 이와 같이 알림 비슷한 기능이 존재하지 않았습니다. 하지만, 현재부터 야심차게 이러한 알림 서비스를 점차 점차 넓혀갈 계획을 가지고 있습니다.

이러한 배경을 통해 앞으로 생길 이메일 및 외부 서비스를 통해 notification을 주는 서버 혹은 서비스는 굉장히 중요할 것이라고 판단했고, 이는 비동기적으로 그리고 데이터 유실을 최대한 줄이고, 유연하게 동작하게 하기 위해 메시지 큐 도입이 필수적일 것이라 판단해, 메시지 큐에 대한 자료를 찾아보다가, 이를 찾아보기에 앞서 이벤트 기반 아키텍쳐에 대한 공부가 선수되어야 할 것 같아서 이에 대해 알아보게 되었습니다.

참조 글

이벤트 기반 아키텍쳐에 대한 구현, 주관을 담은 글은 굉장히 많았습니다. 하지만, 그 중 가장 인상깊게 읽은 우아한 형제들의 회원 시스템 이벤트 기반 아키텍쳐 구축하기를 기반으로 글을 작성해보려 합니다.

회원시스템 이벤트기반 아키텍처 구축하기 | 우아한형제들 기술블로그

논리적 결합도

평소의 저는 이벤트를 발생시킬 때, 목적을 담고, 발생시켰습니다.

제가 하고 있는 서비스를 예를 들어 설명하면, 누군가가 회원가입을 했을 때, 투표함을 만들어주고, 누군가가 우리 반에 입장했다고 알림을 보내야 하는 기능이 있습니다.

이 때, 저는 EnterClassEvent, CreatedVoteBoxEvent라는 두 개의 이벤트를 발생시켜주었습니다.

하지만, 이렇게 되면 회원가입에 대한 추가적인 기능이 늘어날 때마다 이벤트도 늘어나고, 이 이벤트를 받아서 처리하는 곳도 만들어야 하기 때문에 변경지점이 두 개가 되고, 무엇보다 회원가입이 알아야 하는 도메인들이 너무나도 많아집니다.

이를 해결하기 위해 해당 블로그에서는 다음과 같이 설명하고 있습니다.


상대방의 로직을 알고, 목적을 담아 이벤트를 발생하게 되면, 이것은 개념적 결합도를 유지하는 것이기 때문에, 결합도가 느슨해지는 것이 아닙니다.

출처 : 회원시스템 이벤트기반 아키텍처 구축하기 | 우아한형제들 기술블로그

여기서 키 포인트는, 이벤트를 발생시키는 쪽에서는 목적을 담아서는 안된다는 것입니다.

위에서 예시로 들었던 상황에서는 사실 SignUpEvent 만을 발생시켜야 했던 것입니다.

특정한 목적을 가지고 이벤트를 발생시켜서는 안되고, SignUp에 대한 처리가 끝나고 나서, SignUpEvent를 발생시키면 되는 것입니다. (담는 데이터는 최대한 풍부하게 담는 것이 좋은 것 같습니다. 이벤트 버스 내에 실려있는 이벤트들이 메모리를 너무 많이 잡아먹지 않는 선에서 말입니다.)

다만, 의존성 역전을 위해 사용하는 이벤트는 조금 다를 수 있을 것 같긴합니다. 하지만, 이 경우 이벤트가 아닌 인터페이스와 Spring Injection을 활용하여 의존성 역전을 시키는 방법도 존재합니다.

비관심사를 결정하는 것도 굉장히 중요하다.

그리고 이렇게 논리적 강결합을 끊어내기 위해서는 비관심사를 결정하는 것도 굉장히 중요합니다. 그래야지 딱 도메인에 해당하는 작업만을 진행하고 이벤트를 발생시켜 나머지 작업들은 이벤트를 받아서 처리하는 곳에서 알맞게 처리할 수 있습니다.

메시징 시스템

이제 이벤트와 메시징 시스템을 결합해서 사용하는 것을 한번 살펴보겠습니다.

MlyxWth.png

출처 : 회원시스템 이벤트기반 아키텍처 구축하기 | 우아한형제들 기술블로그



출처 : 회원시스템 이벤트기반 아키텍처 구축하기 | 우아한형제들 기술블로그

이전에 이야기 했던 것처럼, 이벤트에 목적을 빼고, login 했다는 이벤트만을 발생하고 있습니다.

그리고 그렇게 발생시킨 Application Event를 Sns Listener가 리스닝하고 Aws SNS에다가 이벤트를 전달, 또 이를 Aws SQS에다가 각각 넣어주어 각각의 Event Worker들에서 Login 이후 수행되어야 하는 로직들을 수행시켜주고 있습니다.

이를 통해 Login 서비스는 로그인만을 진행하면 되게 되었고, 로그인 이후 부가적인 작업들은 각각의 Event Worker들에서 진행하면 되는, 논리적으로 결합도가 굉장히 낮아진 형태를 띄게 되었습니다.

출처 : 회원시스템 이벤트기반 아키텍처 구축하기 | 우아한형제들 기술블로그

그리고 각각의 Event Worker들도 또, SNS를 통해 이벤트를 전파시켜 완전히 다른 시스템으로 메시지를 전달하는 시스템을 만들 수도 있습니다.

위 그림과 같이 말입니다. 이렇게 되면 System 별로 Http 방식으로 통신을 하는 것이 아닌 Event 기반으로 통신할 수 있는 구조가 완료되게 되었습니다.

출처 : 회원시스템 이벤트기반 아키텍처 구축하기 | 우아한형제들 기술블로그

외부 시스템에 대한 이벤트는 도메인 이벤트처럼 페이로드를 제공한다거나 이런 것들이 불가능합니다. 그것을 가진다라는 것 자체가 구독자에게 원하는 바가 있는 것이고, 이로써 논리적인 의존관계가 형성됩니다. 그렇기 때문에 이벤트 구조는 가장 Formal 한 구조를 가지고, 이를 Consumer 입장에서 어떠한 이벤트인지를 명확하게 판단할 수 있어야 합니다.

출처 : 회원시스템 이벤트기반 아키텍처 구축하기 | 우아한형제들 기술블로그

그래서 위와 같은 방식도 존재합니다.

이벤트를 발생시킬 때, 가게 ID만을 넣어서 이벤트를 발생시키면, 이를 처리하는 쪽에서 재차 Http 요청을 통해 상세정보를 얻어오는 형식으로 Zero Payload 형식을 적용할 수 있습니다.

출처 : 회원시스템 이벤트기반 아키텍처 구축하기 | 우아한형제들 기술블로그

최종적으로는 이런 구조이고, 외부 이벤트를 통해 외부 시스템과도 야무지게 통신을 할 수 있게 되었습니다. 하지만, 이런 구조를 유지하기 위해서는 데이터 구조도 굉장히 중요할 것 같습니다. (도메인의 관심사이냐, 비관심사냐에 따라 다르게 설계되어야 한다. 각자 직접 참조 혹은 간접 참조라도 도메인간의 논리적인 의존관계가 생기게 되는 것은 막을 수 없기 때문입니다.)

이벤트 발행 저장소

출처 : 회원시스템 이벤트기반 아키텍처 구축하기 | 우아한형제들 기술블로그

이벤트를 발행시키는 과정에서 이벤트가 유실이 된다거나 (특히 메시지 큐를 사용할 때) 혹은 이를 받는 서버쪽에서의 장애가 발생하여, 혹은 Application 내부에서 이벤트를 발행하다가 예상치 못한 오류가 발생했다고 했을 때, 발행된 이벤트를 정상적으로 처리하지 못할 수도 있습니다.

그렇기 때문에, 발생한 이벤트를 저장할 수 있는 저장소를 만들고, Event Recorder를 구현하여 이벤트를 저장합니다.

여기서 가장 중요한 점은 도메인 이벤트를 발행하는 곳과 도메인 이벤트를 저장하는 곳의 트랜잭션을 하나로 묶어, 원자성이 보장되게 해야 합니다.

그리고 주기적으로 이 이벤트들을 재발행시켜 추후라도 다시 해당 이벤트들이 정상적으로 처리될 수 있도록 합니다.

이 섹션도 굉장히 재미있게 읽었고, 이는 실제 초반에 말한 프로젝트를 진행하면서 실제로 적용한 부분입니다. (이를 통해서 이벤트 재발행이 가능한 구조가 되었고, 그리하여 이벤트 유실을 최대한 막게 되었습니다.)

마무리

해당 글을 읽으며 참 인사이트를 많이 얻었고 회사의 프로젝트를 진행하는데 도움을 참 많이 받았습니다. 또한, 아키텍쳐에 대한 고민도 굉장히 재미있다라는 사실을 깨우쳐주는 글 중 하나였던 것 같습니다.

profile
끊임없이 '성장'하는 개발자 김재연입니다.

0개의 댓글