@startuml !theme mono title User Service 클래스 다이어그램 (상세) ' ==================== ' 공통 컴포넌트 (참조) ' ==================== package "com.kt.event.common" <> { abstract class BaseTimeEntity { - createdAt: LocalDateTime - updatedAt: LocalDateTime } interface ErrorCode { + getCode(): String + getMessage(): String } class BusinessException extends RuntimeException { - errorCode: ErrorCode + getErrorCode(): ErrorCode } interface JwtTokenProvider { + createAccessToken(): String + validateToken(): boolean + getExpirationFromToken(): Date } } package "com.kt.event.user" { ' ==================== ' Presentation Layer ' ==================== package "controller" { class UserController { - userService: UserService - authenticationService: AuthenticationService ' UFR-USER-010: 회원가입 + register(request: RegisterRequest): ResponseEntity ' UFR-USER-020: 로그인 + login(request: LoginRequest): ResponseEntity ' UFR-USER-040: 로그아웃 + logout(authHeader: String): ResponseEntity ' UFR-USER-030: 프로필 관리 + getProfile(principal: UserPrincipal): ResponseEntity + updateProfile(principal: UserPrincipal, request: UpdateProfileRequest): ResponseEntity + changePassword(principal: UserPrincipal, request: ChangePasswordRequest): ResponseEntity } } ' ==================== ' Business Layer (Service) ' ==================== package "service" { interface UserService { + register(request: RegisterRequest): RegisterResponse + getProfile(userId: UUID): ProfileResponse + updateProfile(userId: UUID, request: UpdateProfileRequest): ProfileResponse + changePassword(userId: UUID, request: ChangePasswordRequest): void + updateLastLoginAt(userId: UUID): void } interface AuthenticationService { + login(request: LoginRequest): LoginResponse + logout(token: String): LogoutResponse } } package "service.impl" { class UserServiceImpl implements UserService { - userRepository: UserRepository - storeRepository: StoreRepository - passwordEncoder: PasswordEncoder - jwtTokenProvider: JwtTokenProvider - redisTemplate: RedisTemplate ' UFR-USER-010: 회원가입 + register(request: RegisterRequest): RegisterResponse ' UFR-USER-030: 프로필 관리 + getProfile(userId: UUID): ProfileResponse + updateProfile(userId: UUID, request: UpdateProfileRequest): ProfileResponse + changePassword(userId: UUID, request: ChangePasswordRequest): void ' UFR-USER-020: 로그인 시각 업데이트 + updateLastLoginAt(userId: UUID): void ' 내부 메소드 - saveSession(token: String, userId: UUID, role: String): void } class AuthenticationServiceImpl implements AuthenticationService { - userRepository: UserRepository - storeRepository: StoreRepository - passwordEncoder: PasswordEncoder - jwtTokenProvider: JwtTokenProvider - userService: UserService - redisTemplate: RedisTemplate ' UFR-USER-020: 로그인 + login(request: LoginRequest): LoginResponse ' UFR-USER-040: 로그아웃 + logout(token: String): LogoutResponse ' 내부 메소드 - saveSession(token: String, userId: UUID, role: String): void } } ' ==================== ' Data Access Layer ' ==================== package "repository" { interface UserRepository extends JpaRepository { + findByEmail(email: String): Optional + findByPhoneNumber(phoneNumber: String): Optional + existsByEmail(email: String): boolean + existsByPhoneNumber(phoneNumber: String): boolean + updateLastLoginAt(userId: UUID, lastLoginAt: LocalDateTime): void } interface StoreRepository extends JpaRepository { + findByUserId(userId: UUID): Optional } } ' ==================== ' Domain Layer ' ==================== package "entity" { class User extends BaseTimeEntity { - id: UUID - name: String - phoneNumber: String - email: String - passwordHash: String - role: UserRole - status: UserStatus - lastLoginAt: LocalDateTime - store: Store ' 비즈니스 로직 + updateLastLoginAt(): void + changePassword(newPasswordHash: String): void + updateProfile(name: String, email: String, phoneNumber: String): void + setStore(store: Store): void } enum UserRole { OWNER ADMIN } enum UserStatus { ACTIVE INACTIVE LOCKED WITHDRAWN } class Store extends BaseTimeEntity { - id: UUID - name: String - industry: String - address: String - businessHours: String - user: User ' 비즈니스 로직 + updateInfo(name: String, industry: String, address: String, businessHours: String): void ~ setUser(user: User): void } } ' ==================== ' DTO Layer ' ==================== package "dto.request" { class RegisterRequest { - name: String - phoneNumber: String - email: String - password: String - storeName: String - industry: String - address: String - businessHours: String } class LoginRequest { - email: String - password: String } class UpdateProfileRequest { - name: String - email: String - phoneNumber: String - storeName: String - industry: String - address: String - businessHours: String } class ChangePasswordRequest { - currentPassword: String - newPassword: String } } package "dto.response" { class RegisterResponse { - token: String - userId: UUID - userName: String - storeId: UUID - storeName: String } class LoginResponse { - token: String - userId: UUID - userName: String - role: String - email: String } class LogoutResponse { - success: boolean - message: String } class ProfileResponse { - userId: UUID - userName: String - phoneNumber: String - email: String - role: String - storeId: UUID - storeName: String - industry: String - address: String - businessHours: String - createdAt: LocalDateTime - lastLoginAt: LocalDateTime } } ' ==================== ' Exception Layer ' ==================== package "exception" { enum UserErrorCode { USER_DUPLICATE_EMAIL USER_DUPLICATE_PHONE USER_NOT_FOUND AUTH_FAILED AUTH_INVALID_TOKEN AUTH_TOKEN_EXPIRED AUTH_UNAUTHORIZED PWD_INVALID_CURRENT PWD_SAME_AS_CURRENT - errorCode: ErrorCode + getCode(): String + getMessage(): String } } ' ==================== ' Configuration Layer ' ==================== package "config" { class SecurityConfig { - jwtTokenProvider: JwtTokenProvider - allowedOrigins: String + filterChain(http: HttpSecurity): SecurityFilterChain + corsConfigurationSource(): CorsConfigurationSource + passwordEncoder(): PasswordEncoder } class RedisConfig { - redisHost: String - redisPort: int + redisConnectionFactory(): RedisConnectionFactory + redisTemplate(): RedisTemplate } class AsyncConfig { + taskExecutor(): Executor } class SwaggerConfig { + customOpenAPI(): OpenAPI } } } ' ==================== ' Layer 간 의존성 관계 ' ==================== ' Controller → Service UserController --> UserService : uses UserController --> AuthenticationService : uses ' Service → Repository UserServiceImpl --> UserRepository : uses UserServiceImpl --> StoreRepository : uses AuthenticationServiceImpl --> UserRepository : uses AuthenticationServiceImpl --> StoreRepository : uses AuthenticationServiceImpl --> UserService : uses ' Service → Entity (도메인 로직 호출) UserServiceImpl ..> User : creates/updates UserServiceImpl ..> Store : creates/updates AuthenticationServiceImpl ..> User : reads ' Repository → Entity UserRepository --> User : manages StoreRepository --> Store : manages ' Service → DTO UserServiceImpl ..> RegisterRequest : receives UserServiceImpl ..> UpdateProfileRequest : receives UserServiceImpl ..> ChangePasswordRequest : receives UserServiceImpl ..> RegisterResponse : returns UserServiceImpl ..> ProfileResponse : returns AuthenticationServiceImpl ..> LoginRequest : receives AuthenticationServiceImpl ..> LoginResponse : returns AuthenticationServiceImpl ..> LogoutResponse : returns ' Controller → DTO UserController ..> RegisterRequest : receives UserController ..> LoginRequest : receives UserController ..> UpdateProfileRequest : receives UserController ..> ChangePasswordRequest : receives UserController ..> RegisterResponse : returns UserController ..> LoginResponse : returns UserController ..> LogoutResponse : returns UserController ..> ProfileResponse : returns ' Entity 관계 User "1" -- "0..1" Store : has > User +-- UserRole User +-- UserStatus ' Exception UserServiceImpl ..> UserErrorCode : throws AuthenticationServiceImpl ..> UserErrorCode : throws UserErrorCode --> ErrorCode : wraps ' Configuration SecurityConfig --> JwtTokenProvider : uses SecurityConfig ..> PasswordEncoder : creates UserServiceImpl --> PasswordEncoder : uses AuthenticationServiceImpl --> PasswordEncoder : uses ' Common 컴포넌트 사용 User --|> BaseTimeEntity Store --|> BaseTimeEntity UserServiceImpl ..> JwtTokenProvider : uses AuthenticationServiceImpl ..> JwtTokenProvider : uses UserServiceImpl ..> BusinessException : throws AuthenticationServiceImpl ..> BusinessException : throws ' Notes note top of UserController Presentation Layer - REST API 엔드포인트 제공 - 요청/응답 DTO 변환 - 인증 정보 추출 (UserPrincipal) - Swagger 문서화 end note note top of UserService Business Layer - 비즈니스 로직 처리 - 트랜잭션 관리 - 도메인 객체 조작 - 검증 및 예외 처리 end note note top of UserRepository Data Access Layer - JPA 기반 데이터 액세스 - CRUD 및 커스텀 쿼리 - 트랜잭션 경계 end note note top of User Domain Layer - 핵심 비즈니스 엔티티 - 도메인 로직 포함 - 불변성 및 일관성 보장 end note note right of UserServiceImpl 핵심 기능 1. 회원가입 (register) - 중복 검증 (이메일, 전화번호) - 비밀번호 해싱 - User/Store 생성 - JWT 토큰 발급 - Redis 세션 저장 2. 프로필 관리 - 프로필 조회/수정 - 비밀번호 변경 (현재 비밀번호 검증) 3. 로그인 시각 업데이트 - 비동기 처리 (@Async) end note note right of AuthenticationServiceImpl 핵심 기능 1. 로그인 (login) - 이메일/비밀번호 검증 - JWT 토큰 발급 - Redis 세션 저장 - 최종 로그인 시각 업데이트 2. 로그아웃 (logout) - JWT 토큰 검증 - Redis 세션 삭제 - JWT Blacklist 추가 end note note bottom of User User-Store 관계 - OneToOne 양방향 관계 - User가 Store를 소유 - Cascade ALL, Orphan Removal - Lazy Loading end note @enduml