hgzero/develop/dev/dev-notification.md
2025-10-23 15:23:18 +09:00

20 KiB

Notification Service - 백엔드 개발 결과서

1. 개요

1.1 서비스 설명

  • 서비스명: Notification Service
  • 목적: 회의 초대 및 Todo 할당 알림 발송 서비스
  • 아키텍처 패턴: Layered Architecture
  • 주요 기능:
    • Azure Event Hubs를 통한 이벤트 기반 알림 발송
    • 이메일 템플릿 기반 알림 생성
    • 사용자별 알림 설정 관리
    • 재시도 메커니즘 (Exponential Backoff)
    • 중복 발송 방지 (Idempotency)

1.2 개발 기간

  • 시작일: 2025-10-23
  • 완료일: 2025-10-23
  • 개발자: 준호 (Backend Developer)

2. 기술 스택

2.1 프레임워크 및 라이브러리

  • Spring Boot: 3.3.0
  • Java: 21
  • Spring Data JPA: 3.3.0
  • Spring Security: 6.3.0
  • Spring Mail: 3.3.0
  • Spring Retry: 2.0.6
  • Thymeleaf: 3.1.2
  • Azure Event Hubs: 5.18.4
  • Azure Storage Blob: 12.26.1
  • PostgreSQL Driver: 42.7.3
  • SpringDoc OpenAPI: 2.5.0
  • Lombok: 1.18.32

2.2 데이터베이스

  • DBMS: PostgreSQL 16.4
  • Host: 4.230.159.143:5432
  • Database: notificationdb
  • User: hgzerouser

2.3 메시지 큐

  • Azure Event Hubs: 이벤트 기반 알림 처리
  • Consumer Group: notification-service
  • Checkpoint Store: Azure Blob Storage

2.4 이메일 발송

  • SMTP: Gmail SMTP 또는 사용자 정의 SMTP
  • Port: 587 (TLS)
  • Template Engine: Thymeleaf

3. 구현 내용

3.1 패키지 구조

notification/
└── src/main/java/com/unicorn/hgzero/notification/
    ├── NotificationApplication.java           # Spring Boot 메인 클래스
    │
    ├── domain/                                 # Domain Layer
    │   ├── Notification.java                   # 알림 Entity
    │   ├── NotificationRecipient.java          # 수신자 Entity
    │   └── NotificationSetting.java            # 알림 설정 Entity
    │
    ├── repository/                             # Data Access Layer
    │   ├── NotificationRepository.java
    │   ├── NotificationRecipientRepository.java
    │   └── NotificationSettingRepository.java
    │
    ├── service/                                # Business Logic Layer
    │   ├── NotificationService.java            # 알림 비즈니스 로직
    │   ├── EmailTemplateService.java           # 템플릿 렌더링
    │   └── EmailClient.java                    # 이메일 발송
    │
    ├── controller/                             # Presentation Layer
    │   ├── NotificationController.java         # 알림 조회 API
    │   └── NotificationSettingsController.java # 설정 API
    │
    ├── event/                                  # Event Handler Layer
    │   ├── EventHandler.java                   # Event Hub 핸들러
    │   ├── event/
    │   │   ├── MeetingCreatedEvent.java        # 회의 생성 이벤트 DTO
    │   │   └── TodoAssignedEvent.java          # Todo 할당 이벤트 DTO
    │   └── processor/
    │       └── EventProcessorService.java      # Processor 라이프사이클
    │
    ├── dto/                                    # Data Transfer Objects
    │   ├── request/
    │   │   └── UpdateSettingsRequest.java
    │   └── response/
    │       ├── NotificationResponse.java
    │       ├── NotificationListResponse.java
    │       └── SettingsResponse.java
    │
    ├── config/                                 # Configuration Layer
    │   ├── EventHubConfig.java                 # Event Hub 설정
    │   ├── BlobStorageConfig.java              # Blob Storage 설정
    │   ├── RetryConfig.java                    # 재시도 정책
    │   ├── SecurityConfig.java                 # Spring Security
    │   ├── SwaggerConfig.java                  # Swagger 설정
    │   └── EmailConfig.java                    # Email 설정
    │
    └── exception/                              # Exception Handling
        └── (향후 추가 예정)

3.2 구현된 주요 클래스

3.2.1 Domain Layer (Entity)

