@startuml user-프로필수정
!theme mono

title User Service - 프로필 수정 내부 시퀀스 (UFR-USER-030)

actor Client
participant "UserController" as Controller <<API Layer>>
participant "UserService" as Service <<Business Layer>>
participant "UserRepository" as UserRepo <<Data Layer>>
participant "StoreRepository" as StoreRepo <<Data Layer>>
participant "PasswordEncoder" as PwdEncoder <<Utility>>
participant "Redis\nCache" as Redis <<E>>
participant "User DB\n(PostgreSQL)" as UserDB <<E>>

note over Controller, UserDB
**UFR-USER-030: 프로필 수정**
- 기본 정보: 이름, 전화번호, 이메일
- 매장 정보: 매장명, 업종, 주소, 영업시간
- 비밀번호 변경 (현재 비밀번호 확인 필수)
- 전화번호 변경 시 재인증 필요 (향후 구현)
end note

Client -> Controller: PUT /api/users/profile\nAuthorization: Bearer {JWT}\n(UpdateProfileRequest DTO)
activate Controller

Controller -> Controller: @AuthenticationPrincipal\n(JWT에서 userId 추출)

Controller -> Controller: @Valid 어노테이션 검증\n(이메일 형식, 필드 길이 등)

Controller -> Service: updateProfile(userId, UpdateProfileRequest)
activate Service

== 1단계: 기존 사용자 정보 조회 ==

Service -> UserRepo: findById(userId)
activate UserRepo
UserRepo -> UserDB: 사용자ID로 사용자 조회\n(사용자 정보 조회)
activate UserDB
UserDB --> UserRepo: 사용자 정보
deactivate UserDB
UserRepo --> Service: User 엔티티
deactivate UserRepo

alt 사용자 없음
    Service --> Controller: throw UserNotFoundException\n("사용자를 찾을 수 없습니다")
    Controller --> Client: 404 Not Found\n{"code": "USER_003",\n"error": "사용자를 찾을 수 없습니다"}
    deactivate Service
    deactivate Controller

