mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2026-06-13 14:59:13 +00:00
물리아키텍처 설계 완료
✨ 주요 기능 - 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>
This commit is contained in:
@@ -0,0 +1,328 @@
|
||||
@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
|
||||
Reference in New Issue
Block a user