Notification.java (notification/src/main/java/com/unicorn/hgzero/notification/domain/Notification.java:1)

  • 알림 기본 정보 관리
  • 수신자 목록 (OneToMany)
  • 중복 방지를 위한 eventId (unique)
  • 상태 추적 (PENDING, PROCESSING, SENT, FAILED, PARTIAL)

NotificationRecipient.java (notification/src/main/java/com/unicorn/hgzero/notification/domain/NotificationRecipient.java:1)

  • 수신자별 발송 상태 추적
  • 재시도 로직 지원 (retryCount, nextRetryAt)
  • 발송 성공/실패 처리 메서드

NotificationSetting.java (notification/src/main/java/com/unicorn/hgzero/notification/domain/NotificationSetting.java:1)

  • 사용자별 알림 설정
  • 채널 활성화 (email, SMS, push)
  • 알림 유형별 활성화
  • 방해 금지 시간대 (DND)

3.2.2 Repository Layer

NotificationRepository.java (notification/src/main/java/com/unicorn/hgzero/notification/repository/NotificationRepository.java:1)

  • JpaRepository 확장
  • 커스텀 쿼리 메서드:
    • findByEventId() - 중복 체크
    • findByReferenceIdAndReferenceType() - 참조 조회
    • findByStatusIn() - 배치 처리
    • countByStatusAndCreatedAtBetween() - 통계

NotificationRecipientRepository.java (notification/src/main/java/com/unicorn/hgzero/notification/repository/NotificationRecipientRepository.java:1)

  • 수신자 관리
  • 커스텀 쿼리 메서드:
    • findByNotificationId() - 알림별 수신자
    • findByStatusAndNextRetryAtBefore() - 재시도 대상
    • findByRecipientEmail() - 사용자 히스토리

NotificationSettingRepository.java (notification/src/main/java/com/unicorn/hgzero/notification/repository/NotificationSettingRepository.java:1)

  • 알림 설정 관리
  • 커스텀 쿼리 메서드:
    • findByUserId() - 사용자 설정 조회
    • findByEmailEnabledAndInvitationEnabled() - 발송 대상 필터링

3.2.3 Service Layer

NotificationService.java (notification/src/main/java/com/unicorn/hgzero/notification/service/NotificationService.java:1)

  • 핵심 비즈니스 로직
  • 주요 메서드:
    • sendMeetingInvitation() - 회의 초대 알림 발송
    • sendTodoAssignment() - Todo 할당 알림 발송
    • canSendNotification() - 알림 발송 가능 여부 확인
  • 기능:
    • 이벤트 중복 체크 (eventId)
    • 사용자 알림 설정 확인
    • 템플릿 렌더링 및 이메일 발송
    • 수신자별 상태 추적

EmailTemplateService.java (notification/src/main/java/com/unicorn/hgzero/notification/service/EmailTemplateService.java:1)

  • Thymeleaf 템플릿 렌더링
  • 주요 메서드:
    • renderMeetingInvitation() - 회의 초대 템플릿
    • renderTodoAssigned() - Todo 할당 템플릿
    • renderReminder() - 리마인더 템플릿

EmailClient.java (notification/src/main/java/com/unicorn/hgzero/notification/service/EmailClient.java:1)

  • SMTP 이메일 발송
  • 재시도 메커니즘 (@Retryable)
  • Exponential Backoff (5분 → 15분 → 30분)
  • HTML 및 텍스트 이메일 지원

3.2.4 Controller Layer

NotificationController.java (notification/src/main/java/com/unicorn/hgzero/notification/controller/NotificationController.java:1)

  • 알림 조회 API
  • 엔드포인트:
    • GET /notifications - 알림 목록 조회
    • GET /notifications/{id} - 특정 알림 조회
    • GET /notifications/statistics - 통계 조회

NotificationSettingsController.java (notification/src/main/java/com/unicorn/hgzero/notification/controller/NotificationSettingsController.java:1)

  • 알림 설정 API
  • 엔드포인트:
    • GET /notifications/settings - 설정 조회
    • PUT /notifications/settings - 설정 업데이트

3.2.5 Event Handler Layer

EventHandler.java (notification/src/main/java/com/unicorn/hgzero/notification/event/EventHandler.java:1)

  • Consumer 구현
  • 이벤트 수신 및 처리
  • 토픽별 라우팅 (meeting, todo)
  • 이벤트 유형별 처리 (MeetingCreated, TodoAssigned)
  • Checkpoint 업데이트

