HealthSync_BE/design/내부 시퀀스 설계서 - Goal Service.txt

206 lines
8.9 KiB
Plaintext

!theme mono
skinparam sequenceArrowThickness 2
skinparam sequenceParticipantBorderThickness 2
skinparam sequenceActorBorderThickness 2
skinparam sequenceGroupBorderThickness 2
title Goal Service 내부 시퀀스 다이어그램 (역설계 - 개발 소스 기반)
participant "GoalController" as GoalCtrl
participant "GoalUseCase" as GoalUC
participant "GoalDomainService" as GoalDomainSvc
participant "MissionProgressDomainService" as ProgressDomainSvc
participant "GoalRepository" as GoalRepo
participant "MissionProgressRepository" as ProgressRepo
participant "IntelligenceServiceAdapter" as IntelAdapter
participant "CacheAdapter" as CacheAdapter
participant "EventPublisherAdapter" as EventAdapter
participant "PostgreSQL" as PostgreSQL
participant "Redis Cache" as Redis
participant "Azure Service Bus" as ServiceBus
== 1. POST /api/goals/missions/select (미션 선택 및 목표 설정) ==
GoalCtrl -> GoalUC: selectMissions(missionSelectionRequest)
note right: {userId, selectedMissionIds}
GoalUC -> GoalDomainSvc: validateMissionSelection(userId, missionIds)
GoalDomainSvc -> GoalDomainSvc: checkMaximumMissions(selectedMissionIds)
note right: 최대 5개 미션 제한 검증
GoalUC -> GoalRepo: findActiveGoalByUserId(userId)
GoalRepo -> PostgreSQL: SELECT * FROM user_mission_goals WHERE user_id = ? AND is_active = true
PostgreSQL -> GoalRepo: 기존 활성 목표 조회
alt 기존 활성 목표가 있는 경우
GoalUC -> GoalDomainSvc: deactivateExistingGoal(existingGoal)
GoalDomainSvc -> GoalRepo: updateGoalStatus(goalId, false)
GoalRepo -> PostgreSQL: UPDATE user_mission_goals SET is_active = false
end
GoalUC -> GoalDomainSvc: createNewGoalWithMissions(userId, selectedMissionIds)
GoalDomainSvc -> GoalDomainSvc: generateGoalId()
GoalDomainSvc -> GoalDomainSvc: createUserMissionEntities(goalId, missionIds)
GoalDomainSvc -> GoalRepo: saveGoalWithMissions(goalEntity, missionEntities)
GoalRepo -> PostgreSQL: BEGIN TRANSACTION
GoalRepo -> PostgreSQL: INSERT INTO user_mission_goals
GoalRepo -> PostgreSQL: INSERT INTO user_missions (batch)
GoalRepo -> PostgreSQL: COMMIT
GoalUC -> CacheAdapter: invalidateActiveMissionsCache(userId)
CacheAdapter -> Redis: DEL goals:active:{userId}
GoalUC -> EventAdapter: publishGoalSetEvent(userId, goalId, selectedMissions)
EventAdapter -> ServiceBus: 목표 설정 이벤트 발행
GoalUC -> GoalCtrl: GoalSetupResponse 반환
note right: {goalId, selectedMissions, message, setupCompletedAt}
== 2. GET /api/goals/missions/active (활성 미션 조회) ==
GoalCtrl -> GoalUC: getActiveMissions(userId)
GoalUC -> CacheAdapter: getCachedActiveMissions(userId)
CacheAdapter -> Redis: GET goals:active:{userId}
Redis -> CacheAdapter: 캐시된 활성 미션 또는 null
alt 캐시 미스인 경우
GoalUC -> GoalRepo: findActiveMissionsByUserId(userId)
GoalRepo -> PostgreSQL: SELECT um.*, mp.* FROM user_missions um LEFT JOIN mission_progress mp ON um.mission_id = mp.mission_id WHERE um.user_id = ? AND um.is_active = true
PostgreSQL -> GoalRepo: 활성 미션 및 진행 상황
GoalUC -> ProgressDomainSvc: calculateMissionProgress(activeMissions)
ProgressDomainSvc -> ProgressDomainSvc: calculateCompletionRate(missions)
ProgressDomainSvc -> ProgressDomainSvc: calculateStreakDays(missions)
ProgressDomainSvc -> ProgressDomainSvc: determineTodayStatus(missions)
GoalUC -> CacheAdapter: cacheActiveMissions(userId, processedMissions)
CacheAdapter -> Redis: SETEX goals:active:{userId} 1800 {data}
note right: 30분 TTL로 캐싱
end
GoalUC -> GoalCtrl: ActiveMissionsResponse 반환
note right: {dailyMissions, totalMissions, todayCompletedCount, completionRate}
== 3. PUT /api/goals/missions/{missionId}/complete (미션 완료 처리) ==
GoalCtrl -> GoalUC: completeMission(missionId, missionCompleteRequest)
note right: {userId, completed, completedAt, notes}
GoalUC -> GoalDomainSvc: validateMissionCompletion(userId, missionId)
GoalDomainSvc -> GoalRepo: findUserMission(userId, missionId)
GoalRepo -> PostgreSQL: SELECT * FROM user_missions WHERE user_id = ? AND mission_id = ? AND is_active = true
PostgreSQL -> GoalRepo: 사용자 미션 정보
GoalUC -> ProgressRepo: findTodayProgress(userId, missionId)
ProgressRepo -> PostgreSQL: SELECT * FROM mission_progress WHERE user_id = ? AND mission_id = ? AND DATE(completed_at) = CURRENT_DATE
PostgreSQL -> ProgressRepo: 오늘의 진행 상황
alt 오늘 이미 완료한 경우
GoalUC -> GoalCtrl: 409 Conflict 응답
note right: "오늘 이미 완료된 미션입니다"
else 새로운 완료 기록
GoalUC -> ProgressDomainSvc: recordMissionCompletion(userId, missionId, completionData)
ProgressDomainSvc -> ProgressDomainSvc: calculateNewStreakDays(previousProgress)
ProgressDomainSvc -> ProgressDomainSvc: calculateEarnedPoints(mission, streakDays)
ProgressDomainSvc -> ProgressRepo: saveMissionProgress(progressEntity)
ProgressRepo -> PostgreSQL: INSERT INTO mission_progress
PostgreSQL -> ProgressRepo: 진행 기록 저장 완료
GoalUC -> ProgressDomainSvc: updateMissionStatistics(userId, missionId)
ProgressDomainSvc -> GoalRepo: updateMissionStats(missionId, newStats)
GoalRepo -> PostgreSQL: UPDATE user_missions SET total_completed_count = ?, current_streak_days = ?
end
GoalUC -> CacheAdapter: invalidateUserCaches(userId)
CacheAdapter -> Redis: DEL goals:active:{userId} goals:history:{userId}
GoalUC -> EventAdapter: publishMissionCompletedEvent(userId, missionId, achievementData)
EventAdapter -> ServiceBus: 미션 완료 이벤트 발행
GoalUC -> GoalCtrl: MissionCompleteResponse 반환
note right: {message, status, achievementMessage, newStreakDays, totalCompletedCount, earnedPoints}
== 4. GET /api/goals/missions/history (미션 달성 이력) ==
GoalCtrl -> GoalUC: getMissionHistory(userId, startDate, endDate, missionIds)
GoalUC -> CacheAdapter: getCachedMissionHistory(userId, period)
CacheAdapter -> Redis: GET goals:history:{userId}:{period}
Redis -> CacheAdapter: 캐시된 이력 또는 null
alt 캐시 미스인 경우
GoalUC -> ProgressRepo: findMissionHistoryByPeriod(userId, startDate, endDate, missionIds)
ProgressRepo -> PostgreSQL: SELECT mp.*, um.mission_title FROM mission_progress mp JOIN user_missions um ON mp.mission_id = um.mission_id WHERE mp.user_id = ? AND mp.completed_at BETWEEN ? AND ?
PostgreSQL -> ProgressRepo: 기간별 미션 이력 데이터
GoalUC -> ProgressDomainSvc: analyzeAchievementStatistics(historyData)
ProgressDomainSvc -> ProgressDomainSvc: calculateAchievementRates(histories)
ProgressDomainSvc -> ProgressDomainSvc: findBestStreak(histories)
ProgressDomainSvc -> ProgressDomainSvc: generateInsights(statistics)
ProgressDomainSvc -> ProgressDomainSvc: prepareChartData(histories)
GoalUC -> CacheAdapter: cacheMissionHistory(userId, period, analysisResult)
CacheAdapter -> Redis: SETEX goals:history:{userId}:{period} 3600 {data}
note right: 1시간 TTL로 캐싱
end
GoalUC -> GoalCtrl: MissionHistoryResponse 반환
note right: {totalAchievementRate, periodAchievementRate, bestStreak, missionStats, chartData, period, insights}
== 5. POST /api/goals/missions/reset (목표 재설정) ==
GoalCtrl -> GoalUC: resetMissions(missionResetRequest)
note right: {userId, reason, currentMissionIds}
GoalUC -> IntelAdapter: requestNewMissionRecommendations(userId, reason)
IntelAdapter -> GoalUC: 새로운 추천 미션 목록
GoalUC -> GoalDomainSvc: deactivateCurrentGoal(userId)
GoalDomainSvc -> GoalRepo: updateGoalStatus(userId, false)
GoalRepo -> PostgreSQL: UPDATE user_mission_goals SET is_active = false
GoalUC -> CacheAdapter: invalidateAllUserCaches(userId)
CacheAdapter -> Redis: DEL goals:active:{userId} goals:history:{userId}*
GoalUC -> EventAdapter: publishGoalResetEvent(userId, reason, newRecommendations)
EventAdapter -> ServiceBus: 목표 재설정 이벤트 발행
GoalUC -> GoalCtrl: MissionResetResponse 반환
note right: {message, newRecommendations, resetCompletedAt}
== 예외 처리 ==
alt 미션 개수 초과
GoalDomainSvc -> GoalUC: MissionLimitExceededException
GoalUC -> GoalCtrl: 400 Bad Request
note right: "미션은 최대 5개까지 선택 가능합니다"
end
alt 미션 권한 없음
GoalDomainSvc -> GoalUC: UnauthorizedMissionException
GoalUC -> GoalCtrl: 403 Forbidden
end
alt 이미 완료된 미션
ProgressDomainSvc -> GoalUC: MissionAlreadyCompletedException
GoalUC -> GoalCtrl: 409 Conflict
end
== 트랜잭션 및 캐싱 ==
note over GoalUC, CacheAdapter
**트랜잭션 관리**
- @Transactional 어노테이션 적용
- 미션 선택/완료 시 원자성 보장
- 예외 발생 시 자동 롤백
**캐싱 전략**
- 활성 미션: 30분 TTL
- 이력 데이터: 1시간 TTL
- 완료/재설정 시 관련 캐시 무효화
- 사용자별 캐시 키 분리
end note