!theme mono skinparam sequenceArrowThickness 2 skinparam sequenceParticipantBorderThickness 2 skinparam sequenceActorBorderThickness 2 skinparam sequenceGroupBorderThickness 2 title Health Service 내부 시퀀스 다이어그램 (역설계 - 정상치 기준 비교 포함) participant "HealthController" as HealthCtrl participant "CheckupSyncUseCase" as SyncUC participant "CheckupQueryUseCase" as QueryUC participant "FileUploadUseCase" as FileUC participant "HealthProfileDomainService" as HealthDomainSvc participant "CheckupAnalysisDomainService" as AnalysisDomainSvc participant "NormalRangeDomainService" as NormalDomainSvc participant "HealthRepository" as HealthRepo participant "HealthCheckupRawRepository" as RawRepo participant "NormalRangeRepository" as NormalRepo participant "UserServiceAdapter" as UserAdapter participant "BlobStorageAdapter" as BlobAdapter participant "CacheAdapter" as CacheAdapter participant "EventPublisherAdapter" as EventAdapter participant "PostgreSQL" as PostgreSQL participant "Redis Cache" as Redis participant "Azure Blob Storage" as BlobStorage participant "Azure Service Bus" as ServiceBus == 1. POST /api/health/checkup/sync (건강검진 결과 연동) == HealthCtrl -> SyncUC: syncCheckupData(userId) SyncUC -> UserAdapter: getUserInfo(userId) UserAdapter -> SyncUC: UserInfo 응답 (memberSerialNumber, gender 포함) SyncUC -> RawRepo: findNhisCheckupDataByMemberSerial(memberSerialNumber) RawRepo -> PostgreSQL: SELECT * FROM health_checkup_raw WHERE member_serial_number = ? ORDER BY reference_year DESC PostgreSQL -> RawRepo: 건강보험공단 건강검진 원본 데이터 SyncUC -> HealthRepo: findByMemberSerialNumber(memberSerialNumber) PostgreSQL -> HealthRepo: 기존 가공 데이터 조회 alt 기존 데이터가 없거나 더 최신 원본 데이터가 있는 경우 SyncUC -> NormalRepo: getNormalRangesByGender(userGender) NormalRepo -> PostgreSQL: SELECT * FROM health_normal_ranges WHERE gender_code IN (0, ?) ORDER BY item_code note right: 성별별 + 공통 정상치 기준 조회 PostgreSQL -> NormalRepo: 성별별/공통 정상치 기준 데이터 SyncUC -> AnalysisDomainSvc: transformAndAnalyzeCheckupData(rawData, normalRanges, userGender) AnalysisDomainSvc -> AnalysisDomainSvc: validateAndConvertData(rawData) note right: 건강보험공단 데이터 검증 및 단위 변환 AnalysisDomainSvc -> NormalDomainSvc: compareWithNormalRanges(checkupData, normalRanges) NormalDomainSvc -> NormalDomainSvc: evaluateEachIndicator(indicators, ranges) note right: **각 지표별 정상/주의/위험 판정**\n- BMI, 혈압, 혈당, 콜레스테롤 등\n- 성별별 기준 적용 NormalDomainSvc -> NormalDomainSvc: calculateOverallRiskLevel(indicatorResults) note right: **종합 위험도 레벨 계산**\n- 정상: 80-100점\n- 주의: 60-79점\n- 위험: 0-59점 NormalDomainSvc -> NormalDomainSvc: identifyAbnormalIndicators(indicatorResults) note right: 이상 항목 식별 및 JSON 배열 생성 NormalDomainSvc -> AnalysisDomainSvc: NormalRangeAnalysisResult 반환 AnalysisDomainSvc -> AnalysisDomainSvc: createHealthCheckupEntity(transformedData, analysisResult) AnalysisDomainSvc -> SyncUC: 변환된 HealthCheckupEntity (정상치 비교 결과 포함) SyncUC -> HealthRepo: saveOrUpdateHealthCheckup(healthCheckupEntity) HealthRepo -> PostgreSQL: INSERT/UPDATE health_checkups note right: 정상치 비교 결과도 함께 저장\n- abnormal_indicators: JSON 배열\n- health_score: 0-100점\n- risk_level: normal/caution/danger PostgreSQL -> HealthRepo: 저장 완료 end SyncUC -> CacheAdapter: invalidateUserHealthCache(userId) CacheAdapter -> Redis: DEL health:history:{userId} health:normal:{userId} SyncUC -> EventAdapter: publishHealthDataSyncedEvent(userId, syncResult) EventAdapter -> ServiceBus: 건강데이터 동기화 이벤트 발행 SyncUC -> HealthCtrl: HealthSyncResponse 반환 note right: {syncedRecords, newRecords, updatedRecords, skippedRecords, lastSyncedCheckup, message} == 2. GET /api/health/checkup/history (건강검진 이력 조회) == HealthCtrl -> QueryUC: getHealthCheckupHistory(userId, limit) QueryUC -> CacheAdapter: getCachedHealthHistory(userId) CacheAdapter -> Redis: GET health:history:{userId} Redis -> CacheAdapter: 캐시된 데이터 또는 null alt 캐시 미스인 경우 QueryUC -> HealthRepo: findCheckupHistoryWithDetails(userId, limit) HealthRepo -> PostgreSQL: SELECT * FROM health_checkups WHERE user_id = ? ORDER BY reference_year DESC LIMIT ? PostgreSQL -> HealthRepo: 건강검진 이력 데이터 (정상치 비교 결과 포함) QueryUC -> AnalysisDomainSvc: calculateTrendAnalysis(checkupHistory) AnalysisDomainSvc -> AnalysisDomainSvc: analyzeTrendsByIndicator(historyData) note right: **트렌드 분석**\n- 연도별 변화 추이\n- 개선/악화 항목 식별\n- 평균 건강점수 계산 QueryUC -> CacheAdapter: cacheHealthHistory(userId, analysisResult) CacheAdapter -> Redis: SETEX health:history:{userId} 3600 {data} note right: 1시간 TTL로 캐싱 end QueryUC -> HealthCtrl: HealthHistoryResponse 반환 note right: {checkupHistory, totalRecords, averageHealthScore, trendAnalysis, normalRangeReference} == 3. POST /api/health/checkup/upload (건강검진 파일 업로드) == HealthCtrl -> FileUC: uploadCheckupFile(uploadRequest) note right: {userId, fileName, fileType, fileContent} FileUC -> FileUC: validateFileFormat(fileType, fileContent) note right: 파일 형식 및 크기 검증 (최대 10MB) FileUC -> BlobAdapter: uploadToAzureBlob(fileName, fileContent) BlobAdapter -> BlobStorage: Azure Blob Storage Upload BlobStorage -> BlobAdapter: 업로드 완료 응답 FileUC -> HealthRepo: saveFileMetadata(fileMetadata) HealthRepo -> PostgreSQL: INSERT INTO health_files (user_id, file_name, file_url, file_type, upload_status) PostgreSQL -> HealthRepo: 파일 메타데이터 저장 완료 FileUC -> HealthCtrl: FileUploadResponse 반환 note right: {fileId, uploadUrl, status, message} == 4. GET /api/health/normal-ranges (정상치 기준 조회) == HealthCtrl -> QueryUC: getNormalRangesByGender(genderCode) QueryUC -> CacheAdapter: getCachedNormalRanges(genderCode) CacheAdapter -> Redis: GET normal:ranges:{genderCode} Redis -> CacheAdapter: 캐시된 정상치 기준 또는 null alt 캐시 미스인 경우 QueryUC -> NormalRepo: getNormalRangesByGender(genderCode) NormalRepo -> PostgreSQL: SELECT * FROM health_normal_ranges WHERE gender_code IN (0, ?) ORDER BY item_code PostgreSQL -> NormalRepo: 성별별/공통 정상치 기준 데이터 QueryUC -> CacheAdapter: cacheNormalRanges(genderCode, normalRanges) CacheAdapter -> Redis: SETEX normal:ranges:{genderCode} 86400 {data} note right: 24시간 TTL로 캐싱 (변경 빈도 낮음) end QueryUC -> HealthCtrl: NormalRangeResponse 반환 note right: {normalRanges: [{itemCode, itemName, genderCode, normalMin, normalMax, cautionMin, cautionMax, dangerMin, dangerMax, unit}]} == 예외 처리 (정상치 관련 추가) == alt 정상치 기준 데이터 없음 NormalRepo -> AnalysisDomainSvc: NoNormalRangeFoundException AnalysisDomainSvc -> SyncUC: 기본 정상치 기준 사용 note right: 하드코딩된 기본값으로 대체 end alt 정상치 비교 실패 NormalDomainSvc -> AnalysisDomainSvc: NormalRangeComparisonException AnalysisDomainSvc -> SyncUC: 정상치 비교 없이 기본 저장 note right: 기본 건강검진 데이터만 저장 end alt 파일 업로드 실패 BlobAdapter -> FileUC: BlobStorageException FileUC -> HealthRepo: updateUploadStatus(fileId, "FAILED") FileUC -> HealthCtrl: 500 Internal Server Error end == 캐싱 전략 (정상치 관련 추가) == note over QueryUC, CacheAdapter **확장된 캐싱 전략** - 건강검진 이력 + 정상치 비교: 1시간 캐시 - 정상치 기준 데이터: 24시간 캐시 (변경 빈도 낮음) - 건강 점수 계산 결과: 포함 (이력과 함께) - 트렌드 분석 결과: 포함 (이력과 함께) - 파일 메타데이터: 30분 캐시 - 정상치 기준 업데이트 시 관련 캐시 무효화 end note