EventProcessorService.java (notification/src/main/java/com/unicorn/hgzero/notification/event/processor/EventProcessorService.java:1)

  • EventProcessorClient 라이프사이클 관리
  • @PostConstruct - 시작
  • @PreDestroy - 종료
  • @Retryable - 재시도 지원

3.2.6 Config Layer

EventHubConfig.java (notification/src/main/java/com/unicorn/hgzero/notification/config/EventHubConfig.java:1)

  • EventProcessorClient Bean 생성
  • BlobCheckpointStore 설정
  • 오류 핸들러 등록

BlobStorageConfig.java (notification/src/main/java/com/unicorn/hgzero/notification/config/BlobStorageConfig.java:1)

  • Blob Container Async Client
  • Checkpoint 저장소 연결

RetryConfig.java (notification/src/main/java/com/unicorn/hgzero/notification/config/RetryConfig.java:1)

  • @EnableRetry
  • RetryTemplate 구성
  • Exponential Backoff Policy

SecurityConfig.java (notification/src/main/java/com/unicorn/hgzero/notification/config/SecurityConfig.java:1)

  • Spring Security 설정
  • CORS 설정
  • Stateless 세션 관리

SwaggerConfig.java (notification/src/main/java/com/unicorn/hgzero/notification/config/SwaggerConfig.java:1)

  • OpenAPI 3.0 설정
  • Swagger UI 구성

EmailConfig.java (notification/src/main/java/com/unicorn/hgzero/notification/config/EmailConfig.java:1)

  • JavaMailSender Bean
  • SMTP 설정

4. 데이터베이스 설계

4.1 테이블 구조

notifications (알림 테이블)

CREATE TABLE notifications (
    notification_id VARCHAR(36) PRIMARY KEY,
    event_id VARCHAR(100) UNIQUE NOT NULL,
    reference_id VARCHAR(36) NOT NULL,
    reference_type VARCHAR(20) NOT NULL,
    notification_type VARCHAR(30) NOT NULL,
    title VARCHAR(500) NOT NULL,
    message TEXT,
    status VARCHAR(20) NOT NULL,
    channel VARCHAR(20) NOT NULL,
    sent_count INTEGER NOT NULL DEFAULT 0,
    failed_count INTEGER NOT NULL DEFAULT 0,
    created_at TIMESTAMP NOT NULL,
    sent_at TIMESTAMP
);

CREATE INDEX idx_notification_reference ON notifications(reference_id, reference_type);
CREATE INDEX idx_notification_created_at ON notifications(created_at);

notification_recipients (수신자 테이블)

CREATE TABLE notification_recipients (
    recipient_id VARCHAR(36) PRIMARY KEY,
    notification_id VARCHAR(36) NOT NULL,
    recipient_user_id VARCHAR(100) NOT NULL,
    recipient_name VARCHAR(200) NOT NULL,
    recipient_email VARCHAR(320) NOT NULL,
    status VARCHAR(20) NOT NULL,
    retry_count INTEGER NOT NULL DEFAULT 0,
    sent_at TIMESTAMP,
    error_message VARCHAR(1000),
    next_retry_at TIMESTAMP,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP,
    FOREIGN KEY (notification_id) REFERENCES notifications(notification_id)
);

CREATE INDEX idx_recipient_notification ON notification_recipients(notification_id);
CREATE INDEX idx_recipient_email ON notification_recipients(recipient_email);
CREATE INDEX idx_recipient_status ON notification_recipients(status);

notification_settings (알림 설정 테이블)

CREATE TABLE notification_settings (
    user_id VARCHAR(100) PRIMARY KEY,
    email_enabled BOOLEAN NOT NULL DEFAULT TRUE,
    sms_enabled BOOLEAN NOT NULL DEFAULT FALSE,
    push_enabled BOOLEAN NOT NULL DEFAULT FALSE,
    invitation_enabled BOOLEAN NOT NULL DEFAULT TRUE,
    todo_assigned_enabled BOOLEAN NOT NULL DEFAULT TRUE,
    todo_reminder_enabled BOOLEAN NOT NULL DEFAULT TRUE,
    meeting_reminder_enabled BOOLEAN NOT NULL DEFAULT TRUE,
    minutes_updated_enabled BOOLEAN NOT NULL DEFAULT TRUE,
    todo_completed_enabled BOOLEAN NOT NULL DEFAULT FALSE,
    dnd_enabled BOOLEAN NOT NULL DEFAULT FALSE,
    dnd_start_time TIME,
    dnd_end_time TIME,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP
);

CREATE UNIQUE INDEX idx_setting_user_id ON notification_settings(user_id);

