mirror of
https://github.com/cna-bootcamp/phonebill.git
synced 2026-06-13 03:59:10 +00:00
release
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
|
||||
title Auth Service - Simple Class Design
|
||||
|
||||
package "com.unicorn.phonebill.auth" {
|
||||
|
||||
package "controller" {
|
||||
class AuthController {
|
||||
+login()
|
||||
+logout()
|
||||
+verifyToken()
|
||||
+refreshToken()
|
||||
+getUserPermissions()
|
||||
+checkPermission()
|
||||
+getUserInfo()
|
||||
}
|
||||
}
|
||||
|
||||
package "dto" {
|
||||
class LoginRequest
|
||||
class LoginResponse
|
||||
class RefreshTokenRequest
|
||||
class RefreshTokenResponse
|
||||
class TokenVerifyResponse
|
||||
class PermissionCheckRequest
|
||||
class PermissionCheckResponse
|
||||
class PermissionsResponse
|
||||
class UserInfoResponse
|
||||
class UserInfo
|
||||
class Permission
|
||||
class SuccessResponse
|
||||
}
|
||||
|
||||
package "service" {
|
||||
interface AuthService
|
||||
class AuthServiceImpl
|
||||
interface TokenService
|
||||
class TokenServiceImpl
|
||||
interface PermissionService
|
||||
class PermissionServiceImpl
|
||||
}
|
||||
|
||||
package "domain" {
|
||||
class User
|
||||
enum UserStatus
|
||||
class UserSession
|
||||
class AuthenticationResult
|
||||
class DecodedToken
|
||||
class PermissionResult
|
||||
class TokenRefreshResult
|
||||
class UserInfoDetail
|
||||
}
|
||||
|
||||
package "repository" {
|
||||
interface UserRepository
|
||||
interface UserPermissionRepository
|
||||
interface LoginHistoryRepository
|
||||
|
||||
package "entity" {
|
||||
class UserEntity
|
||||
class UserPermissionEntity
|
||||
class LoginHistoryEntity
|
||||
}
|
||||
|
||||
package "jpa" {
|
||||
interface UserJpaRepository
|
||||
interface UserPermissionJpaRepository
|
||||
interface LoginHistoryJpaRepository
|
||||
}
|
||||
}
|
||||
|
||||
package "config" {
|
||||
class SecurityConfig
|
||||
class JwtConfig
|
||||
class RedisConfig
|
||||
}
|
||||
}
|
||||
|
||||
' Common Base Classes
|
||||
package "Common Module" <<External>> {
|
||||
class ApiResponse<T>
|
||||
class ErrorResponse
|
||||
abstract class BaseTimeEntity
|
||||
enum ErrorCode
|
||||
class BusinessException
|
||||
}
|
||||
|
||||
' 관계 정의 (간단화)
|
||||
AuthController --> AuthService
|
||||
AuthController --> TokenService
|
||||
|
||||
AuthServiceImpl --> UserRepository
|
||||
AuthServiceImpl --> TokenService
|
||||
AuthServiceImpl --> PermissionService
|
||||
AuthServiceImpl --> LoginHistoryRepository
|
||||
|
||||
PermissionServiceImpl --> UserPermissionRepository
|
||||
|
||||
UserRepository --> UserEntity
|
||||
UserPermissionRepository --> UserPermissionEntity
|
||||
LoginHistoryRepository --> LoginHistoryEntity
|
||||
|
||||
UserEntity --|> BaseTimeEntity
|
||||
UserPermissionEntity --|> BaseTimeEntity
|
||||
LoginHistoryEntity --|> BaseTimeEntity
|
||||
|
||||
AuthService <|-- AuthServiceImpl
|
||||
TokenService <|-- TokenServiceImpl
|
||||
PermissionService <|-- PermissionServiceImpl
|
||||
|
||||
UserRepository <|-- UserJpaRepository
|
||||
UserPermissionRepository <|-- UserPermissionJpaRepository
|
||||
LoginHistoryRepository <|-- LoginHistoryJpaRepository
|
||||
|
||||
User --> UserStatus
|
||||
|
||||
' API 매핑표
|
||||
note as N1
|
||||
<b>AuthController API Mapping</b>
|
||||
===
|
||||
<b>POST /auth/login</b>
|
||||
- Method: login(LoginRequest)
|
||||
- Response: ApiResponse<LoginResponse>
|
||||
- Description: 사용자 로그인 처리
|
||||
|
||||
<b>POST /auth/logout</b>
|
||||
- Method: logout()
|
||||
- Response: ApiResponse<SuccessResponse>
|
||||
- Description: 사용자 로그아웃 처리
|
||||
|
||||
<b>GET /auth/verify</b>
|
||||
- Method: verifyToken()
|
||||
- Response: ApiResponse<TokenVerifyResponse>
|
||||
- Description: JWT 토큰 검증
|
||||
|
||||
<b>POST /auth/refresh</b>
|
||||
- Method: refreshToken(RefreshTokenRequest)
|
||||
- Response: ApiResponse<RefreshTokenResponse>
|
||||
- Description: 토큰 갱신
|
||||
|
||||
<b>GET /auth/permissions</b>
|
||||
- Method: getUserPermissions()
|
||||
- Response: ApiResponse<PermissionsResponse>
|
||||
- Description: 사용자 권한 조회
|
||||
|
||||
<b>POST /auth/permissions/check</b>
|
||||
- Method: checkPermission(PermissionCheckRequest)
|
||||
- Response: ApiResponse<PermissionCheckResponse>
|
||||
- Description: 특정 서비스 접근 권한 확인
|
||||
|
||||
<b>GET /auth/user-info</b>
|
||||
- Method: getUserInfo()
|
||||
- Response: ApiResponse<UserInfoResponse>
|
||||
- Description: 사용자 정보 조회
|
||||
end note
|
||||
|
||||
N1 .. AuthController
|
||||
|
||||
' 패키지 구조 설명
|
||||
note as N2
|
||||
<b>패키지 구조 (Layered Architecture)</b>
|
||||
===
|
||||
<b>controller</b>
|
||||
- AuthController: REST API 엔드포인트
|
||||
|
||||
<b>dto</b>
|
||||
- Request/Response 객체들
|
||||
- API 계층과 Service 계층 간 데이터 전송
|
||||
|
||||
<b>service</b>
|
||||
- AuthService: 인증/인가 비즈니스 로직
|
||||
- TokenService: JWT 토큰 관리
|
||||
- PermissionService: 권한 관리
|
||||
|
||||
<b>domain</b>
|
||||
- 도메인 모델 및 비즈니스 엔티티
|
||||
- 비즈니스 로직 포함
|
||||
|
||||
<b>repository</b>
|
||||
- 데이터 접근 계층
|
||||
- entity: JPA 엔티티
|
||||
- jpa: JPA Repository 인터페이스
|
||||
|
||||
<b>config</b>
|
||||
- 설정 클래스들 (Security, JWT, Redis)
|
||||
end note
|
||||
|
||||
N2 .. "com.unicorn.phonebill.auth"
|
||||
|
||||
' 핵심 기능 설명
|
||||
note as N3
|
||||
<b>핵심 기능</b>
|
||||
===
|
||||
<b>인증 (Authentication)</b>
|
||||
- 로그인/로그아웃 처리
|
||||
- JWT 토큰 생성/검증/갱신
|
||||
- 세션 관리 (Redis 캐시)
|
||||
- 로그인 실패 횟수 관리 (5회 실패 시 30분 잠금)
|
||||
|
||||
<b>인가 (Authorization)</b>
|
||||
- 서비스별 접근 권한 확인
|
||||
- 권한 캐싱 (Redis, TTL: 4시간)
|
||||
- Cache-Aside 패턴 적용
|
||||
|
||||
<b>보안</b>
|
||||
- bcrypt 패스워드 해싱
|
||||
- JWT 토큰 기반 인증
|
||||
- Redis 세션 캐싱 (TTL: 30분/24시간)
|
||||
- IP 기반 로그인 이력 추적
|
||||
end note
|
||||
|
||||
N3 .. AuthServiceImpl
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,564 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
|
||||
title Auth Service - Detailed Class Design
|
||||
|
||||
package "com.unicorn.phonebill.auth" {
|
||||
|
||||
package "controller" {
|
||||
class AuthController {
|
||||
-authService: AuthService
|
||||
-tokenService: TokenService
|
||||
|
||||
+login(request: LoginRequest): ApiResponse<LoginResponse>
|
||||
+logout(): ApiResponse<SuccessResponse>
|
||||
+verifyToken(): ApiResponse<TokenVerifyResponse>
|
||||
+refreshToken(request: RefreshTokenRequest): ApiResponse<RefreshTokenResponse>
|
||||
+getUserPermissions(): ApiResponse<PermissionsResponse>
|
||||
+checkPermission(request: PermissionCheckRequest): ApiResponse<PermissionCheckResponse>
|
||||
+getUserInfo(): ApiResponse<UserInfoResponse>
|
||||
}
|
||||
}
|
||||
|
||||
package "dto" {
|
||||
class LoginRequest {
|
||||
-userId: String
|
||||
-password: String
|
||||
-autoLogin: boolean
|
||||
|
||||
+getUserId(): String
|
||||
+getPassword(): String
|
||||
+isAutoLogin(): boolean
|
||||
+validate(): void
|
||||
}
|
||||
|
||||
class LoginResponse {
|
||||
-accessToken: String
|
||||
-refreshToken: String
|
||||
-expiresIn: int
|
||||
-user: UserInfo
|
||||
|
||||
+getAccessToken(): String
|
||||
+getRefreshToken(): String
|
||||
+getExpiresIn(): int
|
||||
+getUser(): UserInfo
|
||||
}
|
||||
|
||||
class RefreshTokenRequest {
|
||||
-refreshToken: String
|
||||
|
||||
+getRefreshToken(): String
|
||||
+validate(): void
|
||||
}
|
||||
|
||||
class RefreshTokenResponse {
|
||||
-accessToken: String
|
||||
-expiresIn: int
|
||||
|
||||
+getAccessToken(): String
|
||||
+getExpiresIn(): int
|
||||
}
|
||||
|
||||
class TokenVerifyResponse {
|
||||
-valid: boolean
|
||||
-user: UserInfo
|
||||
-expiresIn: int
|
||||
|
||||
+isValid(): boolean
|
||||
+getUser(): UserInfo
|
||||
+getExpiresIn(): int
|
||||
}
|
||||
|
||||
class PermissionCheckRequest {
|
||||
-serviceType: String
|
||||
|
||||
+getServiceType(): String
|
||||
+validate(): void
|
||||
}
|
||||
|
||||
class PermissionCheckResponse {
|
||||
-serviceType: String
|
||||
-hasPermission: boolean
|
||||
-permissionDetails: Permission
|
||||
|
||||
+getServiceType(): String
|
||||
+isHasPermission(): boolean
|
||||
+getPermissionDetails(): Permission
|
||||
}
|
||||
|
||||
class PermissionsResponse {
|
||||
-userId: String
|
||||
-permissions: List<Permission>
|
||||
|
||||
+getUserId(): String
|
||||
+getPermissions(): List<Permission>
|
||||
}
|
||||
|
||||
class UserInfoResponse {
|
||||
-userId: String
|
||||
-userName: String
|
||||
-phoneNumber: String
|
||||
-email: String
|
||||
-status: String
|
||||
-lastLoginAt: LocalDateTime
|
||||
-permissions: List<String>
|
||||
|
||||
+getUserId(): String
|
||||
+getUserName(): String
|
||||
+getPhoneNumber(): String
|
||||
+getEmail(): String
|
||||
+getStatus(): String
|
||||
+getLastLoginAt(): LocalDateTime
|
||||
+getPermissions(): List<String>
|
||||
}
|
||||
|
||||
class UserInfo {
|
||||
-userId: String
|
||||
-userName: String
|
||||
-phoneNumber: String
|
||||
-permissions: List<String>
|
||||
|
||||
+getUserId(): String
|
||||
+getUserName(): String
|
||||
+getPhoneNumber(): String
|
||||
+getPermissions(): List<String>
|
||||
}
|
||||
|
||||
class Permission {
|
||||
-permission: String
|
||||
-description: String
|
||||
-granted: boolean
|
||||
|
||||
+getPermission(): String
|
||||
+getDescription(): String
|
||||
+isGranted(): boolean
|
||||
}
|
||||
|
||||
class SuccessResponse {
|
||||
-message: String
|
||||
|
||||
+getMessage(): String
|
||||
}
|
||||
}
|
||||
|
||||
package "service" {
|
||||
interface AuthService {
|
||||
+authenticateUser(userId: String, password: String): AuthenticationResult
|
||||
+getUserInfo(userId: String): UserInfoDetail
|
||||
+refreshUserToken(userId: String): TokenRefreshResult
|
||||
+checkServicePermission(userId: String, serviceType: String): PermissionResult
|
||||
+invalidateUserPermissions(userId: String): void
|
||||
}
|
||||
|
||||
class AuthServiceImpl {
|
||||
-userRepository: UserRepository
|
||||
-tokenService: TokenService
|
||||
-permissionService: PermissionService
|
||||
-redisTemplate: RedisTemplate
|
||||
-passwordEncoder: PasswordEncoder
|
||||
-loginHistoryRepository: LoginHistoryRepository
|
||||
|
||||
+authenticateUser(userId: String, password: String): AuthenticationResult
|
||||
+getUserInfo(userId: String): UserInfoDetail
|
||||
+refreshUserToken(userId: String): TokenRefreshResult
|
||||
+checkServicePermission(userId: String, serviceType: String): PermissionResult
|
||||
+invalidateUserPermissions(userId: String): void
|
||||
-validateLoginAttempts(user: User): void
|
||||
-handleFailedLogin(userId: String): void
|
||||
-handleSuccessfulLogin(user: User): void
|
||||
-createUserSession(user: User, autoLogin: boolean): void
|
||||
-saveLoginHistory(userId: String, ipAddress: String): void
|
||||
}
|
||||
|
||||
interface TokenService {
|
||||
+generateAccessToken(userInfo: UserInfoDetail): String
|
||||
+generateRefreshToken(userId: String): String
|
||||
+validateAccessToken(token: String): DecodedToken
|
||||
+validateRefreshToken(token: String): boolean
|
||||
+extractUserId(token: String): String
|
||||
+getTokenExpiration(token: String): LocalDateTime
|
||||
}
|
||||
|
||||
class TokenServiceImpl {
|
||||
-jwtSecret: String
|
||||
-accessTokenExpiry: int
|
||||
-refreshTokenExpiry: int
|
||||
|
||||
+generateAccessToken(userInfo: UserInfoDetail): String
|
||||
+generateRefreshToken(userId: String): String
|
||||
+validateAccessToken(token: String): DecodedToken
|
||||
+validateRefreshToken(token: String): boolean
|
||||
+extractUserId(token: String): String
|
||||
+getTokenExpiration(token: String): LocalDateTime
|
||||
-createJwtToken(subject: String, claims: Map<String, Object>, expiry: int): String
|
||||
-parseJwtToken(token: String): Claims
|
||||
}
|
||||
|
||||
interface PermissionService {
|
||||
+validateServiceAccess(permissions: List<String>, serviceType: String): PermissionResult
|
||||
+getUserPermissions(userId: String): List<Permission>
|
||||
+cacheUserPermissions(userId: String, permissions: List<Permission>): void
|
||||
+invalidateUserPermissions(userId: String): void
|
||||
}
|
||||
|
||||
class PermissionServiceImpl {
|
||||
-userPermissionRepository: UserPermissionRepository
|
||||
-redisTemplate: RedisTemplate
|
||||
|
||||
+validateServiceAccess(permissions: List<String>, serviceType: String): PermissionResult
|
||||
+getUserPermissions(userId: String): List<Permission>
|
||||
+cacheUserPermissions(userId: String, permissions: List<Permission>): void
|
||||
+invalidateUserPermissions(userId: String): void
|
||||
-mapServiceTypeToPermission(serviceType: String): String
|
||||
-checkPermissionGranted(permissions: List<String>, requiredPermission: String): boolean
|
||||
}
|
||||
}
|
||||
|
||||
package "domain" {
|
||||
class User {
|
||||
-userId: String
|
||||
-userName: String
|
||||
-phoneNumber: String
|
||||
-email: String
|
||||
-passwordHash: String
|
||||
-salt: String
|
||||
-status: UserStatus
|
||||
-loginAttemptCount: int
|
||||
-lockedUntil: LocalDateTime
|
||||
-lastLoginAt: LocalDateTime
|
||||
-createdAt: LocalDateTime
|
||||
-updatedAt: LocalDateTime
|
||||
|
||||
+getUserId(): String
|
||||
+getUserName(): String
|
||||
+getPhoneNumber(): String
|
||||
+getEmail(): String
|
||||
+getPasswordHash(): String
|
||||
+getSalt(): String
|
||||
+getStatus(): UserStatus
|
||||
+getLoginAttemptCount(): int
|
||||
+getLockedUntil(): LocalDateTime
|
||||
+getLastLoginAt(): LocalDateTime
|
||||
+isAccountLocked(): boolean
|
||||
+canAttemptLogin(): boolean
|
||||
+incrementLoginAttempt(): void
|
||||
+resetLoginAttempt(): void
|
||||
+lockAccount(duration: Duration): void
|
||||
+updateLastLoginAt(loginTime: LocalDateTime): void
|
||||
}
|
||||
|
||||
enum UserStatus {
|
||||
ACTIVE
|
||||
INACTIVE
|
||||
LOCKED
|
||||
|
||||
+getValue(): String
|
||||
}
|
||||
|
||||
class UserSession {
|
||||
-userId: String
|
||||
-sessionId: String
|
||||
-userInfo: UserInfoDetail
|
||||
-permissions: List<String>
|
||||
-lastAccessTime: LocalDateTime
|
||||
-createdAt: LocalDateTime
|
||||
-ttl: Duration
|
||||
|
||||
+getUserId(): String
|
||||
+getSessionId(): String
|
||||
+getUserInfo(): UserInfoDetail
|
||||
+getPermissions(): List<String>
|
||||
+getLastAccessTime(): LocalDateTime
|
||||
+getCreatedAt(): LocalDateTime
|
||||
+getTtl(): Duration
|
||||
+updateLastAccessTime(): void
|
||||
+isExpired(): boolean
|
||||
}
|
||||
|
||||
class AuthenticationResult {
|
||||
-success: boolean
|
||||
-accessToken: String
|
||||
-refreshToken: String
|
||||
-userInfo: UserInfoDetail
|
||||
-errorMessage: String
|
||||
|
||||
+isSuccess(): boolean
|
||||
+getAccessToken(): String
|
||||
+getRefreshToken(): String
|
||||
+getUserInfo(): UserInfoDetail
|
||||
+getErrorMessage(): String
|
||||
}
|
||||
|
||||
class DecodedToken {
|
||||
-userId: String
|
||||
-permissions: List<String>
|
||||
-expiresAt: LocalDateTime
|
||||
-issuedAt: LocalDateTime
|
||||
|
||||
+getUserId(): String
|
||||
+getPermissions(): List<String>
|
||||
+getExpiresAt(): LocalDateTime
|
||||
+getIssuedAt(): LocalDateTime
|
||||
+isExpired(): boolean
|
||||
}
|
||||
|
||||
class PermissionResult {
|
||||
-granted: boolean
|
||||
-serviceType: String
|
||||
-reason: String
|
||||
-permissionDetails: Permission
|
||||
|
||||
+isGranted(): boolean
|
||||
+getServiceType(): String
|
||||
+getReason(): String
|
||||
+getPermissionDetails(): Permission
|
||||
}
|
||||
|
||||
class TokenRefreshResult {
|
||||
-newAccessToken: String
|
||||
-expiresIn: int
|
||||
|
||||
+getNewAccessToken(): String
|
||||
+getExpiresIn(): int
|
||||
}
|
||||
|
||||
class UserInfoDetail {
|
||||
-userId: String
|
||||
-userName: String
|
||||
-phoneNumber: String
|
||||
-email: String
|
||||
-status: UserStatus
|
||||
-lastLoginAt: LocalDateTime
|
||||
-permissions: List<String>
|
||||
|
||||
+getUserId(): String
|
||||
+getUserName(): String
|
||||
+getPhoneNumber(): String
|
||||
+getEmail(): String
|
||||
+getStatus(): UserStatus
|
||||
+getLastLoginAt(): LocalDateTime
|
||||
+getPermissions(): List<String>
|
||||
}
|
||||
}
|
||||
|
||||
package "repository" {
|
||||
interface UserRepository {
|
||||
+findUserById(userId: String): Optional<User>
|
||||
+save(user: User): User
|
||||
+incrementLoginAttempt(userId: String): void
|
||||
+resetLoginAttempt(userId: String): void
|
||||
+lockAccount(userId: String, duration: Duration): void
|
||||
+updateLastLoginAt(userId: String, loginTime: LocalDateTime): void
|
||||
}
|
||||
|
||||
interface UserPermissionRepository {
|
||||
+findPermissionsByUserId(userId: String): List<UserPermission>
|
||||
+save(userPermission: UserPermission): UserPermission
|
||||
+deleteByUserId(userId: String): void
|
||||
}
|
||||
|
||||
interface LoginHistoryRepository {
|
||||
+save(loginHistory: LoginHistory): LoginHistory
|
||||
+findByUserIdOrderByLoginTimeDesc(userId: String, pageable: Pageable): List<LoginHistory>
|
||||
}
|
||||
|
||||
package "entity" {
|
||||
class UserEntity {
|
||||
-id: Long
|
||||
-userId: String
|
||||
-userName: String
|
||||
-phoneNumber: String
|
||||
-email: String
|
||||
-passwordHash: String
|
||||
-salt: String
|
||||
-status: String
|
||||
-loginAttemptCount: int
|
||||
-lockedUntil: LocalDateTime
|
||||
-lastLoginAt: LocalDateTime
|
||||
-createdAt: LocalDateTime
|
||||
-updatedAt: LocalDateTime
|
||||
|
||||
+getId(): Long
|
||||
+getUserId(): String
|
||||
+getUserName(): String
|
||||
+getPhoneNumber(): String
|
||||
+getEmail(): String
|
||||
+getPasswordHash(): String
|
||||
+getSalt(): String
|
||||
+getStatus(): String
|
||||
+getLoginAttemptCount(): int
|
||||
+getLockedUntil(): LocalDateTime
|
||||
+getLastLoginAt(): LocalDateTime
|
||||
+getCreatedAt(): LocalDateTime
|
||||
+getUpdatedAt(): LocalDateTime
|
||||
+toDomain(): User
|
||||
}
|
||||
|
||||
class UserPermissionEntity {
|
||||
-id: Long
|
||||
-userId: String
|
||||
-permissionCode: String
|
||||
-status: String
|
||||
-createdAt: LocalDateTime
|
||||
-updatedAt: LocalDateTime
|
||||
|
||||
+getId(): Long
|
||||
+getUserId(): String
|
||||
+getPermissionCode(): String
|
||||
+getStatus(): String
|
||||
+getCreatedAt(): LocalDateTime
|
||||
+getUpdatedAt(): LocalDateTime
|
||||
+toDomain(): UserPermission
|
||||
}
|
||||
|
||||
class LoginHistoryEntity {
|
||||
-id: Long
|
||||
-userId: String
|
||||
-loginTime: LocalDateTime
|
||||
-ipAddress: String
|
||||
-userAgent: String
|
||||
-success: boolean
|
||||
-failureReason: String
|
||||
-createdAt: LocalDateTime
|
||||
|
||||
+getId(): Long
|
||||
+getUserId(): String
|
||||
+getLoginTime(): LocalDateTime
|
||||
+getIpAddress(): String
|
||||
+getUserAgent(): String
|
||||
+isSuccess(): boolean
|
||||
+getFailureReason(): String
|
||||
+getCreatedAt(): LocalDateTime
|
||||
+toDomain(): LoginHistory
|
||||
}
|
||||
}
|
||||
|
||||
package "jpa" {
|
||||
interface UserJpaRepository {
|
||||
+findByUserId(userId: String): Optional<UserEntity>
|
||||
+save(userEntity: UserEntity): UserEntity
|
||||
+existsByUserId(userId: String): boolean
|
||||
}
|
||||
|
||||
interface UserPermissionJpaRepository {
|
||||
+findByUserIdAndStatus(userId: String, status: String): List<UserPermissionEntity>
|
||||
+save(userPermissionEntity: UserPermissionEntity): UserPermissionEntity
|
||||
+deleteByUserId(userId: String): void
|
||||
}
|
||||
|
||||
interface LoginHistoryJpaRepository {
|
||||
+save(loginHistoryEntity: LoginHistoryEntity): LoginHistoryEntity
|
||||
+findByUserIdOrderByLoginTimeDesc(userId: String, pageable: Pageable): List<LoginHistoryEntity>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
package "config" {
|
||||
class SecurityConfig {
|
||||
-jwtSecret: String
|
||||
-accessTokenExpiry: int
|
||||
-refreshTokenExpiry: int
|
||||
|
||||
+passwordEncoder(): PasswordEncoder
|
||||
+corsConfigurationSource(): CorsConfigurationSource
|
||||
+filterChain(http: HttpSecurity): SecurityFilterChain
|
||||
+authenticationManager(): AuthenticationManager
|
||||
}
|
||||
|
||||
class JwtConfig {
|
||||
-secret: String
|
||||
-accessTokenExpiry: int
|
||||
-refreshTokenExpiry: int
|
||||
|
||||
+getSecret(): String
|
||||
+getAccessTokenExpiry(): int
|
||||
+getRefreshTokenExpiry(): int
|
||||
+jwtEncoder(): JwtEncoder
|
||||
+jwtDecoder(): JwtDecoder
|
||||
}
|
||||
|
||||
class RedisConfig {
|
||||
-host: String
|
||||
-port: int
|
||||
-password: String
|
||||
-database: int
|
||||
|
||||
+redisConnectionFactory(): RedisConnectionFactory
|
||||
+redisTemplate(): RedisTemplate<String, Object>
|
||||
+cacheManager(): RedisCacheManager
|
||||
+sessionRedisTemplate(): RedisTemplate<String, UserSession>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
' Common Base Classes 사용
|
||||
package "Common Module" <<External>> {
|
||||
class ApiResponse<T>
|
||||
class ErrorResponse
|
||||
abstract class BaseTimeEntity
|
||||
enum ErrorCode
|
||||
class BusinessException
|
||||
}
|
||||
|
||||
' 관계 정의
|
||||
AuthController --> AuthService : uses
|
||||
AuthController --> TokenService : uses
|
||||
AuthController ..> LoginRequest : uses
|
||||
AuthController ..> LoginResponse : creates
|
||||
AuthController ..> UserInfoResponse : creates
|
||||
AuthController ..> PermissionCheckResponse : creates
|
||||
|
||||
AuthServiceImpl --> UserRepository : uses
|
||||
AuthServiceImpl --> TokenService : uses
|
||||
AuthServiceImpl --> PermissionService : uses
|
||||
AuthServiceImpl --> LoginHistoryRepository : uses
|
||||
|
||||
TokenServiceImpl ..> DecodedToken : creates
|
||||
TokenServiceImpl ..> AuthenticationResult : creates
|
||||
|
||||
PermissionServiceImpl --> UserPermissionRepository : uses
|
||||
|
||||
UserRepository --> UserEntity : works with
|
||||
UserPermissionRepository --> UserPermissionEntity : works with
|
||||
LoginHistoryRepository --> LoginHistoryEntity : works with
|
||||
|
||||
UserRepository --> User : returns
|
||||
UserPermissionRepository --> UserPermission : returns
|
||||
LoginHistoryRepository --> LoginHistory : returns
|
||||
|
||||
UserEntity ..> User : converts to
|
||||
UserPermissionEntity ..> UserPermission : converts to
|
||||
LoginHistoryEntity ..> LoginHistory : converts to
|
||||
|
||||
UserJpaRepository --> UserEntity : manages
|
||||
UserPermissionJpaRepository --> UserPermissionEntity : manages
|
||||
LoginHistoryJpaRepository --> LoginHistoryEntity : manages
|
||||
|
||||
User --> UserStatus : has
|
||||
UserSession --> UserInfoDetail : contains
|
||||
|
||||
AuthServiceImpl ..> AuthenticationResult : creates
|
||||
AuthServiceImpl ..> UserInfoDetail : creates
|
||||
PermissionServiceImpl ..> PermissionResult : creates
|
||||
|
||||
' Inheritance
|
||||
UserEntity --|> BaseTimeEntity
|
||||
UserPermissionEntity --|> BaseTimeEntity
|
||||
LoginHistoryEntity --|> BaseTimeEntity
|
||||
|
||||
AuthService <|-- AuthServiceImpl : implements
|
||||
TokenService <|-- TokenServiceImpl : implements
|
||||
PermissionService <|-- PermissionServiceImpl : implements
|
||||
|
||||
UserRepository <|-- UserJpaRepository : implements
|
||||
UserPermissionRepository <|-- UserPermissionJpaRepository : implements
|
||||
LoginHistoryRepository <|-- LoginHistoryJpaRepository : implements
|
||||
|
||||
' Notes
|
||||
note top of AuthController : "REST API 엔드포인트 제공\n- 로그인/로그아웃\n- 토큰 검증/갱신\n- 권한 확인"
|
||||
note top of AuthServiceImpl : "인증/인가 비즈니스 로직\n- 사용자 인증 처리\n- 세션 관리\n- 권한 검증"
|
||||
note top of TokenServiceImpl : "JWT 토큰 관리\n- 토큰 생성/검증\n- 페이로드 추출"
|
||||
note top of UserEntity : "사용자 정보 저장\n- 로그인 시도 횟수 관리\n- 계정 잠금 처리"
|
||||
note top of RedisConfig : "Redis 캐시 설정\n- 세션 캐싱\n- 권한 캐싱"
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,138 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
title Bill-Inquiry Service - 간단한 클래스 설계
|
||||
|
||||
package "com.unicorn.phonebill.bill" {
|
||||
|
||||
package "controller" {
|
||||
class BillController {
|
||||
-billService: BillService
|
||||
-jwtTokenUtil: JwtTokenUtil
|
||||
}
|
||||
|
||||
note right of BillController : "API 매핑표\n\nGET /bills/menu → getBillMenu()\nPOST /bills/inquiry → inquireBill()\nGET /bills/inquiry/{requestId} → getBillInquiryStatus()\nGET /bills/history → getBillHistory()\n\n모든 메소드는 JWT 인증 필요\nController에는 API로 정의된 메소드만 존재"
|
||||
}
|
||||
|
||||
package "dto" {
|
||||
class BillMenuData
|
||||
class CustomerInfo
|
||||
class BillInquiryRequest
|
||||
class BillInquiryData
|
||||
class BillInquiryAsyncData
|
||||
class BillInquiryStatusData
|
||||
class BillHistoryData
|
||||
class BillHistoryItem
|
||||
class PaginationInfo
|
||||
}
|
||||
|
||||
package "service" {
|
||||
interface BillService
|
||||
class BillServiceImpl
|
||||
interface KosClientService
|
||||
class KosClientServiceImpl
|
||||
interface BillCacheService
|
||||
class BillCacheServiceImpl
|
||||
interface KosAdapterService
|
||||
class KosAdapterServiceImpl
|
||||
interface CircuitBreakerService
|
||||
class CircuitBreakerServiceImpl
|
||||
interface RetryService
|
||||
class RetryServiceImpl
|
||||
interface MvnoApiClient
|
||||
class MvnoApiClientImpl
|
||||
}
|
||||
|
||||
package "domain" {
|
||||
class BillInfo
|
||||
class DiscountInfo
|
||||
class UsageInfo
|
||||
class PaymentInfo
|
||||
class KosRequest
|
||||
class KosResponse
|
||||
class KosData
|
||||
class KosUsage
|
||||
class KosPaymentInfo
|
||||
class MvnoRequest
|
||||
enum CircuitState
|
||||
enum BillInquiryStatus
|
||||
}
|
||||
|
||||
package "repository" {
|
||||
interface BillHistoryRepository
|
||||
interface KosInquiryHistoryRepository
|
||||
|
||||
package "entity" {
|
||||
class BillHistoryEntity
|
||||
class KosInquiryHistoryEntity
|
||||
}
|
||||
|
||||
package "jpa" {
|
||||
interface BillHistoryJpaRepository
|
||||
interface KosInquiryHistoryJpaRepository
|
||||
}
|
||||
}
|
||||
|
||||
package "config" {
|
||||
class RestTemplateConfig
|
||||
class BillCacheConfig
|
||||
class KosConfig
|
||||
class MvnoConfig
|
||||
class CircuitBreakerConfig
|
||||
class AsyncConfig
|
||||
class JwtTokenUtil
|
||||
}
|
||||
}
|
||||
|
||||
' 관계 설정
|
||||
' Controller Layer
|
||||
BillController --> BillService : "uses"
|
||||
BillController --> JwtTokenUtil : "uses"
|
||||
|
||||
' Service Layer Relationships
|
||||
BillServiceImpl ..|> BillService : "implements"
|
||||
BillServiceImpl --> BillCacheService : "uses"
|
||||
BillServiceImpl --> KosClientService : "uses"
|
||||
BillServiceImpl --> BillHistoryRepository : "uses"
|
||||
BillServiceImpl --> MvnoApiClient : "uses"
|
||||
|
||||
KosClientServiceImpl ..|> KosClientService : "implements"
|
||||
KosClientServiceImpl --> KosAdapterService : "uses"
|
||||
KosClientServiceImpl --> CircuitBreakerService : "uses"
|
||||
KosClientServiceImpl --> RetryService : "uses"
|
||||
KosClientServiceImpl --> KosInquiryHistoryRepository : "uses"
|
||||
|
||||
BillCacheServiceImpl ..|> BillCacheService : "implements"
|
||||
BillCacheServiceImpl --> BillHistoryRepository : "uses"
|
||||
|
||||
KosAdapterServiceImpl ..|> KosAdapterService : "implements"
|
||||
KosAdapterServiceImpl --> KosConfig : "uses"
|
||||
|
||||
CircuitBreakerServiceImpl ..|> CircuitBreakerService : "implements"
|
||||
RetryServiceImpl ..|> RetryService : "implements"
|
||||
MvnoApiClientImpl ..|> MvnoApiClient : "implements"
|
||||
|
||||
' Domain Relationships
|
||||
BillInfo --> DiscountInfo : "contains"
|
||||
BillInfo --> UsageInfo : "contains"
|
||||
BillInfo --> PaymentInfo : "contains"
|
||||
KosResponse --> KosData : "contains"
|
||||
KosData --> KosUsage : "contains"
|
||||
KosData --> KosPaymentInfo : "contains"
|
||||
MvnoRequest --> BillInfo : "contains"
|
||||
|
||||
' Repository Relationships
|
||||
BillHistoryRepository --> BillHistoryJpaRepository : "uses"
|
||||
KosInquiryHistoryRepository --> KosInquiryHistoryJpaRepository : "uses"
|
||||
|
||||
' Entity Relationships
|
||||
BillHistoryEntity --|> BaseTimeEntity : "extends"
|
||||
KosInquiryHistoryEntity --|> BaseTimeEntity : "extends"
|
||||
|
||||
' DTO Relationships
|
||||
BillMenuData --> CustomerInfo : "contains"
|
||||
BillInquiryData --> BillInfo : "contains"
|
||||
BillInquiryStatusData --> BillInfo : "contains"
|
||||
BillHistoryData --> BillHistoryItem : "contains"
|
||||
BillHistoryData --> PaginationInfo : "contains"
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,676 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
title Bill-Inquiry Service - 상세 클래스 설계
|
||||
|
||||
' 패키지별 클래스 구조
|
||||
package "com.unicorn.phonebill.bill" {
|
||||
|
||||
package "controller" {
|
||||
class BillController {
|
||||
-billService: BillService
|
||||
-jwtTokenUtil: JwtTokenUtil
|
||||
+getBillMenu(authorization: String): ResponseEntity<ApiResponse<BillMenuData>>
|
||||
+inquireBill(request: BillInquiryRequest, authorization: String): ResponseEntity<ApiResponse<BillInquiryData>>
|
||||
+getBillInquiryStatus(requestId: String, authorization: String): ResponseEntity<ApiResponse<BillInquiryStatusData>>
|
||||
+getBillHistory(lineNumber: String, startDate: String, endDate: String, page: int, size: int, status: String, authorization: String): ResponseEntity<ApiResponse<BillHistoryData>>
|
||||
-extractUserInfoFromToken(authorization: String): JwtTokenVerifyDTO
|
||||
-validateRequestParameters(request: Object): void
|
||||
}
|
||||
}
|
||||
|
||||
package "dto" {
|
||||
' API Request/Response DTOs
|
||||
class BillMenuData {
|
||||
-customerInfo: CustomerInfo
|
||||
-availableMonths: List<String>
|
||||
-currentMonth: String
|
||||
+BillMenuData(customerInfo: CustomerInfo, availableMonths: List<String>, currentMonth: String)
|
||||
+getCustomerInfo(): CustomerInfo
|
||||
+getAvailableMonths(): List<String>
|
||||
+getCurrentMonth(): String
|
||||
}
|
||||
|
||||
class CustomerInfo {
|
||||
-customerId: String
|
||||
-lineNumber: String
|
||||
+CustomerInfo(customerId: String, lineNumber: String)
|
||||
+getCustomerId(): String
|
||||
+getLineNumber(): String
|
||||
}
|
||||
|
||||
class BillInquiryRequest {
|
||||
-lineNumber: String
|
||||
-inquiryMonth: String
|
||||
+BillInquiryRequest()
|
||||
+getLineNumber(): String
|
||||
+setLineNumber(lineNumber: String): void
|
||||
+getInquiryMonth(): String
|
||||
+setInquiryMonth(inquiryMonth: String): void
|
||||
+isValid(): boolean
|
||||
}
|
||||
|
||||
class BillInquiryData {
|
||||
-requestId: String
|
||||
-status: String
|
||||
-billInfo: BillInfo
|
||||
+BillInquiryData(requestId: String, status: String)
|
||||
+BillInquiryData(requestId: String, status: String, billInfo: BillInfo)
|
||||
+getRequestId(): String
|
||||
+getStatus(): String
|
||||
+getBillInfo(): BillInfo
|
||||
+setBillInfo(billInfo: BillInfo): void
|
||||
}
|
||||
|
||||
class BillInquiryAsyncData {
|
||||
-requestId: String
|
||||
-status: String
|
||||
-estimatedTime: String
|
||||
+BillInquiryAsyncData(requestId: String, status: String, estimatedTime: String)
|
||||
+getRequestId(): String
|
||||
+getStatus(): String
|
||||
+getEstimatedTime(): String
|
||||
}
|
||||
|
||||
class BillInquiryStatusData {
|
||||
-requestId: String
|
||||
-status: String
|
||||
-progress: Integer
|
||||
-billInfo: BillInfo
|
||||
-errorMessage: String
|
||||
+BillInquiryStatusData(requestId: String, status: String)
|
||||
+getRequestId(): String
|
||||
+getStatus(): String
|
||||
+getProgress(): Integer
|
||||
+setProgress(progress: Integer): void
|
||||
+getBillInfo(): BillInfo
|
||||
+setBillInfo(billInfo: BillInfo): void
|
||||
+getErrorMessage(): String
|
||||
+setErrorMessage(errorMessage: String): void
|
||||
}
|
||||
|
||||
class BillHistoryData {
|
||||
-items: List<BillHistoryItem>
|
||||
-pagination: PaginationInfo
|
||||
+BillHistoryData(items: List<BillHistoryItem>, pagination: PaginationInfo)
|
||||
+getItems(): List<BillHistoryItem>
|
||||
+getPagination(): PaginationInfo
|
||||
}
|
||||
|
||||
class BillHistoryItem {
|
||||
-requestId: String
|
||||
-lineNumber: String
|
||||
-inquiryMonth: String
|
||||
-requestTime: LocalDateTime
|
||||
-processTime: LocalDateTime
|
||||
-status: String
|
||||
-resultSummary: String
|
||||
+BillHistoryItem()
|
||||
+getRequestId(): String
|
||||
+setRequestId(requestId: String): void
|
||||
+getLineNumber(): String
|
||||
+setLineNumber(lineNumber: String): void
|
||||
+getInquiryMonth(): String
|
||||
+setInquiryMonth(inquiryMonth: String): void
|
||||
+getRequestTime(): LocalDateTime
|
||||
+setRequestTime(requestTime: LocalDateTime): void
|
||||
+getProcessTime(): LocalDateTime
|
||||
+setProcessTime(processTime: LocalDateTime): void
|
||||
+getStatus(): String
|
||||
+setStatus(status: String): void
|
||||
+getResultSummary(): String
|
||||
+setResultSummary(resultSummary: String): void
|
||||
}
|
||||
|
||||
class PaginationInfo {
|
||||
-currentPage: int
|
||||
-totalPages: int
|
||||
-totalItems: long
|
||||
-pageSize: int
|
||||
-hasNext: boolean
|
||||
-hasPrevious: boolean
|
||||
+PaginationInfo(currentPage: int, totalPages: int, totalItems: long, pageSize: int)
|
||||
+getCurrentPage(): int
|
||||
+getTotalPages(): int
|
||||
+getTotalItems(): long
|
||||
+getPageSize(): int
|
||||
+isHasNext(): boolean
|
||||
+isHasPrevious(): boolean
|
||||
}
|
||||
}
|
||||
|
||||
package "service" {
|
||||
interface BillService {
|
||||
+getBillMenuData(userId: String, lineNumber: String): BillMenuData
|
||||
+inquireBill(lineNumber: String, inquiryMonth: String, userId: String): BillInquiryData
|
||||
+getBillInquiryStatus(requestId: String, userId: String): BillInquiryStatusData
|
||||
+getBillHistory(lineNumber: String, startDate: String, endDate: String, page: int, size: int, status: String, userId: String): BillHistoryData
|
||||
}
|
||||
|
||||
class BillServiceImpl {
|
||||
-billCacheService: BillCacheService
|
||||
-kosClientService: KosClientService
|
||||
-billRepository: BillHistoryRepository
|
||||
-mvnoApiClient: MvnoApiClient
|
||||
+getBillMenuData(userId: String, lineNumber: String): BillMenuData
|
||||
+inquireBill(lineNumber: String, inquiryMonth: String, userId: String): BillInquiryData
|
||||
+getBillInquiryStatus(requestId: String, userId: String): BillInquiryStatusData
|
||||
+getBillHistory(lineNumber: String, startDate: String, endDate: String, page: int, size: int, status: String, userId: String): BillHistoryData
|
||||
-generateRequestId(): String
|
||||
-getCurrentMonth(): String
|
||||
-getAvailableMonths(): List<String>
|
||||
-processCurrentMonthInquiry(lineNumber: String, userId: String): BillInquiryData
|
||||
-processSpecificMonthInquiry(lineNumber: String, inquiryMonth: String, userId: String): BillInquiryData
|
||||
-saveBillInquiryHistoryAsync(userId: String, lineNumber: String, inquiryMonth: String, requestId: String, status: String): void
|
||||
-sendResultToMvnoAsync(billInfo: BillInfo): void
|
||||
}
|
||||
|
||||
interface KosClientService {
|
||||
+getBillInfo(lineNumber: String, inquiryMonth: String): BillInfo
|
||||
+isServiceAvailable(): boolean
|
||||
}
|
||||
|
||||
class KosClientServiceImpl {
|
||||
-kosAdapterService: KosAdapterService
|
||||
-circuitBreakerService: CircuitBreakerService
|
||||
-retryService: RetryService
|
||||
-billRepository: KosInquiryHistoryRepository
|
||||
+getBillInfo(lineNumber: String, inquiryMonth: String): BillInfo
|
||||
+isServiceAvailable(): boolean
|
||||
-executeWithCircuitBreaker(lineNumber: String, inquiryMonth: String): BillInfo
|
||||
-executeWithRetry(lineNumber: String, inquiryMonth: String): BillInfo
|
||||
-saveKosInquiryHistory(lineNumber: String, inquiryMonth: String, status: String, errorMessage: String): void
|
||||
}
|
||||
|
||||
interface BillCacheService {
|
||||
+getCachedBillInfo(lineNumber: String, inquiryMonth: String): BillInfo
|
||||
+cacheBillInfo(lineNumber: String, inquiryMonth: String, billInfo: BillInfo): void
|
||||
+getCustomerInfo(userId: String): CustomerInfo
|
||||
+cacheCustomerInfo(userId: String, customerInfo: CustomerInfo): void
|
||||
+evictBillInfoCache(lineNumber: String, inquiryMonth: String): void
|
||||
}
|
||||
|
||||
class BillCacheServiceImpl {
|
||||
-redisTemplate: RedisTemplate<String, Object>
|
||||
-billRepository: BillHistoryRepository
|
||||
+getCachedBillInfo(lineNumber: String, inquiryMonth: String): BillInfo
|
||||
+cacheBillInfo(lineNumber: String, inquiryMonth: String, billInfo: BillInfo): void
|
||||
+getCustomerInfo(userId: String): CustomerInfo
|
||||
+cacheCustomerInfo(userId: String, customerInfo: CustomerInfo): void
|
||||
+evictBillInfoCache(lineNumber: String, inquiryMonth: String): void
|
||||
-buildBillInfoCacheKey(lineNumber: String, inquiryMonth: String): String
|
||||
-buildCustomerInfoCacheKey(userId: String): String
|
||||
-isValidCachedData(cachedData: Object): boolean
|
||||
}
|
||||
|
||||
interface KosAdapterService {
|
||||
+callKosBillInquiry(lineNumber: String, inquiryMonth: String): KosResponse
|
||||
}
|
||||
|
||||
class KosAdapterServiceImpl {
|
||||
-restTemplate: RestTemplate
|
||||
-kosConfig: KosConfig
|
||||
+callKosBillInquiry(lineNumber: String, inquiryMonth: String): KosResponse
|
||||
-buildKosRequest(lineNumber: String, inquiryMonth: String): KosRequest
|
||||
-convertToKosResponse(responseEntity: ResponseEntity<String>): KosResponse
|
||||
-handleKosError(statusCode: HttpStatus, responseBody: String): void
|
||||
}
|
||||
|
||||
interface CircuitBreakerService {
|
||||
+isCallAllowed(): boolean
|
||||
+recordSuccess(): void
|
||||
+recordFailure(): void
|
||||
+getCircuitState(): CircuitState
|
||||
}
|
||||
|
||||
class CircuitBreakerServiceImpl {
|
||||
-failureThreshold: int
|
||||
-recoveryTimeout: long
|
||||
-successThreshold: int
|
||||
-failureCount: AtomicInteger
|
||||
-successCount: AtomicInteger
|
||||
-lastFailureTime: AtomicLong
|
||||
-circuitState: CircuitState
|
||||
+isCallAllowed(): boolean
|
||||
+recordSuccess(): void
|
||||
+recordFailure(): void
|
||||
+getCircuitState(): CircuitState
|
||||
-transitionToOpen(): void
|
||||
-transitionToHalfOpen(): void
|
||||
-transitionToClosed(): void
|
||||
}
|
||||
|
||||
interface RetryService {
|
||||
+executeWithRetry(operation: Supplier<T>): T
|
||||
}
|
||||
|
||||
class RetryServiceImpl {
|
||||
-maxRetries: int
|
||||
-retryDelayMs: long
|
||||
+executeWithRetry(operation: Supplier<T>): T
|
||||
-shouldRetry(exception: Exception, attemptCount: int): boolean
|
||||
-calculateDelay(attemptCount: int): long
|
||||
}
|
||||
|
||||
interface MvnoApiClient {
|
||||
+sendBillResult(billInfo: BillInfo): void
|
||||
}
|
||||
|
||||
class MvnoApiClientImpl {
|
||||
-restTemplate: RestTemplate
|
||||
-mvnoConfig: MvnoConfig
|
||||
+sendBillResult(billInfo: BillInfo): void
|
||||
-buildMvnoRequest(billInfo: BillInfo): MvnoRequest
|
||||
}
|
||||
}
|
||||
|
||||
package "domain" {
|
||||
class BillInfo {
|
||||
-productName: String
|
||||
-contractInfo: String
|
||||
-billingMonth: String
|
||||
-totalAmount: Integer
|
||||
-discountInfo: List<DiscountInfo>
|
||||
-usage: UsageInfo
|
||||
-terminationFee: Integer
|
||||
-deviceInstallment: Integer
|
||||
-paymentInfo: PaymentInfo
|
||||
+BillInfo()
|
||||
+getProductName(): String
|
||||
+setProductName(productName: String): void
|
||||
+getContractInfo(): String
|
||||
+setContractInfo(contractInfo: String): void
|
||||
+getBillingMonth(): String
|
||||
+setBillingMonth(billingMonth: String): void
|
||||
+getTotalAmount(): Integer
|
||||
+setTotalAmount(totalAmount: Integer): void
|
||||
+getDiscountInfo(): List<DiscountInfo>
|
||||
+setDiscountInfo(discountInfo: List<DiscountInfo>): void
|
||||
+getUsage(): UsageInfo
|
||||
+setUsage(usage: UsageInfo): void
|
||||
+getTerminationFee(): Integer
|
||||
+setTerminationFee(terminationFee: Integer): void
|
||||
+getDeviceInstallment(): Integer
|
||||
+setDeviceInstallment(deviceInstallment: Integer): void
|
||||
+getPaymentInfo(): PaymentInfo
|
||||
+setPaymentInfo(paymentInfo: PaymentInfo): void
|
||||
+isComplete(): boolean
|
||||
}
|
||||
|
||||
class DiscountInfo {
|
||||
-name: String
|
||||
-amount: Integer
|
||||
+DiscountInfo()
|
||||
+DiscountInfo(name: String, amount: Integer)
|
||||
+getName(): String
|
||||
+setName(name: String): void
|
||||
+getAmount(): Integer
|
||||
+setAmount(amount: Integer): void
|
||||
}
|
||||
|
||||
class UsageInfo {
|
||||
-voice: String
|
||||
-sms: String
|
||||
-data: String
|
||||
+UsageInfo()
|
||||
+UsageInfo(voice: String, sms: String, data: String)
|
||||
+getVoice(): String
|
||||
+setVoice(voice: String): void
|
||||
+getSms(): String
|
||||
+setSms(sms: String): void
|
||||
+getData(): String
|
||||
+setData(data: String): void
|
||||
}
|
||||
|
||||
class PaymentInfo {
|
||||
-billingDate: String
|
||||
-paymentStatus: String
|
||||
-paymentMethod: String
|
||||
+PaymentInfo()
|
||||
+PaymentInfo(billingDate: String, paymentStatus: String, paymentMethod: String)
|
||||
+getBillingDate(): String
|
||||
+setBillingDate(billingDate: String): void
|
||||
+getPaymentStatus(): String
|
||||
+setPaymentStatus(paymentStatus: String): void
|
||||
+getPaymentMethod(): String
|
||||
+setPaymentMethod(paymentMethod: String): void
|
||||
}
|
||||
|
||||
' KOS 연동 도메인 모델
|
||||
class KosRequest {
|
||||
-lineNumber: String
|
||||
-inquiryMonth: String
|
||||
-requestTime: LocalDateTime
|
||||
+KosRequest(lineNumber: String, inquiryMonth: String)
|
||||
+getLineNumber(): String
|
||||
+getInquiryMonth(): String
|
||||
+getRequestTime(): LocalDateTime
|
||||
+toKosFormat(): Map<String, Object>
|
||||
}
|
||||
|
||||
class KosResponse {
|
||||
-resultCode: String
|
||||
-resultMessage: String
|
||||
-data: KosData
|
||||
-responseTime: LocalDateTime
|
||||
+KosResponse()
|
||||
+getResultCode(): String
|
||||
+setResultCode(resultCode: String): void
|
||||
+getResultMessage(): String
|
||||
+setResultMessage(resultMessage: String): void
|
||||
+getData(): KosData
|
||||
+setData(data: KosData): void
|
||||
+getResponseTime(): LocalDateTime
|
||||
+setResponseTime(responseTime: LocalDateTime): void
|
||||
+isSuccess(): boolean
|
||||
+toBillInfo(): BillInfo
|
||||
}
|
||||
|
||||
class KosData {
|
||||
-productName: String
|
||||
-contractInfo: String
|
||||
-billingMonth: String
|
||||
-charge: Integer
|
||||
-discountInfo: String
|
||||
-usage: KosUsage
|
||||
-estimatedCancellationFee: Integer
|
||||
-deviceInstallment: Integer
|
||||
-billingPaymentInfo: KosPaymentInfo
|
||||
+KosData()
|
||||
+getProductName(): String
|
||||
+setProductName(productName: String): void
|
||||
+getContractInfo(): String
|
||||
+setContractInfo(contractInfo: String): void
|
||||
+getBillingMonth(): String
|
||||
+setBillingMonth(billingMonth: String): void
|
||||
+getCharge(): Integer
|
||||
+setCharge(charge: Integer): void
|
||||
+getDiscountInfo(): String
|
||||
+setDiscountInfo(discountInfo: String): void
|
||||
+getUsage(): KosUsage
|
||||
+setUsage(usage: KosUsage): void
|
||||
+getEstimatedCancellationFee(): Integer
|
||||
+setEstimatedCancellationFee(estimatedCancellationFee: Integer): void
|
||||
+getDeviceInstallment(): Integer
|
||||
+setDeviceInstallment(deviceInstallment: Integer): void
|
||||
+getBillingPaymentInfo(): KosPaymentInfo
|
||||
+setBillingPaymentInfo(billingPaymentInfo: KosPaymentInfo): void
|
||||
}
|
||||
|
||||
class KosUsage {
|
||||
-voice: String
|
||||
-data: String
|
||||
+KosUsage()
|
||||
+getVoice(): String
|
||||
+setVoice(voice: String): void
|
||||
+getData(): String
|
||||
+setData(data: String): void
|
||||
+toUsageInfo(): UsageInfo
|
||||
}
|
||||
|
||||
class KosPaymentInfo {
|
||||
-billingDate: String
|
||||
-paymentStatus: String
|
||||
+KosPaymentInfo()
|
||||
+getBillingDate(): String
|
||||
+setBillingDate(billingDate: String): void
|
||||
+getPaymentStatus(): String
|
||||
+setPaymentStatus(paymentStatus: String): void
|
||||
+toPaymentInfo(): PaymentInfo
|
||||
}
|
||||
|
||||
' MVNO 연동 도메인 모델
|
||||
class MvnoRequest {
|
||||
-billInfo: BillInfo
|
||||
-timestamp: LocalDateTime
|
||||
+MvnoRequest(billInfo: BillInfo)
|
||||
+getBillInfo(): BillInfo
|
||||
+getTimestamp(): LocalDateTime
|
||||
+toRequestBody(): Map<String, Object>
|
||||
}
|
||||
|
||||
enum CircuitState {
|
||||
CLOSED
|
||||
OPEN
|
||||
HALF_OPEN
|
||||
+valueOf(name: String): CircuitState
|
||||
+values(): CircuitState[]
|
||||
}
|
||||
|
||||
enum BillInquiryStatus {
|
||||
PROCESSING("처리중")
|
||||
COMPLETED("완료")
|
||||
FAILED("실패")
|
||||
|
||||
-description: String
|
||||
+BillInquiryStatus(description: String)
|
||||
+getDescription(): String
|
||||
}
|
||||
}
|
||||
|
||||
package "repository" {
|
||||
interface BillHistoryRepository {
|
||||
+findByUserIdAndLineNumberOrderByRequestTimeDesc(userId: String, lineNumber: String, pageable: Pageable): Page<BillHistoryEntity>
|
||||
+findByUserIdAndRequestTimeBetweenOrderByRequestTimeDesc(userId: String, startDate: LocalDateTime, endDate: LocalDateTime, pageable: Pageable): Page<BillHistoryEntity>
|
||||
+findByUserIdAndLineNumberAndStatusOrderByRequestTimeDesc(userId: String, lineNumber: String, status: String, pageable: Pageable): Page<BillHistoryEntity>
|
||||
+save(entity: BillHistoryEntity): BillHistoryEntity
|
||||
+getCustomerInfo(userId: String): CustomerInfo
|
||||
}
|
||||
|
||||
interface KosInquiryHistoryRepository {
|
||||
+save(entity: KosInquiryHistoryEntity): KosInquiryHistoryEntity
|
||||
+findByLineNumberAndInquiryMonthOrderByRequestTimeDesc(lineNumber: String, inquiryMonth: String): List<KosInquiryHistoryEntity>
|
||||
}
|
||||
|
||||
package "entity" {
|
||||
class BillHistoryEntity {
|
||||
-id: Long
|
||||
-userId: String
|
||||
-lineNumber: String
|
||||
-inquiryMonth: String
|
||||
-requestId: String
|
||||
-requestTime: LocalDateTime
|
||||
-processTime: LocalDateTime
|
||||
-status: String
|
||||
-resultSummary: String
|
||||
-billInfoJson: String
|
||||
+BillHistoryEntity()
|
||||
+getId(): Long
|
||||
+setId(id: Long): void
|
||||
+getUserId(): String
|
||||
+setUserId(userId: String): void
|
||||
+getLineNumber(): String
|
||||
+setLineNumber(lineNumber: String): void
|
||||
+getInquiryMonth(): String
|
||||
+setInquiryMonth(inquiryMonth: String): void
|
||||
+getRequestId(): String
|
||||
+setRequestId(requestId: String): void
|
||||
+getRequestTime(): LocalDateTime
|
||||
+setRequestTime(requestTime: LocalDateTime): void
|
||||
+getProcessTime(): LocalDateTime
|
||||
+setProcessTime(processTime: LocalDateTime): void
|
||||
+getStatus(): String
|
||||
+setStatus(status: String): void
|
||||
+getResultSummary(): String
|
||||
+setResultSummary(resultSummary: String): void
|
||||
+getBillInfoJson(): String
|
||||
+setBillInfoJson(billInfoJson: String): void
|
||||
+toBillHistoryItem(): BillHistoryItem
|
||||
+fromBillInfo(billInfo: BillInfo): void
|
||||
}
|
||||
|
||||
class KosInquiryHistoryEntity {
|
||||
-id: Long
|
||||
-lineNumber: String
|
||||
-inquiryMonth: String
|
||||
-requestTime: LocalDateTime
|
||||
-responseTime: LocalDateTime
|
||||
-resultCode: String
|
||||
-resultMessage: String
|
||||
-errorDetail: String
|
||||
+KosInquiryHistoryEntity()
|
||||
+getId(): Long
|
||||
+setId(id: Long): void
|
||||
+getLineNumber(): String
|
||||
+setLineNumber(lineNumber: String): void
|
||||
+getInquiryMonth(): String
|
||||
+setInquiryMonth(inquiryMonth: String): void
|
||||
+getRequestTime(): LocalDateTime
|
||||
+setRequestTime(requestTime: LocalDateTime): void
|
||||
+getResponseTime(): LocalDateTime
|
||||
+setResponseTime(responseTime: LocalDateTime): void
|
||||
+getResultCode(): String
|
||||
+setResultCode(resultCode: String): void
|
||||
+getResultMessage(): String
|
||||
+setResultMessage(resultMessage: String): void
|
||||
+getErrorDetail(): String
|
||||
+setErrorDetail(errorDetail: String): void
|
||||
}
|
||||
}
|
||||
|
||||
package "jpa" {
|
||||
interface BillHistoryJpaRepository {
|
||||
+findByUserIdAndLineNumberOrderByRequestTimeDesc(userId: String, lineNumber: String, pageable: Pageable): Page<BillHistoryEntity>
|
||||
+findByUserIdAndRequestTimeBetweenOrderByRequestTimeDesc(userId: String, startDate: LocalDateTime, endDate: LocalDateTime, pageable: Pageable): Page<BillHistoryEntity>
|
||||
+findByUserIdAndLineNumberAndStatusOrderByRequestTimeDesc(userId: String, lineNumber: String, status: String, pageable: Pageable): Page<BillHistoryEntity>
|
||||
+countByUserIdAndLineNumber(userId: String, lineNumber: String): long
|
||||
}
|
||||
|
||||
interface KosInquiryHistoryJpaRepository {
|
||||
+findByLineNumberAndInquiryMonthOrderByRequestTimeDesc(lineNumber: String, inquiryMonth: String): List<KosInquiryHistoryEntity>
|
||||
+countByResultCode(resultCode: String): long
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
package "config" {
|
||||
class RestTemplateConfig {
|
||||
+kosRestTemplate(): RestTemplate
|
||||
+mvnoRestTemplate(): RestTemplate
|
||||
+kosHttpMessageConverters(): List<HttpMessageConverter<?>>
|
||||
+kosRequestInterceptors(): List<ClientHttpRequestInterceptor>
|
||||
+kosConnectionPoolConfig(): HttpComponentsClientHttpRequestFactory
|
||||
}
|
||||
|
||||
class BillCacheConfig {
|
||||
+billInfoCacheConfiguration(): RedisCacheConfiguration
|
||||
+customerInfoCacheConfiguration(): RedisCacheConfiguration
|
||||
+billCacheKeyGenerator(): KeyGenerator
|
||||
+cacheErrorHandler(): CacheErrorHandler
|
||||
}
|
||||
|
||||
class KosConfig {
|
||||
-baseUrl: String
|
||||
-connectTimeout: int
|
||||
-readTimeout: int
|
||||
-maxRetries: int
|
||||
-retryDelay: long
|
||||
+getBaseUrl(): String
|
||||
+setBaseUrl(baseUrl: String): void
|
||||
+getConnectTimeout(): int
|
||||
+setConnectTimeout(connectTimeout: int): void
|
||||
+getReadTimeout(): int
|
||||
+setReadTimeout(readTimeout: int): void
|
||||
+getMaxRetries(): int
|
||||
+setMaxRetries(maxRetries: int): void
|
||||
+getRetryDelay(): long
|
||||
+setRetryDelay(retryDelay: long): void
|
||||
+getBillInquiryEndpoint(): String
|
||||
}
|
||||
|
||||
class MvnoConfig {
|
||||
-baseUrl: String
|
||||
-connectTimeout: int
|
||||
-readTimeout: int
|
||||
+getBaseUrl(): String
|
||||
+setBaseUrl(baseUrl: String): void
|
||||
+getConnectTimeout(): int
|
||||
+setConnectTimeout(connectTimeout: int): void
|
||||
+getReadTimeout(): int
|
||||
+setReadTimeout(readTimeout: int): void
|
||||
+getSendResultEndpoint(): String
|
||||
}
|
||||
|
||||
class CircuitBreakerConfig {
|
||||
-failureThreshold: int
|
||||
-recoveryTimeoutMs: long
|
||||
-successThreshold: int
|
||||
+getFailureThreshold(): int
|
||||
+setFailureThreshold(failureThreshold: int): void
|
||||
+getRecoveryTimeoutMs(): long
|
||||
+setRecoveryTimeoutMs(recoveryTimeoutMs: long): void
|
||||
+getSuccessThreshold(): int
|
||||
+setSuccessThreshold(successThreshold: int): void
|
||||
}
|
||||
|
||||
class AsyncConfig {
|
||||
+billTaskExecutor(): TaskExecutor
|
||||
+kosTaskExecutor(): TaskExecutor
|
||||
+asyncExceptionHandler(): AsyncUncaughtExceptionHandler
|
||||
}
|
||||
|
||||
class JwtTokenUtil {
|
||||
-secretKey: String
|
||||
-tokenExpiration: long
|
||||
+extractUserId(token: String): String
|
||||
+extractLineNumber(token: String): String
|
||||
+extractPermissions(token: String): List<String>
|
||||
+validateToken(token: String): boolean
|
||||
+isTokenExpired(token: String): boolean
|
||||
+parseToken(token: String): Claims
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
' 관계 설정
|
||||
' Controller Layer
|
||||
BillController --> BillService : "uses"
|
||||
BillController --> JwtTokenUtil : "uses"
|
||||
|
||||
' Service Layer Relationships
|
||||
BillServiceImpl ..|> BillService : "implements"
|
||||
BillServiceImpl --> BillCacheService : "uses"
|
||||
BillServiceImpl --> KosClientService : "uses"
|
||||
BillServiceImpl --> BillHistoryRepository : "uses"
|
||||
BillServiceImpl --> MvnoApiClient : "uses"
|
||||
|
||||
KosClientServiceImpl ..|> KosClientService : "implements"
|
||||
KosClientServiceImpl --> KosAdapterService : "uses"
|
||||
KosClientServiceImpl --> CircuitBreakerService : "uses"
|
||||
KosClientServiceImpl --> RetryService : "uses"
|
||||
KosClientServiceImpl --> KosInquiryHistoryRepository : "uses"
|
||||
|
||||
BillCacheServiceImpl ..|> BillCacheService : "uses"
|
||||
BillCacheServiceImpl --> BillHistoryRepository : "uses"
|
||||
|
||||
KosAdapterServiceImpl ..|> KosAdapterService : "implements"
|
||||
KosAdapterServiceImpl --> KosConfig : "uses"
|
||||
|
||||
CircuitBreakerServiceImpl ..|> CircuitBreakerService : "implements"
|
||||
RetryServiceImpl ..|> RetryService : "implements"
|
||||
MvnoApiClientImpl ..|> MvnoApiClient : "implements"
|
||||
|
||||
' Domain Relationships
|
||||
BillInfo --> DiscountInfo : "contains"
|
||||
BillInfo --> UsageInfo : "contains"
|
||||
BillInfo --> PaymentInfo : "uses"
|
||||
KosResponse --> KosData : "contains"
|
||||
KosData --> KosUsage : "contains"
|
||||
KosData --> KosPaymentInfo : "contains"
|
||||
MvnoRequest --> BillInfo : "contains"
|
||||
|
||||
' Repository Relationships
|
||||
BillHistoryRepository --> BillHistoryJpaRepository : "uses"
|
||||
KosInquiryHistoryRepository --> KosInquiryHistoryJpaRepository : "uses"
|
||||
|
||||
' Entity Relationships
|
||||
BillHistoryEntity --|> BaseTimeEntity : "extends"
|
||||
KosInquiryHistoryEntity --|> BaseTimeEntity : "extends"
|
||||
|
||||
' DTO Relationships
|
||||
BillMenuData --> CustomerInfo : "contains"
|
||||
BillInquiryData --> BillInfo : "contains"
|
||||
BillInquiryStatusData --> BillInfo : "contains"
|
||||
BillHistoryData --> BillHistoryItem : "contains"
|
||||
BillHistoryData --> PaginationInfo : "contains"
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,242 @@
|
||||
# Product-Change Service 클래스 설계서
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 설계 목적
|
||||
Product-Change Service의 상품변경 기능을 구현하기 위한 클래스 구조를 설계합니다.
|
||||
|
||||
### 1.2 설계 원칙
|
||||
- **아키텍처 패턴**: Layered Architecture 적용
|
||||
- **패키지 구조**: com.unicorn.phonebill.product 하위 계층별 구조
|
||||
- **KOS 연동**: Circuit Breaker 패턴으로 외부 시스템 안정성 확보
|
||||
- **캐시 전략**: Redis를 활용한 성능 최적화
|
||||
- **예외 처리**: 계층별 예외 처리 및 비즈니스 예외 정의
|
||||
|
||||
### 1.3 주요 기능
|
||||
- UFR-PROD-010: 상품변경 메뉴 조회
|
||||
- UFR-PROD-020: 상품변경 화면 데이터 조회
|
||||
- UFR-PROD-030: 상품변경 요청 및 사전체크
|
||||
- UFR-PROD-040: KOS 연동 상품변경 처리
|
||||
|
||||
## 2. 패키지 구조도
|
||||
|
||||
```
|
||||
com.unicorn.phonebill.product
|
||||
├── controller/ # 컨트롤러 계층
|
||||
│ └── ProductController # 상품변경 API 컨트롤러
|
||||
├── dto/ # 데이터 전송 객체
|
||||
│ ├── *Request # 요청 DTO 클래스들
|
||||
│ ├── *Response # 응답 DTO 클래스들
|
||||
│ └── *Enum # DTO 관련 열거형
|
||||
├── service/ # 서비스 계층
|
||||
│ ├── ProductService # 상품변경 서비스 인터페이스
|
||||
│ ├── ProductServiceImpl # 상품변경 서비스 구현체
|
||||
│ ├── ProductValidationService # 상품변경 검증 서비스
|
||||
│ ├── ProductCacheService # 상품 캐시 서비스
|
||||
│ ├── KosClientService # KOS 연동 서비스
|
||||
│ ├── CircuitBreakerService # Circuit Breaker 서비스
|
||||
│ └── RetryService # 재시도 서비스
|
||||
├── domain/ # 도메인 계층
|
||||
│ ├── Product # 상품 도메인 모델
|
||||
│ ├── ProductChangeHistory # 상품변경 이력 도메인 모델
|
||||
│ ├── ProductChangeResult # 상품변경 결과 도메인 모델
|
||||
│ └── ProductStatus # 상품 상태 도메인 모델
|
||||
├── repository/ # 저장소 계층
|
||||
│ ├── ProductRepository # 상품 저장소 인터페이스
|
||||
│ ├── ProductChangeHistoryRepository # 상품변경 이력 저장소 인터페이스
|
||||
│ ├── entity/ # JPA 엔티티
|
||||
│ │ └── ProductChangeHistoryEntity
|
||||
│ └── jpa/ # JPA Repository
|
||||
│ └── ProductChangeHistoryJpaRepository
|
||||
├── config/ # 설정 계층
|
||||
│ ├── RestTemplateConfig # REST 통신 설정
|
||||
│ ├── CacheConfig # 캐시 설정
|
||||
│ ├── CircuitBreakerConfig # Circuit Breaker 설정
|
||||
│ └── KosProperties # KOS 연동 설정
|
||||
├── external/ # 외부 연동 계층
|
||||
│ ├── KosRequest # KOS 요청 모델
|
||||
│ ├── KosResponse # KOS 응답 모델
|
||||
│ └── KosAdapterService # KOS 어댑터 서비스
|
||||
└── exception/ # 예외 계층
|
||||
├── ProductChangeException # 상품변경 예외
|
||||
├── ProductValidationException # 상품변경 검증 예외
|
||||
├── KosConnectionException # KOS 연결 예외
|
||||
└── CircuitBreakerException # Circuit Breaker 예외
|
||||
```
|
||||
|
||||
## 3. 계층별 클래스 설계
|
||||
|
||||
### 3.1 Controller Layer
|
||||
|
||||
#### ProductController
|
||||
- **역할**: 상품변경 관련 REST API 엔드포인트 제공
|
||||
- **주요 메소드**:
|
||||
- `getProductMenu()`: 상품변경 메뉴 조회 (GET /products/menu)
|
||||
- `getCustomerInfo(lineNumber)`: 고객 정보 조회 (GET /products/customer/{lineNumber})
|
||||
- `getAvailableProducts()`: 변경 가능한 상품 목록 조회 (GET /products/available)
|
||||
- `validateProductChange(request)`: 상품변경 사전체크 (POST /products/change/validation)
|
||||
- `requestProductChange(request)`: 상품변경 요청 (POST /products/change)
|
||||
- `getProductChangeResult(requestId)`: 상품변경 결과 조회 (GET /products/change/{requestId})
|
||||
- `getProductChangeHistory()`: 상품변경 이력 조회 (GET /products/history)
|
||||
|
||||
### 3.2 Service Layer
|
||||
|
||||
#### ProductService / ProductServiceImpl
|
||||
- **역할**: 상품변경 비즈니스 로직 처리
|
||||
- **의존성**: KosClientService, ProductValidationService, ProductCacheService, ProductChangeHistoryRepository
|
||||
- **주요 기능**: 상품변경 프로세스 전체 조율, 캐시 무효화 처리
|
||||
|
||||
#### ProductValidationService
|
||||
- **역할**: 상품변경 사전체크 로직 처리
|
||||
- **주요 검증**: 판매중인 상품 확인, 사업자 일치 확인, 회선 사용상태 확인
|
||||
- **의존성**: ProductRepository, ProductCacheService, KosClientService
|
||||
|
||||
#### ProductCacheService
|
||||
- **역할**: Redis 캐시를 활용한 성능 최적화
|
||||
- **주요 캐시**: 고객상품정보(4시간), 현재상품정보(2시간), 가용상품목록(24시간), 상품상태(1시간), 회선상태(30분)
|
||||
- **캐시 키 전략**: `{cache_type}:{identifier}` 형식
|
||||
|
||||
#### KosClientService
|
||||
- **역할**: KOS 시스템과의 연동 처리
|
||||
- **의존성**: CircuitBreakerService, RetryService, KosAdapterService
|
||||
- **주요 기능**: KOS API 호출, Circuit Breaker 상태 관리, 재시도 로직
|
||||
|
||||
#### CircuitBreakerService / RetryService
|
||||
- **역할**: 외부 시스템 연동 안정성 보장
|
||||
- **패턴**: Circuit Breaker, Retry 패턴 적용
|
||||
- **설정**: 실패율 임계값, 재시도 횟수, 대기 시간 등
|
||||
|
||||
### 3.3 Domain Layer
|
||||
|
||||
#### Product
|
||||
- **역할**: 상품 정보 도메인 모델
|
||||
- **주요 속성**: productCode, productName, monthlyFee, dataAllowance, voiceAllowance, smsAllowance, status, operatorCode
|
||||
- **비즈니스 메소드**: `canChangeTo()`, `isSameOperator()`
|
||||
|
||||
#### ProductChangeHistory
|
||||
- **역할**: 상품변경 이력 도메인 모델
|
||||
- **주요 속성**: requestId, userId, lineNumber, currentProductCode, targetProductCode, processStatus, requestedAt, processedAt
|
||||
- **상태 관리**: `markAsCompleted()`, `markAsFailed()`
|
||||
|
||||
#### ProductChangeResult
|
||||
- **역할**: 상품변경 처리 결과 도메인 모델
|
||||
- **팩토리 메소드**: `createSuccessResult()`, `createFailureResult()`
|
||||
|
||||
### 3.4 Repository Layer
|
||||
|
||||
#### ProductRepository
|
||||
- **역할**: 상품 데이터 접근 인터페이스
|
||||
- **주요 메소드**: 상품상태 조회, 상품변경 요청 저장, 상태 업데이트
|
||||
|
||||
#### ProductChangeHistoryRepository
|
||||
- **역할**: 상품변경 이력 데이터 접근 인터페이스
|
||||
- **JPA Repository**: ProductChangeHistoryJpaRepository 활용
|
||||
- **Entity**: ProductChangeHistoryEntity (BaseTimeEntity 상속)
|
||||
|
||||
### 3.5 Config Layer
|
||||
|
||||
#### RestTemplateConfig
|
||||
- **역할**: REST 통신 설정
|
||||
- **설정 요소**: Connection Pool, Timeout, HTTP Client 설정
|
||||
|
||||
#### CacheConfig
|
||||
- **역할**: Redis 캐시 설정
|
||||
- **설정 요소**: Redis 연결, Cache Manager, 직렬화 설정
|
||||
|
||||
#### CircuitBreakerConfig
|
||||
- **역할**: Circuit Breaker 및 Retry 설정
|
||||
- **설정 요소**: 실패율 임계값, 최소 호출 수, 대기 시간
|
||||
|
||||
#### KosProperties
|
||||
- **역할**: KOS 연동 설정 프로퍼티
|
||||
- **설정 요소**: baseUrl, connectTimeout, readTimeout, maxRetries, retryDelay
|
||||
|
||||
### 3.6 External Layer
|
||||
|
||||
#### KosAdapterService
|
||||
- **역할**: KOS 시스템 연동 어댑터
|
||||
- **주요 기능**: KOS API 호출, 요청/응답 데이터 변환, HTTP 헤더 설정
|
||||
- **의존성**: KosProperties, RestTemplate
|
||||
|
||||
#### KosRequest / KosResponse
|
||||
- **역할**: KOS 시스템 연동을 위한 요청/응답 모델
|
||||
- **변환**: 내부 도메인 모델 ↔ KOS API 모델
|
||||
|
||||
### 3.7 Exception Layer
|
||||
|
||||
#### ProductChangeException
|
||||
- **역할**: 상품변경 관련 비즈니스 예외
|
||||
- **상속**: BusinessException 상속
|
||||
|
||||
#### ProductValidationException
|
||||
- **역할**: 상품변경 검증 실패 예외
|
||||
- **추가 정보**: 검증 상세 정보 목록 포함
|
||||
|
||||
#### KosConnectionException
|
||||
- **역할**: KOS 연동 관련 예외
|
||||
- **추가 정보**: 연동 서비스명 포함
|
||||
|
||||
#### CircuitBreakerException
|
||||
- **역할**: Circuit Breaker Open 상태 예외
|
||||
- **추가 정보**: 서비스명, 상태 정보 포함
|
||||
|
||||
## 4. 주요 설계 특징
|
||||
|
||||
### 4.1 Layered Architecture 적용
|
||||
- **Controller**: API 엔드포인트 및 HTTP 요청/응답 처리
|
||||
- **Service**: 비즈니스 로직 처리 및 트랜잭션 관리
|
||||
- **Domain**: 핵심 비즈니스 모델 및 도메인 규칙
|
||||
- **Repository**: 데이터 접근 및 영속성 관리
|
||||
|
||||
### 4.2 캐시 전략
|
||||
- **다층 캐시**: Redis를 활용한 성능 최적화
|
||||
- **TTL 차등 적용**: 데이터 특성에 따른 캐시 수명 관리
|
||||
- **캐시 무효화**: 상품변경 완료 시 관련 캐시 제거
|
||||
|
||||
### 4.3 외부 연동 안정성
|
||||
- **Circuit Breaker**: KOS 시스템 장애 시 빠른 실패 처리
|
||||
- **Retry**: 일시적 네트워크 오류에 대한 재시도 로직
|
||||
- **Timeout**: 응답 시간 초과 방지
|
||||
|
||||
### 4.4 예외 처리 전략
|
||||
- **계층별 예외**: 각 계층의 책임에 맞는 예외 정의
|
||||
- **비즈니스 예외**: 도메인 규칙 위반에 대한 명확한 예외
|
||||
- **인프라 예외**: 외부 시스템 연동 실패에 대한 예외
|
||||
|
||||
## 5. API와 클래스 매핑
|
||||
|
||||
| API 엔드포인트 | HTTP Method | Controller 메소드 | 주요 Service |
|
||||
|---|---|---|---|
|
||||
| `/products/menu` | GET | `getProductMenu()` | ProductService |
|
||||
| `/products/customer/{lineNumber}` | GET | `getCustomerInfo()` | ProductService, ProductCacheService |
|
||||
| `/products/available` | GET | `getAvailableProducts()` | ProductService, ProductCacheService |
|
||||
| `/products/change/validation` | POST | `validateProductChange()` | ProductValidationService |
|
||||
| `/products/change` | POST | `requestProductChange()` | ProductService, KosClientService |
|
||||
| `/products/change/{requestId}` | GET | `getProductChangeResult()` | ProductService |
|
||||
| `/products/history` | GET | `getProductChangeHistory()` | ProductService, ProductChangeHistoryRepository |
|
||||
|
||||
## 6. 시퀀스와 클래스 연관관계
|
||||
|
||||
### 6.1 상품변경 요청 시퀀스 매핑
|
||||
- **ProductController** → **ProductServiceImpl** → **ProductValidationService** → **KosClientService** → **KosAdapterService**
|
||||
- **캐시 처리**: ProductCacheService를 통한 Redis 연동
|
||||
- **이력 관리**: ProductChangeHistoryRepository를 통한 DB 저장
|
||||
|
||||
### 6.2 KOS 연동 시퀀스 매핑
|
||||
- **KosClientService** → **CircuitBreakerService** → **RetryService** → **KosAdapterService**
|
||||
- **상태 관리**: ProductChangeHistory 도메인 모델을 통한 상태 추적
|
||||
- **결과 처리**: ProductChangeResult를 통한 성공/실패 처리
|
||||
|
||||
## 7. 설계 파일
|
||||
|
||||
- **상세 클래스 설계**: [product-change.puml](./product-change.puml)
|
||||
- **간단 클래스 설계**: [product-change-simple.puml](./product-change-simple.puml)
|
||||
|
||||
## 8. 관련 문서
|
||||
|
||||
- **API 설계서**: [product-change-service-api.yaml](../api/product-change-service-api.yaml)
|
||||
- **내부 시퀀스 설계서**:
|
||||
- [product-상품변경요청.puml](../sequence/inner/product-상품변경요청.puml)
|
||||
- [product-KOS연동.puml](../sequence/inner/product-KOS연동.puml)
|
||||
- **유저스토리**: [userstory.md](../../userstory.md)
|
||||
- **공통 기반 클래스**: [common-base.puml](./common-base.puml)
|
||||
@@ -0,0 +1,176 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
|
||||
title Common Base Classes - 통신요금 관리 서비스
|
||||
|
||||
package "Common Module" {
|
||||
package "dto" {
|
||||
class ApiResponse<T> {
|
||||
-success: boolean
|
||||
-message: String
|
||||
-data: T
|
||||
-timestamp: LocalDateTime
|
||||
+of(data: T): ApiResponse<T>
|
||||
+success(data: T, message: String): ApiResponse<T>
|
||||
+error(message: String): ApiResponse<T>
|
||||
+getSuccess(): boolean
|
||||
+getMessage(): String
|
||||
+getData(): T
|
||||
+getTimestamp(): LocalDateTime
|
||||
}
|
||||
|
||||
class ErrorResponse {
|
||||
-code: String
|
||||
-message: String
|
||||
-details: String
|
||||
-timestamp: LocalDateTime
|
||||
+ErrorResponse(code: String, message: String, details: String)
|
||||
+getCode(): String
|
||||
+getMessage(): String
|
||||
+getDetails(): String
|
||||
+getTimestamp(): LocalDateTime
|
||||
}
|
||||
|
||||
class JwtTokenDTO {
|
||||
-accessToken: String
|
||||
-refreshToken: String
|
||||
-tokenType: String
|
||||
-expiresIn: long
|
||||
+JwtTokenDTO(accessToken: String, refreshToken: String, expiresIn: long)
|
||||
+getAccessToken(): String
|
||||
+getRefreshToken(): String
|
||||
+getTokenType(): String
|
||||
+getExpiresIn(): long
|
||||
}
|
||||
|
||||
class JwtTokenVerifyDTO {
|
||||
-userId: String
|
||||
-lineNumber: String
|
||||
-permissions: List<String>
|
||||
-expiresAt: LocalDateTime
|
||||
+JwtTokenVerifyDTO(userId: String, lineNumber: String, permissions: List<String>)
|
||||
+getUserId(): String
|
||||
+getLineNumber(): String
|
||||
+getPermissions(): List<String>
|
||||
+getExpiresAt(): LocalDateTime
|
||||
}
|
||||
}
|
||||
|
||||
package "entity" {
|
||||
abstract class BaseTimeEntity {
|
||||
#createdAt: LocalDateTime
|
||||
#updatedAt: LocalDateTime
|
||||
+getCreatedAt(): LocalDateTime
|
||||
+getUpdatedAt(): LocalDateTime
|
||||
+{abstract} getId(): Object
|
||||
}
|
||||
}
|
||||
|
||||
package "exception" {
|
||||
enum ErrorCode {
|
||||
AUTH001("인증 실패")
|
||||
AUTH002("토큰이 유효하지 않음")
|
||||
AUTH003("권한이 부족함")
|
||||
AUTH004("계정이 잠겨있음")
|
||||
AUTH005("토큰이 만료됨")
|
||||
BILL001("요금 조회 실패")
|
||||
BILL002("KOS 연동 실패")
|
||||
BILL003("조회 이력 없음")
|
||||
PROD001("상품변경 실패")
|
||||
PROD002("사전체크 실패")
|
||||
PROD003("상품정보 없음")
|
||||
SYS001("시스템 오류")
|
||||
SYS002("외부 연동 실패")
|
||||
|
||||
-code: String
|
||||
-message: String
|
||||
|
||||
+ErrorCode(code: String, message: String)
|
||||
+getCode(): String
|
||||
+getMessage(): String
|
||||
}
|
||||
|
||||
class BusinessException {
|
||||
-errorCode: ErrorCode
|
||||
-details: String
|
||||
+BusinessException(errorCode: ErrorCode)
|
||||
+BusinessException(errorCode: ErrorCode, details: String)
|
||||
+getErrorCode(): ErrorCode
|
||||
+getDetails(): String
|
||||
}
|
||||
|
||||
class InfraException {
|
||||
-errorCode: ErrorCode
|
||||
-details: String
|
||||
+InfraException(errorCode: ErrorCode)
|
||||
+InfraException(errorCode: ErrorCode, details: String)
|
||||
+getErrorCode(): ErrorCode
|
||||
+getDetails(): String
|
||||
}
|
||||
}
|
||||
|
||||
package "util" {
|
||||
class DateUtil {
|
||||
+{static} getCurrentDateTime(): LocalDateTime
|
||||
+{static} formatDate(date: LocalDateTime, pattern: String): String
|
||||
+{static} parseDate(dateString: String, pattern: String): LocalDateTime
|
||||
+{static} getStartOfMonth(date: LocalDateTime): LocalDateTime
|
||||
+{static} getEndOfMonth(date: LocalDateTime): LocalDateTime
|
||||
+{static} isWithinRange(date: LocalDateTime, start: LocalDateTime, end: LocalDateTime): boolean
|
||||
}
|
||||
|
||||
class SecurityUtil {
|
||||
+{static} encryptPassword(password: String): String
|
||||
+{static} verifyPassword(password: String, encodedPassword: String): boolean
|
||||
+{static} generateSalt(): String
|
||||
+{static} maskPhoneNumber(phoneNumber: String): String
|
||||
+{static} maskUserId(userId: String): String
|
||||
}
|
||||
|
||||
class ValidatorUtil {
|
||||
+{static} isValidPhoneNumber(phoneNumber: String): boolean
|
||||
+{static} isValidUserId(userId: String): boolean
|
||||
+{static} isValidPassword(password: String): boolean
|
||||
+{static} isNotEmpty(value: String): boolean
|
||||
+{static} isValidDateRange(startDate: LocalDateTime, endDate: LocalDateTime): boolean
|
||||
}
|
||||
}
|
||||
|
||||
package "config" {
|
||||
class JpaConfig {
|
||||
+auditorProvider(): AuditorAware<String>
|
||||
+entityManagerFactory(): LocalContainerEntityManagerFactoryBean
|
||||
+transactionManager(): PlatformTransactionManager
|
||||
}
|
||||
|
||||
interface CacheConfig {
|
||||
+redisConnectionFactory(): RedisConnectionFactory
|
||||
+redisTemplate(): RedisTemplate<String, Object>
|
||||
+cacheManager(): CacheManager
|
||||
+redisCacheConfiguration(): RedisCacheConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
package "aop" {
|
||||
class LoggingAspect {
|
||||
-logger: Logger
|
||||
+logExecutionTime(joinPoint: ProceedingJoinPoint): Object
|
||||
+logMethodEntry(joinPoint: JoinPoint): void
|
||||
+logMethodExit(joinPoint: JoinPoint, result: Object): void
|
||||
+logException(joinPoint: JoinPoint, exception: Exception): void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
' 관계 설정
|
||||
ApiResponse --> ErrorResponse : "contains"
|
||||
BusinessException --> ErrorCode : "uses"
|
||||
InfraException --> ErrorCode : "uses"
|
||||
|
||||
' 노트 추가
|
||||
note top of ApiResponse : "모든 API 응답의 표준 구조\n제네릭을 사용한 타입 안전성 보장"
|
||||
note top of BaseTimeEntity : "모든 엔티티의 기본 클래스\nJPA Auditing을 통한 생성/수정 시간 자동 관리"
|
||||
note top of ErrorCode : "시스템 전체의 오류 코드 표준화\n서비스별 오류 코드 체계"
|
||||
note top of LoggingAspect : "AOP를 통한 로깅 처리\n실행 시간 측정 및 예외 로깅"
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,176 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
|
||||
title KOS-Mock Service 클래스 설계 (간단)
|
||||
|
||||
package "com.unicorn.phonebill.kosmock" {
|
||||
|
||||
package "controller" {
|
||||
class KosMockController <<Controller>> {
|
||||
}
|
||||
}
|
||||
|
||||
package "service" {
|
||||
class KosMockService <<Service>> {
|
||||
}
|
||||
|
||||
class BillDataService <<Service>> {
|
||||
}
|
||||
|
||||
class ProductDataService <<Service>> {
|
||||
}
|
||||
|
||||
class ProductValidationService <<Service>> {
|
||||
}
|
||||
|
||||
class MockScenarioService <<Service>> {
|
||||
}
|
||||
}
|
||||
|
||||
package "dto" {
|
||||
class KosBillRequest <<DTO>> {
|
||||
}
|
||||
|
||||
class KosProductChangeRequest <<DTO>> {
|
||||
}
|
||||
|
||||
class MockBillResponse <<DTO>> {
|
||||
}
|
||||
|
||||
class MockProductChangeResponse <<DTO>> {
|
||||
}
|
||||
|
||||
class KosCustomerResponse <<DTO>> {
|
||||
}
|
||||
|
||||
class KosProductResponse <<DTO>> {
|
||||
}
|
||||
|
||||
class BillInfo <<Model>> {
|
||||
}
|
||||
|
||||
class ProductChangeResult <<Model>> {
|
||||
}
|
||||
}
|
||||
|
||||
package "repository" {
|
||||
interface MockDataRepository <<Repository>> {
|
||||
}
|
||||
|
||||
class MockDataRepositoryImpl <<Repository>> {
|
||||
}
|
||||
}
|
||||
|
||||
package "repository.entity" {
|
||||
class KosCustomerEntity <<Entity>> {
|
||||
}
|
||||
|
||||
class KosProductEntity <<Entity>> {
|
||||
}
|
||||
|
||||
class KosBillEntity <<Entity>> {
|
||||
}
|
||||
|
||||
class KosUsageEntity <<Entity>> {
|
||||
}
|
||||
|
||||
class KosContractEntity <<Entity>> {
|
||||
}
|
||||
|
||||
class KosInstallmentEntity <<Entity>> {
|
||||
}
|
||||
|
||||
class KosProductChangeHistoryEntity <<Entity>> {
|
||||
}
|
||||
}
|
||||
|
||||
package "repository.jpa" {
|
||||
interface KosCustomerJpaRepository <<JPA Repository>> {
|
||||
}
|
||||
|
||||
interface KosProductJpaRepository <<JPA Repository>> {
|
||||
}
|
||||
|
||||
interface KosBillJpaRepository <<JPA Repository>> {
|
||||
}
|
||||
|
||||
interface KosUsageJpaRepository <<JPA Repository>> {
|
||||
}
|
||||
|
||||
interface KosContractJpaRepository <<JPA Repository>> {
|
||||
}
|
||||
|
||||
interface KosInstallmentJpaRepository <<JPA Repository>> {
|
||||
}
|
||||
|
||||
interface KosProductChangeHistoryJpaRepository <<JPA Repository>> {
|
||||
}
|
||||
}
|
||||
|
||||
package "config" {
|
||||
class MockProperties <<Configuration>> {
|
||||
}
|
||||
|
||||
class KosMockConfig <<Configuration>> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
package "Common Module" {
|
||||
class ApiResponse<T> <<DTO>> {
|
||||
}
|
||||
|
||||
class BaseTimeEntity <<Entity>> {
|
||||
}
|
||||
|
||||
class BusinessException <<Exception>> {
|
||||
}
|
||||
}
|
||||
|
||||
' 관계 설정
|
||||
KosMockController --> KosMockService
|
||||
KosMockService --> BillDataService
|
||||
KosMockService --> ProductDataService
|
||||
KosMockService --> MockScenarioService
|
||||
BillDataService --> MockDataRepository
|
||||
ProductDataService --> MockDataRepository
|
||||
ProductDataService --> ProductValidationService
|
||||
ProductValidationService --> MockDataRepository
|
||||
MockScenarioService --> MockProperties
|
||||
|
||||
MockDataRepositoryImpl ..|> MockDataRepository
|
||||
MockDataRepositoryImpl --> KosCustomerJpaRepository
|
||||
MockDataRepositoryImpl --> KosProductJpaRepository
|
||||
MockDataRepositoryImpl --> KosBillJpaRepository
|
||||
MockDataRepositoryImpl --> KosUsageJpaRepository
|
||||
MockDataRepositoryImpl --> KosContractJpaRepository
|
||||
MockDataRepositoryImpl --> KosInstallmentJpaRepository
|
||||
MockDataRepositoryImpl --> KosProductChangeHistoryJpaRepository
|
||||
|
||||
KosCustomerJpaRepository --> KosCustomerEntity
|
||||
KosProductJpaRepository --> KosProductEntity
|
||||
KosBillJpaRepository --> KosBillEntity
|
||||
KosUsageJpaRepository --> KosUsageEntity
|
||||
KosContractJpaRepository --> KosContractEntity
|
||||
KosInstallmentJpaRepository --> KosInstallmentEntity
|
||||
KosProductChangeHistoryJpaRepository --> KosProductChangeHistoryEntity
|
||||
|
||||
KosCustomerEntity --|> BaseTimeEntity
|
||||
KosProductEntity --|> BaseTimeEntity
|
||||
KosBillEntity --|> BaseTimeEntity
|
||||
KosUsageEntity --|> BaseTimeEntity
|
||||
KosContractEntity --|> BaseTimeEntity
|
||||
KosInstallmentEntity --|> BaseTimeEntity
|
||||
KosProductChangeHistoryEntity --|> BaseTimeEntity
|
||||
|
||||
KosMockController --> ApiResponse
|
||||
|
||||
note top of KosMockController : **API 매핑표**\n\nPOST /kos/bill/inquiry\n- getBillInfo()\n- 요금조회 시뮬레이션\n\nPOST /kos/product/change\n- processProductChange()\n- 상품변경 시뮬레이션\n\nGET /kos/customer/{customerId}\n- getCustomerInfo()\n- 고객정보 조회\n\nGET /kos/products/available\n- getAvailableProducts()\n- 변경가능 상품목록\n\nGET /kos/line/{lineNumber}/status\n- getLineStatus()\n- 회선상태 조회
|
||||
|
||||
note right of MockScenarioService : **Mock 시나리오 규칙**\n\n요금조회:\n- 01012345678: 정상응답\n- 01012345679: 데이터없음\n- 01012345680: 시스템오류\n- 01012345681: 타임아웃\n\n상품변경:\n- 01012345678: 정상변경\n- 01012345679: 변경불가\n- 01012345680: 시스템오류\n- 01012345681: 잔액부족\n- PROD001→PROD999: 호환불가
|
||||
|
||||
note right of MockDataRepository : **데이터 접근 인터페이스**\n\n주요 메소드:\n- getMockBillTemplate()\n- getProductInfo()\n- getCustomerInfo()\n- saveProductChangeResult()\n- checkProductCompatibility()\n- getCustomerBalance()\n- getContractInfo()
|
||||
|
||||
note bottom of KosMockConfig : **Mock 설정**\n\n환경별 시나리오 설정:\n- mock.scenario.success.delay=500ms\n- mock.scenario.error.rate=5%\n- mock.scenario.timeout.enabled=true\n\n스레드풀 설정:\n- 비동기 로깅 및 메트릭 처리
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,588 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
|
||||
title KOS-Mock Service 클래스 설계 (상세)
|
||||
|
||||
package "com.unicorn.phonebill.kosmock" {
|
||||
|
||||
package "controller" {
|
||||
class KosMockController {
|
||||
-kosMockService: KosMockService
|
||||
+getBillInfo(lineNumber: String, inquiryMonth: String): ResponseEntity<ApiResponse<MockBillResponse>>
|
||||
+processProductChange(changeRequest: KosProductChangeRequest): ResponseEntity<ApiResponse<MockProductChangeResponse>>
|
||||
+getCustomerInfo(customerId: String): ResponseEntity<ApiResponse<KosCustomerResponse>>
|
||||
+getAvailableProducts(): ResponseEntity<ApiResponse<List<KosProductResponse>>>
|
||||
+getLineStatus(lineNumber: String): ResponseEntity<ApiResponse<KosLineStatusResponse>>
|
||||
-validateBillRequest(lineNumber: String, inquiryMonth: String): void
|
||||
-validateProductChangeRequest(request: KosProductChangeRequest): void
|
||||
}
|
||||
}
|
||||
|
||||
package "service" {
|
||||
class KosMockService {
|
||||
-billDataService: BillDataService
|
||||
-productDataService: ProductDataService
|
||||
-mockScenarioService: MockScenarioService
|
||||
+getBillInfo(lineNumber: String, inquiryMonth: String): MockBillResponse
|
||||
+processProductChange(changeRequest: KosProductChangeRequest): MockProductChangeResponse
|
||||
+getCustomerInfo(customerId: String): KosCustomerResponse
|
||||
+getAvailableProducts(): List<KosProductResponse>
|
||||
+getLineStatus(lineNumber: String): KosLineStatusResponse
|
||||
-logMockRequest(requestType: String, requestData: Object): void
|
||||
-updateMetrics(requestType: String, scenario: String, responseTime: long): void
|
||||
}
|
||||
|
||||
class BillDataService {
|
||||
-mockDataRepository: MockDataRepository
|
||||
+generateBillData(lineNumber: String, inquiryMonth: String): BillInfo
|
||||
-calculateDynamicCharges(lineNumber: String, inquiryMonth: String): BillAmount
|
||||
-generateUsageData(lineNumber: String, inquiryMonth: String): UsageInfo
|
||||
-applyDiscounts(billAmount: BillAmount, lineNumber: String): List<DiscountInfo>
|
||||
}
|
||||
|
||||
class ProductDataService {
|
||||
-mockDataRepository: MockDataRepository
|
||||
-productValidationService: ProductValidationService
|
||||
+executeProductChange(changeRequest: KosProductChangeRequest): ProductChangeResult
|
||||
+getProductInfo(productCode: String): KosProduct
|
||||
+getCustomerProducts(customerId: String): List<KosProduct>
|
||||
-calculateNewMonthlyFee(newProductCode: String): Integer
|
||||
}
|
||||
|
||||
class ProductValidationService {
|
||||
-mockDataRepository: MockDataRepository
|
||||
+validateProductChange(changeRequest: KosProductChangeRequest): ValidationResult
|
||||
+checkProductCompatibility(currentProduct: String, newProduct: String): Boolean
|
||||
+checkCustomerEligibility(customerId: String, newProductCode: String): Boolean
|
||||
-validateContractConstraints(customerId: String): Boolean
|
||||
-validateBalance(customerId: String): Boolean
|
||||
}
|
||||
|
||||
class MockScenarioService {
|
||||
-properties: MockProperties
|
||||
+determineScenario(lineNumber: String, inquiryMonth: String): MockScenario
|
||||
+determineProductChangeScenario(lineNumber: String, changeRequest: KosProductChangeRequest): MockScenario
|
||||
+simulateDelay(scenario: MockScenario): void
|
||||
-getScenarioByLineNumber(lineNumber: String): String
|
||||
-getScenarioByProductCodes(currentCode: String, newCode: String): String
|
||||
}
|
||||
}
|
||||
|
||||
package "dto.request" {
|
||||
class KosBillRequest {
|
||||
+lineNumber: String
|
||||
+inquiryMonth: String
|
||||
+validate(): void
|
||||
}
|
||||
|
||||
class KosProductChangeRequest {
|
||||
+transactionId: String
|
||||
+lineNumber: String
|
||||
+currentProductCode: String
|
||||
+newProductCode: String
|
||||
+changeReason: String
|
||||
+effectiveDate: String
|
||||
+validate(): void
|
||||
}
|
||||
}
|
||||
|
||||
package "dto.response" {
|
||||
class MockBillResponse {
|
||||
+resultCode: String
|
||||
+resultMessage: String
|
||||
+billInfo: BillInfo
|
||||
}
|
||||
|
||||
class MockProductChangeResponse {
|
||||
+resultCode: String
|
||||
+resultMessage: String
|
||||
+transactionId: String
|
||||
+changeInfo: ProductChangeResult
|
||||
}
|
||||
|
||||
class KosCustomerResponse {
|
||||
+customerId: String
|
||||
+phoneNumber: String
|
||||
+customerName: String
|
||||
+customerType: String
|
||||
+status: String
|
||||
+currentProduct: KosProduct
|
||||
}
|
||||
|
||||
class KosProductResponse {
|
||||
+productCode: String
|
||||
+productName: String
|
||||
+monthlyFee: Integer
|
||||
+dataLimit: Integer
|
||||
+voiceLimit: Integer
|
||||
+saleStatus: String
|
||||
}
|
||||
|
||||
class KosLineStatusResponse {
|
||||
+lineNumber: String
|
||||
+status: String
|
||||
+activationDate: LocalDate
|
||||
+contractInfo: ContractInfo
|
||||
}
|
||||
}
|
||||
|
||||
package "dto.model" {
|
||||
class BillInfo {
|
||||
+phoneNumber: String
|
||||
+billMonth: String
|
||||
+productName: String
|
||||
+contractInfo: ContractInfo
|
||||
+billAmount: BillAmount
|
||||
+discountInfo: List<DiscountInfo>
|
||||
+usage: UsageInfo
|
||||
+installment: InstallmentInfo
|
||||
+terminationFee: TerminationFeeInfo
|
||||
+billingPaymentInfo: BillingPaymentInfo
|
||||
}
|
||||
|
||||
class ProductChangeResult {
|
||||
+lineNumber: String
|
||||
+newProductCode: String
|
||||
+newProductName: String
|
||||
+changeDate: String
|
||||
+effectiveDate: String
|
||||
+monthlyFee: Integer
|
||||
+processResult: String
|
||||
+resultMessage: String
|
||||
}
|
||||
|
||||
class ContractInfo {
|
||||
+contractType: String
|
||||
+contractStartDate: LocalDate
|
||||
+contractEndDate: LocalDate
|
||||
+remainingMonths: Integer
|
||||
+penaltyAmount: Integer
|
||||
}
|
||||
|
||||
class BillAmount {
|
||||
+basicFee: Integer
|
||||
+callFee: Integer
|
||||
+dataFee: Integer
|
||||
+smsFee: Integer
|
||||
+additionalFee: Integer
|
||||
+discountAmount: Integer
|
||||
+totalAmount: Integer
|
||||
}
|
||||
|
||||
class UsageInfo {
|
||||
+voiceUsage: Integer
|
||||
+dataUsage: Integer
|
||||
+smsUsage: Integer
|
||||
+voiceLimit: Integer
|
||||
+dataLimit: Integer
|
||||
+smsLimit: Integer
|
||||
}
|
||||
|
||||
class DiscountInfo {
|
||||
+discountType: String
|
||||
+discountName: String
|
||||
+discountAmount: Integer
|
||||
+discountRate: BigDecimal
|
||||
}
|
||||
|
||||
class InstallmentInfo {
|
||||
+deviceModel: String
|
||||
+totalAmount: Integer
|
||||
+monthlyAmount: Integer
|
||||
+paidAmount: Integer
|
||||
+remainingAmount: Integer
|
||||
+remainingMonths: Integer
|
||||
}
|
||||
|
||||
class TerminationFeeInfo {
|
||||
+contractPenalty: Integer
|
||||
+installmentRemaining: Integer
|
||||
+otherFees: Integer
|
||||
+totalFee: Integer
|
||||
}
|
||||
|
||||
class BillingPaymentInfo {
|
||||
+dueDate: LocalDate
|
||||
+paymentDate: LocalDate
|
||||
+paymentStatus: String
|
||||
}
|
||||
|
||||
class ValidationResult {
|
||||
+valid: Boolean
|
||||
+errorCode: String
|
||||
+errorMessage: String
|
||||
+errorDetails: String
|
||||
}
|
||||
|
||||
class MockScenario {
|
||||
+type: String
|
||||
+delay: Long
|
||||
+errorCode: String
|
||||
+errorMessage: String
|
||||
}
|
||||
|
||||
class KosProduct {
|
||||
+productCode: String
|
||||
+productName: String
|
||||
+productType: String
|
||||
+monthlyFee: Integer
|
||||
+dataLimit: Integer
|
||||
+voiceLimit: Integer
|
||||
+smsLimit: Integer
|
||||
+saleStatus: String
|
||||
}
|
||||
}
|
||||
|
||||
package "repository" {
|
||||
interface MockDataRepository {
|
||||
+getMockBillTemplate(lineNumber: String): Optional<KosCustomerEntity>
|
||||
+getProductInfo(productCode: String): Optional<KosProductEntity>
|
||||
+getAvailableProducts(): List<KosProductEntity>
|
||||
+getCustomerInfo(customerId: String): Optional<KosCustomerEntity>
|
||||
+saveProductChangeResult(changeRequest: KosProductChangeRequest, result: ProductChangeResult): KosProductChangeHistoryEntity
|
||||
+checkProductCompatibility(currentProductCode: String, newProductCode: String): Boolean
|
||||
+getCustomerBalance(customerId: String): Integer
|
||||
+getContractInfo(customerId: String): Optional<KosContractEntity>
|
||||
}
|
||||
|
||||
class MockDataRepositoryImpl {
|
||||
-customerJpaRepository: KosCustomerJpaRepository
|
||||
-productJpaRepository: KosProductJpaRepository
|
||||
-billJpaRepository: KosBillJpaRepository
|
||||
-usageJpaRepository: KosUsageJpaRepository
|
||||
-discountJpaRepository: KosDiscountJpaRepository
|
||||
-contractJpaRepository: KosContractJpaRepository
|
||||
-installmentJpaRepository: KosInstallmentJpaRepository
|
||||
-terminationFeeJpaRepository: KosTerminationFeeJpaRepository
|
||||
-changeHistoryJpaRepository: KosProductChangeHistoryJpaRepository
|
||||
+getMockBillTemplate(lineNumber: String): Optional<KosCustomerEntity>
|
||||
+getProductInfo(productCode: String): Optional<KosProductEntity>
|
||||
+getAvailableProducts(): List<KosProductEntity>
|
||||
+getCustomerInfo(customerId: String): Optional<KosCustomerEntity>
|
||||
+saveProductChangeResult(changeRequest: KosProductChangeRequest, result: ProductChangeResult): KosProductChangeHistoryEntity
|
||||
+checkProductCompatibility(currentProductCode: String, newProductCode: String): Boolean
|
||||
+getCustomerBalance(customerId: String): Integer
|
||||
+getContractInfo(customerId: String): Optional<KosContractEntity>
|
||||
-buildBillInfo(customer: KosCustomerEntity, inquiryMonth: String): BillInfo
|
||||
-calculateUsage(customer: KosCustomerEntity, inquiryMonth: String): UsageInfo
|
||||
}
|
||||
}
|
||||
|
||||
package "repository.entity" {
|
||||
class KosCustomerEntity {
|
||||
+customerId: String
|
||||
+phoneNumber: String
|
||||
+customerName: String
|
||||
+customerType: String
|
||||
+status: String
|
||||
+regDate: LocalDate
|
||||
+currentProductCode: String
|
||||
+createdAt: LocalDateTime
|
||||
+updatedAt: LocalDateTime
|
||||
}
|
||||
|
||||
class KosProductEntity {
|
||||
+productCode: String
|
||||
+productName: String
|
||||
+productType: String
|
||||
+monthlyFee: Integer
|
||||
+dataLimit: Integer
|
||||
+voiceLimit: Integer
|
||||
+smsLimit: Integer
|
||||
+saleStatus: String
|
||||
+saleStartDate: LocalDate
|
||||
+saleEndDate: LocalDate
|
||||
+createdAt: LocalDateTime
|
||||
+updatedAt: LocalDateTime
|
||||
}
|
||||
|
||||
class KosBillEntity {
|
||||
+billId: Long
|
||||
+customerId: String
|
||||
+phoneNumber: String
|
||||
+billMonth: String
|
||||
+productCode: String
|
||||
+productName: String
|
||||
+basicFee: Integer
|
||||
+callFee: Integer
|
||||
+dataFee: Integer
|
||||
+smsFee: Integer
|
||||
+additionalFee: Integer
|
||||
+discountAmount: Integer
|
||||
+totalAmount: Integer
|
||||
+paymentStatus: String
|
||||
+dueDate: LocalDate
|
||||
+paymentDate: LocalDate
|
||||
+createdAt: LocalDateTime
|
||||
}
|
||||
|
||||
class KosUsageEntity {
|
||||
+usageId: Long
|
||||
+customerId: String
|
||||
+phoneNumber: String
|
||||
+usageMonth: String
|
||||
+voiceUsage: Integer
|
||||
+dataUsage: Integer
|
||||
+smsUsage: Integer
|
||||
+voiceLimit: Integer
|
||||
+dataLimit: Integer
|
||||
+smsLimit: Integer
|
||||
+createdAt: LocalDateTime
|
||||
}
|
||||
|
||||
class KosDiscountEntity {
|
||||
+discountId: Long
|
||||
+customerId: String
|
||||
+phoneNumber: String
|
||||
+billMonth: String
|
||||
+discountType: String
|
||||
+discountName: String
|
||||
+discountAmount: Integer
|
||||
+discountRate: BigDecimal
|
||||
+applyStartDate: LocalDate
|
||||
+applyEndDate: LocalDate
|
||||
+createdAt: LocalDateTime
|
||||
}
|
||||
|
||||
class KosContractEntity {
|
||||
+contractId: Long
|
||||
+customerId: String
|
||||
+phoneNumber: String
|
||||
+contractType: String
|
||||
+contractStartDate: LocalDate
|
||||
+contractEndDate: LocalDate
|
||||
+contractStatus: String
|
||||
+penaltyAmount: Integer
|
||||
+remainingMonths: Integer
|
||||
+createdAt: LocalDateTime
|
||||
}
|
||||
|
||||
class KosInstallmentEntity {
|
||||
+installmentId: Long
|
||||
+customerId: String
|
||||
+phoneNumber: String
|
||||
+deviceModel: String
|
||||
+totalAmount: Integer
|
||||
+monthlyAmount: Integer
|
||||
+paidAmount: Integer
|
||||
+remainingAmount: Integer
|
||||
+installmentMonths: Integer
|
||||
+remainingMonths: Integer
|
||||
+startDate: LocalDate
|
||||
+endDate: LocalDate
|
||||
+status: String
|
||||
+createdAt: LocalDateTime
|
||||
}
|
||||
|
||||
class KosTerminationFeeEntity {
|
||||
+feeId: Long
|
||||
+customerId: String
|
||||
+phoneNumber: String
|
||||
+contractPenalty: Integer
|
||||
+installmentRemaining: Integer
|
||||
+otherFees: Integer
|
||||
+totalFee: Integer
|
||||
+calculatedDate: LocalDate
|
||||
+createdAt: LocalDateTime
|
||||
}
|
||||
|
||||
class KosProductChangeHistoryEntity {
|
||||
+historyId: Long
|
||||
+customerId: String
|
||||
+phoneNumber: String
|
||||
+requestId: String
|
||||
+beforeProductCode: String
|
||||
+afterProductCode: String
|
||||
+changeStatus: String
|
||||
+changeDate: LocalDate
|
||||
+processResult: String
|
||||
+resultMessage: String
|
||||
+requestDatetime: LocalDateTime
|
||||
+processDatetime: LocalDateTime
|
||||
+createdAt: LocalDateTime
|
||||
}
|
||||
}
|
||||
|
||||
package "repository.jpa" {
|
||||
interface KosCustomerJpaRepository {
|
||||
+findByPhoneNumber(phoneNumber: String): Optional<KosCustomerEntity>
|
||||
+findByCustomerId(customerId: String): Optional<KosCustomerEntity>
|
||||
}
|
||||
|
||||
interface KosProductJpaRepository {
|
||||
+findByProductCode(productCode: String): Optional<KosProductEntity>
|
||||
+findBySaleStatus(saleStatus: String): List<KosProductEntity>
|
||||
}
|
||||
|
||||
interface KosBillJpaRepository {
|
||||
+findByPhoneNumberAndBillMonth(phoneNumber: String, billMonth: String): Optional<KosBillEntity>
|
||||
+findByCustomerIdAndBillMonth(customerId: String, billMonth: String): Optional<KosBillEntity>
|
||||
}
|
||||
|
||||
interface KosUsageJpaRepository {
|
||||
+findByPhoneNumberAndUsageMonth(phoneNumber: String, usageMonth: String): Optional<KosUsageEntity>
|
||||
}
|
||||
|
||||
interface KosDiscountJpaRepository {
|
||||
+findByPhoneNumberAndBillMonth(phoneNumber: String, billMonth: String): List<KosDiscountEntity>
|
||||
}
|
||||
|
||||
interface KosContractJpaRepository {
|
||||
+findByCustomerId(customerId: String): Optional<KosContractEntity>
|
||||
+findByPhoneNumber(phoneNumber: String): Optional<KosContractEntity>
|
||||
}
|
||||
|
||||
interface KosInstallmentJpaRepository {
|
||||
+findByCustomerIdAndStatus(customerId: String, status: String): List<KosInstallmentEntity>
|
||||
}
|
||||
|
||||
interface KosTerminationFeeJpaRepository {
|
||||
+findByCustomerId(customerId: String): Optional<KosTerminationFeeEntity>
|
||||
}
|
||||
|
||||
interface KosProductChangeHistoryJpaRepository {
|
||||
+findByRequestId(requestId: String): Optional<KosProductChangeHistoryEntity>
|
||||
+findByPhoneNumberOrderByRequestDatetimeDesc(phoneNumber: String): List<KosProductChangeHistoryEntity>
|
||||
}
|
||||
}
|
||||
|
||||
package "config" {
|
||||
class MockProperties {
|
||||
+scenario: MockScenarioProperties
|
||||
+delay: MockDelayProperties
|
||||
+error: MockErrorProperties
|
||||
}
|
||||
|
||||
class MockScenarioProperties {
|
||||
+successLineNumbers: List<String>
|
||||
+noDataLineNumbers: List<String>
|
||||
+systemErrorLineNumbers: List<String>
|
||||
+timeoutLineNumbers: List<String>
|
||||
}
|
||||
|
||||
class MockDelayProperties {
|
||||
+billInquiry: Long
|
||||
+productChange: Long
|
||||
+timeout: Long
|
||||
}
|
||||
|
||||
class MockErrorProperties {
|
||||
+rate: Double
|
||||
+enabled: Boolean
|
||||
}
|
||||
|
||||
class KosMockConfig {
|
||||
+mockProperties(): MockProperties
|
||||
+mockScenarioService(properties: MockProperties): MockScenarioService
|
||||
+taskExecutor(): ThreadPoolTaskExecutor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
package "Common Module" {
|
||||
package "dto" {
|
||||
class ApiResponse<T> {
|
||||
-success: boolean
|
||||
-message: String
|
||||
-data: T
|
||||
-timestamp: LocalDateTime
|
||||
}
|
||||
|
||||
class ErrorResponse {
|
||||
-code: String
|
||||
-message: String
|
||||
-details: String
|
||||
-timestamp: LocalDateTime
|
||||
}
|
||||
}
|
||||
|
||||
package "entity" {
|
||||
abstract class BaseTimeEntity {
|
||||
#createdAt: LocalDateTime
|
||||
#updatedAt: LocalDateTime
|
||||
}
|
||||
}
|
||||
|
||||
package "exception" {
|
||||
enum ErrorCode {
|
||||
BILL002("KOS 연동 실패")
|
||||
PROD001("상품변경 실패")
|
||||
SYS002("외부 연동 실패")
|
||||
}
|
||||
|
||||
class BusinessException {
|
||||
-errorCode: ErrorCode
|
||||
-details: String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
' 관계 설정
|
||||
KosMockController --> KosMockService : uses
|
||||
KosMockService --> BillDataService : uses
|
||||
KosMockService --> ProductDataService : uses
|
||||
KosMockService --> MockScenarioService : uses
|
||||
BillDataService --> MockDataRepository : uses
|
||||
ProductDataService --> MockDataRepository : uses
|
||||
ProductDataService --> ProductValidationService : uses
|
||||
ProductValidationService --> MockDataRepository : uses
|
||||
MockScenarioService --> MockProperties : uses
|
||||
|
||||
MockDataRepositoryImpl ..|> MockDataRepository : implements
|
||||
MockDataRepositoryImpl --> KosCustomerJpaRepository : uses
|
||||
MockDataRepositoryImpl --> KosProductJpaRepository : uses
|
||||
MockDataRepositoryImpl --> KosBillJpaRepository : uses
|
||||
MockDataRepositoryImpl --> KosUsageJpaRepository : uses
|
||||
MockDataRepositoryImpl --> KosDiscountJpaRepository : uses
|
||||
MockDataRepositoryImpl --> KosContractJpaRepository : uses
|
||||
MockDataRepositoryImpl --> KosInstallmentJpaRepository : uses
|
||||
MockDataRepositoryImpl --> KosTerminationFeeJpaRepository : uses
|
||||
MockDataRepositoryImpl --> KosProductChangeHistoryJpaRepository : uses
|
||||
|
||||
KosCustomerJpaRepository --> KosCustomerEntity : manages
|
||||
KosProductJpaRepository --> KosProductEntity : manages
|
||||
KosBillJpaRepository --> KosBillEntity : manages
|
||||
KosUsageJpaRepository --> KosUsageEntity : manages
|
||||
KosDiscountJpaRepository --> KosDiscountEntity : manages
|
||||
KosContractJpaRepository --> KosContractEntity : manages
|
||||
KosInstallmentJpaRepository --> KosInstallmentEntity : manages
|
||||
KosTerminationFeeJpaRepository --> KosTerminationFeeEntity : manages
|
||||
KosProductChangeHistoryJpaRepository --> KosProductChangeHistoryEntity : manages
|
||||
|
||||
' BaseTimeEntity 상속
|
||||
KosCustomerEntity --|> BaseTimeEntity
|
||||
KosProductEntity --|> BaseTimeEntity
|
||||
KosBillEntity --|> BaseTimeEntity
|
||||
KosUsageEntity --|> BaseTimeEntity
|
||||
KosDiscountEntity --|> BaseTimeEntity
|
||||
KosContractEntity --|> BaseTimeEntity
|
||||
KosInstallmentEntity --|> BaseTimeEntity
|
||||
KosTerminationFeeEntity --|> BaseTimeEntity
|
||||
KosProductChangeHistoryEntity --|> BaseTimeEntity
|
||||
|
||||
' DTO 관계
|
||||
KosMockController --> KosBillRequest : uses
|
||||
KosMockController --> KosProductChangeRequest : uses
|
||||
KosMockController --> MockBillResponse : creates
|
||||
KosMockController --> MockProductChangeResponse : creates
|
||||
KosMockController --> KosCustomerResponse : creates
|
||||
KosMockController --> KosProductResponse : creates
|
||||
KosMockController --> KosLineStatusResponse : creates
|
||||
|
||||
MockBillResponse --> BillInfo : contains
|
||||
MockProductChangeResponse --> ProductChangeResult : contains
|
||||
BillInfo --> ContractInfo : contains
|
||||
BillInfo --> BillAmount : contains
|
||||
BillInfo --> UsageInfo : contains
|
||||
BillInfo --> InstallmentInfo : contains
|
||||
BillInfo --> TerminationFeeInfo : contains
|
||||
BillInfo --> BillingPaymentInfo : contains
|
||||
BillInfo --> DiscountInfo : contains
|
||||
|
||||
' 공통 모듈 사용
|
||||
KosMockController --> ApiResponse : uses
|
||||
KosMockService --> BusinessException : throws
|
||||
ProductValidationService --> ValidationResult : creates
|
||||
MockScenarioService --> MockScenario : creates
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,302 @@
|
||||
# 패키지 구조도 - 통신요금 관리 서비스
|
||||
|
||||
## 전체 패키지 구조
|
||||
|
||||
```
|
||||
com.unicorn.phonebill/
|
||||
├── common/ # 공통 모듈
|
||||
│ ├── dto/
|
||||
│ │ ├── ApiResponse.java # 표준 API 응답 구조
|
||||
│ │ ├── ErrorResponse.java # 오류 응답 구조
|
||||
│ │ ├── JwtTokenDTO.java # JWT 토큰 정보
|
||||
│ │ └── JwtTokenVerifyDTO.java # JWT 토큰 검증 결과
|
||||
│ ├── entity/
|
||||
│ │ └── BaseTimeEntity.java # 기본 엔티티 클래스
|
||||
│ ├── exception/
|
||||
│ │ ├── BusinessException.java # 비즈니스 예외
|
||||
│ │ ├── InfraException.java # 인프라 예외
|
||||
│ │ └── ErrorCode.java # 오류 코드 열거형
|
||||
│ ├── util/
|
||||
│ │ ├── DateUtil.java # 날짜 유틸리티
|
||||
│ │ ├── SecurityUtil.java # 보안 유틸리티
|
||||
│ │ └── ValidatorUtil.java # 검증 유틸리티
|
||||
│ ├── config/
|
||||
│ │ └── JpaConfig.java # JPA 설정
|
||||
│ └── aop/
|
||||
│ └── LoggingAspect.java # 로깅 AOP
|
||||
├── auth/ # 인증 서비스
|
||||
│ ├── AuthApplication.java # Spring Boot 메인 클래스
|
||||
│ ├── controller/
|
||||
│ │ └── AuthController.java # 인증 API 컨트롤러
|
||||
│ ├── dto/
|
||||
│ │ ├── LoginRequest.java # 로그인 요청
|
||||
│ │ ├── LoginResponse.java # 로그인 응답
|
||||
│ │ ├── LogoutRequest.java # 로그아웃 요청
|
||||
│ │ ├── TokenRefreshRequest.java # 토큰 갱신 요청
|
||||
│ │ ├── TokenRefreshResponse.java # 토큰 갱신 응답
|
||||
│ │ ├── PermissionRequest.java # 권한 확인 요청
|
||||
│ │ ├── PermissionResponse.java # 권한 확인 응답
|
||||
│ │ ├── UserInfoResponse.java # 사용자 정보 응답
|
||||
│ │ └── TokenVerifyResponse.java # 토큰 검증 응답
|
||||
│ ├── service/
|
||||
│ │ ├── AuthService.java # 인증 서비스 인터페이스
|
||||
│ │ ├── AuthServiceImpl.java # 인증 서비스 구현체
|
||||
│ │ ├── TokenService.java # 토큰 서비스 인터페이스
|
||||
│ │ ├── TokenServiceImpl.java # 토큰 서비스 구현체
|
||||
│ │ ├── PermissionService.java # 권한 서비스 인터페이스
|
||||
│ │ └── PermissionServiceImpl.java # 권한 서비스 구현체
|
||||
│ ├── domain/
|
||||
│ │ ├── User.java # 사용자 도메인 모델
|
||||
│ │ ├── UserSession.java # 사용자 세션 도메인 모델
|
||||
│ │ ├── LoginResult.java # 로그인 결과
|
||||
│ │ ├── TokenInfo.java # 토큰 정보
|
||||
│ │ ├── Permission.java # 권한 정보
|
||||
│ │ └── UserInfo.java # 사용자 상세 정보
|
||||
│ ├── repository/
|
||||
│ │ ├── UserRepository.java # 사용자 리포지토리 인터페이스
|
||||
│ │ ├── UserRepositoryImpl.java # 사용자 리포지토리 구현체
|
||||
│ │ ├── SessionRepository.java # 세션 리포지토리 인터페이스
|
||||
│ │ ├── SessionRepositoryImpl.java # 세션 리포지토리 구현체
|
||||
│ │ ├── entity/
|
||||
│ │ │ ├── UserEntity.java # 사용자 엔티티
|
||||
│ │ │ ├── UserSessionEntity.java # 사용자 세션 엔티티
|
||||
│ │ │ └── UserPermissionEntity.java # 사용자 권한 엔티티
|
||||
│ │ └── jpa/
|
||||
│ │ ├── UserJpaRepository.java # 사용자 JPA 리포지토리
|
||||
│ │ ├── UserSessionJpaRepository.java # 세션 JPA 리포지토리
|
||||
│ │ └── UserPermissionJpaRepository.java # 권한 JPA 리포지토리
|
||||
│ └── config/
|
||||
│ ├── SecurityConfig.java # 보안 설정
|
||||
│ ├── JwtConfig.java # JWT 설정
|
||||
│ └── RedisConfig.java # Redis 설정
|
||||
├── bill/ # 요금조회 서비스
|
||||
│ ├── BillApplication.java # Spring Boot 메인 클래스
|
||||
│ ├── controller/
|
||||
│ │ └── BillController.java # 요금조회 API 컨트롤러
|
||||
│ ├── dto/
|
||||
│ │ ├── BillMenuResponse.java # 요금조회 메뉴 응답
|
||||
│ │ ├── BillInquiryRequest.java # 요금조회 요청
|
||||
│ │ ├── BillInquiryResponse.java # 요금조회 응답
|
||||
│ │ ├── BillStatusResponse.java # 요금조회 상태 응답
|
||||
│ │ ├── BillHistoryRequest.java # 요금조회 이력 요청
|
||||
│ │ ├── BillHistoryResponse.java # 요금조회 이력 응답
|
||||
│ │ ├── BillDetailInfo.java # 요금 상세 정보
|
||||
│ │ ├── DiscountInfo.java # 할인 정보
|
||||
│ │ └── UsageInfo.java # 사용량 정보
|
||||
│ ├── service/
|
||||
│ │ ├── BillService.java # 요금조회 서비스 인터페이스
|
||||
│ │ ├── BillServiceImpl.java # 요금조회 서비스 구현체
|
||||
│ │ ├── BillCacheService.java # 요금 캐시 서비스 인터페이스
|
||||
│ │ ├── BillCacheServiceImpl.java # 요금 캐시 서비스 구현체
|
||||
│ │ ├── KosClientService.java # KOS 클라이언트 서비스 인터페이스
|
||||
│ │ ├── KosClientServiceImpl.java # KOS 클라이언트 서비스 구현체
|
||||
│ │ ├── BillHistoryService.java # 요금조회 이력 서비스 인터페이스
|
||||
│ │ └── BillHistoryServiceImpl.java # 요금조회 이력 서비스 구현체
|
||||
│ ├── domain/
|
||||
│ │ ├── BillInfo.java # 요금 정보 도메인 모델
|
||||
│ │ ├── BillHistory.java # 요금조회 이력 도메인 모델
|
||||
│ │ ├── KosBillRequest.java # KOS 요금조회 요청
|
||||
│ │ ├── KosBillResponse.java # KOS 요금조회 응답
|
||||
│ │ ├── BillInquiryResult.java # 요금조회 결과
|
||||
│ │ ├── BillStatus.java # 요금조회 상태 열거형
|
||||
│ │ └── RequestStatus.java # 요청 상태 열거형
|
||||
│ ├── repository/
|
||||
│ │ ├── BillHistoryRepository.java # 요금조회 이력 리포지토리 인터페이스
|
||||
│ │ ├── BillHistoryRepositoryImpl.java # 요금조회 이력 리포지토리 구현체
|
||||
│ │ ├── entity/
|
||||
│ │ │ ├── BillHistoryEntity.java # 요금조회 이력 엔티티
|
||||
│ │ │ └── BillRequestEntity.java # 요금조회 요청 엔티티
|
||||
│ │ └── jpa/
|
||||
│ │ ├── BillHistoryJpaRepository.java # 요금조회 이력 JPA 리포지토리
|
||||
│ │ └── BillRequestJpaRepository.java # 요금조회 요청 JPA 리포지토리
|
||||
│ └── config/
|
||||
│ ├── RestTemplateConfig.java # RestTemplate 설정
|
||||
│ ├── CacheConfig.java # 캐시 설정
|
||||
│ ├── CircuitBreakerConfig.java # Circuit Breaker 설정
|
||||
│ ├── RetryConfig.java # 재시도 설정
|
||||
│ ├── AsyncConfig.java # 비동기 설정
|
||||
│ ├── KosApiConfig.java # KOS API 설정
|
||||
│ └── SwaggerConfig.java # Swagger 설정
|
||||
├── product/ # 상품변경 서비스
|
||||
│ ├── ProductApplication.java # Spring Boot 메인 클래스
|
||||
│ ├── controller/
|
||||
│ │ └── ProductController.java # 상품변경 API 컨트롤러
|
||||
│ ├── dto/
|
||||
│ │ ├── ProductMenuResponse.java # 상품변경 메뉴 응답
|
||||
│ │ ├── CustomerInfoResponse.java # 고객정보 응답
|
||||
│ │ ├── AvailableProductsResponse.java # 변경가능 상품 응답
|
||||
│ │ ├── ProductValidationRequest.java # 상품변경 사전체크 요청
|
||||
│ │ ├── ProductValidationResponse.java # 상품변경 사전체크 응답
|
||||
│ │ ├── ProductChangeRequest.java # 상품변경 요청
|
||||
│ │ ├── ProductChangeResponse.java # 상품변경 응답
|
||||
│ │ ├── ProductChangeResultResponse.java # 상품변경 결과 응답
|
||||
│ │ ├── ProductChangeHistoryRequest.java # 상품변경 이력 요청
|
||||
│ │ ├── ProductChangeHistoryResponse.java # 상품변경 이력 응답
|
||||
│ │ ├── ProductInfo.java # 상품 정보
|
||||
│ │ ├── CustomerInfo.java # 고객 정보
|
||||
│ │ ├── ValidationResult.java # 검증 결과
|
||||
│ │ ├── ChangeResult.java # 변경 결과
|
||||
│ │ ├── ProductStatus.java # 상품 상태 열거형
|
||||
│ │ ├── ChangeStatus.java # 변경 상태 열거형
|
||||
│ │ └── ValidationStatus.java # 검증 상태 열거형
|
||||
│ ├── service/
|
||||
│ │ ├── ProductService.java # 상품변경 서비스 인터페이스
|
||||
│ │ ├── ProductServiceImpl.java # 상품변경 서비스 구현체
|
||||
│ │ ├── ProductValidationService.java # 상품변경 검증 서비스 인터페이스
|
||||
│ │ ├── ProductValidationServiceImpl.java # 상품변경 검증 서비스 구현체
|
||||
│ │ ├── ProductCacheService.java # 상품 캐시 서비스 인터페이스
|
||||
│ │ ├── ProductCacheServiceImpl.java # 상품 캐시 서비스 구현체
|
||||
│ │ ├── KosClientService.java # KOS 클라이언트 서비스 인터페이스
|
||||
│ │ ├── KosClientServiceImpl.java # KOS 클라이언트 서비스 구현체
|
||||
│ │ ├── ProductHistoryService.java # 상품변경 이력 서비스 인터페이스
|
||||
│ │ ├── ProductHistoryServiceImpl.java # 상품변경 이력 서비스 구현체
|
||||
│ │ ├── AsyncService.java # 비동기 서비스 인터페이스
|
||||
│ │ └── AsyncServiceImpl.java # 비동기 서비스 구현체
|
||||
│ ├── domain/
|
||||
│ │ ├── Product.java # 상품 도메인 모델
|
||||
│ │ ├── Customer.java # 고객 도메인 모델
|
||||
│ │ ├── ProductChangeHistory.java # 상품변경 이력 도메인 모델
|
||||
│ │ ├── ProductValidation.java # 상품변경 검증 도메인 모델
|
||||
│ │ ├── KosProductChangeRequest.java # KOS 상품변경 요청
|
||||
│ │ ├── KosProductChangeResponse.java # KOS 상품변경 응답
|
||||
│ │ ├── ProductChangeResult.java # 상품변경 결과
|
||||
│ │ ├── ChangeRequestStatus.java # 변경요청 상태 열거형
|
||||
│ │ └── ValidationErrorType.java # 검증 오류 타입 열거형
|
||||
│ ├── repository/
|
||||
│ │ ├── ProductChangeHistoryRepository.java # 상품변경 이력 리포지토리 인터페이스
|
||||
│ │ ├── ProductChangeHistoryRepositoryImpl.java # 상품변경 이력 리포지토리 구현체
|
||||
│ │ ├── ProductRepository.java # 상품 리포지토리 인터페이스
|
||||
│ │ ├── ProductRepositoryImpl.java # 상품 리포지토리 구현체
|
||||
│ │ ├── entity/
|
||||
│ │ │ ├── ProductChangeHistoryEntity.java # 상품변경 이력 엔티티
|
||||
│ │ │ └── ProductEntity.java # 상품 엔티티
|
||||
│ │ └── jpa/
|
||||
│ │ ├── ProductChangeHistoryJpaRepository.java # 상품변경 이력 JPA 리포지토리
|
||||
│ │ └── ProductJpaRepository.java # 상품 JPA 리포지토리
|
||||
│ ├── external/
|
||||
│ │ ├── KosApiClient.java # KOS API 클라이언트
|
||||
│ │ ├── KosAdapterService.java # KOS 어댑터 서비스
|
||||
│ │ └── CircuitBreakerService.java # Circuit Breaker 서비스
|
||||
│ ├── config/
|
||||
│ │ ├── RestTemplateConfig.java # RestTemplate 설정
|
||||
│ │ ├── CacheConfig.java # 캐시 설정
|
||||
│ │ ├── CircuitBreakerConfig.java # Circuit Breaker 설정
|
||||
│ │ ├── AsyncConfig.java # 비동기 설정
|
||||
│ │ ├── RetryConfig.java # 재시도 설정
|
||||
│ │ ├── KosApiConfig.java # KOS API 설정
|
||||
│ │ └── SwaggerConfig.java # Swagger 설정
|
||||
│ └── exception/
|
||||
│ ├── ProductNotFoundException.java # 상품 없음 예외
|
||||
│ ├── ProductValidationException.java # 상품변경 검증 예외
|
||||
│ ├── ProductChangeException.java # 상품변경 예외
|
||||
│ └── KosIntegrationException.java # KOS 연동 예외
|
||||
└── kosmock/ # KOS Mock 서비스
|
||||
├── KosMockApplication.java # Spring Boot 메인 클래스
|
||||
├── controller/
|
||||
│ └── KosMockController.java # KOS Mock API 컨트롤러
|
||||
├── service/
|
||||
│ ├── KosMockService.java # KOS Mock 서비스 인터페이스
|
||||
│ ├── KosMockServiceImpl.java # KOS Mock 서비스 구현체
|
||||
│ ├── BillDataService.java # 요금 데이터 서비스 인터페이스
|
||||
│ ├── BillDataServiceImpl.java # 요금 데이터 서비스 구현체
|
||||
│ ├── ProductDataService.java # 상품 데이터 서비스 인터페이스
|
||||
│ ├── ProductDataServiceImpl.java # 상품 데이터 서비스 구현체
|
||||
│ ├── MockScenarioService.java # Mock 시나리오 서비스 인터페이스
|
||||
│ ├── MockScenarioServiceImpl.java # Mock 시나리오 서비스 구현체
|
||||
│ ├── ProductValidationService.java # 상품 검증 서비스 인터페이스
|
||||
│ └── ProductValidationServiceImpl.java # 상품 검증 서비스 구현체
|
||||
├── dto/
|
||||
│ ├── KosBillRequest.java # KOS 요금조회 요청
|
||||
│ ├── KosBillResponse.java # KOS 요금조회 응답
|
||||
│ ├── KosProductChangeRequest.java # KOS 상품변경 요청
|
||||
│ ├── KosProductChangeResponse.java # KOS 상품변경 응답
|
||||
│ ├── KosCustomerInfoResponse.java # KOS 고객정보 응답
|
||||
│ ├── KosAvailableProductsResponse.java # KOS 변경가능 상품 응답
|
||||
│ ├── KosLineStatusResponse.java # KOS 회선상태 응답
|
||||
│ ├── MockScenario.java # Mock 시나리오
|
||||
│ ├── KosBillInfo.java # KOS 요금 정보
|
||||
│ ├── KosProductInfo.java # KOS 상품 정보
|
||||
│ ├── KosCustomerInfo.java # KOS 고객 정보
|
||||
│ ├── KosUsageInfo.java # KOS 사용량 정보
|
||||
│ ├── KosDiscountInfo.java # KOS 할인 정보
|
||||
│ ├── KosContractInfo.java # KOS 약정 정보
|
||||
│ ├── KosInstallmentInfo.java # KOS 할부 정보
|
||||
│ ├── KosTerminationFeeInfo.java # KOS 해지비용 정보
|
||||
│ └── KosValidationResult.java # KOS 검증 결과
|
||||
├── repository/
|
||||
│ ├── MockDataRepository.java # Mock 데이터 리포지토리 인터페이스
|
||||
│ ├── MockDataRepositoryImpl.java # Mock 데이터 리포지토리 구현체
|
||||
│ ├── entity/
|
||||
│ │ ├── KosCustomerEntity.java # KOS 고객정보 엔티티
|
||||
│ │ ├── KosProductEntity.java # KOS 상품정보 엔티티
|
||||
│ │ ├── KosBillEntity.java # KOS 요금정보 엔티티
|
||||
│ │ ├── KosUsageEntity.java # KOS 사용량정보 엔티티
|
||||
│ │ ├── KosDiscountEntity.java # KOS 할인정보 엔티티
|
||||
│ │ ├── KosContractEntity.java # KOS 약정정보 엔티티
|
||||
│ │ ├── KosInstallmentEntity.java # KOS 할부정보 엔티티
|
||||
│ │ ├── KosTerminationFeeEntity.java # KOS 해지비용정보 엔티티
|
||||
│ │ └── KosProductChangeHistoryEntity.java # KOS 상품변경이력 엔티티
|
||||
│ └── jpa/
|
||||
│ ├── KosCustomerJpaRepository.java # KOS 고객정보 JPA 리포지토리
|
||||
│ ├── KosProductJpaRepository.java # KOS 상품정보 JPA 리포지토리
|
||||
│ ├── KosBillJpaRepository.java # KOS 요금정보 JPA 리포지토리
|
||||
│ ├── KosUsageJpaRepository.java # KOS 사용량정보 JPA 리포지토리
|
||||
│ ├── KosDiscountJpaRepository.java # KOS 할인정보 JPA 리포지토리
|
||||
│ ├── KosContractJpaRepository.java # KOS 약정정보 JPA 리포지토리
|
||||
│ ├── KosInstallmentJpaRepository.java # KOS 할부정보 JPA 리포지토리
|
||||
│ ├── KosTerminationFeeJpaRepository.java # KOS 해지비용정보 JPA 리포지토리
|
||||
│ └── KosProductChangeHistoryJpaRepository.java # KOS 상품변경이력 JPA 리포지토리
|
||||
└── config/
|
||||
├── MockDataConfig.java # Mock 데이터 설정
|
||||
├── MockDelayConfig.java # Mock 지연 설정
|
||||
└── SwaggerConfig.java # Swagger 설정
|
||||
```
|
||||
|
||||
## 패키지 구성 요약
|
||||
|
||||
### 📊 서비스별 클래스 수
|
||||
|
||||
| 서비스 | 총 클래스 수 | Controller | DTO | Service | Domain | Repository | Config/기타 |
|
||||
|--------|-------------|------------|-----|---------|--------|------------|------------|
|
||||
| Common | 14개 | - | 4개 | - | - | 1개 | 9개 |
|
||||
| Auth | 26개 | 1개 | 9개 | 6개 | 6개 | 7개 | 3개 |
|
||||
| Bill-Inquiry | 29개 | 1개 | 9개 | 8개 | 7개 | 4개 | 7개 |
|
||||
| Product-Change | 44개 | 1개 | 17개 | 12개 | 9개 | 4개 | 7개 |
|
||||
| KOS-Mock | 39개 | 1개 | 16개 | 10개 | - | 20개 | 3개 |
|
||||
| **전체** | **152개** | **4개** | **55개** | **36개** | **22개** | **36개** | **29개** |
|
||||
|
||||
### 🏗️ 아키텍처 패턴별 구성
|
||||
|
||||
**Layered 아키텍처 (Auth, Bill-Inquiry, Product-Change)**
|
||||
- Controller → Service → Domain → Repository → Entity 계층 구조
|
||||
- 각 계층별 명확한 책임 분리
|
||||
- 인터페이스 기반 의존성 주입
|
||||
|
||||
**간단한 Layered 아키텍처 (KOS-Mock)**
|
||||
- Controller → Service → Repository → Entity 구조
|
||||
- Mock 데이터 제공에 특화된 단순 구조
|
||||
- 시나리오 기반 응답 처리
|
||||
|
||||
### 🔗 주요 공통 컴포넌트 활용
|
||||
|
||||
**모든 서비스에서 공통 사용**
|
||||
- `ApiResponse<T>`: 표준 API 응답 구조
|
||||
- `BaseTimeEntity`: 생성/수정 시간 자동 관리
|
||||
- `ErrorCode`: 표준화된 오류 코드 체계
|
||||
- `BusinessException`/`InfraException`: 계층별 예외 처리
|
||||
|
||||
**공통 설정 및 유틸리티**
|
||||
- `JpaConfig`: JPA 설정 통합
|
||||
- `LoggingAspect`: AOP 기반 로깅
|
||||
- `DateUtil`, `SecurityUtil`, `ValidatorUtil`: 공통 유틸리티
|
||||
|
||||
### 📝 설계 원칙 준수 현황
|
||||
|
||||
✅ **유저스토리 완벽 매칭**: 10개 유저스토리의 모든 요구사항 반영
|
||||
✅ **API 설계서 완전 일치**: Controller 메소드가 API 엔드포인트와 정확히 매칭
|
||||
✅ **내부시퀀스 반영**: Service, Repository 클래스가 시퀀스 다이어그램과 일치
|
||||
✅ **아키텍처 패턴 적용**: 서비스별 지정된 아키텍처 패턴 정확히 구현
|
||||
✅ **관계 표현 완료**: 상속, 구현, 의존성, 연관, 집약, 컴포지션 관계 모두 표현
|
||||
✅ **공통 컴포넌트 활용**: BaseTimeEntity, ApiResponse 등 공통 클래스 적극 활용
|
||||
|
||||
이 패키지 구조는 마이크로서비스 아키텍처에 최적화되어 있으며, 각 서비스의 독립성과 확장성을 보장합니다.
|
||||
@@ -0,0 +1,255 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
|
||||
title Product-Change Service - 간단 클래스 설계
|
||||
|
||||
' ============= 패키지 정의 =============
|
||||
package "com.unicorn.phonebill.product" {
|
||||
|
||||
' ============= Controller Layer =============
|
||||
package "controller" {
|
||||
class ProductController {
|
||||
' API 매핑 정보는 아래 Note에 표시
|
||||
}
|
||||
}
|
||||
|
||||
' ============= DTO Layer =============
|
||||
package "dto" {
|
||||
' Request DTOs
|
||||
class ProductChangeValidationRequest
|
||||
class ProductChangeRequest
|
||||
|
||||
' Response DTOs
|
||||
class ProductMenuResponse
|
||||
class CustomerInfoResponse
|
||||
class AvailableProductsResponse
|
||||
class ProductChangeValidationResponse
|
||||
class ProductChangeResponse
|
||||
class ProductChangeResultResponse
|
||||
class ProductChangeHistoryResponse
|
||||
|
||||
' Data DTOs
|
||||
class ProductInfo
|
||||
class CustomerInfo
|
||||
class ContractInfo
|
||||
class MenuItem
|
||||
class ValidationDetail
|
||||
class ProductChangeHistoryItem
|
||||
class PaginationInfo
|
||||
|
||||
' Enums
|
||||
enum ValidationResult {
|
||||
SUCCESS
|
||||
FAILURE
|
||||
}
|
||||
|
||||
enum ProcessStatus {
|
||||
PENDING
|
||||
PROCESSING
|
||||
COMPLETED
|
||||
FAILED
|
||||
}
|
||||
|
||||
enum LineStatus {
|
||||
ACTIVE
|
||||
SUSPENDED
|
||||
TERMINATED
|
||||
}
|
||||
|
||||
enum CheckType {
|
||||
PRODUCT_AVAILABLE
|
||||
OPERATOR_MATCH
|
||||
LINE_STATUS
|
||||
}
|
||||
|
||||
enum CheckResult {
|
||||
PASS
|
||||
FAIL
|
||||
}
|
||||
}
|
||||
|
||||
' ============= Service Layer =============
|
||||
package "service" {
|
||||
interface ProductService
|
||||
|
||||
class ProductServiceImpl
|
||||
|
||||
class ProductValidationService
|
||||
|
||||
class ProductCacheService
|
||||
|
||||
class KosClientService
|
||||
|
||||
class CircuitBreakerService
|
||||
|
||||
class RetryService
|
||||
}
|
||||
|
||||
' ============= Domain Layer =============
|
||||
package "domain" {
|
||||
class Product
|
||||
|
||||
class ProductChangeHistory
|
||||
|
||||
class ProductChangeResult
|
||||
|
||||
class ProductStatus
|
||||
|
||||
enum ProductStatus {
|
||||
ACTIVE
|
||||
INACTIVE
|
||||
DISCONTINUED
|
||||
}
|
||||
|
||||
enum CacheType {
|
||||
CUSTOMER_PRODUCT
|
||||
CURRENT_PRODUCT
|
||||
AVAILABLE_PRODUCTS
|
||||
PRODUCT_STATUS
|
||||
LINE_STATUS
|
||||
}
|
||||
}
|
||||
|
||||
' ============= Repository Layer =============
|
||||
package "repository" {
|
||||
interface ProductRepository
|
||||
|
||||
interface ProductChangeHistoryRepository
|
||||
|
||||
package "entity" {
|
||||
class ProductChangeHistoryEntity
|
||||
}
|
||||
|
||||
package "jpa" {
|
||||
interface ProductChangeHistoryJpaRepository
|
||||
}
|
||||
}
|
||||
|
||||
' ============= Config Layer =============
|
||||
package "config" {
|
||||
class RestTemplateConfig
|
||||
|
||||
class CacheConfig
|
||||
|
||||
class CircuitBreakerConfig
|
||||
|
||||
class KosProperties
|
||||
}
|
||||
|
||||
' ============= External Interface =============
|
||||
package "external" {
|
||||
class KosRequest
|
||||
|
||||
class KosResponse
|
||||
|
||||
class KosAdapterService
|
||||
}
|
||||
|
||||
' ============= Exception Classes =============
|
||||
package "exception" {
|
||||
class ProductChangeException
|
||||
|
||||
class ProductValidationException
|
||||
|
||||
class KosConnectionException
|
||||
|
||||
class CircuitBreakerException
|
||||
}
|
||||
}
|
||||
|
||||
' Import Common Classes
|
||||
class "com.unicorn.phonebill.common.dto.ApiResponse" as ApiResponse
|
||||
class "com.unicorn.phonebill.common.entity.BaseTimeEntity" as BaseTimeEntity
|
||||
class "com.unicorn.phonebill.common.exception.BusinessException" as BusinessException
|
||||
|
||||
' ============= 관계 설정 =============
|
||||
|
||||
' Controller Layer Relationships
|
||||
ProductController --> ProductService : "uses"
|
||||
ProductController --> ApiResponse : "returns"
|
||||
|
||||
' DTO Layer Relationships
|
||||
ProductMenuResponse --> ProductInfo : "contains"
|
||||
CustomerInfoResponse --> CustomerInfo : "contains"
|
||||
CustomerInfo --> ProductInfo : "contains"
|
||||
CustomerInfo --> ContractInfo : "contains"
|
||||
AvailableProductsResponse --> ProductInfo : "contains"
|
||||
ProductChangeValidationResponse --> ValidationDetail : "contains"
|
||||
ProductChangeResponse --> ProductInfo : "contains"
|
||||
ProductChangeHistoryResponse --> ProductChangeHistoryItem : "contains"
|
||||
ProductChangeHistoryResponse --> PaginationInfo : "contains"
|
||||
|
||||
' Service Layer Relationships
|
||||
ProductService <|.. ProductServiceImpl : "implements"
|
||||
ProductServiceImpl --> KosClientService : "uses"
|
||||
ProductServiceImpl --> ProductValidationService : "uses"
|
||||
ProductServiceImpl --> ProductCacheService : "uses"
|
||||
ProductServiceImpl --> ProductChangeHistoryRepository : "uses"
|
||||
|
||||
ProductValidationService --> ProductRepository : "uses"
|
||||
ProductValidationService --> ProductCacheService : "uses"
|
||||
ProductValidationService --> KosClientService : "uses"
|
||||
|
||||
ProductCacheService --> KosClientService : "uses"
|
||||
|
||||
KosClientService --> CircuitBreakerService : "uses"
|
||||
KosClientService --> RetryService : "uses"
|
||||
KosClientService --> KosAdapterService : "uses"
|
||||
|
||||
' Domain Layer Relationships
|
||||
ProductChangeResult --> Product : "contains"
|
||||
|
||||
' Repository Layer Relationships
|
||||
ProductRepository <-- ProductServiceImpl : "uses"
|
||||
ProductChangeHistoryRepository <-- ProductServiceImpl : "uses"
|
||||
ProductChangeHistoryRepository --> ProductChangeHistoryJpaRepository : "uses"
|
||||
ProductChangeHistoryEntity --|> BaseTimeEntity : "extends"
|
||||
|
||||
' Config Layer Relationships
|
||||
RestTemplateConfig --> KosClientService : "configures"
|
||||
CacheConfig --> ProductCacheService : "configures"
|
||||
CircuitBreakerConfig --> CircuitBreakerService : "configures"
|
||||
KosProperties --> KosClientService : "configures"
|
||||
|
||||
' External Interface Relationships
|
||||
KosAdapterService --> KosRequest : "creates"
|
||||
KosAdapterService --> KosResponse : "processes"
|
||||
KosClientService --> KosAdapterService : "uses"
|
||||
|
||||
' Exception Relationships
|
||||
ProductChangeException --|> BusinessException : "extends"
|
||||
ProductValidationException --|> BusinessException : "extends"
|
||||
KosConnectionException --|> BusinessException : "extends"
|
||||
CircuitBreakerException --|> BusinessException : "extends"
|
||||
|
||||
ProductValidationException --> ValidationDetail : "contains"
|
||||
|
||||
' ============= API 매핑표 =============
|
||||
note top of ProductController
|
||||
**ProductController API 매핑표**
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ HTTP Method │ URL Path │ Method Name │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ GET │ /products/menu │ getProductMenu() │
|
||||
│ GET │ /products/customer/{line} │ getCustomerInfo(lineNumber) │
|
||||
│ GET │ /products/available │ getAvailableProducts() │
|
||||
│ POST │ /products/change/validation │ validateProductChange() │
|
||||
│ POST │ /products/change │ requestProductChange() │
|
||||
│ GET │ /products/change/{requestId} │ getProductChangeResult() │
|
||||
│ GET │ /products/history │ getProductChangeHistory() │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
**주요 기능**
|
||||
• UFR-PROD-010: 상품변경 메뉴 조회
|
||||
• UFR-PROD-020: 상품변경 화면 데이터 조회
|
||||
• UFR-PROD-030: 상품변경 요청 및 사전체크
|
||||
• UFR-PROD-040: KOS 연동 상품변경 처리
|
||||
|
||||
**설계 특징**
|
||||
• Layered 아키텍처 패턴 적용
|
||||
• KOS 연동을 위한 Circuit Breaker 패턴
|
||||
• Redis 캐시를 활용한 성능 최적화
|
||||
• 비동기 이력 저장 처리
|
||||
end note
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,722 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
|
||||
title Product-Change Service - 상세 클래스 설계
|
||||
|
||||
' ============= 패키지 정의 =============
|
||||
package "com.unicorn.phonebill.product" {
|
||||
|
||||
' ============= Controller Layer =============
|
||||
package "controller" {
|
||||
class ProductController {
|
||||
-productService: ProductService
|
||||
-log: Logger
|
||||
+getProductMenu(): ResponseEntity<ApiResponse<ProductMenuResponse>>
|
||||
+getCustomerInfo(lineNumber: String): ResponseEntity<ApiResponse<CustomerInfoResponse>>
|
||||
+getAvailableProducts(currentProductCode: String, operatorCode: String): ResponseEntity<ApiResponse<AvailableProductsResponse>>
|
||||
+validateProductChange(request: ProductChangeValidationRequest): ResponseEntity<ApiResponse<ProductChangeValidationResponse>>
|
||||
+requestProductChange(request: ProductChangeRequest): ResponseEntity<ApiResponse<ProductChangeResponse>>
|
||||
+getProductChangeResult(requestId: String): ResponseEntity<ApiResponse<ProductChangeResultResponse>>
|
||||
+getProductChangeHistory(lineNumber: String, startDate: LocalDate, endDate: LocalDate, page: int, size: int): ResponseEntity<ApiResponse<ProductChangeHistoryResponse>>
|
||||
-extractUserIdFromToken(): String
|
||||
}
|
||||
}
|
||||
|
||||
' ============= DTO Layer =============
|
||||
package "dto" {
|
||||
' Request DTOs
|
||||
class ProductChangeValidationRequest {
|
||||
-lineNumber: String
|
||||
-currentProductCode: String
|
||||
-targetProductCode: String
|
||||
+getLineNumber(): String
|
||||
+getCurrentProductCode(): String
|
||||
+getTargetProductCode(): String
|
||||
}
|
||||
|
||||
class ProductChangeRequest {
|
||||
-lineNumber: String
|
||||
-currentProductCode: String
|
||||
-targetProductCode: String
|
||||
-requestDate: LocalDateTime
|
||||
-changeEffectiveDate: LocalDate
|
||||
+getLineNumber(): String
|
||||
+getCurrentProductCode(): String
|
||||
+getTargetProductCode(): String
|
||||
+getRequestDate(): LocalDateTime
|
||||
+getChangeEffectiveDate(): LocalDate
|
||||
}
|
||||
|
||||
' Response DTOs
|
||||
class ProductMenuResponse {
|
||||
-customerId: String
|
||||
-lineNumber: String
|
||||
-currentProduct: ProductInfo
|
||||
-menuItems: List<MenuItem>
|
||||
+getCustomerId(): String
|
||||
+getLineNumber(): String
|
||||
+getCurrentProduct(): ProductInfo
|
||||
+getMenuItems(): List<MenuItem>
|
||||
}
|
||||
|
||||
class CustomerInfoResponse {
|
||||
-customerInfo: CustomerInfo
|
||||
+getCustomerInfo(): CustomerInfo
|
||||
}
|
||||
|
||||
class AvailableProductsResponse {
|
||||
-products: List<ProductInfo>
|
||||
-totalCount: int
|
||||
+getProducts(): List<ProductInfo>
|
||||
+getTotalCount(): int
|
||||
}
|
||||
|
||||
class ProductChangeValidationResponse {
|
||||
-validationResult: ValidationResult
|
||||
-validationDetails: List<ValidationDetail>
|
||||
-failureReason: String
|
||||
+getValidationResult(): ValidationResult
|
||||
+getValidationDetails(): List<ValidationDetail>
|
||||
+getFailureReason(): String
|
||||
}
|
||||
|
||||
class ProductChangeResponse {
|
||||
-requestId: String
|
||||
-processStatus: ProcessStatus
|
||||
-resultCode: String
|
||||
-resultMessage: String
|
||||
-changedProduct: ProductInfo
|
||||
-processedAt: LocalDateTime
|
||||
+getRequestId(): String
|
||||
+getProcessStatus(): ProcessStatus
|
||||
+getResultCode(): String
|
||||
+getResultMessage(): String
|
||||
+getChangedProduct(): ProductInfo
|
||||
+getProcessedAt(): LocalDateTime
|
||||
}
|
||||
|
||||
class ProductChangeResultResponse {
|
||||
-requestId: String
|
||||
-lineNumber: String
|
||||
-processStatus: ProcessStatus
|
||||
-currentProductCode: String
|
||||
-targetProductCode: String
|
||||
-requestedAt: LocalDateTime
|
||||
-processedAt: LocalDateTime
|
||||
-resultCode: String
|
||||
-resultMessage: String
|
||||
-failureReason: String
|
||||
+getRequestId(): String
|
||||
+getLineNumber(): String
|
||||
+getProcessStatus(): ProcessStatus
|
||||
+getCurrentProductCode(): String
|
||||
+getTargetProductCode(): String
|
||||
+getRequestedAt(): LocalDateTime
|
||||
+getProcessedAt(): LocalDateTime
|
||||
+getResultCode(): String
|
||||
+getResultMessage(): String
|
||||
+getFailureReason(): String
|
||||
}
|
||||
|
||||
class ProductChangeHistoryResponse {
|
||||
-history: List<ProductChangeHistoryItem>
|
||||
-pagination: PaginationInfo
|
||||
+getHistory(): List<ProductChangeHistoryItem>
|
||||
+getPagination(): PaginationInfo
|
||||
}
|
||||
|
||||
' Data DTOs
|
||||
class ProductInfo {
|
||||
-productCode: String
|
||||
-productName: String
|
||||
-monthlyFee: BigDecimal
|
||||
-dataAllowance: String
|
||||
-voiceAllowance: String
|
||||
-smsAllowance: String
|
||||
-isAvailable: boolean
|
||||
-operatorCode: String
|
||||
+getProductCode(): String
|
||||
+getProductName(): String
|
||||
+getMonthlyFee(): BigDecimal
|
||||
+getDataAllowance(): String
|
||||
+getVoiceAllowance(): String
|
||||
+getSmsAllowance(): String
|
||||
+isAvailable(): boolean
|
||||
+getOperatorCode(): String
|
||||
}
|
||||
|
||||
class CustomerInfo {
|
||||
-customerId: String
|
||||
-lineNumber: String
|
||||
-customerName: String
|
||||
-currentProduct: ProductInfo
|
||||
-lineStatus: LineStatus
|
||||
-contractInfo: ContractInfo
|
||||
+getCustomerId(): String
|
||||
+getLineNumber(): String
|
||||
+getCustomerName(): String
|
||||
+getCurrentProduct(): ProductInfo
|
||||
+getLineStatus(): LineStatus
|
||||
+getContractInfo(): ContractInfo
|
||||
}
|
||||
|
||||
class ContractInfo {
|
||||
-contractDate: LocalDate
|
||||
-termEndDate: LocalDate
|
||||
-earlyTerminationFee: BigDecimal
|
||||
+getContractDate(): LocalDate
|
||||
+getTermEndDate(): LocalDate
|
||||
+getEarlyTerminationFee(): BigDecimal
|
||||
}
|
||||
|
||||
class MenuItem {
|
||||
-menuId: String
|
||||
-menuName: String
|
||||
-available: boolean
|
||||
+getMenuId(): String
|
||||
+getMenuName(): String
|
||||
+isAvailable(): boolean
|
||||
}
|
||||
|
||||
class ValidationDetail {
|
||||
-checkType: CheckType
|
||||
-result: CheckResult
|
||||
-message: String
|
||||
+getCheckType(): CheckType
|
||||
+getResult(): CheckResult
|
||||
+getMessage(): String
|
||||
}
|
||||
|
||||
class ProductChangeHistoryItem {
|
||||
-requestId: String
|
||||
-lineNumber: String
|
||||
-processStatus: ProcessStatus
|
||||
-currentProductCode: String
|
||||
-currentProductName: String
|
||||
-targetProductCode: String
|
||||
-targetProductName: String
|
||||
-requestedAt: LocalDateTime
|
||||
-processedAt: LocalDateTime
|
||||
-resultMessage: String
|
||||
+getRequestId(): String
|
||||
+getLineNumber(): String
|
||||
+getProcessStatus(): ProcessStatus
|
||||
+getCurrentProductCode(): String
|
||||
+getCurrentProductName(): String
|
||||
+getTargetProductCode(): String
|
||||
+getTargetProductName(): String
|
||||
+getRequestedAt(): LocalDateTime
|
||||
+getProcessedAt(): LocalDateTime
|
||||
+getResultMessage(): String
|
||||
}
|
||||
|
||||
class PaginationInfo {
|
||||
-page: int
|
||||
-size: int
|
||||
-totalElements: long
|
||||
-totalPages: int
|
||||
-hasNext: boolean
|
||||
-hasPrevious: boolean
|
||||
+getPage(): int
|
||||
+getSize(): int
|
||||
+getTotalElements(): long
|
||||
+getTotalPages(): int
|
||||
+isHasNext(): boolean
|
||||
+isHasPrevious(): boolean
|
||||
}
|
||||
|
||||
' Enum Classes
|
||||
enum ValidationResult {
|
||||
SUCCESS
|
||||
FAILURE
|
||||
}
|
||||
|
||||
enum ProcessStatus {
|
||||
PENDING
|
||||
PROCESSING
|
||||
COMPLETED
|
||||
FAILED
|
||||
}
|
||||
|
||||
enum LineStatus {
|
||||
ACTIVE
|
||||
SUSPENDED
|
||||
TERMINATED
|
||||
}
|
||||
|
||||
enum CheckType {
|
||||
PRODUCT_AVAILABLE
|
||||
OPERATOR_MATCH
|
||||
LINE_STATUS
|
||||
}
|
||||
|
||||
enum CheckResult {
|
||||
PASS
|
||||
FAIL
|
||||
}
|
||||
}
|
||||
|
||||
' ============= Service Layer =============
|
||||
package "service" {
|
||||
interface ProductService {
|
||||
+getProductMenuData(userId: String): ProductMenuResponse
|
||||
+getCustomerInfo(lineNumber: String): CustomerInfo
|
||||
+getAvailableProducts(currentProductCode: String, operatorCode: String): List<ProductInfo>
|
||||
+validateProductChange(request: ProductChangeValidationRequest): ProductChangeValidationResponse
|
||||
+requestProductChange(request: ProductChangeRequest, userId: String): ProductChangeResponse
|
||||
+getProductChangeResult(requestId: String): ProductChangeResultResponse
|
||||
+getProductChangeHistory(lineNumber: String, startDate: LocalDate, endDate: LocalDate, pageable: Pageable): ProductChangeHistoryResponse
|
||||
}
|
||||
|
||||
class ProductServiceImpl {
|
||||
-kosClientService: KosClientService
|
||||
-productValidationService: ProductValidationService
|
||||
-productCacheService: ProductCacheService
|
||||
-productChangeHistoryRepository: ProductChangeHistoryRepository
|
||||
-log: Logger
|
||||
+getProductMenuData(userId: String): ProductMenuResponse
|
||||
+getCustomerInfo(lineNumber: String): CustomerInfo
|
||||
+getAvailableProducts(currentProductCode: String, operatorCode: String): List<ProductInfo>
|
||||
+validateProductChange(request: ProductChangeValidationRequest): ProductChangeValidationResponse
|
||||
+requestProductChange(request: ProductChangeRequest, userId: String): ProductChangeResponse
|
||||
+getProductChangeResult(requestId: String): ProductChangeResultResponse
|
||||
+getProductChangeHistory(lineNumber: String, startDate: LocalDate, endDate: LocalDate, pageable: Pageable): ProductChangeHistoryResponse
|
||||
-filterAvailableProducts(products: List<ProductInfo>, currentProductCode: String): List<ProductInfo>
|
||||
-invalidateCustomerCache(userId: String): void
|
||||
}
|
||||
|
||||
class ProductValidationService {
|
||||
-productRepository: ProductRepository
|
||||
-productCacheService: ProductCacheService
|
||||
-kosClientService: KosClientService
|
||||
-log: Logger
|
||||
+validateProductChange(request: ProductChangeValidationRequest): ValidationResult
|
||||
+validateProductAvailability(productCode: String): ValidationDetail
|
||||
+validateOperatorMatch(customerOperatorCode: String, productCode: String): ValidationDetail
|
||||
+validateLineStatus(lineNumber: String): ValidationDetail
|
||||
-createValidationDetail(checkType: CheckType, result: CheckResult, message: String): ValidationDetail
|
||||
}
|
||||
|
||||
class ProductCacheService {
|
||||
-redisTemplate: RedisTemplate<String, Object>
|
||||
-kosClientService: KosClientService
|
||||
-log: Logger
|
||||
+getCustomerProductInfo(userId: String): CustomerInfo
|
||||
+getCurrentProductInfo(userId: String): ProductInfo
|
||||
+getAvailableProducts(): List<ProductInfo>
|
||||
+getProductStatus(productCode: String): ProductStatus
|
||||
+getLineStatus(lineNumber: String): LineStatus
|
||||
+invalidateCustomerCache(userId: String): void
|
||||
+cacheCustomerProductInfo(userId: String, customerInfo: CustomerInfo): void
|
||||
+cacheAvailableProducts(products: List<ProductInfo>): void
|
||||
-getCacheKey(prefix: String, identifier: String): String
|
||||
-getCacheTTL(cacheType: CacheType): Duration
|
||||
}
|
||||
|
||||
class KosClientService {
|
||||
-restTemplate: RestTemplate
|
||||
-circuitBreakerService: CircuitBreakerService
|
||||
-retryService: RetryService
|
||||
-kosProperties: KosProperties
|
||||
-log: Logger
|
||||
+getCustomerInfo(userId: String): CustomerInfo
|
||||
+getCurrentProduct(userId: String): ProductInfo
|
||||
+getAvailableProducts(): List<ProductInfo>
|
||||
+getLineStatus(lineNumber: String): LineStatus
|
||||
+processProductChange(changeRequest: ProductChangeRequest): ProductChangeResult
|
||||
-buildKosRequest(request: Object): KosRequest
|
||||
-handleKosResponse(response: ResponseEntity<String>): KosResponse
|
||||
-mapToCustomerInfo(kosResponse: KosResponse): CustomerInfo
|
||||
-mapToProductInfo(kosResponse: KosResponse): ProductInfo
|
||||
}
|
||||
|
||||
class CircuitBreakerService {
|
||||
-circuitBreakerRegistry: CircuitBreakerRegistry
|
||||
-log: Logger
|
||||
+isCallAllowed(serviceName: String): boolean
|
||||
+recordSuccess(serviceName: String): void
|
||||
+recordFailure(serviceName: String): void
|
||||
+getCircuitBreakerState(serviceName: String): CircuitBreaker.State
|
||||
-configureCircuitBreaker(serviceName: String): CircuitBreaker
|
||||
}
|
||||
|
||||
class RetryService {
|
||||
-retryRegistry: RetryRegistry
|
||||
-log: Logger
|
||||
+<T> executeWithRetry(operation: Supplier<T>, serviceName: String): T
|
||||
+<T> executeProductChangeWithRetry(operation: Supplier<T>): T
|
||||
-configureRetry(serviceName: String): Retry
|
||||
-isRetryableException(exception: Exception): boolean
|
||||
}
|
||||
}
|
||||
|
||||
' ============= Domain Layer =============
|
||||
package "domain" {
|
||||
class Product {
|
||||
-productCode: String
|
||||
-productName: String
|
||||
-monthlyFee: BigDecimal
|
||||
-dataAllowance: String
|
||||
-voiceAllowance: String
|
||||
-smsAllowance: String
|
||||
-status: ProductStatus
|
||||
-operatorCode: String
|
||||
-isAvailable: boolean
|
||||
+getProductCode(): String
|
||||
+getProductName(): String
|
||||
+getMonthlyFee(): BigDecimal
|
||||
+getDataAllowance(): String
|
||||
+getVoiceAllowance(): String
|
||||
+getSmsAllowance(): String
|
||||
+getStatus(): ProductStatus
|
||||
+getOperatorCode(): String
|
||||
+isAvailable(): boolean
|
||||
+canChangeTo(targetProduct: Product): boolean
|
||||
+isSameOperator(operatorCode: String): boolean
|
||||
}
|
||||
|
||||
class ProductChangeHistory {
|
||||
-requestId: String
|
||||
-userId: String
|
||||
-lineNumber: String
|
||||
-currentProductCode: String
|
||||
-targetProductCode: String
|
||||
-processStatus: ProcessStatus
|
||||
-requestedAt: LocalDateTime
|
||||
-processedAt: LocalDateTime
|
||||
-resultCode: String
|
||||
-resultMessage: String
|
||||
-failureReason: String
|
||||
+getRequestId(): String
|
||||
+getUserId(): String
|
||||
+getLineNumber(): String
|
||||
+getCurrentProductCode(): String
|
||||
+getTargetProductCode(): String
|
||||
+getProcessStatus(): ProcessStatus
|
||||
+getRequestedAt(): LocalDateTime
|
||||
+getProcessedAt(): LocalDateTime
|
||||
+getResultCode(): String
|
||||
+getResultMessage(): String
|
||||
+getFailureReason(): String
|
||||
+isCompleted(): boolean
|
||||
+isFailed(): boolean
|
||||
+markAsCompleted(resultCode: String, resultMessage: String): void
|
||||
+markAsFailed(failureReason: String): void
|
||||
}
|
||||
|
||||
class ProductChangeResult {
|
||||
-requestId: String
|
||||
-success: boolean
|
||||
-resultCode: String
|
||||
-resultMessage: String
|
||||
-newProduct: Product
|
||||
-processedAt: LocalDateTime
|
||||
+getRequestId(): String
|
||||
+isSuccess(): boolean
|
||||
+getResultCode(): String
|
||||
+getResultMessage(): String
|
||||
+getNewProduct(): Product
|
||||
+getProcessedAt(): LocalDateTime
|
||||
+createSuccessResult(requestId: String, newProduct: Product, message: String): ProductChangeResult
|
||||
+createFailureResult(requestId: String, errorCode: String, errorMessage: String): ProductChangeResult
|
||||
}
|
||||
|
||||
class ProductStatus {
|
||||
-productCode: String
|
||||
-status: String
|
||||
-salesStatus: String
|
||||
-operatorCode: String
|
||||
+getProductCode(): String
|
||||
+getStatus(): String
|
||||
+getSalesStatus(): String
|
||||
+getOperatorCode(): String
|
||||
+isAvailableForSale(): boolean
|
||||
+isActive(): boolean
|
||||
}
|
||||
|
||||
' Enum Classes
|
||||
enum ProductStatus {
|
||||
ACTIVE
|
||||
INACTIVE
|
||||
DISCONTINUED
|
||||
}
|
||||
|
||||
enum CacheType {
|
||||
CUSTOMER_PRODUCT(Duration.ofHours(4))
|
||||
CURRENT_PRODUCT(Duration.ofHours(2))
|
||||
AVAILABLE_PRODUCTS(Duration.ofHours(24))
|
||||
PRODUCT_STATUS(Duration.ofHours(1))
|
||||
LINE_STATUS(Duration.ofMinutes(30))
|
||||
|
||||
-ttl: Duration
|
||||
+CacheType(ttl: Duration)
|
||||
+getTtl(): Duration
|
||||
}
|
||||
}
|
||||
|
||||
' ============= Repository Layer =============
|
||||
package "repository" {
|
||||
interface ProductRepository {
|
||||
+getProductStatus(productCode: String): ProductStatus
|
||||
+saveChangeRequest(changeRequest: ProductChangeHistory): ProductChangeHistory
|
||||
+updateProductChangeStatus(requestId: String, status: ProcessStatus, resultCode: String, resultMessage: String): void
|
||||
+findProductChangeHistory(lineNumber: String, startDate: LocalDate, endDate: LocalDate, pageable: Pageable): Page<ProductChangeHistory>
|
||||
}
|
||||
|
||||
interface ProductChangeHistoryRepository {
|
||||
+save(history: ProductChangeHistory): ProductChangeHistory
|
||||
+findByRequestId(requestId: String): Optional<ProductChangeHistory>
|
||||
+findByLineNumberAndRequestedAtBetween(lineNumber: String, startDate: LocalDateTime, endDate: LocalDateTime, pageable: Pageable): Page<ProductChangeHistory>
|
||||
+findByUserIdAndRequestedAtBetween(userId: String, startDate: LocalDateTime, endDate: LocalDateTime, pageable: Pageable): Page<ProductChangeHistory>
|
||||
+existsByRequestId(requestId: String): boolean
|
||||
}
|
||||
|
||||
package "entity" {
|
||||
class ProductChangeHistoryEntity {
|
||||
-id: Long
|
||||
-requestId: String
|
||||
-userId: String
|
||||
-lineNumber: String
|
||||
-currentProductCode: String
|
||||
-currentProductName: String
|
||||
-targetProductCode: String
|
||||
-targetProductName: String
|
||||
-processStatus: ProcessStatus
|
||||
-requestedAt: LocalDateTime
|
||||
-processedAt: LocalDateTime
|
||||
-resultCode: String
|
||||
-resultMessage: String
|
||||
-failureReason: String
|
||||
-createdAt: LocalDateTime
|
||||
-updatedAt: LocalDateTime
|
||||
+getId(): Long
|
||||
+getRequestId(): String
|
||||
+getUserId(): String
|
||||
+getLineNumber(): String
|
||||
+getCurrentProductCode(): String
|
||||
+getCurrentProductName(): String
|
||||
+getTargetProductCode(): String
|
||||
+getTargetProductName(): String
|
||||
+getProcessStatus(): ProcessStatus
|
||||
+getRequestedAt(): LocalDateTime
|
||||
+getProcessedAt(): LocalDateTime
|
||||
+getResultCode(): String
|
||||
+getResultMessage(): String
|
||||
+getFailureReason(): String
|
||||
+getCreatedAt(): LocalDateTime
|
||||
+getUpdatedAt(): LocalDateTime
|
||||
+toDomain(): ProductChangeHistory
|
||||
+fromDomain(history: ProductChangeHistory): ProductChangeHistoryEntity
|
||||
}
|
||||
}
|
||||
|
||||
package "jpa" {
|
||||
interface ProductChangeHistoryJpaRepository {
|
||||
+findByRequestId(requestId: String): Optional<ProductChangeHistoryEntity>
|
||||
+findByLineNumberAndRequestedAtBetween(lineNumber: String, startDate: LocalDateTime, endDate: LocalDateTime, pageable: Pageable): Page<ProductChangeHistoryEntity>
|
||||
+findByUserIdAndRequestedAtBetween(userId: String, startDate: LocalDateTime, endDate: LocalDateTime, pageable: Pageable): Page<ProductChangeHistoryEntity>
|
||||
+existsByRequestId(requestId: String): boolean
|
||||
+countByProcessStatus(status: ProcessStatus): long
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
' ============= Config Layer =============
|
||||
package "config" {
|
||||
class RestTemplateConfig {
|
||||
+restTemplate(): RestTemplate
|
||||
+kosRestTemplate(): RestTemplate
|
||||
+connectionPoolTaskExecutor(): ThreadPoolTaskExecutor
|
||||
-createConnectionPoolManager(): PoolingHttpClientConnectionManager
|
||||
-createRequestConfig(): RequestConfig
|
||||
}
|
||||
|
||||
class CacheConfig {
|
||||
+redisConnectionFactory(): LettuceConnectionFactory
|
||||
+redisTemplate(): RedisTemplate<String, Object>
|
||||
+cacheManager(): RedisCacheManager
|
||||
+redisCacheConfiguration(): RedisCacheConfiguration
|
||||
-createRedisConfiguration(): RedisStandaloneConfiguration
|
||||
}
|
||||
|
||||
class CircuitBreakerConfig {
|
||||
+circuitBreakerRegistry(): CircuitBreakerRegistry
|
||||
+retryRegistry(): RetryRegistry
|
||||
+kosCircuitBreaker(): CircuitBreaker
|
||||
+kosRetry(): Retry
|
||||
-createCircuitBreakerConfig(): CircuitBreakerConfig
|
||||
-createRetryConfig(): RetryConfig
|
||||
}
|
||||
|
||||
class KosProperties {
|
||||
-baseUrl: String
|
||||
-connectTimeout: Duration
|
||||
-readTimeout: Duration
|
||||
-maxRetries: int
|
||||
-retryDelay: Duration
|
||||
-circuitBreakerFailureRateThreshold: float
|
||||
-circuitBreakerMinimumNumberOfCalls: int
|
||||
-circuitBreakerWaitDurationInOpenState: Duration
|
||||
+getBaseUrl(): String
|
||||
+getConnectTimeout(): Duration
|
||||
+getReadTimeout(): Duration
|
||||
+getMaxRetries(): int
|
||||
+getRetryDelay(): Duration
|
||||
+getCircuitBreakerFailureRateThreshold(): float
|
||||
+getCircuitBreakerMinimumNumberOfCalls(): int
|
||||
+getCircuitBreakerWaitDurationInOpenState(): Duration
|
||||
}
|
||||
}
|
||||
|
||||
' External Interface Classes (KOS 연동)
|
||||
package "external" {
|
||||
class KosRequest {
|
||||
-transactionId: String
|
||||
-lineNumber: String
|
||||
-currentProductCode: String
|
||||
-newProductCode: String
|
||||
-changeReason: String
|
||||
-effectiveDate: String
|
||||
+getTransactionId(): String
|
||||
+getLineNumber(): String
|
||||
+getCurrentProductCode(): String
|
||||
+getNewProductCode(): String
|
||||
+getChangeReason(): String
|
||||
+getEffectiveDate(): String
|
||||
}
|
||||
|
||||
class KosResponse {
|
||||
-resultCode: String
|
||||
-resultMessage: String
|
||||
-transactionId: String
|
||||
-data: Object
|
||||
+getResultCode(): String
|
||||
+getResultMessage(): String
|
||||
+getTransactionId(): String
|
||||
+getData(): Object
|
||||
+isSuccess(): boolean
|
||||
+getErrorDetail(): String
|
||||
}
|
||||
|
||||
class KosAdapterService {
|
||||
-kosProperties: KosProperties
|
||||
-restTemplate: RestTemplate
|
||||
-objectMapper: ObjectMapper
|
||||
-log: Logger
|
||||
+callKosProductChange(changeRequest: ProductChangeRequest): ProductChangeResult
|
||||
+getCustomerInfoFromKos(userId: String): CustomerInfo
|
||||
+getAvailableProductsFromKos(): List<ProductInfo>
|
||||
+getLineStatusFromKos(lineNumber: String): LineStatus
|
||||
-buildKosUrl(endpoint: String): String
|
||||
-createHttpHeaders(): HttpHeaders
|
||||
-handleKosError(response: ResponseEntity<String>): void
|
||||
}
|
||||
}
|
||||
|
||||
' Exception Classes
|
||||
package "exception" {
|
||||
class ProductChangeException {
|
||||
-errorCode: String
|
||||
-details: String
|
||||
+ProductChangeException(message: String)
|
||||
+ProductChangeException(errorCode: String, message: String, details: String)
|
||||
+getErrorCode(): String
|
||||
+getDetails(): String
|
||||
}
|
||||
|
||||
class ProductValidationException {
|
||||
-validationErrors: List<ValidationDetail>
|
||||
+ProductValidationException(message: String, validationErrors: List<ValidationDetail>)
|
||||
+getValidationErrors(): List<ValidationDetail>
|
||||
}
|
||||
|
||||
class KosConnectionException {
|
||||
-serviceName: String
|
||||
+KosConnectionException(serviceName: String, message: String, cause: Throwable)
|
||||
+getServiceName(): String
|
||||
}
|
||||
|
||||
class CircuitBreakerException {
|
||||
-serviceName: String
|
||||
+CircuitBreakerException(serviceName: String, message: String)
|
||||
+getServiceName(): String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
' Import Common Classes
|
||||
class "com.unicorn.phonebill.common.dto.ApiResponse" as ApiResponse
|
||||
class "com.unicorn.phonebill.common.entity.BaseTimeEntity" as BaseTimeEntity
|
||||
class "com.unicorn.phonebill.common.exception.ErrorCode" as ErrorCode
|
||||
class "com.unicorn.phonebill.common.exception.BusinessException" as BusinessException
|
||||
|
||||
' ============= 관계 설정 =============
|
||||
|
||||
' Controller Layer Relationships
|
||||
ProductController --> ProductService : "uses"
|
||||
ProductController --> ApiResponse : "returns"
|
||||
|
||||
' DTO Layer Relationships
|
||||
ProductMenuResponse --> ProductInfo : "contains"
|
||||
CustomerInfoResponse --> CustomerInfo : "contains"
|
||||
CustomerInfo --> ProductInfo : "contains"
|
||||
CustomerInfo --> ContractInfo : "contains"
|
||||
AvailableProductsResponse --> ProductInfo : "contains"
|
||||
ProductChangeValidationResponse --> ValidationDetail : "contains"
|
||||
ProductChangeResponse --> ProductInfo : "contains"
|
||||
ProductChangeHistoryResponse --> ProductChangeHistoryItem : "contains"
|
||||
ProductChangeHistoryResponse --> PaginationInfo : "contains"
|
||||
ValidationDetail --> CheckType : "uses"
|
||||
ValidationDetail --> CheckResult : "uses"
|
||||
|
||||
' Service Layer Relationships
|
||||
ProductService <|.. ProductServiceImpl : "implements"
|
||||
ProductServiceImpl --> KosClientService : "uses"
|
||||
ProductServiceImpl --> ProductValidationService : "uses"
|
||||
ProductServiceImpl --> ProductCacheService : "uses"
|
||||
ProductServiceImpl --> ProductChangeHistoryRepository : "uses"
|
||||
|
||||
ProductValidationService --> ProductRepository : "uses"
|
||||
ProductValidationService --> ProductCacheService : "uses"
|
||||
ProductValidationService --> KosClientService : "uses"
|
||||
|
||||
ProductCacheService --> KosClientService : "uses"
|
||||
|
||||
KosClientService --> CircuitBreakerService : "uses"
|
||||
KosClientService --> RetryService : "uses"
|
||||
KosClientService --> KosAdapterService : "uses"
|
||||
|
||||
' Domain Layer Relationships
|
||||
ProductChangeHistory --> ProcessStatus : "uses"
|
||||
Product --> ProductStatus : "uses"
|
||||
ProductChangeResult --> Product : "contains"
|
||||
ProductStatus --> ProductStatus : "uses"
|
||||
|
||||
' Repository Layer Relationships
|
||||
ProductRepository <-- ProductServiceImpl : "uses"
|
||||
ProductChangeHistoryRepository <-- ProductServiceImpl : "uses"
|
||||
ProductChangeHistoryRepository --> ProductChangeHistoryJpaRepository : "uses"
|
||||
ProductChangeHistoryEntity --|> BaseTimeEntity : "extends"
|
||||
ProductChangeHistoryEntity --> ProcessStatus : "uses"
|
||||
|
||||
' Config Layer Relationships
|
||||
RestTemplateConfig --> KosClientService : "configures"
|
||||
CacheConfig --> ProductCacheService : "configures"
|
||||
CircuitBreakerConfig --> CircuitBreakerService : "configures"
|
||||
KosProperties --> KosClientService : "configures"
|
||||
|
||||
' External Interface Relationships
|
||||
KosAdapterService --> KosRequest : "creates"
|
||||
KosAdapterService --> KosResponse : "processes"
|
||||
KosClientService --> KosAdapterService : "uses"
|
||||
|
||||
' Exception Relationships
|
||||
ProductChangeException --|> BusinessException : "extends"
|
||||
ProductValidationException --|> BusinessException : "extends"
|
||||
KosConnectionException --|> BusinessException : "extends"
|
||||
CircuitBreakerException --|> BusinessException : "extends"
|
||||
|
||||
ProductValidationException --> ValidationDetail : "contains"
|
||||
ProductChangeException --> ErrorCode : "uses"
|
||||
|
||||
@enduml
|
||||
Reference in New Issue
Block a user