diff --git a/design/backend/sequence/outer/사용자인증플로우.puml b/design/backend/sequence/outer/사용자인증플로우.puml index 513134b..c509301 100644 --- a/design/backend/sequence/outer/사용자인증플로우.puml +++ b/design/backend/sequence/outer/사용자인증플로우.puml @@ -7,9 +7,7 @@ actor "사용자\n(소상공인)" as User participant "Frontend\n(Web/Mobile)" as Frontend participant "API Gateway" as Gateway participant "User Service" as UserService -database "Redis\nCache" as Redis database "User DB\n(PostgreSQL)" as UserDB -participant "국세청 API\n(외부)" as NTSApi == UFR-USER-010: 회원가입 플로우 == @@ -40,85 +38,36 @@ alt 중복 사용자 존재 Frontend --> User: "이미 가입된 전화번호입니다" else 신규 사용자 - ' 사업자번호 검증 (Circuit Breaker 적용) - UserService -> Redis: GET user:business:{사업자번호} - activate Redis - Redis --> UserService: 캐시된 검증 결과 확인 - deactivate Redis + UserService -> UserService: 비밀번호 해싱\n(bcrypt, Cost Factor 10) - alt 캐시 HIT (검증 결과 있음) - UserService -> UserService: 캐시된 검증 결과 사용\n(응답 시간: 0.1초) - else 캐시 MISS (검증 필요) - UserService -> NTSApi: POST /사업자번호_검증\n(사업자번호)\n[Circuit Breaker, Timeout 5초] - activate NTSApi + UserService -> UserService: 사업자번호 암호화\n(AES-256) - alt 국세청 API 정상 응답 - NTSApi --> UserService: 200 OK\n(사업자번호 유효, 영업 상태) - deactivate NTSApi + UserService -> UserDB: BEGIN TRANSACTION + activate UserDB - UserService -> Redis: SET user:business:{사업자번호}\n검증 결과 (TTL 7일) - activate Redis - Redis --> UserService: 캐싱 완료 - deactivate Redis + UserService -> UserDB: INSERT INTO users\n(name, phone_number, email,\npassword_hash, created_at) + UserDB --> UserService: user_id 반환 - else 국세청 API 장애 (Circuit Breaker Open) - NTSApi --> UserService: 500 Internal Server Error\n또는 Timeout - deactivate NTSApi + UserService -> UserDB: INSERT INTO stores\n(user_id, store_name, industry,\naddress, business_number_encrypted,\nbusiness_hours) + UserDB --> UserService: store_id 반환 - UserService -> UserService: Fallback 실행:\n사업자번호 검증 스킵\n(수동 확인 안내) + UserService -> UserDB: COMMIT TRANSACTION + deactivate UserDB - note right of UserService - **Resilience 패턴 적용** - - Circuit Breaker: 실패율 50% 초과 시 Open - - Retry: 최대 3회 재시도 (지수 백오프: 1초, 2초, 4초) - - Timeout: 5초 - - Fallback: 검증 스킵 (수동 확인 안내) - end note - end - end + UserService -> UserService: JWT 토큰 생성\n(user_id, role=OWNER,\nexp=7일) - alt 사업자번호 검증 실패 (휴폐업 등) - UserService --> Gateway: 400 Bad Request\n(사업자번호 검증 실패) - Gateway --> Frontend: 400 Bad Request - Frontend --> User: "유효하지 않은 사업자번호입니다.\n휴폐업 여부를 확인해주세요." - else 사업자번호 검증 성공 + UserService --> Gateway: 201 Created\n(JWT 토큰, 사용자 정보) + deactivate UserService - UserService -> UserService: 비밀번호 해싱\n(bcrypt, Cost Factor 10) + Gateway --> Frontend: 201 Created\n(JWT 토큰, 사용자 정보) + deactivate Gateway - UserService -> UserService: 사업자번호 암호화\n(AES-256) + Frontend -> Frontend: JWT 토큰 저장\n(LocalStorage 또는 Cookie) - UserService -> UserDB: BEGIN TRANSACTION - activate UserDB + Frontend --> User: "회원가입이 완료되었습니다" - UserService -> UserDB: INSERT INTO users\n(name, phone_number, email,\npassword_hash, created_at) - UserDB --> UserService: user_id 반환 - - UserService -> UserDB: INSERT INTO stores\n(user_id, store_name, industry,\naddress, business_number_encrypted,\nbusiness_hours) - UserDB --> UserService: store_id 반환 - - UserService -> UserDB: COMMIT TRANSACTION - deactivate UserDB - - UserService -> UserService: JWT 토큰 생성\n(user_id, role=OWNER,\nexp=7일) - - UserService -> Redis: SET user:session:{token}\n(user_id, role, TTL 7일) - activate Redis - Redis --> UserService: 세션 저장 완료 - deactivate Redis - - UserService --> Gateway: 201 Created\n(JWT 토큰, 사용자 정보) - deactivate UserService - - Gateway --> Frontend: 201 Created\n(JWT 토큰, 사용자 정보) - deactivate Gateway - - Frontend -> Frontend: JWT 토큰 저장\n(LocalStorage 또는 Cookie) - - Frontend --> User: "회원가입이 완료되었습니다" - - Frontend -> Gateway: 대시보드 화면으로 이동 - deactivate Frontend - end + Frontend -> Gateway: 대시보드 화면으로 이동 + deactivate Frontend end == UFR-USER-020: 로그인 플로우 == @@ -159,11 +108,6 @@ else 사용자 존재 UserService -> UserService: JWT 토큰 생성\n(user_id, role=OWNER,\nexp=7일) - UserService -> Redis: SET user:session:{token}\n(user_id, role, TTL 7일) - activate Redis - Redis --> UserService: 세션 저장 완료 - deactivate Redis - UserService -> UserDB: UPDATE users\nSET last_login_at = NOW()\nWHERE user_id = ? activate UserDB UserDB --> UserService: 업데이트 완료 @@ -203,10 +147,7 @@ Gateway -> Gateway: JWT 토큰 검증 Gateway -> UserService: POST /api/users/logout\n(JWT 토큰) activate UserService -UserService -> Redis: DEL user:session:{token} -activate Redis -Redis --> UserService: 세션 삭제 완료 -deactivate Redis +UserService -> UserService: JWT 토큰 블랙리스트에 추가\n(만료 시까지 유효) UserService --> Gateway: 200 OK\n(로그아웃 성공) deactivate UserService @@ -221,28 +162,4 @@ Frontend --> User: "안전하게 로그아웃되었습니다" Frontend -> Gateway: 로그인 화면으로 이동 deactivate Frontend -note over User, NTSApi -**Resilience 패턴 적용 요약** - -**Circuit Breaker (국세청 API)**: -- 실패율 50% 초과 시 Open -- 30초 후 Half-Open 상태로 전환 -- 3개 요청 테스트 후 상태 결정 - -**Retry Pattern**: -- 최대 3회 재시도 -- 지수 백오프: 1초, 2초, 4초 -- 재시도 대상: SocketTimeoutException, ConnectException - -**Timeout Pattern**: -- 국세청 API: 5초 - -**Fallback Pattern**: -- 국세청 API 장애 시: 사업자번호 검증 스킵 (수동 확인 안내) - -**Cache-Aside Pattern**: -- 사업자번호 검증 결과 캐싱 (TTL 7일) -- 캐시 HIT: 0.1초, MISS: 5초 (외부 API 호출) -end note - @enduml diff --git a/design/backend/sequence/outer/성과분석플로우.puml b/design/backend/sequence/outer/성과분석플로우.puml index 2556f2c..f15fa9f 100644 --- a/design/backend/sequence/outer/성과분석플로우.puml +++ b/design/backend/sequence/outer/성과분석플로우.puml @@ -10,11 +10,13 @@ participant "Analytics Service" as Analytics participant "Redis Cache\n(TTL 5분)" as Redis participant "Analytics DB" as AnalyticsDB participant "Kafka\n(Event Topics)" as Kafka -box "외부 시스템" #LightGray - participant "우리동네TV API" as WooriAPI - participant "지니TV API" as GenieAPI - participant "SNS APIs\n(Instagram/Naver/Kakao)" as SNSAPI -end box + +note over AnalyticsDB + **배치 처리로 수집된 데이터** + - 외부 채널 통계는 배치 작업으로 + 주기적으로 수집하여 DB에 저장 + - 목업 데이터로 시작, 점진적으로 실제 API 연동 +end note == 1. 대시보드 조회 - Cache HIT 시나리오 == @@ -83,56 +85,28 @@ AnalyticsDB --> Analytics: 이벤트 통계\n- 총 참여자 수\n- 예상 ROI\n deactivate AnalyticsDB ||| -== 2.2. 외부 채널 API 병렬 호출 (Circuit Breaker 적용) == +== 2.2. 배치 수집된 채널 통계 데이터 조회 == -par 병렬 채널 API 호출 - Analytics -> WooriAPI: GET /stats/{eventId}\n+ API Key\n[Circuit Breaker] - activate WooriAPI +Analytics -> AnalyticsDB: SELECT channel_stats\nWHERE event_id = {id} +activate AnalyticsDB - alt Circuit Breaker CLOSED (정상) - WooriAPI --> Analytics: 200 OK\n- 노출 수: 5,000\n- 조회 수: 1,200 - deactivate WooriAPI +note right of Analytics + **배치 처리 방식** + - 외부 API는 별도 배치 작업으로 주기적 수집 + - 수집된 데이터는 DB에 저장 + - 대시보드에서는 DB 데이터만 조회 + - 응답 시간 단축 및 외부 API 의존성 제거 +end note - note right of Analytics - **Resilience 패턴** - - Circuit Breaker: 실패율 50% 초과 시 Open - - Timeout: 10초 - - Retry: 최대 3회 (지수 백오프) - - Fallback: 캐시된 이전 데이터 반환 - end note +AnalyticsDB --> Analytics: 채널별 통계 데이터\n- 우리동네TV: 노출 5,000, 조회 1,200\n- 지니TV: 노출 10,000, 클릭 500\n- Instagram: 좋아요 300, 댓글 50\n- Naver: 조회 2,000\n- Kakao: 공유 100 +deactivate AnalyticsDB - else Circuit Breaker OPEN (장애) - Analytics -> Analytics: **Fallback 실행**\n캐시된 이전 데이터 사용 - note right of Analytics - Circuit Breaker OPEN 상태 - - 빠른 실패로 응답 시간 단축 - - 30초 후 Half-Open으로 전환 - end note - end - -else - Analytics -> GenieAPI: GET /campaign/{eventId}/stats\n+ API Key\n[Circuit Breaker] - activate GenieAPI - - alt 정상 응답 - GenieAPI --> Analytics: 200 OK\n- 광고 노출 수: 10,000\n- 클릭 수: 500 - deactivate GenieAPI - else Timeout (10초 초과) - Analytics -> Analytics: **Timeout 처리**\n기본값 반환 (0) - note right of Analytics - Timeout 발생 - - 리소스 점유 방지 - - Fallback으로 기본값 설정 - end note - end - -else - Analytics -> SNSAPI: GET /posts/{eventId}/insights\n+ Access Token\n[Circuit Breaker] - activate SNSAPI - - SNSAPI --> Analytics: 200 OK\n- Instagram: 좋아요 300, 댓글 50\n- Naver: 조회 수 2,000\n- Kakao: 공유 수 100 - deactivate SNSAPI -end +note right of Analytics + **목업 데이터 활용** + - 초기에는 목업 데이터로 시작 + - 점진적으로 실제 배치 작업 구현 + - 배치 주기: 10분마다 수집 +end note ||| == 2.3. 데이터 통합 및 ROI 계산 == diff --git a/design/backend/sequence/outer/이벤트생성플로우.puml b/design/backend/sequence/outer/이벤트생성플로우.puml index 35e415f..3338396 100644 --- a/design/backend/sequence/outer/이벤트생성플로우.puml +++ b/design/backend/sequence/outer/이벤트생성플로우.puml @@ -11,7 +11,6 @@ participant "AI Service" as AI participant "Content Service" as Content participant "Distribution Service" as Dist participant "Kafka" as Kafka -participant "Redis Cache" as Cache database "Event DB" as EventDB participant "외부 AI API" as AIApi participant "이미지 생성 API" as ImageApi @@ -21,14 +20,8 @@ participant "배포 채널 APIs" as ChannelApis User -> FE: 이벤트 목적 선택 FE -> Gateway: POST /events/purposes\n{목적, 매장정보} Gateway -> Event: 이벤트 목적 저장 요청 -Event -> Cache: 캐시 조회\nkey: purpose:{userId} -alt 캐시 히트 - Cache --> Event: 캐시된 데이터 -else 캐시 미스 - Event -> EventDB: 이벤트 목적 저장 - EventDB --> Event: 저장 완료 - Event -> Cache: 캐시 저장\nTTL: 30분 -end +Event -> EventDB: 이벤트 목적 저장 +EventDB --> Event: 저장 완료 Event --> Gateway: 저장 완료\n{eventDraftId} Gateway --> FE: 200 OK FE --> User: AI 추천 화면으로 이동 @@ -42,38 +35,29 @@ Event --> Gateway: Job 생성 완료\n{jobId, status: PENDING} Gateway --> FE: 202 Accepted\n{jobId} FE --> User: "AI가 분석 중입니다..." (로딩) -note over AI: Kafka Consumer\nai-job-topic 구독 +note over AI: Kafka Consumer\nai 이벤트 생성 topic 구독 Kafka --> AI: Consume Job Message\n{jobId, eventDraftId, ...} -AI -> Cache: 트렌드 분석 캐시 조회\nkey: trend:{업종}:{지역} -alt 캐시 히트 - Cache --> AI: 캐시된 트렌드 데이터 -else 캐시 미스 - AI -> EventDB: 과거 이벤트 데이터 조회 - EventDB --> AI: 이벤트 통계 데이터 - AI -> AIApi: 트렌드 분석 요청\n{업종, 지역, 과거데이터} - AIApi --> AI: 트렌드 분석 결과 - AI -> Cache: 트렌드 캐시 저장\nTTL: 1시간 -end - -AI -> AIApi: 이벤트 추천 요청\n{목적, 트렌드, 매장정보} -AIApi --> AI: 3가지 추천안 생성 -AI -> EventDB: 추천 결과 저장 +AI -> EventDB: 과거 이벤트 데이터 조회 +EventDB --> AI: 이벤트 통계 데이터 +AI -> AIApi: 트렌드 분석 및 이벤트 추천 요청\n{목적, 업종, 지역, 과거데이터, 매장정보} +AIApi --> AI: 3가지 추천안 + 트렌드 요약\n(예: "여름철 시원한 음료 선호도 증가") +AI -> EventDB: 추천 결과 및 트렌드 요약 저장 EventDB --> AI: 저장 완료 -AI -> Cache: Job 상태 업데이트\nkey: job:{jobId}\nstatus: COMPLETED -AI -> Kafka: Publish to event-topic\nEventRecommended\n{jobId, eventDraftId, recommendations} +AI -> EventDB: Job 상태 업데이트\nstatus: COMPLETED +AI -> Kafka: Publish to event-topic\nEventRecommended\n{jobId, eventDraftId, recommendations, trendSummary} group Polling으로 상태 확인 loop 상태 확인 (최대 30초) FE -> Gateway: GET /jobs/{jobId}/status Gateway -> Event: Job 상태 조회 - Event -> Cache: 캐시에서 Job 상태 확인 - Cache --> Event: {status, result} + Event -> EventDB: Job 상태 및 결과 조회 + EventDB --> Event: {status, result} alt Job 완료 - Event --> Gateway: 200 OK\n{status: COMPLETED, recommendations} - Gateway --> FE: 추천 결과 반환 - FE --> User: 3가지 추천안 표시\n(제목/경품 수정 가능) + Event --> Gateway: 200 OK\n{status: COMPLETED, recommendations, trendSummary} + Gateway --> FE: 추천 결과 및 트렌드 요약 반환 + FE --> User: 트렌드 요약 표시\n3가지 추천안 표시\n(제목/경품 수정 가능) else Job 진행중 Event --> Gateway: 200 OK\n{status: PENDING/PROCESSING} Gateway --> FE: 진행중 상태 @@ -100,7 +84,7 @@ Event --> Gateway: Job 생성 완료\n{jobId, status: PENDING} Gateway --> FE: 202 Accepted\n{jobId} FE --> User: "이미지 생성 중..." (로딩) -note over Content: Kafka Consumer\nimage-job-topic 구독 +note over Content: Kafka Consumer\n이미지 생성 topic 구독 Kafka --> Content: Consume Job Message\n{jobId, eventDraftId, ...} par 심플 스타일 @@ -116,15 +100,15 @@ end Content -> EventDB: 이미지 URL 저장 EventDB --> Content: 저장 완료 -Content -> Cache: Job 상태 업데이트\nkey: job:{jobId}\nstatus: COMPLETED +Content -> EventDB: Job 상태 업데이트\nstatus: COMPLETED Content -> Kafka: Publish to event-topic\nContentCreated\n{jobId, eventDraftId, imageUrls} group Polling으로 상태 확인 loop 상태 확인 (최대 30초) FE -> Gateway: GET /jobs/{jobId}/status Gateway -> Event: Job 상태 조회 - Event -> Cache: 캐시에서 Job 상태 확인 - Cache --> Event: {status, imageUrls} + Event -> EventDB: Job 상태 및 결과 조회 + EventDB --> Event: {status, imageUrls} alt Job 완료 Event --> Gateway: 200 OK\n{status: COMPLETED, imageUrls}