이벤트 기반 아키텍처

Event-driven Architecture

방법

서비스끼리 서로를 직접 부르는 대신 일어난 사건을 내보내고, 관심 있는 쪽이 알아서 받아 가게 만드는 설계 방식입니다.

등장 배경

기능이 늘어나면 한 동작에 딸려 일어나야 할 일도 함께 늘어납니다. 페이지덱에서 문서를 저장하면 같이 편집하던 사람에게 알림이 가야 하고, 검색 색인을 다시 만들어야 하고, 활동 기록에 한 줄이 남아야 하고, 사용량 집계에도 반영돼야 합니다.

가장 먼저 떠오르는 방법은 저장하는 코드가 그 넷을 차례로 부르는 것입니다. 처음 둘까지는 괜찮지만, 기능을 하나 붙일 때마다 저장 코드를 열어 한 줄씩 넣게 되고 반년 뒤에는 문서 저장 함수가 자기 일과 상관없는 일곱 군데를 알고 있게 됩니다. 어느 쪽을 고쳐도 저장을 다시 확인해야 하는 상태, 곧 결합도가 높은 상태입니다.

더 곤란한 것은 실패와 속도가 함께 따라온다는 점입니다. 알림 쪽이 느려지면 저장 버튼이 같이 느려지고, 색인 쪽이 오류를 내면 저장까지 실패합니다. 태오가 "알림 서버 때문에 문서가 안 저장됩니다" 라고 말하게 되는데, 사용자에게 알림과 저장은 아무 상관이 없습니다.

이벤트 기반 아키텍처는 부르는 방향을 뒤집습니다. 저장한 쪽은 "문서가 저장됐다" 는 사실 하나만 내보내고 누가 그것으로 무엇을 하는지는 모릅니다. 알림이든 색인이든 필요한 쪽이 그 사실을 구독해 가져갑니다.

사용 절차

내보낼 사건을 정하는 일부터 시작해 받는 쪽을 붙이고 실패를 다루는 데까지 여섯 단계를 밟습니다.

  1. 사건 고르기 — 업무에서 실제로 벌어진 사실만 사건으로 삼습니다. 이름은 document.saved 처럼 과거형으로 짓습니다. send_notification 같은 명령형 이름을 붙이면 내보내는 쪽이 받는 쪽을 다시 알게 되어 방향을 뒤집은 의미가 사라집니다.
  2. 담을 내용 정하기 — 문서 번호, 저장한 사람, 저장 시각처럼 받는 쪽이 되묻지 않고 일할 만큼을 담습니다. 본문처럼 크고 자주 바뀌는 것은 번호만 담고 필요할 때 가져가게 둡니다.
  3. 전달 장치 고르기 — 내보낸 사건을 맡아 두었다가 구독자에게 넘기는 중개자가 필요합니다. 받는 쪽이 하나면 작업을 줄 세워 하나씩 넘기는 메시지 큐로 충분하고, 여럿이 같은 사건을 받아야 하면 사건을 복제해 뿌려 주는 장치를 씁니다.
  4. 받는 쪽 붙이기 — 사건을 구독해 자기 일을 하는 쪽입니다. 알림, 색인, 집계가 각자 붙어 같은 document.saved 를 받아 갑니다. 하나가 멈춰도 나머지는 그대로 돌고, 멈춘 쪽은 살아난 뒤 밀린 사건부터 처리합니다.
  5. 중복과 실패 다루기 — 중개자는 응답이 없으면 사건을 다시 보내기 때문에 같은 사건이 두 번 도착하는 일이 정상 범위 안에서 벌어집니다. 받는 쪽은 같은 사건을 여러 번 받아도 결과가 달라지지 않는 성질, 곧 멱등성을 갖춰야 합니다. 사건마다 붙은 고유 번호를 기록해 두고 이미 처리한 번호면 넘기는 식입니다.
  6. 흐름 들여다보기 — 사건마다 상관 번호를 붙여 지나간 자리에 함께 남깁니다. 직접 부르면 오류 기록 하나에 전체 경로가 찍히지만 여기서는 조각이 흩어져서, 밖에서 안을 들여다보는 수단인 관측성이 없으면 어디에서 끊겼는지 찾지 못합니다.

적합한 상황과 한계

