[SSE-1] 실시간 알림 서비스 구현해보기 feat. AOP
회사에서 대시보드성 페이지를 개발하던 도중, 실시간 알림 서비스를 구현해야 할 필요가 생겼다.
DB에 변경 사항이 생기면 서버에서 사용자에게 알림을 보내, 페이지 새로고침을 하게 유도해야하는 상황이었다.
이것은 서버에서 클라이언트로 일방적으로 데이터를 보내는 단방향 통신의 성격이 강하므로,
해당 기능에 적합한 SSE 기술을 도입하여 처리하기로 하였다.
SSE란?
Server Sent Events의 줄임말로써, 일단 동작 흐름부터 살펴보자.
연결 이후 서버에서만 클라이언트로 일방적으로 데이터를 송신한다.
동작 방식은 처음에 클라이언트와 서버가 연결을 하고, 데이터를 송·수신하고, 종료시점에 연결을 끊는다.
여기서 한 가지 의문점이 생겼다.
웹소켓에서 클라이언트가 수신만 하면 기능적으로 똑같은 거 아님 ?
결론적으로 기능적인 목표는 같다.
하지만 기능이 같다고 해서 통신 효율성, 관리성, 구현 편의성 등 세부적인 것도 같다고 할 수는 없을 것이다.
SSE vs 웹소켓
| 구분 | Server-Sent Events (SSE) | WebSocket |
|---|---|---|
| 통신 방향 | 단방향 (Server → Client) | 양방향 (Server ↔ Client) |
| 프로토콜 | HTTP 기반 (text/event-stream) | 별도 프로토콜 (WS/WSS) |
| 연결 방식 | 지속적인 HTTP 연결 유지 | TCP 기반의 새로운 소켓 연결 |
| 데이터 형식 | UTF-8 텍스트 데이터만 지원 | 텍스트 및 바이너리 데이터 지원 |
| 자동 재연결 | 브라우저 API에 내장 (매우 쉬움) | 직접 구현 필요 (복잡도 증가) |
| 방화벽 호환성 | 우수함 (표준 HTTP 포트 사용) | 일부 환경에서 차단 가능 |
| 주요 사용처 | 실시간 알림, 주식 시세, 로그/피드 스트리밍, 라이브 스코어 | 실시간 채팅, 온라인 게임, 협업 도구 |
이렇듯 대시보드 알림 같은 단방향성 기능이라면, SSE를 사용하는게 더 나은 선택이라는 것을 알 수 있다.
기능 구현 리스트
현재 구현해야 할 기능 전체는 이와 같다.
- A테이블의 데이터에 변화가 생기면 특정 권한의 사용자에게 알림 메세지를 보낸다.
- 사용자는 우측 상단의 종 모양 버튼을 클릭하면 수신된 알림 메세지를 확인할 수 있다.
- 알림 메세지를 확인한 사용자는 새로고침을 하여 대시보드성 페이지를 최신화 시킨다.
여기서 SSE는 1번에 해당하기 때문에 해당 기능을 중점적으로 살펴보자.
구현 방안
SSE를 적용하는 방법에는 크게 3가지 방안이 있다.
- DB를 직접 감지한다.
- DB 설정 및 스키마 변경이 필요함
- DB별 사용 방법이 달라 각각 적용해야 함
- DB를 수정하는 것은 현재 상황과 맞지 않아 채택 X
- 앱 내 서비스로 구현한다.
- 감지하려는 테이블에 입력이 생기면 알림 보내는 기능을 따로 넣어줘야 함
- AOP를 사용하여 재사용성을 최대화하면 도입할만하다고 생각하여 채택 O
- 감지하려는 테이블에 입력이 생기면 알림 보내는 기능을 따로 넣어줘야 함
- Debezium, Kafka같은 서드파티를 이용한다.
- DB의 행 단위 변경 사항을 실시간으로 캡처하여 메세징 시스템으로 스트리밍 할 수 있음
- 현재 사용하지 않는 플랫폼을 해당 기능만을 위해 도입하기는 힘들어 채택 X
- DB의 행 단위 변경 사항을 실시간으로 캡처하여 메세징 시스템으로 스트리밍 할 수 있음
최종 구현 방안 및 목표: 앱 내 서비스로 구현하되, Spring boot의 기능과 AOP를 적극 활용하여 재사용성 최대화 및 코드 중복 최소화
AOP
AOP를 활용한다고 했는데 AOP가 뭘까? 정의를 살펴보자.
Aspect-Oriented Programming(관점 지향 프로그래밍). 정의는 그렇다고 한다.
AOP의 핵심 개념: 관심사의 분리
AOP는 프로그램 내에서 여러 부분에 걸쳐 반복되는 공통 기능을 한곳에서 관리하는 기술이다.
- 핵심 관심사: 비즈니스 로직
- ex) 주문하기, 결제하기
- 횡단 관심사: 여러 로직에 공통으로 들어가는 기능
- ex) 로그 남기기, 권한 체크
개념을 정리하다보니, 결국 기존의 모듈화와 같은 말 아닌가? 라는 생각이 들었다.
그래서 기존의 모듈화와 AOP의 차이점을 좀 더 자세히 확인해보았다.
일반 모듈화 vs AOP
| 구분 | 일반 모듈화 (페이징 등) | AOP (로깅, 트랜잭션 등) |
|---|---|---|
| 관점 | 수직적 (로직의 일부로 포함됨) | 수평적 (여러 로직을 가로질러 적용됨) |
| 호출 주체 | 개발자가 직접 코드 내에서 호출 | 프레임워크나 런타임이 자동으로 주입 |
| 의존성 | 비즈니스 로직이 공통 모듈에 의존함 | 비즈니스 로직은 공통 모듈의 존재를 모름 |
| 적용 적합성 | 데이터 처리 등 비즈니스에 밀접한 기능 | 보안, 로그, 트랜잭션 등 부가적인 기능 |
이 둘의 결정적인 차이점은 누가, 어디서, 어떻게 호출하느냐에 있다는 점이다.
일반적인 모듈화는 로직 안에 공통 기능을 호출하는 코드가 반드시 포함되어야 한다는 점이고,
AOP는 외부에서 해당 로직에 공통 기능을 찌르듯이 삽입한다는 점이다.
특정 테이블에 Insert, Update, Delete가 될 때, 짧게 로그에 쌓아야 하는 기능을 만들어야한다고 생각해보자.
만약 서비스 내에 해당 입력을 수행하는 로직이 100개라면,
일반 모듈화의 경우는 모든 로직에 100개의 로그 공통 코드를 삽입해야만 한다.
여기서 AOP를 적용하면 해당 문제를 해결할 수 있다.
‘특정 패키지, 특정 메서드가 실행될 때 로그 공통 코드를 실행해라’ 라고 설정만 해두면,
실행 시점에 맞춰서 자동으로 기능이 덧붙여지므로 관련 코드를 단 한 줄도 적을 필요가 없게 된다.
지금 나에게 필요한 기능도 ‘특정 테이블의 데이터에 변화가 생기면 사용자에게 알림을 전송해라’ 이므로,
알림 전송 기능을 AOP로 만들어서 해당 테이블에 입력하는 부분에 설정 해 놓으면, 테이블 변경 감지 알림 기능이 완성되는 것이다.
AOP의 실행 시점
로깅, 트랜잭션 같은 기능을 수행할 때 중요한건 실행 시점일 것이다.
그렇다면 AOP는 언제 어디서 실행이 될까?
일단 런타임 시점이 기반이다. 그리고 애플리케이션 실행 중에 프록시를 생성한 뒤 삽입하여 처리한다.
쉽게 얘기하면 메서드 호출 시에 작동한다는 얘기이다.
메서드의 실행 흐름 중 어느 단계에 끼어들지는 상세하게 결정할 수 있다.
스프링 AOP Advice 종류 및 특징
| Advice 유형 | 실행 시점 | 주요 용도 및 특징 |
|---|---|---|
| @Before | 대상 메서드 호출 직전 | 입력값 검증(Validation), 단순 로그 기록 등 |
| @AfterReturning | 메서드가 예외 없이 성공적으로 종료된 후 | 결과값 가공, 성공 결과 로깅 등 |
| @AfterThrowing | 메서드 실행 중 예외(Exception)가 발생했을 때 | 에러 로그 기록, 공통 예외 처리 등 |
| @After | 성공/실패 여부와 상관없이 무조건(Finally) | 자원 해제(Resource 클린업) 등 |
| @Around | 메서드 호출 전/후 전체 | 실행 시간 측정, 메서드 실행 여부 결정 등 |
어떤 기능인지에 따라 호출 시점도 제각기 다를 것이다.
만약 한 메서드에 모든 Advice가 걸려 있다면 다음과 같은 순서로 진행된다.
1. Around (시작 부분)
↓
2. Before
↓
3. (실제 비즈니스 로직 실행)
↓
4. AfterReturning (성공 시) 또는 AfterThrowing (실패 시)
↓
5. After (무조건 실행)
↓
6. Around (마무리 부분)
AOP vs Filter vs Interceptor
실행 시점 관련한 것들을 보다 보니, 비슷한 기능을 하는 다른 기술들이 생각났다.
바로 Filter랑, Interceptor였다. AOP를 포함하여 스프링 기반 애플리케이션에서
요청이 들어오고 나가는 과정 중에 특정 시점에 개입할 수 있는 대표적인 기술들이라고 볼 수 있다.
| 구분 | Filter | Interceptor | AOP |
|---|---|---|---|
| 위치 | 서블릿 컨테이너 (최외곽) | 스프링 MVC 계층 사이 | 서비스/매퍼 계층 (최하단) |
| 대상 | 모든 웹 요청 | 컨트롤러 호출 시 | 모든 메서드 호출 시 |
| 접근성 | HttpServletRequest/Response | 컨트롤러 객체 및 메서드 정보 | 메서드 파라미터 및 리턴값 |
| 용도 | 보안, 인코딩, CORS | 로그인/권한 체크, 공통 뷰 데이터 | 트랜잭션, 성능 로그, 비즈니스 공통 처리 |
알림 기능은 매서드 단계까지 개입해야하기 때문에 AOP가 유일한 선택지이다.
실행 순서 비교
1. Filter
↓
2. Interceptor
↓
3. AOP
↓
4.(Business Logic)
↓
5. AOP
↓
6. Interceptor
↓
7. Filter
다음 목표
SSE의 정의와 동작 방식, AOP의 개념과 구조는 충분히 학습했고 기능 구현 리스트와 구현 방안까지 명확히 선정했다.
이제 실제로 이 방식을 적용하고, 구현을 해봐야 완전히 내껄로 만들 수 있을 것이다.
현재 Spring Boot을 사용하여 프로젝트를 진행중이기 때문에
개발도 Spring Boot AOP를 사용하여 진행할 예정이다.
