206 lines
8.9 KiB
Plaintext
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 |