@startuml user-회원가입 !theme mono title User Service - 회원가입 내부 시퀀스 (UFR-USER-010) participant "UserController" as Controller <> participant "UserService" as Service <> participant "UserRepository" as UserRepo <> participant "StoreRepository" as StoreRepo <> participant "PasswordEncoder" as PwdEncoder <> participant "JwtTokenProvider" as JwtProvider <> participant "Redis\nCache" as Redis <> participant "User DB\n(PostgreSQL)" as UserDB <> actor Client note over Controller, UserDB **UFR-USER-010: 회원가입** - 기본 정보: 이름, 전화번호, 이메일, 비밀번호 - 매장 정보: 매장명, 업종, 주소, 영업시간, 사업자번호 - 이메일/전화번호 중복 검사 - 트랜잭션 처리 - JWT 토큰 발급 end note Client -> Controller: POST /api/users/register\n{"name": "홍길동",\n"phoneNumber": "01012345678",\n"email": "hong@example.com",\n"password": "password123"} activate Controller Controller -> Controller: 입력값 검증\n(이메일 형식, 비밀번호 8자 이상 등) Controller -> Service: register(RegisterRequest) activate Service == 1단계: 이메일 중복 확인 == Service -> UserRepo: findByEmail(email) activate UserRepo UserRepo -> UserDB: 이메일로 사용자 조회\n(중복 가입 확인) activate UserDB UserDB --> UserRepo: 조회 결과 반환 또는 없음 deactivate UserDB UserRepo --> Service: Optional deactivate UserRepo alt 이메일 중복 존재 Service --> Controller: throw DuplicateEmailException\n("이미 가입된 이메일입니다") Controller --> Client: 400 Bad Request\n{"code": "USER_001",\n"message": "이미 가입된 이메일입니다"} deactivate Service deactivate Controller else 이메일 신규 == 2단계: 전화번호 중복 확인 == Service -> UserRepo: findByPhoneNumber(phoneNumber) activate UserRepo UserRepo -> UserDB: 전화번호로 사용자 조회\n(중복 가입 확인) activate UserDB UserDB --> UserRepo: 조회 결과 반환 또는 없음 deactivate UserDB UserRepo --> Service: Optional deactivate UserRepo alt 전화번호 중복 존재 Service --> Controller: throw DuplicatePhoneException\n("이미 가입된 전화번호입니다") Controller --> Client: 400 Bad Request\n{"code": "USER_002",\n"message": "이미 가입된 전화번호입니다"} deactivate Service deactivate Controller else 신규 사용자 == 3단계: 비밀번호 해싱 == Service -> PwdEncoder: encode(rawPassword) activate PwdEncoder PwdEncoder -> PwdEncoder: bcrypt 해싱\n(Cost Factor 10) PwdEncoder --> Service: passwordHash deactivate PwdEncoder == 4단계: 데이터베이스 트랜잭션 == Service -> UserDB: 트랜잭션 시작 activate UserDB Service -> UserRepo: save(User) activate UserRepo UserRepo -> UserDB: 사용자 정보 저장\n(이름, 전화번호, 이메일,\n비밀번호해시, 생성일시)\n저장 후 사용자ID 반환 UserDB --> UserRepo: 생성된 사용자ID UserRepo --> Service: User 엔티티\n(userId 포함) deactivate UserRepo Service -> StoreRepo: save(Store) activate StoreRepo StoreRepo -> UserDB: 매장 정보 저장\n(사용자ID, 매장명, 업종,\n주소, 사업자번호, 영업시간)\n저장 후 매장ID 반환 UserDB --> StoreRepo: 생성된 매장ID StoreRepo --> Service: Store 엔티티\n(storeId 포함) deactivate StoreRepo Service -> UserDB: 트랜잭션 커밋 UserDB --> Service: 커밋 완료 deactivate UserDB == 5단계: JWT 토큰 생성 == Service -> JwtProvider: generateToken(userId, role) activate JwtProvider JwtProvider -> JwtProvider: JWT 토큰 생성\n(Claims: userId, role=OWNER,\nexp=7일) JwtProvider --> Service: JWT 토큰 deactivate JwtProvider == 6단계: 세션 저장 == Service -> Redis: 세션 정보 저장\nKey: user:session:{token}\nValue: {userId, role}\nTTL: 7일 activate Redis Redis --> Service: 저장 완료 deactivate Redis == 7단계: 응답 반환 == Service -> Service: 회원가입 응답 DTO 생성 Service --> Controller: RegisterResponse\n{token, userId, userName, storeId, storeName} deactivate Service Controller --> Client: 201 Created\n{"token": "eyJhbGc...",\n"userId": 123,\n"userName": "홍길동",\n"storeId": 456,\n"storeName": "맛있는집"} deactivate Controller end end end note over Controller, UserDB **Transaction Rollback 처리** - 트랜잭션 실패 시 자동 Rollback - User/Store INSERT 중 하나라도 실패 시 전체 롤백 - 예외: DataAccessException, ConstraintViolationException **보안 처리** - 비밀번호: bcrypt 해싱 (Cost Factor 10) - JWT 토큰: 7일 만료, 서버 세션과 동기화 **성능 목표** - 평균 응답 시간: 1.0초 이내 - P95 응답 시간: 1.5초 이내 - 트랜잭션 처리: 0.5초 이내 **에러 코드** - USER_001: 이메일 중복 - USER_002: 전화번호 중복 end note @enduml