else 사용자 존재

    == 2단계: 비밀번호 변경 요청 처리 ==

    alt 비밀번호 변경 요청 O
        Service -> Service: 현재 비밀번호 검증 필요 확인

        Service -> PwdEncoder: matches(currentPassword,\nuser.getPasswordHash())
        activate PwdEncoder
        PwdEncoder -> PwdEncoder: bcrypt compare
        PwdEncoder --> Service: boolean (일치 여부)
        deactivate PwdEncoder

        alt 현재 비밀번호 불일치
            Service --> Controller: throw InvalidPasswordException\n("현재 비밀번호가 일치하지 않습니다")
            Controller --> Client: 400 Bad Request\n{"code": "USER_004",\n"error": "현재 비밀번호가\n일치하지 않습니다"}
            deactivate Service
            deactivate Controller

        else 현재 비밀번호 일치
            Service -> Service: 새 비밀번호 유효성 검증\n(8자 이상, 영문/숫자/특수문자)

            Service -> PwdEncoder: encode(newPassword)
            activate PwdEncoder
            PwdEncoder -> PwdEncoder: bcrypt 해싱\n(Cost Factor 10)
            PwdEncoder --> Service: newPasswordHash
            deactivate PwdEncoder

            Service -> Service: user.setPasswordHash(newPasswordHash)
        end
    end

    == 3단계: 엔티티 수정 준비 (메모리상 변경) ==

    note right of Service
    **JPA Dirty Checking**
    - 트랜잭션 시작 전 엔티티 수정 (메모리상)
    - 트랜잭션 커밋 시 변경 감지하여 UPDATE 자동 실행
    - 변경된 필드만 UPDATE 쿼리에 포함
    end note

    alt 이름 변경
        Service -> Service: user.setName(newName)
    end

    alt 전화번호 변경
        Service -> Service: user.setPhoneNumber(newPhoneNumber)
        note right of Service
        **향후 구현: 재인증 필요**
        - SMS 인증 또는 이메일 인증
        - 인증 완료 후에만 변경 반영
        end note
    end

    alt 이메일 변경
        Service -> Service: user.setEmail(newEmail)
    end

    == 4단계: 매장 정보 수정 준비 (메모리상 변경) ==

    Service -> StoreRepo: findByUserId(userId)
    activate StoreRepo
    StoreRepo -> UserDB: 사용자ID로 매장 조회\n(매장 정보 조회)
    activate UserDB
    UserDB --> StoreRepo: 매장 정보
    deactivate UserDB
    StoreRepo --> Service: Store 엔티티
    deactivate StoreRepo

    alt 매장명 변경
        Service -> Service: store.setStoreName(newStoreName)
    end

    alt 업종 변경
        Service -> Service: store.setIndustry(newIndustry)
    end

    alt 주소 변경
        Service -> Service: store.setAddress(newAddress)
    end

    alt 영업시간 변경
        Service -> Service: store.setBusinessHours(newBusinessHours)
    end

    == 5단계: 데이터베이스 트랜잭션 ==

    Service -> UserDB: 트랜잭션 시작
    activate UserDB

    note right of Service
    **Optimistic Locking**
    - @Version 필드로 동시 수정 감지
    - 다른 트랜잭션이 먼저 수정한 경우
    - OptimisticLockException 발생
    end note

    Service -> UserRepo: save(user)
    activate UserRepo
    UserRepo -> UserDB: 사용자 정보 업데이트\n(이름, 전화번호, 이메일,\n비밀번호해시, 수정일시,\n버전 증가)\nOptimistic Lock 적용
    UserDB --> UserRepo: 업데이트 완료 (1 row affected)
    UserRepo --> Service: User 엔티티
    deactivate UserRepo

    alt 동시성 충돌 (version 불일치)
        UserRepo --> Service: throw OptimisticLockException
        Service --> Controller: throw ConcurrentModificationException\n("다른 사용자가 수정 중입니다")
        Controller --> Client: 409 Conflict\n{"code": "USER_005",\n"error": "다른 세션에서 프로필을\n수정했습니다.\n새로고침 후 다시 시도하세요"}
        Service -> UserDB: 트랜잭션 롤백
        deactivate UserDB
        deactivate Service
        deactivate Controller
    else 정상 업데이트

        Service -> StoreRepo: save(store)
        activate StoreRepo
        StoreRepo -> UserDB: 매장 정보 업데이트\n(매장명, 업종, 주소,\n영업시간, 수정일시,\n버전 증가)\nOptimistic Lock 적용
        UserDB --> StoreRepo: 업데이트 완료 (1 row affected)
        StoreRepo --> Service: Store 엔티티
        deactivate StoreRepo

        Service -> UserDB: 트랜잭션 커밋
        UserDB --> Service: 트랜잭션 커밋 완료
        deactivate UserDB

        == 6단계: 캐시 무효화 (선택적) ==

        note right of Service
        **캐시 무효화 전략**
        - 세션 정보는 변경 없음 (JWT 유지)
        - 프로필 캐시가 있다면 무효화
        end note

        alt 프로필 캐시 사용 중
            Service -> Redis: 프로필 캐시 삭제\n(캐시키: user:profile:{userId})
            activate Redis
            Redis --> Service: 캐시 삭제 완료
            deactivate Redis
        end

        == 7단계: 응답 반환 ==

        Service -> Service: 응답 DTO 생성\n(UpdateProfileResponse)
        Service --> Controller: UpdateProfileResponse\n(userId, userName, email,\nstoreId, storeName)
        deactivate Service

        Controller --> Client: 200 OK\n{"userId": 123,\n"userName": "홍길동",\n"email": "hong@example.com",\n"storeId": 456,\n"storeName": "맛있는집"}
        deactivate Controller
    end
end

note over Controller, UserDB
**Transaction Rollback 처리**
- 트랜잭션 실패 시 자동 Rollback
- User/Store UPDATE 중 하나라도 실패 시 전체 롤백
- OptimisticLockException 발생 시 409 Conflict 반환

**동시성 제어**
- Optimistic Locking: @Version 필드로 동시 수정 감지
- 충돌 감지 시: 409 Conflict 반환 (사용자에게 재시도 안내)
- Lost Update 방지: version 필드 자동 증가

**보안 처리**
- 비밀번호 변경: 현재 비밀번호 확인 필수
- JWT 인증: Controller에서 @AuthenticationPrincipal로 userId 추출
- 권한 검증: 본인만 수정 가능

**성능 목표**
- 평균 응답 시간: 0.3초 이내
- P95 응답 시간: 0.5초 이내
- 트랜잭션 격리 수준: READ_COMMITTED

**향후 개선사항**
- 전화번호 변경: SMS/이메일 재인증 구현
- 이메일 변경: 이메일 인증 구현
- 변경 이력 추적: Audit Log 기록

**에러 코드**
- USER_003: 사용자 없음
- USER_004: 현재 비밀번호 불일치
- USER_005: 동시성 충돌 (다른 세션에서 수정)
end note

@enduml
