mirror of
https://github.com/cna-bootcamp/phonebill.git
synced 2026-06-13 12:09:10 +00:00
release
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
title Auth Service - 권한 확인 내부 시퀀스
|
||||
|
||||
participant "API Gateway" as Gateway
|
||||
participant "AuthController" as Controller
|
||||
participant "AuthService" as Service
|
||||
participant "PermissionService" as PermService
|
||||
participant "Redis Cache<<E>>" as Redis
|
||||
participant "UserRepository" as UserRepo
|
||||
participant "Auth DB<<E>>" as AuthDB
|
||||
|
||||
== UFR-AUTH-020: 서비스별 접근 권한 확인 ==
|
||||
|
||||
Gateway -> Controller: GET /check-permission/{serviceType}\nAuthorization: Bearer {accessToken}\nPath: serviceType = "BILL_INQUIRY" | "PRODUCT_CHANGE"
|
||||
activate Controller
|
||||
|
||||
Controller -> Controller: JWT 토큰에서 userId 추출\n(이미 Gateway에서 1차 검증 완료)
|
||||
|
||||
Controller -> Service: checkServicePermission(userId, serviceType)
|
||||
activate Service
|
||||
|
||||
== Cache-First 패턴으로 권한 정보 조회 ==
|
||||
|
||||
Service -> Redis: getUserPermissions(userId)\nKey: user_permissions:{userId}
|
||||
activate Redis
|
||||
|
||||
alt 권한 캐시 Hit
|
||||
Redis --> Service: 권한 정보 반환\n{permissions: [BILL_INQUIRY, PRODUCT_CHANGE, ...]}
|
||||
deactivate Redis
|
||||
note right: 권한 캐시 히트\n- TTL: 4시간\n- 빠른 응답 < 10ms
|
||||
|
||||
else 권한 캐시 Miss
|
||||
Redis --> Service: null (권한 캐시 없음)
|
||||
deactivate Redis
|
||||
|
||||
Service -> UserRepo: getUserPermissions(userId)
|
||||
activate UserRepo
|
||||
|
||||
UserRepo -> AuthDB: SELECT p.permission_code\nFROM user_permissions up\nJOIN permissions p ON up.permission_id = p.id\nWHERE up.user_id = ? AND up.status = 'ACTIVE'
|
||||
activate AuthDB
|
||||
AuthDB --> UserRepo: 권한 목록 반환
|
||||
deactivate AuthDB
|
||||
|
||||
UserRepo --> Service: List<Permission>
|
||||
deactivate UserRepo
|
||||
|
||||
Service -> Redis: cacheUserPermissions\nKey: user_permissions:{userId}\nValue: {permissions}\nTTL: 4시간
|
||||
activate Redis
|
||||
Redis --> Service: 권한 캐싱 완료
|
||||
deactivate Redis
|
||||
end
|
||||
|
||||
Service -> PermService: validateServiceAccess(permissions, serviceType)
|
||||
activate PermService
|
||||
|
||||
PermService -> PermService: 서비스별 권한 매핑 확인
|
||||
note right: 권한 매핑 규칙\n- BILL_INQUIRY: 요금조회 권한\n- PRODUCT_CHANGE: 상품변경 권한\n- 관리자는 모든 권한 보유
|
||||
|
||||
alt 요금조회 서비스 (BILL_INQUIRY)
|
||||
PermService -> PermService: 권한 목록에서\n"BILL_INQUIRY" 또는 "ADMIN" 권한 확인
|
||||
|
||||
alt 권한 있음
|
||||
PermService --> Service: PermissionResult{granted: true, serviceType: "BILL_INQUIRY"}
|
||||
else 권한 없음
|
||||
PermService --> Service: PermissionResult{granted: false, reason: "요금조회 권한이 없습니다"}
|
||||
end
|
||||
|
||||
else 상품변경 서비스 (PRODUCT_CHANGE)
|
||||
PermService -> PermService: 권한 목록에서\n"PRODUCT_CHANGE" 또는 "ADMIN" 권한 확인
|
||||
|
||||
alt 권한 있음
|
||||
PermService --> Service: PermissionResult{granted: true, serviceType: "PRODUCT_CHANGE"}
|
||||
else 권한 없음
|
||||
PermService --> Service: PermissionResult{granted: false, reason: "상품변경 권한이 없습니다"}
|
||||
end
|
||||
|
||||
else 잘못된 서비스 타입
|
||||
PermService --> Service: PermissionResult{granted: false, reason: "올바르지 않은 서비스 타입입니다"}
|
||||
end
|
||||
|
||||
deactivate PermService
|
||||
|
||||
== 권한 확인 결과 처리 ==
|
||||
|
||||
alt 접근 권한 있음
|
||||
Service -> Service: 접근 로그 기록 (비동기)
|
||||
note right: 접근 로그\n- userId, serviceType\n- 접근 시간, IP 주소
|
||||
|
||||
Service --> Controller: PermissionGranted{permission: "granted"}
|
||||
deactivate Service
|
||||
|
||||
Controller --> Gateway: 200 OK\n{permission: "granted", serviceType: serviceType}
|
||||
deactivate Controller
|
||||
|
||||
else 접근 권한 없음
|
||||
Service -> Service: 권한 거부 로그 기록 (비동기)
|
||||
note right: 권한 거부 로그\n- userId, serviceType\n- 거부 사유, 시간
|
||||
|
||||
Service --> Controller: PermissionDenied{reason: "서비스 이용 권한이 없습니다"}
|
||||
deactivate Service
|
||||
|
||||
Controller --> Gateway: 403 Forbidden\n{permission: "denied", reason: "서비스 이용 권한이 없습니다"}
|
||||
deactivate Controller
|
||||
end
|
||||
|
||||
== 권한 캐시 무효화 처리 ==
|
||||
|
||||
note over Service, Redis
|
||||
권한 변경 시 캐시 무효화
|
||||
- 사용자 권한 변경
|
||||
- 권한 정책 변경
|
||||
- 관리자에 의한 권한 갱신
|
||||
end note
|
||||
|
||||
Controller -> Service: invalidateUserPermissions(userId)
|
||||
activate Service
|
||||
|
||||
Service -> Redis: deleteUserPermissions\nKey: user_permissions:{userId}
|
||||
activate Redis
|
||||
Redis --> Service: 캐시 삭제 완료
|
||||
deactivate Redis
|
||||
|
||||
Service -> Redis: deleteUserSession\nKey: user_session:{userId}
|
||||
activate Redis
|
||||
Redis --> Service: 세션 삭제 완료
|
||||
deactivate Redis
|
||||
note right: 권한 변경 시\n세션도 함께 무효화
|
||||
|
||||
Service --> Controller: 권한 캐시 무효화 완료
|
||||
deactivate Service
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,107 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
title Auth Service - 사용자 로그인 내부 시퀀스
|
||||
|
||||
participant "API Gateway" as Gateway
|
||||
participant "AuthController" as Controller
|
||||
participant "AuthService" as Service
|
||||
participant "UserRepository" as UserRepo
|
||||
participant "TokenService" as TokenService
|
||||
participant "Redis Cache<<E>>" as Redis
|
||||
participant "Auth DB<<E>>" as AuthDB
|
||||
|
||||
== UFR-AUTH-010: 사용자 로그인 처리 ==
|
||||
|
||||
Gateway -> Controller: POST /login\n{userId, password, autoLogin}
|
||||
activate Controller
|
||||
|
||||
Controller -> Controller: 입력값 유효성 검사\n(userId, password 필수값 확인)
|
||||
note right: 입력값 검증\n- userId: not null, not empty\n- password: not null, 최소 8자
|
||||
|
||||
alt 입력값 오류
|
||||
Controller --> Gateway: 400 Bad Request\n"입력값을 확인해주세요"
|
||||
else 입력값 정상
|
||||
Controller -> Service: authenticateUser(userId, password)
|
||||
activate Service
|
||||
|
||||
Service -> Service: 로그인 시도 횟수 체크
|
||||
Service -> UserRepo: findUserById(userId)
|
||||
activate UserRepo
|
||||
|
||||
UserRepo -> AuthDB: SELECT user_id, password_hash, salt,\nlocked_until, login_attempt_count\nWHERE user_id = ?
|
||||
activate AuthDB
|
||||
AuthDB --> UserRepo: 사용자 정보 반환
|
||||
deactivate AuthDB
|
||||
|
||||
UserRepo --> Service: User Entity 반환
|
||||
deactivate UserRepo
|
||||
|
||||
alt 사용자 없음
|
||||
Service --> Controller: UserNotFoundException
|
||||
Controller --> Gateway: 401 Unauthorized\n"ID 또는 비밀번호를 확인해주세요"
|
||||
else 계정 잠김 (5회 연속 실패)
|
||||
Service -> Service: 잠금 시간 확인\n(현재시간 < locked_until)
|
||||
Service --> Controller: AccountLockedException
|
||||
Controller --> Gateway: 401 Unauthorized\n"30분간 계정이 잠금되었습니다"
|
||||
else 정상 계정
|
||||
Service -> Service: 비밀번호 검증\nbcrypt.checkpw(password, storedHash)
|
||||
|
||||
alt 비밀번호 불일치
|
||||
Service -> UserRepo: incrementLoginAttempt(userId)
|
||||
activate UserRepo
|
||||
UserRepo -> AuthDB: UPDATE users\nSET login_attempt_count = login_attempt_count + 1\nWHERE user_id = ?
|
||||
AuthDB --> UserRepo: 업데이트 완료
|
||||
deactivate UserRepo
|
||||
|
||||
alt 5회째 실패
|
||||
Service -> UserRepo: lockAccount(userId, 30분)
|
||||
activate UserRepo
|
||||
UserRepo -> AuthDB: UPDATE users\nSET locked_until = NOW() + INTERVAL 30 MINUTE\nWHERE user_id = ?
|
||||
deactivate UserRepo
|
||||
Service --> Controller: AccountLockedException
|
||||
Controller --> Gateway: 401 Unauthorized\n"5회 연속 실패하여 30분간 잠금"
|
||||
else 1~4회 실패
|
||||
Service --> Controller: AuthenticationException
|
||||
Controller --> Gateway: 401 Unauthorized\n"ID 또는 비밀번호를 확인해주세요"
|
||||
end
|
||||
else 비밀번호 일치 (로그인 성공)
|
||||
Service -> UserRepo: resetLoginAttempt(userId)
|
||||
activate UserRepo
|
||||
UserRepo -> AuthDB: UPDATE users\nSET login_attempt_count = 0\nWHERE user_id = ?
|
||||
deactivate UserRepo
|
||||
|
||||
== 토큰 생성 및 세션 처리 ==
|
||||
|
||||
Service -> TokenService: generateAccessToken(userInfo)
|
||||
activate TokenService
|
||||
TokenService -> TokenService: JWT 생성\n(payload: {userId, permissions}\nexpiry: 30분)
|
||||
TokenService --> Service: accessToken
|
||||
deactivate TokenService
|
||||
|
||||
Service -> TokenService: generateRefreshToken(userId)
|
||||
activate TokenService
|
||||
TokenService -> TokenService: JWT 생성\n(payload: {userId}\nexpiry: 24시간 또는 autoLogin 기준)
|
||||
TokenService --> Service: refreshToken
|
||||
deactivate TokenService
|
||||
|
||||
Service -> Redis: setUserSession\nKey: user_session:{userId}\nValue: {userInfo, permissions}\nTTL: autoLogin ? 24시간 : 30분
|
||||
activate Redis
|
||||
Redis --> Service: 세션 저장 완료
|
||||
deactivate Redis
|
||||
|
||||
Service -> UserRepo: saveLoginHistory(userId, ipAddress, loginTime)
|
||||
activate UserRepo
|
||||
UserRepo -> AuthDB: INSERT INTO login_history\n(user_id, login_time, ip_address)
|
||||
note right: 비동기 처리로\n응답 성능에 영향 없음
|
||||
deactivate UserRepo
|
||||
|
||||
Service --> Controller: AuthenticationResult\n{accessToken, refreshToken, userInfo}
|
||||
deactivate Service
|
||||
|
||||
Controller --> Gateway: 200 OK\n{accessToken, refreshToken, userInfo}
|
||||
deactivate Controller
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,147 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
title Auth Service - 토큰 검증 내부 시퀀스
|
||||
|
||||
participant "API Gateway" as Gateway
|
||||
participant "AuthController" as Controller
|
||||
participant "AuthService" as Service
|
||||
participant "TokenService" as TokenService
|
||||
participant "Redis Cache<<E>>" as Redis
|
||||
participant "UserRepository" as UserRepo
|
||||
participant "Auth DB<<E>>" as AuthDB
|
||||
|
||||
== UFR-AUTH-020: 사용자 정보 조회 및 토큰 검증 ==
|
||||
|
||||
Gateway -> Controller: GET /user-info\nAuthorization: Bearer {accessToken}
|
||||
activate Controller
|
||||
|
||||
Controller -> TokenService: validateAccessToken(accessToken)
|
||||
activate TokenService
|
||||
|
||||
TokenService -> TokenService: JWT 토큰 파싱 및 검증\n- 서명 검증\n- 만료 시간 확인\n- 토큰 구조 검증
|
||||
|
||||
alt 토큰 무효 (만료/변조/형식오류)
|
||||
TokenService --> Controller: InvalidTokenException
|
||||
Controller --> Gateway: 401 Unauthorized\n"토큰이 유효하지 않습니다"
|
||||
else 토큰 유효
|
||||
TokenService -> TokenService: 토큰에서 userId 추출
|
||||
TokenService --> Controller: DecodedToken{userId, permissions, exp}
|
||||
deactivate TokenService
|
||||
|
||||
Controller -> Service: getUserInfo(userId)
|
||||
activate Service
|
||||
|
||||
== Cache-Aside 패턴으로 사용자 정보 조회 ==
|
||||
|
||||
Service -> Redis: getUserSession(userId)\nKey: user_session:{userId}
|
||||
activate Redis
|
||||
|
||||
alt 캐시 Hit
|
||||
Redis --> Service: 사용자 세션 데이터 반환\n{userInfo, permissions, lastAccess}
|
||||
deactivate Redis
|
||||
note right: 캐시 히트\n응답 시간 < 50ms
|
||||
|
||||
Service -> Service: 세션 유효성 확인\n(lastAccess 시간 체크)
|
||||
|
||||
else 캐시 Miss (세션 만료 또는 없음)
|
||||
Redis --> Service: null (캐시 데이터 없음)
|
||||
deactivate Redis
|
||||
|
||||
Service -> UserRepo: findUserById(userId)
|
||||
activate UserRepo
|
||||
|
||||
UserRepo -> AuthDB: SELECT user_id, name, permissions, status\nWHERE user_id = ? AND status = 'ACTIVE'
|
||||
activate AuthDB
|
||||
AuthDB --> UserRepo: 사용자 정보 반환
|
||||
deactivate AuthDB
|
||||
|
||||
alt 사용자 없음 또는 비활성
|
||||
UserRepo --> Service: null
|
||||
deactivate UserRepo
|
||||
Service --> Controller: UserNotFoundException
|
||||
Controller --> Gateway: 401 Unauthorized\n"사용자 정보를 찾을 수 없습니다"
|
||||
else 사용자 정보 존재
|
||||
UserRepo --> Service: User Entity
|
||||
deactivate UserRepo
|
||||
|
||||
Service -> Service: UserInfo 및 Permission 매핑
|
||||
|
||||
Service -> Redis: setUserSession\nKey: user_session:{userId}\nValue: {userInfo, permissions, lastAccess}\nTTL: 30분
|
||||
activate Redis
|
||||
Redis --> Service: 세션 재생성 완료
|
||||
deactivate Redis
|
||||
end
|
||||
end
|
||||
|
||||
alt 세션 정보 획득 성공
|
||||
Service -> Service: lastAccess 시간 업데이트
|
||||
Service -> Redis: updateLastAccess\nKey: user_session:{userId}
|
||||
activate Redis
|
||||
Redis --> Service: 업데이트 완료
|
||||
deactivate Redis
|
||||
|
||||
Service --> Controller: UserInfoResponse\n{userInfo, permissions}
|
||||
deactivate Service
|
||||
|
||||
Controller --> Gateway: 200 OK\n{userInfo, permissions}
|
||||
deactivate Controller
|
||||
else 세션 정보 획득 실패
|
||||
Service --> Controller: SessionNotFoundException
|
||||
Controller --> Gateway: 401 Unauthorized\n"세션이 만료되었습니다"
|
||||
end
|
||||
end
|
||||
|
||||
== 토큰 갱신 처리 ==
|
||||
|
||||
note over Gateway, AuthDB
|
||||
토큰 갱신 요청 시 별도 엔드포인트 처리
|
||||
POST /auth/refresh
|
||||
end note
|
||||
|
||||
Gateway -> Controller: POST /refresh\n{refreshToken}
|
||||
activate Controller
|
||||
|
||||
Controller -> TokenService: validateRefreshToken(refreshToken)
|
||||
activate TokenService
|
||||
|
||||
TokenService -> TokenService: Refresh Token 검증\n- JWT 서명 확인\n- 만료 시간 확인\n- 토큰 타입 확인
|
||||
|
||||
alt Refresh Token 무효
|
||||
TokenService --> Controller: InvalidTokenException
|
||||
Controller --> Gateway: 401 Unauthorized\n"토큰 갱신이 필요합니다"
|
||||
else Refresh Token 유효
|
||||
TokenService -> TokenService: userId 추출
|
||||
TokenService --> Controller: userId
|
||||
deactivate TokenService
|
||||
|
||||
Controller -> Service: refreshUserToken(userId)
|
||||
activate Service
|
||||
|
||||
Service -> Redis: getUserSession(userId)
|
||||
activate Redis
|
||||
Redis --> Service: 세션 데이터 확인
|
||||
deactivate Redis
|
||||
|
||||
alt 세션 유효
|
||||
Service -> TokenService: generateAccessToken(userInfo)
|
||||
activate TokenService
|
||||
TokenService --> Service: 새로운 AccessToken (30분)
|
||||
deactivate TokenService
|
||||
|
||||
Service -> Redis: updateUserSession\n새로운 토큰 정보로 세션 업데이트
|
||||
activate Redis
|
||||
Redis --> Service: 세션 업데이트 완료
|
||||
deactivate Redis
|
||||
|
||||
Service --> Controller: TokenRefreshResponse\n{newAccessToken}
|
||||
deactivate Service
|
||||
|
||||
Controller --> Gateway: 200 OK\n{accessToken}
|
||||
deactivate Controller
|
||||
else 세션 무효
|
||||
Service --> Controller: SessionExpiredException
|
||||
Controller --> Gateway: 401 Unauthorized\n"재로그인이 필요합니다"
|
||||
end
|
||||
end
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,150 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
title Bill-Inquiry Service - KOS 연동 내부 시퀀스
|
||||
|
||||
participant "BillInquiryService" as Service
|
||||
participant "KosClientService" as KosClient
|
||||
participant "CircuitBreakerService" as CircuitBreaker
|
||||
participant "RetryService" as RetryService
|
||||
participant "KosAdapterService" as KosAdapter
|
||||
participant "BillRepository" as BillRepo
|
||||
participant "Bill DB<<E>>" as BillDB
|
||||
participant "KOS-Mock Service<<E>>" as KOSMock
|
||||
|
||||
== UFR-BILL-030: KOS 요금조회 서비스 연동 ==
|
||||
|
||||
Service -> KosClient: getBillInfo(lineNumber, inquiryMonth)
|
||||
activate KosClient
|
||||
|
||||
KosClient -> CircuitBreaker: isCallAllowed()
|
||||
activate CircuitBreaker
|
||||
|
||||
alt Circuit Breaker - OPEN 상태 (장애 감지)
|
||||
CircuitBreaker --> KosClient: Circuit Open\n"서비스 일시 장애"
|
||||
deactivate CircuitBreaker
|
||||
|
||||
KosClient -> KosClient: Fallback 처리\n- 최근 캐시 데이터 확인\n- 기본 응답 준비
|
||||
KosClient --> Service: FallbackException\n"일시적으로 서비스 이용이 어렵습니다"
|
||||
note right: Circuit Breaker Open\n- 빠른 실패로 시스템 보호\n- 장애 전파 방지
|
||||
|
||||
else Circuit Breaker - CLOSED/HALF_OPEN 상태
|
||||
CircuitBreaker --> KosClient: Call Allowed
|
||||
deactivate CircuitBreaker
|
||||
|
||||
KosClient -> RetryService: executeWithRetry(kosCall)
|
||||
activate RetryService
|
||||
|
||||
== Retry 패턴 적용 ==
|
||||
|
||||
loop 최대 3회 재시도
|
||||
RetryService -> KosAdapter: callKosBillInquiry(lineNumber, inquiryMonth)
|
||||
activate KosAdapter
|
||||
|
||||
KosAdapter -> KosAdapter: 요청 데이터 변환\n- lineNumber 포맷 검증\n- inquiryMonth 형식 변환\n- 인증 헤더 설정
|
||||
|
||||
== KOS-Mock Service 호출 ==
|
||||
|
||||
KosAdapter -> KOSMock: POST /kos/bill/inquiry\nContent-Type: application/json\n{\n "lineNumber": "01012345678",\n "inquiryMonth": "202412"\n}
|
||||
activate KOSMock
|
||||
note right: KOS-Mock 서비스\n- 실제 KOS 시스템 대신 Mock 응답\n- 타임아웃: 3초\n- 다양한 시나리오 시뮬레이션
|
||||
|
||||
alt KOS-Mock 정상 응답
|
||||
KOSMock --> KosAdapter: 200 OK\n{\n "resultCode": "0000",\n "resultMessage": "성공",\n "data": {\n "productName": "5G 프리미엄",\n "contractInfo": "24개월 약정",\n "billingMonth": "202412",\n "charge": 75000,\n "discountInfo": "가족할인 10000원",\n "usage": {\n "voice": "250분",\n "data": "20GB"\n },\n "estimatedCancellationFee": 120000,\n "deviceInstallment": 35000,\n "billingPaymentInfo": {\n "billingDate": "2024-12-25",\n "paymentStatus": "완료"\n }\n }\n}
|
||||
deactivate KOSMock
|
||||
|
||||
KosAdapter -> KosAdapter: 응답 데이터 변환\n- KOS 응답 → 내부 BillInfo 모델\n- 데이터 유효성 검증\n- Null 안전 처리
|
||||
|
||||
KosAdapter --> RetryService: BillInfo 객체
|
||||
deactivate KosAdapter
|
||||
break 성공 시 재시도 중단
|
||||
|
||||
else KOS-Mock 오류 응답 (4xx, 5xx)
|
||||
KOSMock --> KosAdapter: 오류 응답\n{\n "resultCode": "E001",\n "resultMessage": "회선번호가 존재하지 않습니다"\n}
|
||||
deactivate KOSMock
|
||||
|
||||
KosAdapter -> KosAdapter: 오류 코드별 예외 매핑\n- E001: InvalidLineNumberException\n- E002: DataNotFoundException\n- E999: SystemErrorException
|
||||
|
||||
KosAdapter --> RetryService: KosServiceException
|
||||
deactivate KosAdapter
|
||||
|
||||
else 네트워크 오류 (타임아웃, 연결 실패)
|
||||
KOSMock --> KosAdapter: IOException/TimeoutException
|
||||
deactivate KOSMock
|
||||
|
||||
KosAdapter --> RetryService: NetworkException
|
||||
deactivate KosAdapter
|
||||
|
||||
end
|
||||
|
||||
alt 재시도 가능한 오류 (네트워크, 일시적 오류)
|
||||
RetryService -> RetryService: 재시도 대기\n- 1차: 1초 대기\n- 2차: 2초 대기\n- 3차: 3초 대기
|
||||
note right: Exponential Backoff\n재시도 간격 증가
|
||||
else 재시도 불가능한 오류 (비즈니스 로직 오류)
|
||||
break 재시도 중단
|
||||
end
|
||||
end
|
||||
|
||||
alt 재시도 성공
|
||||
RetryService --> KosClient: BillInfo
|
||||
deactivate RetryService
|
||||
|
||||
KosClient -> CircuitBreaker: recordSuccess()
|
||||
activate CircuitBreaker
|
||||
CircuitBreaker -> CircuitBreaker: 성공 카운트 증가\nCircuit 상태 유지 또는 CLOSED로 변경
|
||||
deactivate CircuitBreaker
|
||||
|
||||
== 연동 이력 저장 ==
|
||||
|
||||
KosClient -> BillRepo: saveKosInquiryHistory(lineNumber, inquiryMonth, "SUCCESS")
|
||||
activate BillRepo
|
||||
BillRepo -> BillDB: INSERT INTO kos_inquiry_history\n(line_number, inquiry_month, request_time, \n response_time, result_code, result_message)
|
||||
activate BillDB
|
||||
note right: 비동기 처리\n- 성능 최적화\n- 연동 추적
|
||||
BillDB --> BillRepo: 이력 저장 완료
|
||||
deactivate BillDB
|
||||
deactivate BillRepo
|
||||
|
||||
KosClient --> Service: BillInfo 반환
|
||||
deactivate KosClient
|
||||
|
||||
else 모든 재시도 실패
|
||||
RetryService --> KosClient: MaxRetryExceededException
|
||||
deactivate RetryService
|
||||
|
||||
KosClient -> CircuitBreaker: recordFailure()
|
||||
activate CircuitBreaker
|
||||
CircuitBreaker -> CircuitBreaker: 실패 카운트 증가\n임계값 초과 시 Circuit OPEN
|
||||
deactivate CircuitBreaker
|
||||
|
||||
KosClient -> BillRepo: saveKosInquiryHistory(lineNumber, inquiryMonth, "FAILURE")
|
||||
activate BillRepo
|
||||
BillRepo -> BillDB: INSERT INTO kos_inquiry_history\n(line_number, inquiry_month, request_time, \n response_time, result_code, result_message, error_detail)
|
||||
deactivate BillRepo
|
||||
|
||||
KosClient --> Service: KosConnectionException\n"KOS 시스템 연동 실패"
|
||||
deactivate KosClient
|
||||
end
|
||||
end
|
||||
|
||||
== Circuit Breaker 상태 관리 ==
|
||||
|
||||
note over CircuitBreaker
|
||||
Circuit Breaker 설정:
|
||||
- 실패 임계값: 5회 연속 실패
|
||||
- 타임아웃: 3초
|
||||
- 반열림 대기시간: 30초
|
||||
- 성공 임계값: 3회 연속 성공 시 복구
|
||||
end note
|
||||
|
||||
== KOS-Mock 서비스 시나리오 ==
|
||||
|
||||
note over KOSMock
|
||||
Mock 응답 시나리오:
|
||||
1. 정상 케이스: 완전한 요금 정보 반환
|
||||
2. 데이터 없음: 해당월 데이터 없음 (E002)
|
||||
3. 잘못된 회선: 존재하지 않는 회선번호 (E001)
|
||||
4. 시스템 오류: 일시적 장애 시뮬레이션 (E999)
|
||||
5. 타임아웃: 응답 지연 시뮬레이션
|
||||
end note
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,166 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
title Bill-Inquiry Service - 요금조회 요청 내부 시퀀스
|
||||
|
||||
participant "API Gateway" as Gateway
|
||||
participant "BillController" as Controller
|
||||
participant "BillInquiryService" as Service
|
||||
participant "BillCacheService" as CacheService
|
||||
participant "BillRepository" as BillRepo
|
||||
participant "KosClientService" as KosClient
|
||||
participant "Redis Cache<<E>>" as Redis
|
||||
participant "Bill DB<<E>>" as BillDB
|
||||
participant "MVNO AP Server<<E>>" as MVNO
|
||||
|
||||
== UFR-BILL-010: 요금조회 메뉴 접근 ==
|
||||
|
||||
Gateway -> Controller: GET /api/bill/menu\nAuthorization: Bearer {accessToken}
|
||||
activate Controller
|
||||
|
||||
Controller -> Controller: 토큰에서 userId, 회선번호 추출
|
||||
|
||||
Controller -> Service: getBillMenuData(userId)
|
||||
activate Service
|
||||
|
||||
Service -> CacheService: getCustomerInfo(userId)
|
||||
activate CacheService
|
||||
|
||||
CacheService -> Redis: GET customer_info:{userId}
|
||||
activate Redis
|
||||
|
||||
alt 고객 정보 캐시 Hit
|
||||
Redis --> CacheService: 고객 정보 반환\n{lineNumber, customerName, serviceStatus}
|
||||
deactivate Redis
|
||||
note right: 캐시 히트\n- TTL: 4시간\n- 빠른 응답
|
||||
else 고객 정보 캐시 Miss
|
||||
Redis --> CacheService: null
|
||||
deactivate Redis
|
||||
|
||||
CacheService -> BillRepo: getCustomerInfo(userId)
|
||||
activate BillRepo
|
||||
BillRepo -> BillDB: SELECT line_number, customer_name, service_status\nFROM customer_info\nWHERE user_id = ?
|
||||
activate BillDB
|
||||
BillDB --> BillRepo: 고객 정보
|
||||
deactivate BillDB
|
||||
BillRepo --> CacheService: CustomerInfo
|
||||
deactivate BillRepo
|
||||
|
||||
CacheService -> Redis: SET customer_info:{userId}\nValue: customerInfo\nTTL: 4시간
|
||||
activate Redis
|
||||
Redis --> CacheService: 캐싱 완료
|
||||
deactivate Redis
|
||||
end
|
||||
|
||||
CacheService --> Service: CustomerInfo{lineNumber, customerName}
|
||||
deactivate CacheService
|
||||
|
||||
Service -> Service: 요금조회 메뉴 데이터 구성\n- 회선번호 표시\n- 조회월 선택 옵션 (최근 12개월)\n- 기본값: 당월
|
||||
|
||||
Service --> Controller: BillMenuResponse\n{lineNumber, availableMonths, currentMonth}
|
||||
deactivate Service
|
||||
|
||||
Controller --> Gateway: 200 OK\n요금조회 메뉴 데이터
|
||||
deactivate Controller
|
||||
|
||||
== UFR-BILL-020: 요금조회 신청 처리 ==
|
||||
|
||||
Gateway -> Controller: POST /api/bill/inquiry\n{lineNumber, inquiryMonth?}\nAuthorization: Bearer {accessToken}
|
||||
activate Controller
|
||||
|
||||
Controller -> Controller: 입력값 검증\n- lineNumber: 필수, 11자리 숫자\n- inquiryMonth: 선택, YYYYMM 형식
|
||||
|
||||
alt 입력값 오류
|
||||
Controller --> Gateway: 400 Bad Request\n"입력값을 확인해주세요"
|
||||
else 입력값 정상
|
||||
Controller -> Service: inquireBill(lineNumber, inquiryMonth, userId)
|
||||
activate Service
|
||||
|
||||
Service -> Service: 조회월 처리\ninquiryMonth가 null이면 현재월로 설정
|
||||
|
||||
== Cache-Aside 패턴으로 요금 정보 조회 ==
|
||||
|
||||
Service -> CacheService: getCachedBillInfo(lineNumber, inquiryMonth)
|
||||
activate CacheService
|
||||
|
||||
CacheService -> Redis: GET bill_info:{lineNumber}:{inquiryMonth}
|
||||
activate Redis
|
||||
|
||||
alt 요금 정보 캐시 Hit (1시간 TTL 내)
|
||||
Redis --> CacheService: 캐시된 요금 정보\n{productName, billingMonth, charge, discount, usage...}
|
||||
deactivate Redis
|
||||
CacheService --> Service: BillInfo (캐시된 데이터)
|
||||
deactivate CacheService
|
||||
note right: 캐시 히트\n- KOS 호출 없이 즉시 응답\n- 응답 시간 < 100ms
|
||||
|
||||
Service -> Service: 캐시 데이터 유효성 확인\n(생성 시간, 데이터 완전성 체크)
|
||||
|
||||
else 요금 정보 캐시 Miss
|
||||
Redis --> CacheService: null
|
||||
deactivate Redis
|
||||
CacheService --> Service: null (캐시 데이터 없음)
|
||||
deactivate CacheService
|
||||
|
||||
== KOS 연동을 통한 요금 정보 조회 ==
|
||||
|
||||
Service -> KosClient: getBillInfo(lineNumber, inquiryMonth)
|
||||
activate KosClient
|
||||
note right: 다음 단계에서 상세 처리\n(bill-KOS연동.puml 참조)
|
||||
KosClient --> Service: BillInfo 또는 Exception
|
||||
deactivate KosClient
|
||||
|
||||
alt KOS 연동 성공
|
||||
Service -> CacheService: cacheBillInfo(lineNumber, inquiryMonth, billInfo)
|
||||
activate CacheService
|
||||
CacheService -> Redis: SET bill_info:{lineNumber}:{inquiryMonth}\nValue: billInfo\nTTL: 1시간
|
||||
activate Redis
|
||||
Redis --> CacheService: 캐싱 완료
|
||||
deactivate Redis
|
||||
deactivate CacheService
|
||||
|
||||
else KOS 연동 실패
|
||||
Service -> Service: 오류 로그 기록
|
||||
Service --> Controller: BillInquiryException\n"요금 조회에 실패하였습니다"
|
||||
Controller --> Gateway: 500 Internal Server Error
|
||||
Gateway --> "Client": 오류 메시지 표시
|
||||
end
|
||||
end
|
||||
|
||||
alt 요금 정보 획득 성공
|
||||
== 요금조회 결과 전송 (UFR-BILL-040) ==
|
||||
|
||||
Service -> MVNO: sendBillResult(billInfo)
|
||||
activate MVNO
|
||||
MVNO --> Service: 전송 완료 확인
|
||||
deactivate MVNO
|
||||
|
||||
Service -> Service: 요금조회 이력 저장 준비\n{userId, lineNumber, inquiryMonth, resultStatus}
|
||||
|
||||
Service -> BillRepo: saveBillInquiryHistory(historyData)
|
||||
activate BillRepo
|
||||
note right: 비동기 처리\n응답 성능에 영향 없음
|
||||
BillRepo -> BillDB: INSERT INTO bill_inquiry_history\n(user_id, line_number, inquiry_month, \n inquiry_time, result_status)
|
||||
activate BillDB
|
||||
BillDB --> BillRepo: 이력 저장 완료
|
||||
deactivate BillDB
|
||||
deactivate BillRepo
|
||||
|
||||
Service --> Controller: BillInquiryResult\n{productName, billingMonth, charge, discount, usage, \n estimatedCancellationFee, deviceInstallment, billingInfo}
|
||||
deactivate Service
|
||||
|
||||
Controller --> Gateway: 200 OK\n요금조회 결과 데이터
|
||||
deactivate Controller
|
||||
end
|
||||
end
|
||||
|
||||
== 오류 처리 및 로깅 ==
|
||||
|
||||
note over Controller, BillDB
|
||||
각 단계별 오류 처리:
|
||||
1. 입력값 검증 오류 → 400 Bad Request
|
||||
2. 권한 없음 → 403 Forbidden
|
||||
3. KOS 연동 오류 → Circuit Breaker 적용
|
||||
4. 캐시 장애 → KOS 직접 호출로 우회
|
||||
5. DB 오류 → 트랜잭션 롤백 후 재시도
|
||||
end note
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,170 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
title KOS-Mock Service - 상품변경 내부 시퀀스
|
||||
|
||||
participant "Product-Change Service<<E>>" as ProductService
|
||||
participant "KosMockController" as Controller
|
||||
participant "KosMockService" as Service
|
||||
participant "ProductDataService" as ProductDataService
|
||||
participant "ProductValidationService" as ValidationService
|
||||
participant "MockScenarioService" as ScenarioService
|
||||
participant "MockDataRepository" as MockRepo
|
||||
participant "Mock Data Store<<E>>" as MockDB
|
||||
|
||||
== KOS-Mock 상품변경 시뮬레이션 ==
|
||||
|
||||
ProductService -> Controller: POST /kos/product/change\nContent-Type: application/json\n{\n "transactionId": "TXN20241201001",\n "lineNumber": "01012345678",\n "currentProductCode": "PROD001",\n "newProductCode": "PROD002",\n "changeReason": "고객 요청",\n "effectiveDate": "20241201"\n}
|
||||
activate Controller
|
||||
|
||||
Controller -> Controller: 요청 데이터 유효성 검사\n- transactionId: 필수, 중복 체크\n- lineNumber: 11자리 숫자 형식\n- productCode: 상품코드 형식\n- effectiveDate: YYYYMMDD 형식
|
||||
|
||||
alt 입력값 오류
|
||||
Controller --> ProductService: 400 Bad Request\n{\n "resultCode": "E400",\n "resultMessage": "요청 데이터가 올바르지 않습니다"\n}
|
||||
else 입력값 정상
|
||||
Controller -> Service: processProductChange(changeRequest)
|
||||
activate Service
|
||||
|
||||
== Mock 시나리오 결정 ==
|
||||
|
||||
Service -> ScenarioService: determineProductChangeScenario(lineNumber, changeRequest)
|
||||
activate ScenarioService
|
||||
|
||||
ScenarioService -> ScenarioService: 회선번호 및 상품코드 기반 시나리오 결정
|
||||
note right: Mock 상품변경 시나리오\n- 01012345678: 정상 변경\n- 01012345679: 변경 불가\n- 01012345680: 시스템 오류\n- 01012345681: 잔액 부족\n- PROD001→PROD999: 호환 불가\n- 기타: 정상 처리
|
||||
|
||||
alt 정상 변경 케이스
|
||||
ScenarioService -> ScenarioService: 상품 호환성 확인
|
||||
alt 호환 가능한 상품 변경
|
||||
ScenarioService --> Service: MockScenario{type: "SUCCESS", delay: 2000ms}
|
||||
else 호환 불가능한 상품 변경 (PROD001→PROD999)
|
||||
ScenarioService --> Service: MockScenario{type: "INCOMPATIBLE", delay: 1000ms}
|
||||
end
|
||||
|
||||
else 변경 불가 케이스 (01012345679)
|
||||
ScenarioService --> Service: MockScenario{type: "NOT_ALLOWED", delay: 1500ms}
|
||||
|
||||
else 잔액 부족 케이스 (01012345681)
|
||||
ScenarioService --> Service: MockScenario{type: "INSUFFICIENT_BALANCE", delay: 1200ms}
|
||||
|
||||
else 시스템 오류 케이스 (01012345680)
|
||||
ScenarioService --> Service: MockScenario{type: "SYSTEM_ERROR", delay: 3000ms}
|
||||
end
|
||||
|
||||
deactivate ScenarioService
|
||||
|
||||
Service -> Service: 시나리오별 처리 지연\n(실제 KOS 상품변경 처리 시간 모사)
|
||||
note right: 상품변경은 복잡한 처리\n실제보다 긴 응답 시간
|
||||
|
||||
alt SUCCESS 시나리오
|
||||
Service -> ValidationService: validateProductChange(changeRequest)
|
||||
activate ValidationService
|
||||
|
||||
ValidationService -> MockRepo: getProductInfo(newProductCode)
|
||||
activate MockRepo
|
||||
|
||||
MockRepo -> MockDB: SELECT product_name, price, features\nFROM mock_products\nWHERE product_code = ?
|
||||
activate MockDB
|
||||
MockDB --> MockRepo: 상품 정보
|
||||
deactivate MockDB
|
||||
|
||||
MockRepo --> ValidationService: ProductInfo
|
||||
deactivate MockRepo
|
||||
|
||||
ValidationService -> ValidationService: 상품변경 가능 여부 확인\n- 현재 상품에서 변경 가능한지\n- 고객 자격 조건 만족하는지\n- 계약 조건 확인
|
||||
|
||||
ValidationService --> Service: ValidationResult{valid: true}
|
||||
deactivate ValidationService
|
||||
|
||||
Service -> ProductDataService: executeProductChange(changeRequest)
|
||||
activate ProductDataService
|
||||
|
||||
ProductDataService -> MockRepo: saveProductChangeResult(changeRequest)
|
||||
activate MockRepo
|
||||
|
||||
MockRepo -> MockDB: INSERT INTO mock_product_change_history\n(transaction_id, line_number, \n current_product_code, new_product_code,\n change_date, process_result)
|
||||
activate MockDB
|
||||
MockDB --> MockRepo: 변경 이력 저장 완료
|
||||
deactivate MockDB
|
||||
|
||||
MockRepo --> ProductDataService: 저장 완료
|
||||
deactivate MockRepo
|
||||
|
||||
ProductDataService -> ProductDataService: 상품변경 완료 정보 생성\n- 새로운 상품 정보\n- 변경 적용일\n- 변경 후 요금 정보
|
||||
|
||||
ProductDataService --> Service: ProductChangeResult\n{\n lineNumber: "01012345678",\n newProductCode: "PROD002",\n newProductName: "5G 프리미엄",\n changeDate: "20241201",\n effectiveDate: "20241201",\n monthlyFee: 75000,\n processResult: "정상"\n}
|
||||
deactivate ProductDataService
|
||||
|
||||
Service --> Controller: MockProductChangeResponse\n{\n "resultCode": "0000",\n "resultMessage": "상품변경 완료",\n "transactionId": "TXN20241201001",\n "data": productChangeResult\n}
|
||||
deactivate Service
|
||||
|
||||
Controller --> ProductService: 200 OK\n상품변경 성공 응답
|
||||
deactivate Controller
|
||||
|
||||
else NOT_ALLOWED 시나리오
|
||||
Service -> Service: 변경 불가 응답 구성
|
||||
Service --> Controller: MockErrorResponse\n{\n "resultCode": "E101",\n "resultMessage": "현재 상품에서 요청한 상품으로 변경할 수 없습니다",\n "transactionId": "TXN20241201001",\n "errorDetail": "약정 기간 내 상품변경 제한"\n}
|
||||
Controller --> ProductService: 400 Bad Request
|
||||
|
||||
else INCOMPATIBLE 시나리오
|
||||
Service -> Service: 호환 불가 응답 구성
|
||||
Service --> Controller: MockErrorResponse\n{\n "resultCode": "E102",\n "resultMessage": "호환되지 않는 상품입니다",\n "transactionId": "TXN20241201001",\n "errorDetail": "선택한 상품은 현재 단말기와 호환되지 않습니다"\n}
|
||||
Controller --> ProductService: 400 Bad Request
|
||||
|
||||
else INSUFFICIENT_BALANCE 시나리오
|
||||
Service -> Service: 잔액 부족 응답 구성
|
||||
Service --> Controller: MockErrorResponse\n{\n "resultCode": "E103",\n "resultMessage": "잔액이 부족하여 상품변경을 할 수 없습니다",\n "transactionId": "TXN20241201001",\n "errorDetail": "미납금 정리 후 상품변경 가능"\n}
|
||||
Controller --> ProductService: 400 Bad Request
|
||||
|
||||
else SYSTEM_ERROR 시나리오
|
||||
Service -> Service: 시스템 오류 응답 구성
|
||||
Service --> Controller: MockErrorResponse\n{\n "resultCode": "E999",\n "resultMessage": "시스템 일시 장애로 상품변경 처리를 할 수 없습니다",\n "transactionId": "TXN20241201001"\n}
|
||||
Controller --> ProductService: 500 Internal Server Error
|
||||
end
|
||||
end
|
||||
|
||||
== Mock 상품 데이터 관리 ==
|
||||
|
||||
note over MockRepo, MockDB
|
||||
Mock 상품변경 데이터:
|
||||
1. mock_products: 상품 정보 및 요금
|
||||
2. mock_product_compatibility: 상품 간 변경 가능 매트릭스
|
||||
3. mock_customer_eligibility: 고객별 상품 변경 자격
|
||||
4. mock_product_change_history: 변경 이력 추적
|
||||
|
||||
상품 변경 규칙:
|
||||
- 기본 상품 → 프리미엄: 가능
|
||||
- 프리미엄 → 기본: 약정 조건 확인 필요
|
||||
- 5G → 4G: 단말기 호환성 확인
|
||||
- 데이터 무제한 → 제한: 즉시 가능
|
||||
end note
|
||||
|
||||
== Mock 비즈니스 로직 시뮬레이션 ==
|
||||
|
||||
Service -> Service: 추가 비즈니스 로직 처리 (비동기)
|
||||
note right: Mock 비즈니스 시나리오\n1. 고객 알림 발송 시뮬레이션\n2. 정산 시스템 연동 시뮬레이션\n3. 단말기 설정 변경 시뮬레이션\n4. 부가서비스 자동 해지/가입
|
||||
|
||||
== 상품변경 고객 정보 조회 (UFR-PROD-020 지원) ==
|
||||
|
||||
note over Controller, MockDB
|
||||
Mock 서비스는 상품변경 화면을 위한
|
||||
고객 정보 및 상품 정보도 제공:
|
||||
|
||||
GET /kos/customer/{customerId}
|
||||
- 고객 정보, 현재 상품 정보
|
||||
|
||||
GET /kos/products/available
|
||||
- 변경 가능한 상품 목록
|
||||
|
||||
GET /kos/line/{lineNumber}/status
|
||||
- 회선 상태 정보
|
||||
end note
|
||||
|
||||
== Mock 상품변경 트랜잭션 추적 ==
|
||||
|
||||
Service -> Service: 트랜잭션 상태 추적 (비동기)
|
||||
note right: Mock 트랜잭션 관리\n- 트랜잭션 ID별 상태 추적\n- 중복 요청 방지\n- 롤백 시나리오 시뮬레이션\n- 분산 트랜잭션 패턴 테스트
|
||||
|
||||
Service -> Service: Mock 메트릭 업데이트 (비동기)
|
||||
note right: Mock 서비스 지표\n- 상품변경 성공/실패율\n- 시나리오별 처리 통계\n- 응답 시간 분포\n- 오류 패턴 분석
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,139 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
title KOS-Mock Service - 요금조회 내부 시퀀스
|
||||
|
||||
participant "Bill-Inquiry Service<<E>>" as BillService
|
||||
participant "KosMockController" as Controller
|
||||
participant "KosMockService" as Service
|
||||
participant "BillDataService" as BillDataService
|
||||
participant "MockScenarioService" as ScenarioService
|
||||
participant "MockDataRepository" as MockRepo
|
||||
participant "Mock Data Store<<E>>" as MockDB
|
||||
|
||||
== KOS-Mock 요금조회 시뮬레이션 ==
|
||||
|
||||
BillService -> Controller: POST /kos/bill/inquiry\nContent-Type: application/json\n{\n "lineNumber": "01012345678",\n "inquiryMonth": "202412"\n}
|
||||
activate Controller
|
||||
|
||||
Controller -> Controller: 요청 데이터 유효성 검사\n- lineNumber: 11자리 숫자 형식\n- inquiryMonth: YYYYMM 형식\n- 필수값 확인
|
||||
|
||||
alt 입력값 오류
|
||||
Controller --> BillService: 400 Bad Request\n{\n "resultCode": "E400",\n "resultMessage": "입력값이 올바르지 않습니다"\n}
|
||||
else 입력값 정상
|
||||
Controller -> Service: getBillInfo(lineNumber, inquiryMonth)
|
||||
activate Service
|
||||
|
||||
== Mock 시나리오 결정 ==
|
||||
|
||||
Service -> ScenarioService: determineScenario(lineNumber, inquiryMonth)
|
||||
activate ScenarioService
|
||||
|
||||
ScenarioService -> ScenarioService: 회선번호 기반 시나리오 결정
|
||||
note right: Mock 시나리오 규칙\n- 01012345678: 정상 케이스\n- 01012345679: 데이터 없음\n- 01012345680: 시스템 오류\n- 01012345681: 타임아웃 시뮬레이션\n- 기타: 정상 케이스로 처리
|
||||
|
||||
alt 정상 케이스 (01012345678 또는 기타)
|
||||
ScenarioService --> Service: MockScenario{type: "SUCCESS", delay: 500ms}
|
||||
else 데이터 없음 케이스 (01012345679)
|
||||
ScenarioService --> Service: MockScenario{type: "NO_DATA", delay: 300ms}
|
||||
else 시스템 오류 케이스 (01012345680)
|
||||
ScenarioService --> Service: MockScenario{type: "SYSTEM_ERROR", delay: 1000ms}
|
||||
else 타임아웃 시뮬레이션 (01012345681)
|
||||
ScenarioService --> Service: MockScenario{type: "TIMEOUT", delay: 5000ms}
|
||||
end
|
||||
|
||||
deactivate ScenarioService
|
||||
|
||||
Service -> Service: 시나리오별 지연 처리\n(실제 KOS 응답 시간 시뮬레이션)
|
||||
note right: Thread.sleep(scenario.delay)\n실제 KOS 응답 시간 모사
|
||||
|
||||
alt SUCCESS 시나리오
|
||||
Service -> BillDataService: generateBillData(lineNumber, inquiryMonth)
|
||||
activate BillDataService
|
||||
|
||||
BillDataService -> MockRepo: getMockBillTemplate(lineNumber)
|
||||
activate MockRepo
|
||||
|
||||
MockRepo -> MockDB: SELECT * FROM mock_bill_templates\nWHERE line_number = ? OR is_default = true
|
||||
activate MockDB
|
||||
MockDB --> MockRepo: Mock 데이터 템플릿
|
||||
deactivate MockDB
|
||||
|
||||
MockRepo --> BillDataService: BillTemplate
|
||||
deactivate MockRepo
|
||||
|
||||
BillDataService -> BillDataService: 동적 데이터 생성\n- 조회월 기반 요금 계산\n- 사용량 랜덤 생성\n- 할인정보 적용
|
||||
|
||||
BillDataService --> Service: BillInfo\n{\n productName: "5G 프리미엄",\n contractInfo: "24개월 약정",\n billingMonth: "202412",\n charge: 75000,\n discountInfo: "가족할인 10000원",\n usage: {voice: "250분", data: "20GB"},\n estimatedCancellationFee: 120000,\n deviceInstallment: 35000,\n billingPaymentInfo: {\n billingDate: "2024-12-25",\n paymentStatus: "완료"\n }\n}
|
||||
deactivate BillDataService
|
||||
|
||||
Service -> Service: 응답 데이터 구성
|
||||
Service --> Controller: MockBillResponse\n{\n "resultCode": "0000",\n "resultMessage": "성공",\n "data": billInfo\n}
|
||||
deactivate Service
|
||||
|
||||
Controller --> BillService: 200 OK\n정상 요금조회 응답
|
||||
deactivate Controller
|
||||
|
||||
else NO_DATA 시나리오
|
||||
Service -> Service: 데이터 없음 응답 구성
|
||||
Service --> Controller: MockErrorResponse\n{\n "resultCode": "E002",\n "resultMessage": "해당 월의 요금 데이터가 존재하지 않습니다",\n "data": null\n}
|
||||
Controller --> BillService: 200 OK\n(비즈니스 오류는 200으로 응답)
|
||||
|
||||
else SYSTEM_ERROR 시나리오
|
||||
Service -> Service: 시스템 오류 응답 구성
|
||||
Service --> Controller: MockErrorResponse\n{\n "resultCode": "E999",\n "resultMessage": "시스템 일시 장애가 발생했습니다",\n "data": null\n}
|
||||
Controller --> BillService: 500 Internal Server Error
|
||||
|
||||
else TIMEOUT 시나리오
|
||||
Service -> Service: 타임아웃 시뮬레이션\n(5초 대기 후 응답)
|
||||
note right: KOS 타임아웃 시나리오\nCircuit Breaker 테스트용
|
||||
|
||||
alt 클라이언트가 타임아웃 전에 대기
|
||||
Service --> Controller: 지연된 정상 응답
|
||||
Controller --> BillService: 200 OK (지연 응답)
|
||||
else 클라이언트 타임아웃 (3초)
|
||||
note right: 클라이언트에서 타임아웃으로\n연결 종료됨
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
== Mock 데이터 관리 ==
|
||||
|
||||
note over MockRepo, MockDB
|
||||
Mock 데이터베이스 구조:
|
||||
1. mock_bill_templates: 요금 템플릿 데이터
|
||||
2. mock_scenarios: 시나리오별 설정
|
||||
3. mock_usage_patterns: 사용량 패턴 데이터
|
||||
4. mock_products: 상품 정보 데이터
|
||||
|
||||
동적 데이터 생성:
|
||||
- 회선번호별 고유 패턴
|
||||
- 월별 사용량 변화
|
||||
- 계절별 요금 변동
|
||||
- 할인 정책 적용
|
||||
end note
|
||||
|
||||
== Mock 시나리오 설정 ==
|
||||
|
||||
note over ScenarioService
|
||||
Mock 시나리오 관리:
|
||||
1. 환경변수로 시나리오 설정 가능
|
||||
2. 회선번호 패턴 기반 동작 결정
|
||||
3. 응답 지연 시간 조절
|
||||
4. 오류율 시뮬레이션
|
||||
5. 부하 테스트 지원
|
||||
|
||||
설정 예시:
|
||||
- mock.scenario.success.delay=500ms
|
||||
- mock.scenario.error.rate=5%
|
||||
- mock.scenario.timeout.enabled=true
|
||||
end note
|
||||
|
||||
== 로깅 및 모니터링 ==
|
||||
|
||||
Service -> Service: Mock 요청/응답 로깅 (비동기)
|
||||
note right: Mock 서비스 모니터링\n- 요청 통계\n- 시나리오별 호출 현황\n- 응답 시간 분석\n- 오류 패턴 추적
|
||||
|
||||
Service -> Service: 메트릭 업데이트 (비동기)
|
||||
note right: Mock 서비스 지표\n- 총 호출 횟수\n- 시나리오별 분포\n- 평균 응답 시간\n- 성공/실패 비율
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,183 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
title Product-Change Service - KOS 연동 내부 시퀀스
|
||||
|
||||
participant "ProductChangeService" as Service
|
||||
participant "KosClientService" as KosClient
|
||||
participant "CircuitBreakerService" as CircuitBreaker
|
||||
participant "RetryService" as RetryService
|
||||
participant "KosAdapterService" as KosAdapter
|
||||
participant "ProductRepository" as ProductRepo
|
||||
participant "Product DB<<E>>" as ProductDB
|
||||
participant "KOS-Mock Service<<E>>" as KOSMock
|
||||
participant "MVNO AP Server<<E>>" as MVNO
|
||||
|
||||
== UFR-PROD-040: KOS 상품변경 처리 ==
|
||||
|
||||
note over Service
|
||||
사전체크가 통과된 상품변경 요청에 대해
|
||||
KOS 시스템과 연동하여 실제 상품변경 처리
|
||||
end note
|
||||
|
||||
Service -> KosClient: processProductChange(changeRequest)
|
||||
activate KosClient
|
||||
|
||||
KosClient -> CircuitBreaker: isCallAllowed()
|
||||
activate CircuitBreaker
|
||||
|
||||
alt Circuit Breaker - OPEN 상태
|
||||
CircuitBreaker --> KosClient: Circuit Open\n"시스템 일시 장애"
|
||||
deactivate CircuitBreaker
|
||||
|
||||
KosClient -> MVNO: sendSystemErrorNotification\n"시스템 일시 장애, 잠시 후 재시도"
|
||||
activate MVNO
|
||||
MVNO --> KosClient: 장애 안내 전송 완료
|
||||
deactivate MVNO
|
||||
|
||||
KosClient --> Service: CircuitBreakerException\n"시스템 일시 장애, 잠시 후 재시도"
|
||||
|
||||
else Circuit Breaker - CLOSED/HALF_OPEN 상태
|
||||
CircuitBreaker --> KosClient: Call Allowed
|
||||
deactivate CircuitBreaker
|
||||
|
||||
KosClient -> RetryService: executeProductChangeWithRetry(changeRequest)
|
||||
activate RetryService
|
||||
|
||||
loop 최대 3회 재시도 (상품변경은 중요한 거래)
|
||||
RetryService -> KosAdapter: callKosProductChange(changeRequest)
|
||||
activate KosAdapter
|
||||
|
||||
KosAdapter -> KosAdapter: 요청 데이터 변환\n- 회선번호 형식 검증\n- 상품코드 매핑\n- 거래ID 생성\n- 인증 헤더 설정
|
||||
|
||||
== KOS-Mock Service 상품변경 호출 ==
|
||||
|
||||
KosAdapter -> KOSMock: POST /kos/product/change\nContent-Type: application/json\n{\n "transactionId": "TXN20241201001",\n "lineNumber": "01012345678",\n "currentProductCode": "PROD001",\n "newProductCode": "PROD002",\n "changeReason": "고객 요청",\n "effectiveDate": "20241201"\n}
|
||||
activate KOSMock
|
||||
note right: KOS-Mock 상품변경 서비스\n- 실제 KOS 대신 Mock 처리\n- 타임아웃: 5초 (중요 거래)\n- 성공/실패 시나리오 시뮬레이션
|
||||
|
||||
alt KOS-Mock 상품변경 성공
|
||||
KOSMock --> KosAdapter: 200 OK\n{\n "resultCode": "0000",\n "resultMessage": "상품변경 완료",\n "transactionId": "TXN20241201001",\n "data": {\n "lineNumber": "01012345678",\n "newProductCode": "PROD002",\n "newProductName": "5G 프리미엄",\n "changeDate": "20241201",\n "effectiveDate": "20241201",\n "processResult": "정상"\n }\n}
|
||||
deactivate KOSMock
|
||||
|
||||
KosAdapter -> KosAdapter: 성공 응답 데이터 변환\n- KOS 응답 → ProductChangeResult\n- 상품변경 완료 정보 매핑
|
||||
|
||||
KosAdapter --> RetryService: ProductChangeResult{success: true}
|
||||
deactivate KosAdapter
|
||||
break 성공 시 재시도 중단
|
||||
|
||||
else KOS-Mock 상품변경 실패
|
||||
KOSMock --> KosAdapter: 400 Bad Request\n{\n "resultCode": "E101",\n "resultMessage": "상품변경 처리 실패",\n "transactionId": "TXN20241201001",\n "errorDetail": "현재 상품에서 요청한 상품으로 변경할 수 없습니다"\n}
|
||||
deactivate KOSMock
|
||||
|
||||
KosAdapter -> KosAdapter: 실패 응답 데이터 변환\n- 오류 코드별 예외 매핑\n- E101: ProductChangeNotAllowedException\n- E102: InsufficientBalanceException\n- E999: SystemErrorException
|
||||
|
||||
KosAdapter --> RetryService: ProductChangeException{reason: errorDetail}
|
||||
deactivate KosAdapter
|
||||
|
||||
else 네트워크 오류 (타임아웃, 연결 실패)
|
||||
KOSMock --> KosAdapter: IOException/TimeoutException
|
||||
deactivate KOSMock
|
||||
|
||||
KosAdapter --> RetryService: NetworkException
|
||||
deactivate KosAdapter
|
||||
end
|
||||
|
||||
alt 재시도 가능한 오류 (네트워크, 일시적 오류)
|
||||
RetryService -> RetryService: 재시도 대기\n- 1차: 2초 대기\n- 2차: 5초 대기\n- 3차: 10초 대기
|
||||
note right: 상품변경은 중요한 거래\n재시도 간격을 길게 설정
|
||||
else 재시도 불가능한 오류 (비즈니스 로직 오류)
|
||||
break 재시도 중단
|
||||
end
|
||||
end
|
||||
|
||||
alt 상품변경 성공
|
||||
RetryService --> KosClient: ProductChangeResult{success: true}
|
||||
deactivate RetryService
|
||||
|
||||
KosClient -> CircuitBreaker: recordSuccess()
|
||||
activate CircuitBreaker
|
||||
CircuitBreaker -> CircuitBreaker: 성공 카운트 증가
|
||||
deactivate CircuitBreaker
|
||||
|
||||
== UFR-PROD-040: 상품변경 완료 처리 ==
|
||||
|
||||
KosClient -> MVNO: sendProductChangeResult\n{newProductCode, processResult: "정상", message: "상품 변경이 완료되었다"}
|
||||
activate MVNO
|
||||
MVNO --> KosClient: 변경완료 결과 전송 완료
|
||||
deactivate MVNO
|
||||
|
||||
KosClient -> ProductRepo: updateProductChangeStatus(transactionId, "COMPLETED", result)
|
||||
activate ProductRepo
|
||||
ProductRepo -> ProductDB: UPDATE product_change_request\nSET status = 'COMPLETED',\n completion_time = NOW(),\n new_product_code = ?,\n result_message = 'COMPLETED'\nWHERE transaction_id = ?
|
||||
activate ProductDB
|
||||
ProductDB --> ProductRepo: 상태 업데이트 완료
|
||||
deactivate ProductDB
|
||||
|
||||
ProductRepo -> ProductDB: INSERT INTO product_change_history\n(transaction_id, line_number, \n current_product_code, new_product_code,\n change_date, process_result, result_message)
|
||||
activate ProductDB
|
||||
note right: 비동기 처리\n상품변경 이력 저장
|
||||
ProductDB --> ProductRepo: 이력 저장 완료
|
||||
deactivate ProductDB
|
||||
deactivate ProductRepo
|
||||
|
||||
KosClient --> Service: ProductChangeSuccess\n{newProductCode, changeDate, message: "상품 변경이 완료되었다"}
|
||||
deactivate KosClient
|
||||
|
||||
else 상품변경 실패
|
||||
RetryService --> KosClient: ProductChangeException
|
||||
deactivate RetryService
|
||||
|
||||
KosClient -> CircuitBreaker: recordFailure()
|
||||
activate CircuitBreaker
|
||||
CircuitBreaker -> CircuitBreaker: 실패 카운트 증가
|
||||
deactivate CircuitBreaker
|
||||
|
||||
KosClient -> MVNO: sendProductChangeResult\n{processResult: "실패", failureReason, message: "상품 변경에 실패하여 실패 사유에 따라 문구를 화면에 출력한다"}
|
||||
activate MVNO
|
||||
MVNO --> KosClient: 변경실패 결과 전송 완료
|
||||
deactivate MVNO
|
||||
|
||||
KosClient -> ProductRepo: updateProductChangeStatus(transactionId, "FAILED", errorReason)
|
||||
activate ProductRepo
|
||||
ProductRepo -> ProductDB: UPDATE product_change_request\nSET status = 'FAILED',\n completion_time = NOW(),\n failure_reason = ?,\n result_message = 'FAILED'\nWHERE transaction_id = ?
|
||||
activate ProductDB
|
||||
ProductDB --> ProductRepo: 상태 업데이트 완료
|
||||
deactivate ProductDB
|
||||
|
||||
ProductRepo -> ProductDB: INSERT INTO product_change_history\n(..., process_result = 'FAILED', error_detail)
|
||||
activate ProductDB
|
||||
ProductDB --> ProductRepo: 실패 이력 저장 완료
|
||||
deactivate ProductDB
|
||||
deactivate ProductRepo
|
||||
|
||||
KosClient --> Service: ProductChangeFailure\n{reason, message: "상품 변경 요청을 실패하였다"}
|
||||
deactivate KosClient
|
||||
end
|
||||
end
|
||||
|
||||
== 상품변경 결과 후처리 ==
|
||||
|
||||
alt 상품변경 성공
|
||||
Service -> Service: 캐시 무효화 처리
|
||||
Service -> "Redis Cache<<E>>": 고객 상품 정보 캐시 삭제\nDEL customer_product:{userId}\nDEL current_product:{userId}
|
||||
note right: 변경된 상품 정보로\n캐시 갱신 필요
|
||||
|
||||
Service -> Service: 고객 알림 처리 (비동기)\n- SMS/Push 알림\n- 이메일 통지
|
||||
note right: 상품변경 완료\n고객 안내 필요
|
||||
|
||||
else 상품변경 실패
|
||||
Service -> Service: 실패 분석 및 로깅\n- 실패 패턴 분석\n- 모니터링 지표 업데이트
|
||||
note right: 실패 원인 분석\n서비스 개선 활용
|
||||
end
|
||||
|
||||
== 트랜잭션 무결성 보장 ==
|
||||
|
||||
note over Service, ProductDB
|
||||
상품변경 트랜잭션 처리:
|
||||
1. KOS 연동 성공 → 로컬 DB 상태 업데이트
|
||||
2. 로컬 DB 실패 → KOS 보상 트랜잭션 (롤백)
|
||||
3. 데이터 일관성 보장
|
||||
4. 분산 트랜잭션 패턴 적용
|
||||
end note
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,246 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
title Product-Change Service - 상품변경 요청 내부 시퀀스
|
||||
|
||||
participant "API Gateway" as Gateway
|
||||
participant "ProductController" as Controller
|
||||
participant "ProductChangeService" as Service
|
||||
participant "ProductCacheService" as CacheService
|
||||
participant "ProductValidationService" as ValidationService
|
||||
participant "ProductRepository" as ProductRepo
|
||||
participant "KosClientService" as KosClient
|
||||
participant "Redis Cache<<E>>" as Redis
|
||||
participant "Product DB<<E>>" as ProductDB
|
||||
participant "MVNO AP Server<<E>>" as MVNO
|
||||
|
||||
== UFR-PROD-010: 상품변경 메뉴 접근 ==
|
||||
|
||||
Gateway -> Controller: GET /product/menu\nAuthorization: Bearer {accessToken}
|
||||
activate Controller
|
||||
|
||||
Controller -> Controller: JWT 토큰에서 userId 추출
|
||||
|
||||
Controller -> Service: getProductMenuData(userId)
|
||||
activate Service
|
||||
|
||||
Service -> CacheService: getCustomerProductInfo(userId)
|
||||
activate CacheService
|
||||
|
||||
CacheService -> Redis: GET customer_product:{userId}
|
||||
activate Redis
|
||||
|
||||
alt 고객 상품 정보 캐시 Hit
|
||||
Redis --> CacheService: 고객 상품 정보 반환\n{lineNumber, customerId, currentProductCode, productName}
|
||||
deactivate Redis
|
||||
note right: 캐시 히트\n- TTL: 4시간\n- 빠른 응답
|
||||
|
||||
else 고객 상품 정보 캐시 Miss
|
||||
Redis --> CacheService: null
|
||||
deactivate Redis
|
||||
|
||||
CacheService -> KosClient: getCustomerInfo(userId)
|
||||
activate KosClient
|
||||
note right: KOS-Mock에서 고객 정보 조회\n(kos-mock-상품변경.puml 참조)
|
||||
KosClient --> CacheService: CustomerProductInfo
|
||||
deactivate KosClient
|
||||
|
||||
CacheService -> Redis: SET customer_product:{userId}\nValue: customerProductInfo\nTTL: 4시간
|
||||
activate Redis
|
||||
Redis --> CacheService: 캐싱 완료
|
||||
deactivate Redis
|
||||
end
|
||||
|
||||
CacheService --> Service: CustomerProductInfo
|
||||
deactivate CacheService
|
||||
|
||||
Service --> Controller: ProductMenuResponse\n{lineNumber, customerId, currentProduct}
|
||||
deactivate Service
|
||||
|
||||
Controller --> Gateway: 200 OK\n상품변경 메뉴 데이터
|
||||
deactivate Controller
|
||||
|
||||
== UFR-PROD-020: 상품변경 화면 접근 ==
|
||||
|
||||
Gateway -> Controller: GET /product/change\nAuthorization: Bearer {accessToken}
|
||||
activate Controller
|
||||
|
||||
Controller -> Service: getProductChangeScreen(userId)
|
||||
activate Service
|
||||
|
||||
== 현재 상품 정보 및 변경 가능 상품 목록 조회 ==
|
||||
|
||||
Service -> CacheService: getCurrentProductInfo(userId)
|
||||
activate CacheService
|
||||
CacheService -> Redis: GET current_product:{userId}
|
||||
activate Redis
|
||||
|
||||
alt 현재 상품 정보 캐시 Miss
|
||||
Redis --> CacheService: null
|
||||
deactivate Redis
|
||||
|
||||
CacheService -> KosClient: getCurrentProduct(userId)
|
||||
activate KosClient
|
||||
KosClient --> CacheService: CurrentProductInfo
|
||||
deactivate KosClient
|
||||
|
||||
CacheService -> Redis: SET current_product:{userId}\nTTL: 2시간
|
||||
activate Redis
|
||||
Redis --> CacheService: 캐싱 완료
|
||||
deactivate Redis
|
||||
else 현재 상품 정보 캐시 Hit
|
||||
Redis --> CacheService: CurrentProductInfo
|
||||
deactivate Redis
|
||||
end
|
||||
|
||||
CacheService --> Service: CurrentProductInfo
|
||||
deactivate CacheService
|
||||
|
||||
Service -> CacheService: getAvailableProducts()
|
||||
activate CacheService
|
||||
|
||||
CacheService -> Redis: GET available_products:all
|
||||
activate Redis
|
||||
|
||||
alt 상품 목록 캐시 Miss
|
||||
Redis --> CacheService: null
|
||||
deactivate Redis
|
||||
|
||||
CacheService -> KosClient: getAvailableProducts()
|
||||
activate KosClient
|
||||
KosClient --> CacheService: List<AvailableProduct>
|
||||
deactivate KosClient
|
||||
|
||||
CacheService -> Redis: SET available_products:all\nTTL: 24시간
|
||||
activate Redis
|
||||
Redis --> CacheService: 캐싱 완료
|
||||
deactivate Redis
|
||||
else 상품 목록 캐시 Hit
|
||||
Redis --> CacheService: List<AvailableProduct>
|
||||
deactivate Redis
|
||||
end
|
||||
|
||||
CacheService --> Service: List<AvailableProduct>
|
||||
deactivate CacheService
|
||||
|
||||
Service -> Service: 변경 가능한 상품 필터링\n- 현재 상품과 다른 상품\n- 판매중인 상품\n- 사업자 일치 상품
|
||||
|
||||
Service --> Controller: ProductChangeScreenResponse\n{currentProduct, availableProducts}
|
||||
deactivate Service
|
||||
|
||||
Controller --> Gateway: 200 OK\n상품변경 화면 데이터
|
||||
deactivate Controller
|
||||
|
||||
== UFR-PROD-030: 상품변경 요청 및 사전체크 ==
|
||||
|
||||
Gateway -> Controller: POST /product/request\n{\n "lineNumber": "01012345678",\n "currentProductCode": "PROD001",\n "newProductCode": "PROD002"\n}\nAuthorization: Bearer {accessToken}
|
||||
activate Controller
|
||||
|
||||
Controller -> Controller: 입력값 검증\n- lineNumber: 11자리 숫자\n- productCode: 필수값, 형식 확인
|
||||
|
||||
alt 입력값 오류
|
||||
Controller --> Gateway: 400 Bad Request\n"입력값을 확인해주세요"
|
||||
else 입력값 정상
|
||||
Controller -> Service: requestProductChange(changeRequest, userId)
|
||||
activate Service
|
||||
|
||||
== 상품변경 사전체크 수행 ==
|
||||
|
||||
Service -> ValidationService: validateProductChange(changeRequest)
|
||||
activate ValidationService
|
||||
|
||||
ValidationService -> ValidationService: 1. 판매중인 상품 확인
|
||||
ValidationService -> CacheService: getProductStatus(newProductCode)
|
||||
activate CacheService
|
||||
CacheService -> Redis: GET product_status:{newProductCode}
|
||||
|
||||
alt 상품 상태 캐시 Miss
|
||||
Redis --> CacheService: null
|
||||
CacheService -> ProductRepo: getProductStatus(newProductCode)
|
||||
activate ProductRepo
|
||||
ProductRepo -> ProductDB: SELECT status, sales_status\nFROM products\nWHERE product_code = ?
|
||||
activate ProductDB
|
||||
ProductDB --> ProductRepo: 상품 상태 정보
|
||||
deactivate ProductDB
|
||||
ProductRepo --> CacheService: ProductStatus
|
||||
deactivate ProductRepo
|
||||
|
||||
CacheService -> Redis: SET product_status:{newProductCode}\nTTL: 1시간
|
||||
else 상품 상태 캐시 Hit
|
||||
Redis --> CacheService: ProductStatus
|
||||
end
|
||||
|
||||
deactivate Redis
|
||||
CacheService --> ValidationService: ProductStatus
|
||||
deactivate CacheService
|
||||
|
||||
alt 신규 상품이 판매 중이 아님
|
||||
ValidationService --> Service: ValidationException\n"현재 판매중인 상품이 아닙니다"
|
||||
else 신규 상품 판매 중
|
||||
ValidationService -> ValidationService: 2. 사업자 일치 확인
|
||||
ValidationService -> ValidationService: 고객 사업자와 상품 사업자 비교
|
||||
|
||||
alt 사업자 불일치
|
||||
ValidationService --> Service: ValidationException\n"변경 요청한 사업자에서 판매중인 상품이 아닙니다"
|
||||
else 사업자 일치
|
||||
ValidationService -> ValidationService: 3. 회선 사용상태 확인
|
||||
ValidationService -> CacheService: getLineStatus(lineNumber)
|
||||
activate CacheService
|
||||
|
||||
CacheService -> Redis: GET line_status:{lineNumber}
|
||||
activate Redis
|
||||
alt 회선 상태 캐시 Miss
|
||||
Redis --> CacheService: null
|
||||
deactivate Redis
|
||||
CacheService -> KosClient: getLineStatus(lineNumber)
|
||||
activate KosClient
|
||||
KosClient --> CacheService: LineStatus
|
||||
deactivate KosClient
|
||||
CacheService -> Redis: SET line_status:{lineNumber}\nTTL: 30분
|
||||
activate Redis
|
||||
Redis --> CacheService: 캐싱 완료
|
||||
deactivate Redis
|
||||
else 회선 상태 캐시 Hit
|
||||
Redis --> CacheService: LineStatus
|
||||
deactivate Redis
|
||||
end
|
||||
|
||||
CacheService --> ValidationService: LineStatus
|
||||
deactivate CacheService
|
||||
|
||||
alt 회선이 사용 중이 아님 (정지 상태)
|
||||
ValidationService --> Service: ValidationException\n"변경 요청 회선은 사용 중인 상태가 아닙니다"
|
||||
else 회선 사용 중 (정상)
|
||||
ValidationService --> Service: ValidationResult{success: true}
|
||||
deactivate ValidationService
|
||||
|
||||
Service -> ProductRepo: saveChangeRequest(changeRequest, "PRE_CHECK_PASSED")
|
||||
activate ProductRepo
|
||||
ProductRepo -> ProductDB: INSERT INTO product_change_request\n(user_id, line_number, current_product_code, \n new_product_code, request_time, status)
|
||||
activate ProductDB
|
||||
ProductDB --> ProductRepo: 요청 저장 완료
|
||||
deactivate ProductDB
|
||||
deactivate ProductRepo
|
||||
|
||||
Service --> Controller: PreCheckResult{success: true, message: "상품 변경이 진행되었다"}
|
||||
deactivate Service
|
||||
|
||||
Controller --> Gateway: 200 OK\n{status: "PRE_CHECK_PASSED", message: "상품 사전 체크에 성공하였다"}
|
||||
deactivate Controller
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
== 사전체크 실패 처리 ==
|
||||
|
||||
alt 사전체크 실패
|
||||
Service -> ProductRepo: saveChangeRequest(changeRequest, "PRE_CHECK_FAILED")
|
||||
activate ProductRepo
|
||||
ProductRepo -> ProductDB: INSERT INTO product_change_request\n(..., status, failure_reason)
|
||||
deactivate ProductRepo
|
||||
|
||||
Service --> Controller: PreCheckException{reason: failureReason}
|
||||
Controller --> Gateway: 400 Bad Request\n{status: "PRE_CHECK_FAILED", message: "상품 사전 체크에 실패하였다"}
|
||||
end
|
||||
|
||||
@enduml
|
||||
Reference in New Issue
Block a user