사용자 참여도 향상 도메인별 이벤트 설계
🏗 이벤트 드리븐 아키텍처 개요
사용자 참여도 향상 시스템은 이벤트 드리븐 아키텍처(Event-Driven Architecture)를 기반으로 설계되어, 각 도메인에서 발생하는 비즈니스 이벤트를 통해 사용자 참여도 향상 메시징을 트리거합니다.
🔄 이벤트 플로우 구조 (Cloud Run Zero Scaling 최적화)
🛌 Sleep 도메인 이벤트
Sleep 도메인은 수면 데이터 분석 및 RTIB 처방과 관련된 참여도 향상 메시징 이벤트를 담당합니다.
크로스 도메인 이벤트
1. RTIB 전날 알림 트리거
export interface RtibPreNotificationTriggeredCrossEvent extends BaseCrossDomainEvent<{
userId: string;
userCycleId: string;
currentDayIndex: number; // 현재 치료 일차
nextRtibDayIndex: number; // 내일 rTIB 계산 예정 일차 (개별적)
rtibType: 'first' | 'subsequent'; // 첫 rTIB 인지 후속 rTIB 인지
sleepLogCount: number; // 현재까지의 유효 수면기록 개수
requiredSleepLogs: number; // 필요한 수면기록 개수 (첫 rTIB: 4개, 후속: 5개)
isEligible: boolean; // 조건 충족 여부
rtibSchedule: {
firstRtibDay?: number; // 첫 rTIB 제공일 (후속 rTIB인 경우)
daysSinceFirstRtib?: number; // 첫 rTIB 이후 경과 일수
};
}> {
type: 'sleep.rtib.pre_notification.triggered';
}
발송 조건: 사용자별 개별 rTIB 스케줄 + 수면기록 조건 충족
발송 시점: 각 사용자의 rTIB 계산 전날 (개별적, 8~∞일차)
트리거 방식: GCP Cloud Scheduler가 매일 오전 9시에 전체 사용자 개별 체크
연관 Timeline: RTIB 나오기 전날 알림 (사용자별 동적 스케줄)
2. 수면기록 부족 리마인드
export interface SleepLogReminderInsufficientDataCrossEvent extends BaseCrossDomainEvent<{
userId: string;
userCycleId: string;
dayIndex: number; // 현재 치료 일차 (8~21일차)
currentLogCount: number; // 현재 유효 수면기록 개수 (0~3개)
requiredLogCount: number; // 필요한 수면기록 개수 (4개)
daysSinceStart: number; // 치료 시작 후 경과 일수
reminderCount: number; // 해당 사용자의 수면기록 부족 리마인드 발송 횟수
maxReminderPeriod: number; // 최대 리마인드 기간 (21일차까지)
reminderType: 'insufficient_data_for_first_rtib';
}> {
type: 'sleep.sleep_log.reminder.insufficient_data';
}
발송 조건: 8~21일차 사용자 중 첫 rTIB 미수령 + 유효 수면기록 4개 미만
트리거 방식: GCP Cloud Scheduler가 매일 오후 2시에 첫 rTIB 미수령 사용자 체크
연관 Timeline: 수면기록 부족 리마인드 (첫 rTIB 계산을 위한)
3. RTIB 처방 알림
export interface RtibPrescriptionNotificationSentCrossEvent extends BaseCrossDomainEvent<{
userId: string;
userCycleId: string;
dayIndex: number;
rtibValue: number; // 처방된 RTIB 값 (분 단위)
previousRtibValue?: number; // 이전 RTIB 값 (변경된 경우)
prescriptionType: 'initial' | 'updated';
sentAt: string;
}> {
type: 'sleep.rtib.prescription.notification.sent';
}
발송 조건: RTIB 계산 완료 후
발송 시점: 처방일 오전
연관 Timeline: RTIB 처방 알림
👤 User 도메인 이벤트
User 도메인은 사용자 생명주기 및 치료 과정과 관련된 참여도 향상 메시징 이벤트를 담당합니다.
크로스 도메인 이벤트
1. 처방 안내 알림
export interface PrescriptionGuidanceNotificationSentCrossEvent extends BaseCrossDomainEvent<{
userId: string;
userCycleId: string;
phoneNumber: string; // 솔닥에서 제공받은 전화번호
hospitalName: string; // 처방 병원명
prescriptionDate: string; // 처방 날짜
sentAt: string;
notificationChannel: 'alimtalk' | 'sms';
}> {
type: 'user.prescription.guidance.notification.sent';
}
발송 조건: 솔닥 결제 완료 시점
연관 Timeline: 최초 가입 시 처방 안내
2. 회원가입 완료 알림
export interface RegistrationCompletionNotificationSentCrossEvent extends BaseCrossDomainEvent<{
userId: string;
userCycleId: string;
userName: string;
registeredAt: string;
firstCrmCallScheduled: boolean; // 첫 CRM call 예약 여부
estimatedCallDate: string; // 예상 통화 일정 (영업일 기준 2일 이내)
sentAt: string;
}> {
type: 'user.registration.completion.notification.sent';
}
발송 조건: 슬립큐 앱 회원가입 완료
연관 Timeline: 회원가입 완료 알림
3. 치료 종료 경고 알림
export interface TreatmentEndWarningNotificationSentCrossEvent extends BaseCrossDomainEvent<{
userId: string;
userCycleId: string;
dayIndex: number; // 36일차
daysRemaining: number; // 남은 치료 일수 (7일)
treatmentEndDate: string; // 치료 종료 예정일
sentAt: string;
}> {
type: 'user.treatment.end.warning.notification.sent';
}
발송 조건: 치료 36일차
연관 Timeline: 치료 종료 7일 전 경고
4. 치료 완료 축하 알림
export interface TreatmentCompletionNotificationSentCrossEvent extends BaseCrossDomainEvent<{
userId: string;
userCycleId: string;
completedAt: string; // 치료 완료 일시
totalTreatmentDays: number; // 실제 치료 일수
finalQuestionnaireCompleted: boolean; // 최종 설문 완료 여부
sentAt: string;
}> {
type: 'user.treatment.completion.notification.sent';
}
발송 조건: 모든 치료 과정 완료
연관 Timeline: 치료 완료 축하 메시지
5. 미접속 사용자 리마인드
export interface InactivityReminderTriggeredCrossEvent extends BaseCrossDomainEvent<{
userId: string;
userCycleId: string;
dayIndex: number; // 사용자의 현재 치료 일차
lastAccessDate: string; // 마지막 접속 일시
daysSinceLastAccess: number; // 마지막 접속 후 경과 일수
reminderCount: number; // 해당 사용자의 미접속 리마인드 발송 횟수
userCycleStatus: string; // UserCycle 상태 확인용
}> {
type: 'user.inactivity.reminder.triggered';
}
발송 조건: 5일 이상 미접속 + 활성 UserCycle
발송 시점: 5일차 + 7n일차
트리거 방식: GCP Cloud Scheduler가 매일 오후 2시에 사용자 접속 이력 체크
연관 Timeline: 5일 이상 미접속 리마인드
📋 Questionnaire 도메인 이벤트
Questionnaire 도메인은 설문 조사와 관련된 모든 참여도 향상 메시징 이벤트를 담당합니다.
크로스 도메인 이벤트
1. 설문 회차 리마인드 트리거
export interface QuestionnaireRoundReminderTriggeredCrossEvent extends BaseCrossDomainEvent<{
userId: string;
userCycleId: string;
roundNumber: number; // 2, 3, 4(최종)
dayIndex: number; // 15, 29, 42+ 일차
questionnaireTypes: string[]; // 리마인드 대상 설문 타입들
reminderType: 'scheduled' | 'overdue'; // 예정된 알림인지 연체된 알림인지
triggeredAt: string;
}> {
type: 'questionnaire.questionnaire.round.reminder.triggered';
}
발송 조건: 설문 회차 시작일
발송 시점: 15일차 (2번째), 29일차 (3번째) 오전 10시
트리거 방식: GCP Cloud Scheduler가 매일 오전 10시에 설문 리마인드 대상자 체크
2. 설문 회차 리마인드 발송
export interface QuestionnaireRoundReminderSentCrossEvent extends BaseCrossDomainEvent<{
userId: string;
userCycleId: string;
roundNumber: number; // 2 또는 3
dayIndex: number; // 15일차 또는 29일차
sentAt: string;
notificationChannel: string; // 'alimtalk' | 'sms'
questionnaireTypes: string[]; // 안내된 설문 타입들
messageVariant: 'standard' | 'urgent'; // 일반 메시지인지 긴급 메시지인지
}> {
type: 'questionnaire.questionnaire.round.reminder.sent';
}
연관 Timeline: 2번째, 3번째 설문 리마인드
3. 최종 설문 리마인드
export interface FinalQuestionnaireReminderSentCrossEvent extends BaseCrossDomainEvent<{
userId: string;
userCycleId: string;
dayIndex: number; // 43 + 7n
weeksSinceTreatmentEnd: number; // 치료 종료 후 경과 주차
reminderCount: number; // 리마인드 발송 횟수
sentAt: string;
notificationChannel: string; // 'alimtalk' | 'sms'
finalQuestionnaireTypes: string[]; // 최종 완료 필요한 설문 타입들
remainingTypes: string[]; // 아직 완료하지 않은 설문 타입들
maxReminders: number; // 최대 리마인드 횟수
}> {
type: 'questionnaire.final.questionnaire.reminder.sent';
}
발송 조건: 최종 설문 미완료
발송 시점: 43일차 이후 매주 오전 10시
트리거 방식: GCP Cloud Scheduler가 매주 월요일 오전 10시에 최종 설문 미완료자 체크
최대 발송: 12주
연관 Timeline: 최종 설문 미완료 리마인드
🤖 Agent Treatment Flow 도메인 이벤트
Agent Treatment Flow 도메인은 CRM 상담 관련 참여도 향상 메시징 이벤트를 담당합니다.
크로스 도메인 이벤트
1. CRM Call 리마인드 예약
export interface CrmCallReminderScheduledCrossEvent extends BaseCrossDomainEvent<{
userId: string;
userCycleId: string;
callRound: number; // 1, 2, 3차 통화
scheduledDate: string; // 예약된 통화 날짜
scheduledTime: string; // 예약된 통화 시간
counselorId: string; // 상담원 ID
reminderDate: string; // 리마인드 발송 예정일 (하루 전)
contactNumber: string; // 통화 예정 연락처
}> {
type: 'agent-treatment-flow.crm.call.reminder.scheduled';
}
발송 조건: CRM 상담원이 통화 일정 설정 완료
연관 Timeline: CRM call 예약
2. CRM Call 리마인드 발송
export interface CrmCallReminderSentCrossEvent extends BaseCrossDomainEvent<{
userId: string;
userCycleId: string;
callRound: number; // 2, 3차 통화 (7일차, 21일차)
dayIndex: number; // 6일차 (7일차 리마인드), 20일차 (21일차 리마인드)
callDate: string; // 통화 예정 날짜
callTime: string; // 통화 예정 시간
contactNumber: string; // 통화 연락처
counselorName?: string; // 상담원 이름 (선택사항)
sentAt: string;
notificationChannel: 'alimtalk' | 'sms';
}> {
type: 'agent-treatment-flow.crm.call.reminder.sent';
}
발송 조건: CRM 상담원이 대시보드에서 [알림톡 보내기] 버튼 클릭
발송 시점: 6일차 (7일차 통화 리마인드), 20일차 (21일차 통화 리마인드)
연관 Timeline: CRM call 리마인드
📨 Notification 도메인 이벤트
Notification 도메인은 모든 참여도 향상 메시징의 실제 발송 처리를 담당합니다.
공통 알림 처리 이벤트
1. 알림 발송 성공
export interface NotificationDeliveredCrossEvent extends BaseCrossDomainEvent<{
userId: string;
notificationId: string;
notificationType: string; // 알림 타입 (sleep.rtib.pre_notification 등)
channel: 'alimtalk' | 'sms' | 'push';
deliveredAt: string;
messageId: string; // 외부 서비스 메시지 ID
recipientNumber: string; // 수신자 전화번호 (마스킹)
}> {
type: 'notification.delivered';
}
2. 알림 발송 실패
export interface NotificationFailedCrossEvent extends BaseCrossDomainEvent<{
userId: string;
notificationId: string;
notificationType: string;
channel: 'alimtalk' | 'sms' | 'push';
failedAt: string;
errorCode: string;
errorMessage: string;
retryCount: number;
willRetry: boolean;
fallbackChannel?: 'sms'; // 대체 채널
}> {
type: 'notification.failed';
}
🔄 이벤트 매핑 테이블 (GCP Scheduler + Pub/Sub + n8n 기반)
| Timeline 요구사항 | 담당 도메인 | 스케줄 트리거 | 도메인 이벤트 | n8n 워크플로우 |
|---|---|---|---|---|
| 최초 가입 시 처방 안내 | User | 솔닥 API 호출 | USER_REGISTERED | prescription-guidance-workflow |
| 회원가입 완료 알림 | User | 앱 가입 완료 | USER_CREATED | registration-completion-workflow |
| 2번째 설문 리마인드 | Questionnaire | GCP Scheduler (매일 10시) | QUESTIONNAIRE_ROUND_REMINDER_TRIGGERED | questionnaire-round-2-workflow |
| 3번째 설문 리마인드 | Questionnaire | GCP Scheduler (매일 10시) | QUESTIONNAIRE_ROUND_REMINDER_TRIGGERED | questionnaire-round-3-workflow |
| RTIB 전날 알림 | Sleep | GCP Scheduler (매일 9시) | RTIB_PRE_NOTIFICATION_TRIGGERED | rtib-pre-notification-workflow (사용자별 개별 스케줄) |
| 수면기록 부족 리마인드 | Sleep | GCP Scheduler (매일 14시) | SLEEP_LOG_REMINDER_INSUFFICIENT_DATA | sleep-log-insufficient-workflow (첫 rTIB 위한) |
| 5일 미접속 리마인드 | User | GCP Scheduler (매일 14시) | USER_INACTIVITY_REMINDER_TRIGGERED | user-inactivity-reminder-workflow |
| RTIB 처방 알림 | Sleep | RTIB 계산 완료 시 | RTIB_PRESCRIPTION_NOTIFICATION_SENT | rtib-prescription-workflow |
| CRM call 리마인드 | Agent Treatment Flow | CRM 대시보드 수동 | CRM_CALL_REMINDER_SCHEDULED | crm-call-reminder-workflow |
| 치료 종료 7일 전 경고 | User | GCP Scheduler (매일 10시) | TREATMENT_END_WARNING_NOTIFICATION_SENT | treatment-end-warning-workflow |
| 최종 설문 리마인드 | Questionnaire | GCP Scheduler (매주 월요일 10시) | FINAL_QUESTIONNAIRE_REMINDER_SENT | final-questionnaire-workflow |
| 치료 완료 축하 | User | 최종 설문 완료 시 | TREATMENT_COMPLETION_NOTIFICATION_SENT | treatment-completion-workflow |
스케줄링 방식별 분류
🕒 GCP Scheduler 기반 (정기 실행)
- 매일 09:00: RTIB 전날 알림 체크 (사용자별 개별 rTIB 스케줄 확인)
- 매일 10:00: 설문 리마인드 체크, 치료 종료 경고 체크
- 매일 14:00: 수면기록 부족 사용자 체크 (첫 rTIB 미수령자 대상), 미접속 사용자 체크
- 매주 월요일 10:00: 최종 설문 리마인드 체크
🎯 이벤트 기반 (즉시 실행)
- 솔닥 API 호출: 처방 안내 (결제 완료 시)
- 앱 가입 완료: 회원가입 완료 알림
- RTIB 계산 완료: RTIB 처방 알림
- 최종 설문 완료: 치료 완료 축하
👨💼 수동 트리거
- CRM 대시보드: 상담원이 수동으로 메시지 발송 버튼 클릭
🛡 이벤트 검증 및 에러 처리
이벤트 스키마 검증
- 모든 이벤트는 JSON Schema 기반 검증
- 필수 필드 누락 시 이벤트 처리 중단
- 잘못된 데이터 타입 시 에러 로깅 및 알림
중복 발송 방지
- 이벤트 ID 기반 중복 처리 방지
- 동일한 알림이 24시간 내 중복 발송 시 차단
- 사용자별 알림 발송 이력 관리
실패 처리 및 재시도
- 카카오톡 메시지 발송 실패 시 자동 재시도 (최대 3회)
- 카카오톡 실패 시 SMS 대체 발송
- 모든 실패는 에러 로그 및 모니터링 대시보드에 기록