5. API 명세

5.1 알림 조회 API

알림 목록 조회

  • URL: GET /notifications
  • Query Parameters:
    • referenceType: MEETING | TODO (optional)
    • notificationType: INVITATION | TODO_ASSIGNED | ... (optional)
    • status: PENDING | SENT | FAILED | PARTIAL (optional)
    • startDate: ISO DateTime (optional)
    • endDate: ISO DateTime (optional)
  • Response: List

알림 상세 조회

  • URL: GET /notifications/{notificationId}
  • Path Parameter: notificationId (String)
  • Response: NotificationResponse

알림 통계 조회

  • URL: GET /notifications/statistics
  • Query Parameters:
    • startDate: ISO DateTime (required)
    • endDate: ISO DateTime (required)
  • Response: { sent, failed, partial, total }

5.2 알림 설정 API

알림 설정 조회

  • URL: GET /notifications/settings
  • Query Parameter: userId (String, required)
  • Response: SettingsResponse

알림 설정 업데이트

  • URL: PUT /notifications/settings
  • Query Parameter: userId (String, required)
  • Request Body: UpdateSettingsRequest
  • Response: SettingsResponse

6. 이벤트 처리 흐름

6.1 회의 초대 알림 발송

1. Meeting Service → Event Hub (MeetingCreatedEvent)
2. Event Hub → NotificationService (EventHandler.accept())
3. EventHandler → NotificationService.sendMeetingInvitation()
4. NotificationService:
   - 중복 체크 (eventId)
   - Notification 엔티티 생성
   - 각 참석자별:
     - 알림 설정 확인
     - NotificationRecipient 생성
     - EmailTemplateService.renderMeetingInvitation()
     - EmailClient.sendHtmlEmail()
     - 상태 업데이트 (SENT/FAILED)
   - Notification 상태 업데이트 (SENT/PARTIAL/FAILED)
5. EventHandler → eventContext.updateCheckpoint()

6.2 Todo 할당 알림 발송

1. Meeting/AI Service → Event Hub (TodoAssignedEvent)
2. Event Hub → NotificationService (EventHandler.accept())
3. EventHandler → NotificationService.sendTodoAssignment()
4. NotificationService:
   - 중복 체크 (eventId)
   - Notification 엔티티 생성
   - 알림 설정 확인 (assignee)
   - NotificationRecipient 생성
   - EmailTemplateService.renderTodoAssigned()
   - EmailClient.sendHtmlEmail()
   - 상태 업데이트 (SENT/FAILED)
5. EventHandler → eventContext.updateCheckpoint()

7. 재시도 메커니즘

7.1 재시도 정책

  • 최대 재시도 횟수: 3회
  • Backoff 전략: Exponential Backoff
  • 초기 대기 시간: 5분 (300,000ms)
  • 최대 대기 시간: 30분 (1,800,000ms)
  • 배수: 2.0

7.2 재시도 시나리오

  1. 이메일 발송 실패 시:

    • EmailClient의 @Retryable 어노테이션 적용
    • 1차 실패 → 5분 후 재시도
    • 2차 실패 → 10분 후 재시도
    • 3차 실패 → 20분 후 재시도
    • 최종 실패 → NotificationRecipient 상태를 FAILED로 변경
  2. Event Processor 시작 실패 시:

    • EventProcessorService의 @Retryable 적용
    • 최대 3번 재시도 (2초, 4초, 8초 대기)

8. 설정 파일

8.1 application.yml

  • 위치: notification/src/main/resources/application.yml
  • 주요 설정:
    • 데이터베이스 연결 (PostgreSQL)
    • Azure Event Hubs 연결
    • Azure Blob Storage 연결
    • SMTP 설정
    • Thymeleaf 템플릿
    • Actuator
    • Logging
    • SpringDoc OpenAPI

8.2 환경 변수 (필수)

# 데이터베이스
DB_PASSWORD=<PostgreSQL 비밀번호>

# Azure Event Hub
AZURE_EVENTHUB_CONNECTION_STRING=<Event Hub 연결 문자열>

# Azure Blob Storage
AZURE_STORAGE_CONNECTION_STRING=<Blob Storage 연결 문자열>

# 이메일
MAIL_USERNAME=<SMTP 사용자명>
MAIL_PASSWORD=<SMTP 비밀번호>

# JWT (향후 추가)
JWT_SECRET=<JWT 비밀 키>

9. 빌드 및 실행

