kt-event-marketing/design/backend/class/participation-service.puml
jhbkjh 3075a5d49f 물리아키텍처 설계 완료
 주요 기능
- Azure 기반 물리아키텍처 설계 (개발환경/운영환경)
- 7개 마이크로서비스 물리 구조 설계
- 네트워크 아키텍처 다이어그램 작성 (Mermaid)
- 환경별 비교 분석 및 마스터 인덱스 문서

📁 생성 파일
- design/backend/physical/physical-architecture.md (마스터)
- design/backend/physical/physical-architecture-dev.md (개발환경)
- design/backend/physical/physical-architecture-prod.md (운영환경)
- design/backend/physical/*.mmd (4개 Mermaid 다이어그램)

🎯 핵심 성과
- 비용 최적화: 개발환경 월 $143, 운영환경 월 $2,860
- 확장성: 개발환경 100명 → 운영환경 10,000명 (100배)
- 가용성: 개발환경 95% → 운영환경 99.9%
- 보안: 다층 보안 아키텍처 (L1~L4)

🛠️ 기술 스택
- Azure Kubernetes Service (AKS)
- Azure Database for PostgreSQL Flexible
- Azure Cache for Redis Premium
- Azure Service Bus Premium
- Application Gateway + WAF

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 15:13:01 +09:00

329 lines
12 KiB
Plaintext

@startuml
!theme mono
title Participation Service 클래스 다이어그램 (상세)
package "com.kt.event.participation" {
package "presentation.controller" {
class ParticipationController {
- participationService: ParticipationService
+ participate(eventId: String, request: ParticipationRequest): ResponseEntity<ApiResponse<ParticipationResponse>>
+ getParticipants(eventId: String, storeVisited: Boolean, pageable: Pageable): ResponseEntity<ApiResponse<PageResponse<ParticipationResponse>>>
+ getParticipant(eventId: String, participantId: String): ResponseEntity<ApiResponse<ParticipationResponse>>
}
class WinnerController {
- winnerDrawService: WinnerDrawService
+ drawWinners(eventId: String, request: DrawWinnersRequest): ResponseEntity<ApiResponse<DrawWinnersResponse>>
+ getWinners(eventId: String, pageable: Pageable): ResponseEntity<ApiResponse<PageResponse<ParticipationResponse>>>
}
class DebugController {
+ health(): ResponseEntity<Map<String, Object>>
}
}
package "application.service" {
class ParticipationService {
- participantRepository: ParticipantRepository
- kafkaProducerService: KafkaProducerService
+ participate(eventId: String, request: ParticipationRequest): ParticipationResponse
+ getParticipants(eventId: String, storeVisited: Boolean, pageable: Pageable): PageResponse<ParticipationResponse>
+ getParticipant(eventId: String, participantId: String): ParticipationResponse
}
class WinnerDrawService {
- participantRepository: ParticipantRepository
- drawLogRepository: DrawLogRepository
+ drawWinners(eventId: String, request: DrawWinnersRequest): DrawWinnersResponse
+ getWinners(eventId: String, pageable: Pageable): PageResponse<ParticipationResponse>
- createDrawPool(participants: List<Participant>, applyBonus: Boolean): List<Participant>
}
}
package "application.dto" {
class ParticipationRequest {
- name: String
- phoneNumber: String
- email: String
- channel: String
- storeVisited: Boolean
- agreeMarketing: Boolean
- agreePrivacy: Boolean
}
class ParticipationResponse {
- participantId: String
- eventId: String
- name: String
- phoneNumber: String
- email: String
- channel: String
- storeVisited: Boolean
- bonusEntries: Integer
- agreeMarketing: Boolean
- agreePrivacy: Boolean
- isWinner: Boolean
- winnerRank: Integer
- wonAt: LocalDateTime
- participatedAt: LocalDateTime
+ from(participant: Participant): ParticipationResponse
}
class DrawWinnersRequest {
- winnerCount: Integer
- applyStoreVisitBonus: Boolean
}
class DrawWinnersResponse {
- eventId: String
- totalParticipants: Integer
- winnerCount: Integer
- drawnAt: LocalDateTime
- winners: List<WinnerSummary>
class WinnerSummary {
- participantId: String
- name: String
- phoneNumber: String
- rank: Integer
}
}
}
package "domain.participant" {
class Participant extends BaseTimeEntity {
- id: Long
- participantId: String
- eventId: String
- name: String
- phoneNumber: String
- email: String
- channel: String
- storeVisited: Boolean
- bonusEntries: Integer
- agreeMarketing: Boolean
- agreePrivacy: Boolean
- isWinner: Boolean
- winnerRank: Integer
- wonAt: LocalDateTime
+ generateParticipantId(eventId: String, sequenceNumber: Long): String
+ calculateBonusEntries(storeVisited: Boolean): Integer
+ markAsWinner(rank: Integer): void
+ prePersist(): void
}
interface ParticipantRepository extends JpaRepository {
+ existsByEventIdAndPhoneNumber(eventId: String, phoneNumber: String): boolean
+ findMaxSequenceByDatePrefix(datePrefix: String): Integer
+ findByEventIdAndIsWinnerFalse(eventId: String): List<Participant>
+ findByEventIdOrderByCreatedAtDesc(eventId: String, pageable: Pageable): Page<Participant>
+ findByEventIdAndStoreVisitedOrderByCreatedAtDesc(eventId: String, storeVisited: Boolean, pageable: Pageable): Page<Participant>
+ findByEventIdAndParticipantId(eventId: String, participantId: String): Optional<Participant>
+ countByEventId(eventId: String): long
+ findByEventIdAndIsWinnerTrueOrderByWinnerRankAsc(eventId: String, pageable: Pageable): Page<Participant>
}
}
package "domain.draw" {
class DrawLog extends BaseTimeEntity {
- id: Long
- eventId: String
- totalParticipants: Integer
- winnerCount: Integer
- applyStoreVisitBonus: Boolean
- algorithm: String
- drawnAt: LocalDateTime
- drawnBy: String
}
interface DrawLogRepository extends JpaRepository {
+ existsByEventId(eventId: String): boolean
}
}
package "exception" {
class ParticipationException extends BusinessException {
+ ParticipationException(errorCode: ErrorCode)
+ ParticipationException(errorCode: ErrorCode, message: String)
}
class DuplicateParticipationException extends ParticipationException {
+ DuplicateParticipationException()
}
class EventNotFoundException extends ParticipationException {
+ EventNotFoundException()
}
class EventNotActiveException extends ParticipationException {
+ EventNotActiveException()
}
class ParticipantNotFoundException extends ParticipationException {
+ ParticipantNotFoundException()
}
class AlreadyDrawnException extends ParticipationException {
+ AlreadyDrawnException()
}
class InsufficientParticipantsException extends ParticipationException {
+ InsufficientParticipantsException(participantCount: long, winnerCount: int)
}
class NoWinnersYetException extends ParticipationException {
+ NoWinnersYetException()
}
}
package "infrastructure.kafka" {
class KafkaProducerService {
- PARTICIPANT_REGISTERED_TOPIC: String
- kafkaTemplate: KafkaTemplate<String, Object>
+ publishParticipantRegistered(event: ParticipantRegisteredEvent): void
}
package "event" {
class ParticipantRegisteredEvent {
- eventId: String
- participantId: String
- name: String
- phoneNumber: String
- email: String
- storeVisited: Boolean
- bonusEntries: Integer
- participatedAt: LocalDateTime
+ from(participant: Participant): ParticipantRegisteredEvent
}
}
}
package "infrastructure.config" {
class SecurityConfig {
+ securityFilterChain(http: HttpSecurity): SecurityFilterChain
}
}
}
package "com.kt.event.common" {
package "entity" {
abstract class BaseTimeEntity {
- createdAt: LocalDateTime
- updatedAt: LocalDateTime
}
}
package "dto" {
class "ApiResponse<T>" {
- success: boolean
- data: T
- errorCode: String
- message: String
- timestamp: LocalDateTime
+ success(data: T): ApiResponse<T>
+ error(errorCode: String, message: String): ApiResponse<T>
}
class "PageResponse<T>" {
- content: List<T>
- totalElements: long
- totalPages: int
- number: int
- size: int
- first: boolean
- last: boolean
+ of(page: Page<T>): PageResponse<T>
}
}
package "exception" {
interface ErrorCode {
+ getCode(): String
+ getMessage(): String
}
class BusinessException extends RuntimeException {
- errorCode: ErrorCode
- details: String
+ getErrorCode(): ErrorCode
+ getDetails(): String
}
}
}
' Presentation Layer 관계
ParticipationController --> ParticipationService : uses
ParticipationController --> ParticipationRequest : uses
ParticipationController --> ParticipationResponse : uses
ParticipationController --> "ApiResponse<T>" : uses
ParticipationController --> "PageResponse<T>" : uses
WinnerController --> WinnerDrawService : uses
WinnerController --> DrawWinnersRequest : uses
WinnerController --> DrawWinnersResponse : uses
WinnerController --> ParticipationResponse : uses
WinnerController --> "ApiResponse<T>" : uses
WinnerController --> "PageResponse<T>" : uses
' Application Layer 관계
ParticipationService --> ParticipantRepository : uses
ParticipationService --> KafkaProducerService : uses
ParticipationService --> ParticipationRequest : uses
ParticipationService --> ParticipationResponse : uses
ParticipationService --> Participant : uses
ParticipationService --> DuplicateParticipationException : throws
ParticipationService --> EventNotFoundException : throws
ParticipationService --> ParticipantNotFoundException : throws
ParticipationService --> "PageResponse<T>" : uses
WinnerDrawService --> ParticipantRepository : uses
WinnerDrawService --> DrawLogRepository : uses
WinnerDrawService --> DrawWinnersRequest : uses
WinnerDrawService --> DrawWinnersResponse : uses
WinnerDrawService --> ParticipationResponse : uses
WinnerDrawService --> Participant : uses
WinnerDrawService --> DrawLog : uses
WinnerDrawService --> AlreadyDrawnException : throws
WinnerDrawService --> InsufficientParticipantsException : throws
WinnerDrawService --> NoWinnersYetException : throws
WinnerDrawService --> "PageResponse<T>" : uses
' DTO 관계
ParticipationResponse --> Participant : converts from
DrawWinnersResponse +-- DrawWinnersResponse.WinnerSummary
' Domain Layer 관계
Participant --|> BaseTimeEntity : extends
DrawLog --|> BaseTimeEntity : extends
ParticipantRepository --> Participant : manages
DrawLogRepository --> DrawLog : manages
' Exception 관계
ParticipationException --|> BusinessException : extends
ParticipationException --> ErrorCode : uses
DuplicateParticipationException --|> ParticipationException : extends
EventNotFoundException --|> ParticipationException : extends
EventNotActiveException --|> ParticipationException : extends
ParticipantNotFoundException --|> ParticipationException : extends
AlreadyDrawnException --|> ParticipationException : extends
InsufficientParticipantsException --|> ParticipationException : extends
NoWinnersYetException --|> ParticipationException : extends
' Infrastructure Layer 관계
KafkaProducerService --> ParticipantRegisteredEvent : uses
ParticipantRegisteredEvent --> Participant : converts from
note top of ParticipationController : 이벤트 참여 및 참여자 조회 API\n- POST /events/{eventId}/participate\n- GET /events/{eventId}/participants\n- GET /events/{eventId}/participants/{participantId}
note top of WinnerController : 당첨자 추첨 및 조회 API\n- POST /events/{eventId}/draw-winners\n- GET /events/{eventId}/winners
note top of Participant : 이벤트 참여자 엔티티\n- 중복 참여 방지 (eventId + phoneNumber)\n- 매장 방문 보너스 응모권 관리\n- 당첨자 상태 관리
note top of DrawLog : 당첨자 추첨 로그\n- 추첨 이력 관리\n- 재추첨 방지
note top of KafkaProducerService : Kafka 이벤트 발행\n- 참여자 등록 이벤트 발행
@enduml