한 가지 일에 반응할 곳이 여럿이고 앞으로 더 늘 것 같으면 잘 맞습니다. 결제가 승인됐다는 사건 하나에 영수증 발송, 사용량 갱신, 회계 기록, 고객 성공 팀 알림이 달리는 식입니다. 받는 쪽이 넷에서 여섯으로 늘어도 결제 코드는 열지 않습니다.

보내는 쪽이 결과를 기다릴 이유가 없을 때도 맞습니다. 사용자는 문서가 저장됐다는 응답만 받으면 되고 알림이 3초 뒤에 가든 30초 뒤에 가든 상관하지 않습니다.

반대로 세 가지가 한계로 돌아옵니다.

  1. 즉시 확인이 필요한 일에는 맞지 않습니다. 결제 승인 여부는 사용자가 화면 앞에서 기다리는 동안 답이 나와야 해서, 사건을 던져 놓고 나중에 처리해서는 화면을 그릴 수 없습니다.
  2. 잠깐 어긋난 상태가 정상입니다. 저장은 끝났는데 검색에는 아직 안 걸리는 몇 초가 늘 생깁니다. 이 몇 초를 제품 쪽이 받아들일 수 있는지, 받아들인다면 그동안 화면에 무엇을 보여 줄지를 먼저 정해야 합니다.
  3. 따라가기가 어렵습니다. 알림이 안 갔다는 신고 하나를 확인하려면 내보낸 기록, 중개자 상태, 받는 쪽 기록을 각각 열어 봐야 합니다. 마흔다섯 명 규모에서 전부에 적용하면 얻는 것보다 들여다보는 비용이 큽니다. 대개는 모듈러 모놀리스처럼 한 덩어리로 두되 안을 모듈로 가르고, 받는 쪽이 여럿인 몇 군데에만 사건을 씁니다.

실무 용례

"그건 저장 쪽에서 직접 부르지 말고 이벤트로 빼죠."

새 기능을 기존 코드 안에 끼워 넣지 말고 사건을 구독해 가져가게 하자는 제안입니다.

"알림이 20초쯤 늦게 갈 수 있는데 괜찮으세요?"

사건 방식으로 가려는데 제품 쪽이 그 지연을 받아들이는지 묻는 말입니다.

"그 소비자 멱등성 처리 돼 있나요?"

같은 사건이 두 번 도착해도 영수증이 두 번 나가지 않는지 확인하는 질문입니다.

페이지덱에서는 문서 저장과 결제 두 군데에만 이 방식을 씁니다. 지현이 문서 저장 뒤에 무언가를 하려는 기능을 가져오면 태오는 기존 사건을 하나 더 구독하는 것으로 끝내고, 저장 코드는 건드리지 않습니다.

흔한 오해

비동기로 처리하니까 시스템이 전체적으로 빨라진다고 읽는 경우가 많습니다. 저장 버튼이 실제로 빨리 돌아오니 그렇게 느끼기 쉽습니다. 하지만 해야 할 일의 총량은 그대로입니다. 빨라진 것은 사용자가 응답을 받는 시점이고, 알림이 실제로 도착하는 시점은 오히려 늦어집니다. 받는 쪽이 감당할 속도보다 사건이 빨리 쌓이면 밀린 양이 계속 늘어납니다.

이벤트 기반 아키텍처를 마이크로서비스와 같은 말로 쓰는 것도 흔한 오해입니다. 서비스를 나누면 그 사이를 사건으로 잇는 경우가 많아서 그렇습니다. 실제로는 다른 질문에 대한 답입니다. 마이크로서비스는 배포 단위를 몇 덩어리로 나눌지를 정하고, 이벤트 기반 아키텍처는 그 덩어리들이 어떻게 말을 걸지를 정합니다. 하나로 배포하는 모놀리스 안에서도 사건을 쓸 수 있고, 잘게 나눠 놓고 직접 부르기만 하는 구성도 흔합니다. 둘을 묶어서 생각하면 서비스를 나눌 준비가 안 된 팀이 나누는 일부터 시작하게 됩니다.

자주 헷갈리는 용어

용어무엇인가이벤트 기반 아키텍처와의 차이
메시지 큐작업을 줄 세워 비동기로 넘기는 장치사건을 나르는 부품 하나입니다. 이벤트 기반 아키텍처는 무엇을 사건으로 삼고 어떻게 잇는지에 대한 설계이고, 메시지 큐는 그 설계를 실제로 굴릴 때 쓰는 도구입니다

같이 보면 좋은 페이지

마지막 업데이트 2026-09-16