9.1 빌드 방법

IntelliJ IDEA 사용

  1. IntelliJ에서 프로젝트 열기
  2. Gradle 탭에서 notification → Tasks → build → build 더블클릭
  3. 또는 우측 상단 Build → Build Project

커맨드 라인 (Gradle 설치 필요)

# Gradle Wrapper 생성 (프로젝트 루트에서)
gradle wrapper

# 빌드
./gradlew :notification:build

# 빌드 결과 확인
ls notification/build/libs/

9.2 실행 방법

IntelliJ IDEA 사용

  1. NotificationApplication.java 파일 열기
  2. main() 메서드 좌측의 실행 버튼 클릭
  3. 또는 Run → Run 'NotificationApplication'

JAR 파일 실행

java -jar notification/build/libs/notification.jar

9.3 서버 포트

  • 기본 포트: 8084
  • 변경 방법: 환경 변수 SERVER_PORT 설정

10. 테스트

10.1 API 테스트 (Swagger UI)

  1. 서버 실행 후 브라우저에서 접속:

    http://localhost:8084/swagger-ui.html
    
  2. API 엔드포인트 테스트:

    • GET /notifications - 알림 목록 조회
    • GET /notifications/{id} - 특정 알림 조회
    • GET /notifications/settings - 알림 설정 조회
    • PUT /notifications/settings - 알림 설정 업데이트

10.2 이벤트 발행 테스트

  1. Meeting Service에서 MeetingCreatedEvent 발행
  2. Notification Service 로그 확인:
    이벤트 수신 - Topic: meeting, EventType: MeetingCreated
    회의 초대 알림 처리 시작 - MeetingId: xxx, EventId: xxx
    회의 초대 알림 발송 성공 - Email: xxx@xxx.com
    

10.3 Health Check

curl http://localhost:8084/actuator/health

11. 향후 개선 사항

11.1 기능 개선

  1. SMS 발송 지원: 현재 이메일만 지원, SMS 발송 기능 추가
  2. Push 알림 지원: 모바일 Push 알림 기능 추가
  3. 리마인더 스케줄링: 회의 및 Todo 리마인더 자동 발송
  4. 배치 알림 발송: 대량 알림 발송 최적화
  5. 알림 템플릿 관리: 템플릿 동적 관리 및 다국어 지원

11.2 성능 개선

  1. 비동기 이메일 발송: @Async를 사용한 비동기 처리
  2. 캐싱 적용: 알림 설정 캐싱으로 DB 부하 감소
  3. 배치 처리: 대량 수신자 알림의 배치 처리
  4. Connection Pool 최적화: HikariCP 설정 최적화

11.3 모니터링 개선

  1. 메트릭 수집: Prometheus 메트릭 추가
  2. 로그 집계: ELK Stack 연동
  3. 알림 대시보드: Grafana 대시보드 구성
  4. 알림 실패 알람: 발송 실패 시 관리자 알림

12. 문제 해결 가이드

12.1 이메일 발송 실패

증상: 이메일이 발송되지 않음

원인 및 해결방법:

  1. SMTP 인증 정보 확인:

    MAIL_USERNAME=<이메일 주소>
    MAIL_PASSWORD=<앱 비밀번호>
    
  2. Gmail 사용 시 앱 비밀번호 생성:

    • Google 계정 → 보안 → 2단계 인증 활성화
    • 앱 비밀번호 생성
  3. 방화벽 확인:

    • 포트 587 (TLS) 또는 465 (SSL) 개방 확인

12.2 Event Hub 연결 실패

증상: 이벤트를 수신하지 못함

원인 및 해결방법:

  1. 연결 문자열 확인:

    AZURE_EVENTHUB_CONNECTION_STRING=<연결 문자열>
    
  2. Consumer Group 확인:

    azure:
      eventhub:
        consumer-group: notification-service
    
  3. Event Hub 방화벽 설정 확인

12.3 데이터베이스 연결 실패

증상: 애플리케이션 시작 실패

원인 및 해결방법:

  1. PostgreSQL 서버 상태 확인

  2. 연결 정보 확인:

    datasource:
      url: jdbc:postgresql://4.230.159.143:5432/notificationdb
      username: hgzerouser
      password: ${DB_PASSWORD}
    
  3. 방화벽 확인: 포트 5432 개방 확인


13. 참고 자료

13.1 문서

13.2 외부 라이브러리


작성일: 2025-10-23 작성자: 준호 (Backend Developer) 버전: 1.0