홈으로 돌아가기

SPA에서 SSE 알림: 폴링 없이 실시간

이 글은 SSE와 Mercure 기반 SPA 비동기 알림 아키텍처를 설명합니다. DB 변경 추적을 위한 CDC 통합, 타이핑을 위한 OpenAPI 스키마 재사용. 개발자를 위한 코드 예제와 다이어그램.

SPA에서 SSE 푸시 알림: 아키텍처와 코드
Advertisement 728x90

SSE와 Mercure를 활용한 SPA의 비동기 데이터 업데이트

프론트엔드 애플리케이션은 백엔드 변경 사항을 반영하기 위해 지속적인 데이터 업데이트가 필요합니다. 표준 API 호출은 응답 시점의 상태를 캡처하지만, 백엔드는 계속해서 이벤트를 처리합니다: 계좌 충전, 콘텐츠 검토, 고객 지원 답변 등. 이를 해결하기 위해 SSE 기반의 푸시 메커니즘이 사용되며, 기존 OpenAPI 명세와 통합됩니다.

이 시스템은 풀 방식(주기적 요청)과 푸시 방식(실시간 알림)을 결합합니다. SSE는 HTTP/2를 통한 단방향 이벤트 스트림을 제공하여 폴링에 비해 부하를 최소화합니다.

비동기 알림의 활용 사례

비동기 알림은 비즈니스 프로세스에서 매우 중요합니다:

Google AdInline article slot
  • 잔액 충전: 즉시 구매를 위한 현재 상태 표시.
  • 콘텐츠 검토: 검토 후 상태 업데이트.
  • 고객 지원 답변: 새 티켓에 대한 즉각적인 알림.

일반적인 경우, 사용자(광고주 또는 계약자)는 요청 카운터를 추적합니다: 조치가 필요한 수, 읽지 않은 댓글 수, 배치 확인 수. 이 데이터는 API 엔드포인트 /rest/User/info(operationId: getInfo)를 통해 확인할 수 있습니다.

/rest/User/info:
  get:
    tags:
      - User
    summary: 현재 사용자 정보 반환
    operationId: getInfo
    responses:
      200:
        description: 현재 사용자 정보
        content:
          application/json:
            schema:
              type: object
              properties:
                id:
                  type: integer
                  format: int32
                  minimum: 0
                locale:
                  type: string
                  enum: [ru, en]
                login:
                  type: string
                  minimum: 3
                  maximum: 320
                seoCounters:
                  $ref: '#/components/schemas/SeoCounters'

SeoCounters 스키마는 다음과 같은 구조를 정의합니다:

SeoCounters:
  type: object
  properties:
    nofRequests:
      type: integer
      format: int32
      minimum: 0
    nofLinksStatusNeedApprove:
      type: integer
      format: int32
      minimum: 0
    nofLinksWithUnreadComments:
      type: integer
      format: int32
      minimum: 0
    nofChanges:
      type: integer
      format: int32
      minimum: 0
    nofPlacementCheck:
      type: integer
      format: int32
      minimum: 0

CDC와 데이터 플랫폼 통합

user_counters 테이블의 변경 사항은 CDC(Change Data Capture)를 통해 추적됩니다. 수정 스트림은 데이터 플랫폼(기업용 ETL 시스템)으로 전송됩니다. 거기서 이벤트는 SSE 허브를 통해 UI로 푸시됩니다.

Google AdInline article slot

아키텍처는 헥사고날 모델을 사용합니다: OpenAPI 명세는 자동으로 REST 어댑터와 TypeScript 타입을 생성합니다. 프론트엔드의 경우 타입은 다음과 같습니다:

/* Generated by orval */
export interface SeoCounters {
  nofRequests?: number;
  nofLinksStatusNeedApprove?: number;
  nofLinksWithUnreadComments?: number;
  nofChanges?: number;
  nofPlacementCheck?: number;
}

export type GetInfo200 = {
  id: number;
  locale: 'ru' | 'en';
  login: string;
  seoCounters?: SeoCounters;
};

이러한 타입은 SSE 이벤트 처리에 재사용되어 타입 안전성을 보장합니다.

애플리케이션에서 SSE 구현하기

SSE(Server-Sent Events)는 서버 푸시를 위한 표준으로, 브라우저에서 지원됩니다. EventSource는 엔드포인트에 연결됩니다:

Google AdInline article slot
const evtSource = new EventSource('/sse-endpoint', {
  withCredentials: true,
});

evtSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  // 타입이 지정된 데이터로 스토어 업데이트
  updateSeoCounters(data.seoCounters);
};

인증, 토픽, 확장성을 위해 Mercure가 사용됩니다—SSE 기반의 오픈소스 허브입니다. 이 허브는 JWT와 함께 REST를 통해 발행을 수락하고 구독자에게 브로드캐스트합니다.

발행 예시:

curl -d 'topic=https://example.com/books/1' \
     -d 'data={"foo": "updated value"}' \
     -H 'Authorization: Bearer <JWT>' \
     -X POST https://hub/.well-known/mercure

JWT는 토픽에 대한 발행/구독 권한을 정의합니다. Mercure는 외부 데이터베이스와 함께 컨테이너로 배포되어 CDC 스트림과 통합됩니다.

이 접근법의 장점

  • 스키마 재사용: OpenAPI 구조가 풀과 푸시에서 중복 없이 사용됩니다.
  • 실시간: 변경 사항이 폴링 없이 즉시 반영됩니다.
  • 확장성: Mercure 허브가 부하를 분산시킵니다.

모니터링을 위한 주요 카운터 목록:

  • nofRequests — 조치 요청.
  • nofLinksStatusNeedApprove — 승인 대기 중인 링크.
  • nofLinksWithUnreadComments — 읽지 않은 댓글.
  • nofPlacementCheck — 배치 확인.

핵심 요약

  • SSE와 Mercure는 WebSocket 오버헤드 없이 타입이 지정된 푸시 알림을 제공합니다.
  • CDC는 ETL 프로세스를 위해 데이터베이스 변경 사항을 실시간으로 캡처합니다.
  • OpenAPI에서 TypeScript 타입을 자동 생성하여 데이터 일관성을 보장합니다.
  • 이 접근법은 수천 명의 사용자를 가진 고부하 시스템에 확장 가능합니다.
  • Mercure의 JWT는 알림 토픽에 대한 접근을 관리합니다.

— Editorial Team

Advertisement 728x90

다음 읽기