User 서비스 내부 시퀀스 다이어그램 개선

- 회원가입: 사업자번호 암호화 위치 명시, 성능 지표 추가, 에러 코드 표준화 (USER_001, USER_002)
- 로그인: 비동기 처리 설명 추가, Rate Limiting 보안 강화, 성능 목표 추가, 에러 코드 표준화 (AUTH_001)
- 프로필수정: 트랜잭션 범위 명확화, Optimistic Locking 추가, 동시성 제어, 에러 코드 표준화 (USER_003~005)
- 로그아웃: API Gateway 연계 시나리오, 로그아웃 이벤트 발행, 성능 목표 추가, 에러 코드 표준화 (AUTH_002)
- .gitignore 추가: .npm-global 및 기타 임시 파일 제외

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
박세원
2025-10-22 14:37:25 +09:00
parent d876763477
commit f7f16e2d18
5 changed files with 196 additions and 47 deletions
@@ -43,7 +43,7 @@ deactivate UserRepo
alt 사용자 없음
Service --> Controller: throw UserNotFoundException\n("사용자를 찾을 수 없습니다")
Controller --> Client: 404 Not Found\n{"error": "사용자를 찾을 수 없습니다"}
Controller --> Client: 404 Not Found\n{"code": "USER_003",\n"error": "사용자를 찾을 수 없습니다"}
deactivate Service
deactivate Controller
@@ -62,7 +62,7 @@ else 사용자 존재
alt 현재 비밀번호 불일치
Service --> Controller: throw InvalidPasswordException\n("현재 비밀번호가 일치하지 않습니다")
Controller --> Client: 400 Bad Request\n{"error": "현재 비밀번호가\n일치하지 않습니다"}
Controller --> Client: 400 Bad Request\n{"code": "USER_004",\n"error": "현재 비밀번호가\n일치하지 않습니다"}
deactivate Service
deactivate Controller
@@ -79,7 +79,14 @@ else 사용자 존재
end
end
== 3단계: 기본 정보 업데이트 ==
== 3단계: 엔티티 수정 준비 (메모리상 변경) ==
note right of Service
**JPA Dirty Checking**
- 트랜잭션 시작 전 엔티티 수정 (메모리상)
- 트랜잭션 커밋 시 변경 감지하여 UPDATE 자동 실행
- 변경된 필드만 UPDATE 쿼리에 포함
end note
alt 이름 변경
Service -> Service: user.setName(newName)
@@ -98,7 +105,7 @@ else 사용자 존재
Service -> Service: user.setEmail(newEmail)
end
== 4단계: 매장 정보 업데이트 ==
== 4단계: 매장 정보 수정 준비 (메모리상 변경) ==
Service -> StoreRepo: findByUserId(userId)
activate StoreRepo
@@ -130,62 +137,98 @@ else 사용자 존재
Service -> UserDB: BEGIN TRANSACTION
activate UserDB
note right of Service
**Optimistic Locking**
- @Version 필드로 동시 수정 감지
- 다른 트랜잭션이 먼저 수정한 경우
- OptimisticLockException 발생
end note
Service -> UserRepo: save(user)
activate UserRepo
UserRepo -> UserDB: UPDATE users\nSET name = ?, phone_number = ?,\nemail = ?, password_hash = ?,\nupdated_at = NOW()\nWHERE user_id = ?
UserDB --> UserRepo: 업데이트 완료
UserRepo -> UserDB: UPDATE users\nSET name = ?, phone_number = ?,\nemail = ?, password_hash = ?,\nupdated_at = NOW(),\nversion = version + 1\nWHERE user_id = ? AND version = ?
UserDB --> UserRepo: 업데이트 완료 (1 row affected)
UserRepo --> Service: User 엔티티
deactivate UserRepo
Service -> StoreRepo: save(store)
activate StoreRepo
StoreRepo -> UserDB: UPDATE stores\nSET store_name = ?, industry = ?,\naddress = ?, business_hours = ?,\nupdated_at = NOW()\nWHERE store_id = ?
UserDB --> StoreRepo: 업데이트 완료
StoreRepo --> Service: Store 엔티티
deactivate StoreRepo
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: ROLLBACK TRANSACTION
deactivate UserDB
deactivate Service
deactivate Controller
else 정상 업데이트
Service -> UserDB: COMMIT TRANSACTION
UserDB --> Service: 트랜잭션 커밋 완료
deactivate UserDB
Service -> StoreRepo: save(store)
activate StoreRepo
StoreRepo -> UserDB: UPDATE stores\nSET store_name = ?, industry = ?,\naddress = ?, business_hours = ?,\nupdated_at = NOW(),\nversion = version + 1\nWHERE store_id = ? AND version = ?
UserDB --> StoreRepo: 업데이트 완료 (1 row affected)
StoreRepo --> Service: Store 엔티티
deactivate StoreRepo
== 6단계: 캐시 무효화 (선택적) ==
note right of Service
**캐시 무효화 전략**
- 세션 정보는 변경 없음 (JWT 유지)
- 프로필 캐시가 있다면 무효화
end note
alt 프로필 캐시 사용 중
Service -> Redis: DEL user:profile:{userId}
activate Redis
Redis --> Service: 캐시 삭제 완료
deactivate Redis
Service -> UserDB: COMMIT TRANSACTION
UserDB --> Service: 트랜잭션 커밋 완료
deactivate UserDB
end
== 7단계: 응답 반환 ==
== 6단계: 캐시 무효화 (선택적) ==
Service -> Service: 응답 DTO 생성\n(UpdateProfileResponse)
Service --> Controller: UpdateProfileResponse\n(userId, userName, email,\nstoreId, storeName)
deactivate Service
note right of Service
**캐시 무효화 전략**
- 세션 정보는 변경 없음 (JWT 유지)
- 프로필 캐시가 있다면 무효화
end note
Controller --> Client: 200 OK\n{"userId": 123,\n"userName": "홍길동",\n"email": "hong@example.com",\n"storeId": 456,\n"storeName": "맛있는집"}
deactivate Controller
alt 프로필 캐시 사용 중
Service -> Redis: DEL 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