# 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 (알림 테이블) ```sql 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 (수신자 테이블) ```sql 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 (알림 설정 테이블) ```sql 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 환경 변수 (필수) ```bash # 데이터베이스 DB_PASSWORD= # Azure Event Hub AZURE_EVENTHUB_CONNECTION_STRING= # Azure Blob Storage AZURE_STORAGE_CONNECTION_STRING= # 이메일 MAIL_USERNAME= MAIL_PASSWORD= # JWT (향후 추가) JWT_SECRET= ``` --- ## 9. 빌드 및 실행 ### 9.1 빌드 방법 #### IntelliJ IDEA 사용 1. IntelliJ에서 프로젝트 열기 2. Gradle 탭에서 notification → Tasks → build → build 더블클릭 3. 또는 우측 상단 Build → Build Project #### 커맨드 라인 (Gradle 설치 필요) ```bash # 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 파일 실행 ```bash 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 ```bash 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 인증 정보 확인: ```bash MAIL_USERNAME=<이메일 주소> MAIL_PASSWORD=<앱 비밀번호> ``` 2. Gmail 사용 시 앱 비밀번호 생성: - Google 계정 → 보안 → 2단계 인증 활성화 - 앱 비밀번호 생성 3. 방화벽 확인: - 포트 587 (TLS) 또는 465 (SSL) 개방 확인 ### 12.2 Event Hub 연결 실패 **증상**: 이벤트를 수신하지 못함 **원인 및 해결방법**: 1. 연결 문자열 확인: ```bash AZURE_EVENTHUB_CONNECTION_STRING=<연결 문자열> ``` 2. Consumer Group 확인: ```yaml azure: eventhub: consumer-group: notification-service ``` 3. Event Hub 방화벽 설정 확인 ### 12.3 데이터베이스 연결 실패 **증상**: 애플리케이션 시작 실패 **원인 및 해결방법**: 1. PostgreSQL 서버 상태 확인 2. 연결 정보 확인: ```yaml datasource: url: jdbc:postgresql://4.230.159.143:5432/notificationdb username: hgzerouser password: ${DB_PASSWORD} ``` 3. 방화벽 확인: 포트 5432 개방 확인 --- ## 13. 참고 자료 ### 13.1 문서 - [백엔드개발가이드](claude/dev-backend.md) - [패키지구조도](develop/dev/package-structure-notification.md) - [API설계서](design/backend/api/API설계서.md) - [데이터베이스설치결과서](develop/database/exec/db-exec-dev.md) ### 13.2 외부 라이브러리 - [Spring Boot Documentation](https://spring.io/projects/spring-boot) - [Azure Event Hubs Java SDK](https://learn.microsoft.com/en-us/azure/event-hubs/event-hubs-java-get-started-send) - [Spring Retry](https://github.com/spring-projects/spring-retry) - [Thymeleaf](https://www.thymeleaf.org/) --- **작성일**: 2025-10-23 **작성자**: 준호 (Backend Developer) **버전**: 1.0