!theme mono skinparam sequenceArrowThickness 2 skinparam sequenceParticipantBorderThickness 2 skinparam sequenceActorBorderThickness 2 skinparam sequenceGroupBorderThickness 2 title User Service 내부 시퀀스 다이어그램 (역설계 - 개발 소스 기반) participant "AuthController" as AuthCtrl participant "UserController" as UserCtrl participant "AuthUseCase" as AuthUC participant "UserUseCase" as UserUC participant "AuthDomainService" as AuthDomainSvc participant "UserDomainService" as UserDomainSvc participant "UserRepository" as UserRepo participant "OccupationRepository" as OccupationRepo participant "GoogleAuthAdapter" as AuthACL participant "JwtTokenAdapter" as JwtAdapter participant "EventPublisherAdapter" as EventPub participant "PostgreSQL" as DB participant "Google SSO" as GoogleSSO participant "Azure Service Bus" as ServiceBus == 1. POST /api/users/auth/google-login (구글 로그인) == AuthCtrl -> AuthUC: authenticateWithGoogle(googleLoginRequest) note right: {googleAccessToken, googleIdToken} AuthUC -> AuthDomainSvc: validateGoogleTokens(accessToken, idToken) AuthDomainSvc -> AuthACL: verifyGoogleToken(accessToken, idToken) AuthACL -> GoogleSSO: Google Token Verification API GoogleSSO -> AuthACL: Verified User Info AuthACL -> AuthDomainSvc: GoogleUserInfo 응답 AuthDomainSvc -> UserRepo: findByGoogleId(googleId) UserRepo -> DB: SELECT * FROM users WHERE google_id = ? DB -> UserRepo: User 엔티티 또는 null alt 신규 사용자인 경우 AuthDomainSvc -> AuthDomainSvc: createNewUser(googleUserInfo) AuthDomainSvc -> UserRepo: saveUser(newUser) UserRepo -> DB: INSERT INTO users DB -> UserRepo: 저장 완료 AuthUC -> EventPub: publishUserRegisteredEvent(user) EventPub -> ServiceBus: 신규 사용자 등록 이벤트 발행 end AuthUC -> JwtAdapter: generateTokens(user) JwtAdapter -> AuthUC: {accessToken, refreshToken} AuthUC -> UserRepo: updateLastLoginAt(user.id) UserRepo -> DB: UPDATE users SET last_login_at = NOW() DB -> UserRepo: 업데이트 완료 AuthUC -> AuthCtrl: LoginResponse 반환 note right: {accessToken, refreshToken, userId, isNewUser, message} == 2. POST /api/users/profile/complete (프로필 완료) == AuthCtrl -> UserUC: completeUserProfile(userRegistrationRequest) note right: JWT에서 추출한 userId + {name, birthDate, occupation} UserUC -> UserDomainSvc: validateProfileData(profileData) UserDomainSvc -> OccupationRepo: validateOccupationCode(occupation) OccupationRepo -> DB: SELECT * FROM occupation_types WHERE occupation_code = ? DB -> OccupationRepo: Occupation 엔티티 UserDomainSvc -> UserRepo: findById(userId) UserRepo -> DB: SELECT * FROM users WHERE id = ? DB -> UserRepo: User 엔티티 UserDomainSvc -> UserDomainSvc: updateUserProfile(user, profileData) UserDomainSvc -> UserRepo: updateUser(updatedUser) UserRepo -> DB: UPDATE users SET name = ?, birth_date = ?, occupation = ?, updated_at = NOW() DB -> UserRepo: 업데이트 완료 UserUC -> EventPub: publishUserProfileUpdatedEvent(user) EventPub -> ServiceBus: 회원정보 업데이트 이벤트 발행 UserUC -> AuthCtrl: UserRegistrationResponse 반환 note right: {userId, message, status, profileCompletedAt} == 3. GET /api/users/profile (사용자 기본 정보 조회) == AuthCtrl -> UserUC: getUserProfile(userId) note right: JWT에서 추출한 userId UserUC -> UserRepo: findById(userId) UserRepo -> DB: SELECT * FROM users WHERE id = ? DB -> UserRepo: User 엔티티 UserUC -> UserDomainSvc: calculateAge(user.birthDate) note right: 생년월일로부터 나이 계산 UserUC -> OccupationRepo: findOccupationName(user.occupation) OccupationRepo -> DB: SELECT occupation_name FROM occupation_types WHERE occupation_code = ? DB -> OccupationRepo: 직업명 UserUC -> AuthCtrl: UserProfileResponse 반환 note right: {userId, name, age, occupation, registeredAt, lastLoginAt} == 4. GET /api/users/occupations (직업 목록 조회) == UserCtrl -> UserUC: getAllOccupations() UserUC -> OccupationRepo: findAllOccupations() OccupationRepo -> DB: SELECT * FROM occupation_types ORDER BY category, occupation_name DB -> OccupationRepo: Occupation 목록 UserUC -> UserCtrl: OccupationListResponse 반환 note right: {occupations: [{occupationCode, occupationName, category}], totalCount} == 5. GET /api/users/occupations/name (직업명 조회) == UserCtrl -> UserUC: getOccupationName(occupationCode) UserUC -> OccupationRepo: findOccupationByCode(occupationCode) OccupationRepo -> DB: SELECT * FROM occupation_types WHERE occupation_code = ? DB -> OccupationRepo: Occupation 엔티티 UserUC -> UserCtrl: OccupationNameResponse 반환 note right: {occupationCode, occupationName} == 6. GET /api/users/occupations/code (직업코드 조회) == UserCtrl -> UserUC: getOccupationCode(occupationName) UserUC -> OccupationRepo: findOccupationByName(occupationName) OccupationRepo -> DB: SELECT * FROM occupation_types WHERE occupation_name = ? DB -> OccupationRepo: Occupation 엔티티 UserUC -> UserCtrl: OccupationCodeResponse 반환 note right: {occupationCode, occupationName} == 예외 처리 == alt 인증 실패 시 AuthACL -> AuthUC: AuthenticationException AuthUC -> AuthCtrl: 401 Unauthorized 응답 end alt 사용자 정보 없음 UserRepo -> UserUC: UserNotFoundException UserUC -> AuthCtrl: 404 Not Found 응답 end alt 입력 데이터 검증 실패 UserDomainSvc -> UserUC: ValidationException UserUC -> AuthCtrl: 400 Bad Request 응답 note right: 유효성 검증 오류 메시지 포함 end alt 직업 코드 없음 OccupationRepo -> UserUC: OccupationNotFoundException UserUC -> UserCtrl: 404 Not Found 응답 end == 트랜잭션 처리 == note over UserUC, UserRepo **트랜잭션 범위** - 사용자 생성/수정 작업 - 이벤트 발행은 트랜잭션 커밋 후 - @Transactional 어노테이션 적용 end note == 보안 처리 == note over AuthUC, AuthACL **보안 고려사항** - JWT 토큰 생성 시 적절한 만료시간 설정 - Google SSO 응답 데이터 검증 - 개인정보 로깅 제외 - 비밀번호 없이 OAuth만 사용 end note