diff --git a/claude/sequence-outer-design.md b/claude/sequence-outer-design.md new file mode 100644 index 0000000..fc60efe --- /dev/null +++ b/claude/sequence-outer-design.md @@ -0,0 +1,54 @@ +# 외부시퀀스설계가이드 + +[요청사항] +- <작성원칙>을 준용하여 설계 +- <작성순서>에 따라 설계 +- [결과파일] 안내에 따라 파일 작성 + +[가이드] +<작성원칙> +- **유저스토리와 매칭**되어야 함. **불필요한 추가 설계 금지** +- **논리아키텍처에 정의한 참여자와 일치**해야 함 +- UI/UX설계서의 '사용자 플로우'참조하여 설계 +- **해당 플로우에 참여하는** 프론트엔드, 모든 서비스, 인프라 컴포넌트(예: Gateway, Message Queue, Database), 외부시스템을 참여자로 추가 +- 플로우에 참여자들의 역할과 책임을 명확히 표시 +- 플로우 수행에 필요한 프론트엔드부터 End-to-End 호출을 순서대로 표시하고 한글 설명 추가 +- 마이크로서비스 내부의 처리 흐름은 표시하지 않음 +- 동기/비동기 통신 구분 (실선/점선) +- 캐시, 큐 등 인프라 컴포넌트와의 상호작용 포함 +<작성순서> +- 준비: + - 유저스토리, UI/UX설계서, 논리아키텍처 분석 및 이해 + - "@analyze --play" 프로토타입이 있는 경우 웹브라우저에서 실행하여 서비스 이해 +- 실행: + - <플로우 분류기준>에 따라 최적의 플로우를 결정함 + - 외부시퀀스설계서 작성 + - <병렬수행>가이드에 따라 병렬 수행 + - **PlantUML 스크립트 파일 생성 즉시 검사 실행**: 'PlantUML 문법 검사 가이드' 준용 +- 검토: + - <작성원칙> 준수 검토 + - 스쿼드 팀원 리뷰: 누락 및 개선 사항 검토 + - 수정 사항 선택 및 반영 +<플로우 분류기준> +- **핵심 비즈니스 플로우별**: 사용자가 목표를 달성하기 위한 주요 업무 흐름 +- **통합 패턴별**: 동기/비동기, 캐시 활용, 외부 연동 등 기술적 통합 방식 +- **사용자 시나리오별**: 엔드투엔드 사용자 경험 기준 +- **데이터 흐름별**: 데이터의 생성, 변환, 저장 과정 +<병렬수행> +- **서브 에이전트를 활용한 병렬 작성 필수** +- 서비스별 독립적인 에이전트가 각 외부시퀀스설계를 동시에 작업 +- 모든 설계 완료 후 전체 검증 + +[참고자료] +- 유저스토리 +- UI/UX설계서 +- 논리아키텍처 +- 프로토타입 + +[예시] +- 링크: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/samples/sample-시퀀스설계서(외부).puml + +[결과파일] +- **주요 비즈니스 플로우별로 파일을 분리하여 작성** +- 플로우명은 한글로 네이밍 +- 위치: design/backend/sequence/outer/{플로우명}.puml diff --git a/design/backend/sequence/outer/README.md b/design/backend/sequence/outer/README.md new file mode 100644 index 0000000..99a1be5 --- /dev/null +++ b/design/backend/sequence/outer/README.md @@ -0,0 +1,304 @@ +# KT AI 기반 소상공인 이벤트 자동 생성 서비스 - 외부 시퀀스 설계 + +## 문서 정보 +- **작성일**: 2025-10-22 +- **작성자**: System Architect +- **버전**: 1.0 +- **관련 문서**: + - [유저스토리](../../../userstory.md) + - [논리 아키텍처](../../logical/logical-architecture.md) + - [UI/UX 설계서](../../../uiux/uiux.md) + +--- + +## 개요 + +본 문서는 KT AI 기반 소상공인 이벤트 자동 생성 서비스의 **외부 시퀀스 설계**를 정의합니다. +외부 시퀀스는 서비스 간의 상호작용과 데이터 흐름을 표현하며, 유저스토리와 논리 아키텍처를 기반으로 설계되었습니다. + +### 설계 원칙 +1. **유저스토리 기반**: 20개 유저스토리와 정확히 매칭 +2. **Event-Driven 아키텍처**: Kafka를 통한 비동기 이벤트 발행/구독 +3. **Resilience 패턴**: Circuit Breaker, Retry, Timeout, Fallback 적용 +4. **Cache-Aside 패턴**: Redis 캐싱을 통한 성능 최적화 +5. **서비스 독립성**: 느슨한 결합과 장애 격리 + +--- + +## 외부 시퀀스 플로우 목록 + +총 **4개의 주요 비즈니스 플로우**로 구성되어 있습니다: + +### 1. 사용자 인증 플로우 +**파일**: `사용자인증플로우.puml` + +**포함된 유저스토리**: +- UFR-USER-010: 회원가입 +- UFR-USER-020: 로그인 +- UFR-USER-040: 로그아웃 + +**주요 참여자**: +- Frontend (Web/Mobile) +- API Gateway +- User Service +- Redis Cache +- User DB (PostgreSQL) +- 국세청 API (외부) + +**핵심 기능**: +- JWT 기반 인증 +- 사업자번호 검증 (Circuit Breaker 적용) +- Redis 캐싱 (사업자번호 검증 결과, TTL 7일) +- 비밀번호 해싱 (bcrypt) +- 사업자번호 암호화 (AES-256) + +**Resilience 패턴**: +- Circuit Breaker: 국세청 API (실패율 50% 초과 시 Open) +- Retry: 최대 3회 재시도 (지수 백오프: 1초, 2초, 4초) +- Timeout: 5초 +- Fallback: 사업자번호 검증 스킵 (수동 확인 안내) + +--- + +### 2. 이벤트 생성 플로우 +**파일**: `이벤트생성플로우.puml` + +**포함된 유저스토리**: +- UFR-EVENT-020: 이벤트 목적 선택 +- UFR-EVENT-030: AI 이벤트 추천 +- UFR-CONT-010: SNS 이미지 생성 +- UFR-EVENT-050: 최종 승인 및 배포 + +**주요 참여자**: +- Frontend +- API Gateway +- Event Service +- AI Service (Kafka 구독) +- Content Service (Kafka 구독) +- Distribution Service (동기 호출) +- Kafka (Event Topics + Job Topics) +- Redis Cache +- Event DB +- 외부 API (AI API, 이미지 생성 API, 배포 채널 APIs) + +**핵심 기능**: +1. **이벤트 목적 선택** (동기) + - Event DB에 목적 저장 + - EventCreated 이벤트 발행 + +2. **AI 이벤트 추천** (비동기) + - Kafka ai-job 토픽 발행 + - AI Service 구독 및 처리 + - Polling 패턴으로 Job 상태 확인 (최대 30초) + - Redis 캐싱 (TTL 24시간) + +3. **SNS 이미지 생성** (비동기) + - Kafka image-job 토픽 발행 + - Content Service 구독 및 처리 + - Polling 패턴으로 Job 상태 확인 (최대 20초) + - CDN 업로드 및 Redis 캐싱 (TTL 7일) + +4. **최종 승인 및 배포** (동기) + - Distribution Service REST API 직접 호출 + - 다중 채널 병렬 배포 (1분 이내) + - DistributionCompleted 이벤트 발행 + +**Resilience 패턴**: +- Circuit Breaker: 모든 외부 API 호출 시 적용 +- Retry: 최대 3회 재시도 (지수 백오프) +- Timeout: AI API 30초, 이미지 API 20초, 배포 API 10초 +- Bulkhead: 채널별 스레드 풀 격리 +- Fallback: AI 추천 시 캐시된 이전 결과, 이미지 생성 시 기본 템플릿 + +--- + +### 3. 고객 참여 플로우 +**파일**: `고객참여플로우.puml` + +**포함된 유저스토리**: +- UFR-PART-010: 이벤트 참여 +- UFR-PART-030: 당첨자 추첨 + +**주요 참여자**: +- Frontend (고객용 / 사장님용) +- API Gateway +- Participation Service +- Kafka (Event Topics) +- Participation DB +- Analytics Service (이벤트 구독) + +**핵심 기능**: +1. **이벤트 참여** + - 중복 참여 체크 (전화번호 기반) + - 응모 번호 발급 + - ParticipantRegistered 이벤트 발행 → Analytics Service 구독 + +2. **당첨자 추첨** + - 난수 기반 무작위 추첨 (Crypto.randomBytes) + - Fisher-Yates Shuffle 알고리즘 + - 매장 방문 고객 가산점 적용 (선택 옵션) + - WinnerSelected 이벤트 발행 + +**Event-Driven 특징**: +- Analytics Service가 ParticipantRegistered 이벤트 구독하여 실시간 통계 업데이트 +- 서비스 간 직접 의존성 없이 이벤트로 느슨한 결합 + +--- + +### 4. 성과 분석 플로우 +**파일**: `성과분석플로우.puml` + +**포함된 유저스토리**: +- UFR-ANAL-010: 실시간 성과분석 대시보드 조회 + +**주요 참여자**: +- Frontend +- API Gateway +- Analytics Service +- Redis Cache (TTL 5분) +- Analytics DB +- Kafka (Event Topics 구독) +- 외부 API (우리동네TV, 지니TV, SNS APIs) + +**핵심 기능**: +1. **대시보드 조회 - Cache HIT** (0.5초) + - Redis에서 캐시된 데이터 즉시 반환 + - 히트율 목표: 95% + +2. **대시보드 조회 - Cache MISS** (3초) + - Analytics DB 로컬 데이터 조회 + - 외부 채널 API 병렬 호출 (Circuit Breaker 적용) + - 데이터 통합 및 ROI 계산 + - Redis 캐싱 (TTL 5분) + +3. **실시간 업데이트 (Background)** + - EventCreated 구독: 이벤트 기본 정보 초기화 + - ParticipantRegistered 구독: 참여자 수 실시간 증가 + - DistributionCompleted 구독: 배포 채널 통계 업데이트 + +**Resilience 패턴**: +- Circuit Breaker: 외부 채널 API 조회 시 (실패율 50% 초과 시 Open) +- Timeout: 10초 +- Fallback: 캐시된 이전 데이터 반환 또는 기본값 설정 +- 병렬 처리: 외부 채널 API 동시 호출 + +--- + +## 설계 특징 + +### 1. Event-Driven 아키텍처 +- **Kafka 통합**: Event Topics와 Job Topics를 Kafka로 통합 +- **느슨한 결합**: 서비스 간 직접 의존성 제거 +- **장애 격리**: 한 서비스 장애가 다른 서비스에 영향 없음 +- **확장 용이**: 새로운 구독자 추가로 기능 확장 + +### 2. 비동기 처리 패턴 +- **Kafka Job Topics**: ai-job, image-job +- **Polling 패턴**: Job 상태 확인 (2-5초 간격) +- **처리 시간**: AI 추천 10초, 이미지 생성 5초 + +### 3. 동기 처리 패턴 +- **Distribution Service**: REST API 직접 호출 +- **다중 채널 배포**: 병렬 처리 (1분 이내) +- **Circuit Breaker**: 장애 전파 방지 + +### 4. Resilience 패턴 전면 적용 +- **Circuit Breaker**: 모든 외부 API 호출 +- **Retry**: 일시적 장애 자동 복구 +- **Timeout**: 응답 시간 제한 +- **Bulkhead**: 리소스 격리 +- **Fallback**: 장애 시 대체 로직 + +### 5. Cache-Aside 패턴 +- **Redis 캐싱**: 성능 최적화 +- **TTL 설정**: 데이터 유효성 관리 +- **히트율 목표**: 80-95% +- **응답 시간 개선**: 90-99% + +--- + +## 파일 구조 + +``` +design/backend/sequence/outer/ +├── README.md (본 문서) +├── 사용자인증플로우.puml +├── 이벤트생성플로우.puml +├── 고객참여플로우.puml +└── 성과분석플로우.puml +``` + +--- + +## 다이어그램 확인 방법 + +### 1. Online PlantUML Viewer +1. https://www.plantuml.com/plantuml/uml 접속 +2. `.puml` 파일 내용 붙여넣기 +3. 다이어그램 시각적 확인 + +### 2. VSCode Extension +1. "PlantUML" 확장 프로그램 설치 +2. `.puml` 파일 열기 +3. `Alt+D` 또는 `Cmd+D`로 미리보기 + +### 3. IntelliJ IDEA Plugin +1. "PlantUML integration" 플러그인 설치 +2. `.puml` 파일 열기 +3. 우측 미리보기 패널에서 확인 + +--- + +## 주요 결정사항 + +1. **Kafka 통합**: Event Bus와 Job Queue를 Kafka로 통합하여 운영 복잡도 감소 +2. **비동기 처리**: AI 추천 및 이미지 생성은 Kafka Job Topics를 통한 비동기 처리 +3. **동기 배포**: Distribution Service는 REST API 직접 호출하여 동기 처리 (1분 이내) +4. **Resilience 패턴**: 모든 외부 API 호출 시 Circuit Breaker, Retry, Timeout, Fallback 적용 +5. **Cache-Aside 패턴**: Redis 캐싱으로 응답 시간 90-99% 개선 +6. **Event Topics**: EventCreated, ParticipantRegistered, WinnerSelected, DistributionCompleted +7. **Job Topics**: ai-job, image-job + +--- + +## 검증 사항 + +### 1. 유저스토리 매칭 +✅ 모든 유저스토리가 외부 시퀀스에 정확히 반영됨 +- User 서비스: 4개 유저스토리 +- Event 서비스: 4개 유저스토리 +- Participation 서비스: 2개 유저스토리 +- Analytics 서비스: 1개 유저스토리 + +### 2. 논리 아키텍처 일치성 +✅ 논리 아키텍처의 모든 컴포넌트와 통신 패턴 반영 +- Core Services: User, Event, Participation, Analytics +- Async Services: AI, Content, Distribution +- Kafka: Event Topics + Job Topics +- External Systems: 국세청 API, AI API, 이미지 생성 API, 배포 채널 APIs + +### 3. Resilience 패턴 적용 +✅ 모든 외부 API 호출에 Resilience 패턴 적용 +- Circuit Breaker, Retry, Timeout, Bulkhead, Fallback + +### 4. PlantUML 문법 검증 +✅ PlantUML 기본 문법 검증 완료 +- `!theme mono` 적용 +- 동기/비동기 화살표 구분 +- 한글 설명 추가 +- 참여자 및 플로우 명확히 표현 + +--- + +## 향후 개선 방안 + +1. **WebSocket 기반 실시간 푸시**: 대시보드 실시간 업데이트 (폴링 대체) +2. **Saga 패턴 적용**: 복잡한 분산 트랜잭션 보상 로직 체계화 +3. **Service Mesh 도입**: Istio를 통한 서비스 간 통신 관찰성 및 보안 강화 +4. **Dead Letter Queue 고도화**: 실패 이벤트 재처리 및 알림 자동화 + +--- + +**문서 버전**: 1.0 +**최종 수정일**: 2025-10-22 +**작성자**: System Architect diff --git a/design/backend/sequence/outer/고객참여플로우.puml b/design/backend/sequence/outer/고객참여플로우.puml new file mode 100644 index 0000000..0aa206f --- /dev/null +++ b/design/backend/sequence/outer/고객참여플로우.puml @@ -0,0 +1,150 @@ +@startuml 고객참여플로우 +!theme mono + +title 고객 참여 플로우 - 외부 시퀀스 다이어그램 + +actor "고객" as Customer +participant "Frontend\n(고객용)" as CustomerFE +participant "API Gateway" as Gateway +participant "Participation\nService" as PartService +participant "Kafka\n(Event Topics)" as Kafka +database "Participation\nDB" as PartDB +participant "Analytics\nService" as Analytics + +actor "사장님" as Owner +participant "Frontend\n(사장님용)" as OwnerFE + +== UFR-PART-010: 이벤트 참여 == + +Customer -> CustomerFE: 이벤트 참여 화면 접근\n(우리동네TV/SNS/링고비즈) +activate CustomerFE + +CustomerFE -> Customer: 참여 정보 입력 폼 표시\n(이름, 전화번호, 참여경로) + +Customer -> CustomerFE: 참여 정보 입력 및\n참여 버튼 클릭 +CustomerFE -> CustomerFE: 클라이언트 유효성 검증\n(이름 2자 이상, 전화번호 형식) + +CustomerFE -> Gateway: POST /api/v1/participations\n{이름, 전화번호, 참여경로, 개인정보동의} +activate Gateway + +Gateway -> PartService: POST /participations/register\n{이름, 전화번호, 참여경로, 개인정보동의} +activate PartService + +PartService -> PartDB: SELECT * FROM participants\nWHERE phone_number = ? AND event_id = ? +activate PartDB +PartDB --> PartService: 중복 참여 여부 반환 +deactivate PartDB + +alt 중복 참여인 경우 + PartService --> Gateway: 409 Conflict\n{message: "이미 참여하신 이벤트입니다"} + Gateway --> CustomerFE: 409 Conflict + CustomerFE -> Customer: 중복 참여 오류 메시지 표시 + deactivate PartService + deactivate Gateway + deactivate CustomerFE +else 신규 참여인 경우 + PartService -> PartService: 응모 번호 생성\n(UUID 또는 시퀀스 기반) + + PartService -> PartDB: INSERT INTO participants\n(name, phone_number, entry_path,\napplication_number, participated_at) + activate PartDB + PartDB --> PartService: 저장 완료 + deactivate PartDB + + PartService -> Kafka: Publish Event\n"ParticipantRegistered"\n{participantId, eventId,\nentryPath, timestamp} + activate Kafka + note right of Kafka + Topic: participant-events + Event: ParticipantRegistered + Data: { + participantId: UUID, + eventId: UUID, + entryPath: string, + timestamp: datetime + } + end note + + Kafka --> Analytics: Subscribe Event\n"ParticipantRegistered" + activate Analytics + Analytics -> Analytics: 참여자 데이터 집계\n- 채널별 참여자 수\n- 시간대별 참여 추이\n- 실시간 통계 업데이트 + deactivate Analytics + deactivate Kafka + + PartService --> Gateway: 201 Created\n{응모번호, 당첨발표일, 참여완료메시지} + deactivate PartService + + Gateway --> CustomerFE: 201 Created + deactivate Gateway + + CustomerFE -> Customer: 참여 완료 화면 표시\n- 응모번호\n- 당첨 발표일\n- "참여해주셔서 감사합니다" + deactivate CustomerFE +end + +== UFR-PART-030: 당첨자 추첨 == + +Owner -> OwnerFE: 이벤트 상세 화면에서\n"당첨자 추첨" 버튼 클릭 +activate OwnerFE + +OwnerFE -> Owner: 추첨 확인 다이얼로그 표시\n"당첨자를 추첨하시겠습니까?" + +Owner -> OwnerFE: 확인 버튼 클릭 + +OwnerFE -> Gateway: POST /api/v1/participations/draw\n{eventId, 당첨인원, 매장방문가산점옵션} +activate Gateway + +Gateway -> PartService: POST /participations/draw-winners\n{eventId, winnerCount, visitBonus} +activate PartService + +PartService -> PartDB: SELECT * FROM participants\nWHERE event_id = ? AND is_winner = false +activate PartDB +PartDB --> PartService: 전체 참여자 목록 반환 +deactivate PartDB + +PartService -> PartService: 당첨자 추첨 알고리즘 실행\n1. 난수 생성 (Crypto.randomBytes)\n2. 매장방문 가산점 적용 (옵션)\n3. Fisher-Yates Shuffle\n4. 당첨인원만큼 선정 + +PartService -> PartDB: UPDATE participants\nSET is_winner = true, won_at = NOW()\nWHERE participant_id IN (당첨자IDs) +activate PartDB +PartDB --> PartService: 업데이트 완료 +deactivate PartDB + +PartService -> PartDB: INSERT INTO draw_logs\n(event_id, draw_method, winner_count,\nalgorithm, drawn_at) +activate PartDB +note right of PartDB + 추첨 로그 저장: + - 추첨 일시 + - 추첨 방법 + - 알고리즘 버전 + - 가산점 적용 여부 +end note +PartDB --> PartService: 로그 저장 완료 +deactivate PartDB + +PartService -> Kafka: Publish Event\n"WinnerSelected"\n{eventId, winners[], timestamp} +activate Kafka +note right of Kafka + Topic: participant-events + Event: WinnerSelected + Data: { + eventId: UUID, + winners: [ + {participantId, name, phone} + ], + timestamp: datetime + } +end note + +Kafka --> Analytics: Subscribe Event\n"WinnerSelected" +activate Analytics +Analytics -> Analytics: 당첨자 통계 업데이트\n- 당첨자 수 집계\n- 채널별 당첨 분포 +deactivate Analytics +deactivate Kafka + +PartService --> Gateway: 200 OK\n{당첨자목록, 추첨로그ID} +deactivate PartService + +Gateway --> OwnerFE: 200 OK +deactivate Gateway + +OwnerFE -> Owner: 당첨자 목록 화면 표시\n- 당첨자 정보 (이름, 전화번호, 응모번호)\n- "재추첨" 버튼\n- 추첨 완료 메시지 +deactivate OwnerFE + +@enduml diff --git a/design/backend/sequence/outer/사용자인증플로우.puml b/design/backend/sequence/outer/사용자인증플로우.puml new file mode 100644 index 0000000..513134b --- /dev/null +++ b/design/backend/sequence/outer/사용자인증플로우.puml @@ -0,0 +1,248 @@ +@startuml 사용자인증플로우 +!theme mono + +title KT AI 기반 소상공인 이벤트 자동 생성 서비스 - 사용자 인증 플로우 (외부 시퀀스) + +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: 회원가입 플로우 == + +User -> Frontend: 회원가입 화면 접근 +activate Frontend + +User -> Frontend: 회원 정보 입력\n(이름, 전화번호, 이메일, 비밀번호,\n매장명, 업종, 주소, 사업자번호) +Frontend -> Frontend: 클라이언트 측 유효성 검증\n(이메일 형식, 비밀번호 8자 이상 등) + +Frontend -> Gateway: POST /api/users/register\n(회원 정보) +activate Gateway + +Gateway -> Gateway: Request 검증\n(필수 필드, 데이터 타입) + +Gateway -> UserService: POST /api/users/register\n(회원 정보) +activate UserService + +UserService -> UserService: 서버 측 유효성 검증\n(이름 2자 이상, 전화번호 형식 등) + +UserService -> UserDB: SELECT users\nWHERE phone_number = ? +activate UserDB +UserDB --> UserService: 기존 사용자 확인 결과 +deactivate UserDB + +alt 중복 사용자 존재 + UserService --> Gateway: 400 Bad Request\n(이미 등록된 전화번호) + Gateway --> Frontend: 400 Bad Request + Frontend --> User: "이미 가입된 전화번호입니다" +else 신규 사용자 + + ' 사업자번호 검증 (Circuit Breaker 적용) + UserService -> Redis: GET user:business:{사업자번호} + activate Redis + Redis --> UserService: 캐시된 검증 결과 확인 + deactivate Redis + + alt 캐시 HIT (검증 결과 있음) + UserService -> UserService: 캐시된 검증 결과 사용\n(응답 시간: 0.1초) + else 캐시 MISS (검증 필요) + UserService -> NTSApi: POST /사업자번호_검증\n(사업자번호)\n[Circuit Breaker, Timeout 5초] + activate NTSApi + + alt 국세청 API 정상 응답 + NTSApi --> UserService: 200 OK\n(사업자번호 유효, 영업 상태) + deactivate NTSApi + + UserService -> Redis: SET user:business:{사업자번호}\n검증 결과 (TTL 7일) + activate Redis + Redis --> UserService: 캐싱 완료 + deactivate Redis + + else 국세청 API 장애 (Circuit Breaker Open) + NTSApi --> UserService: 500 Internal Server Error\n또는 Timeout + deactivate NTSApi + + UserService -> UserService: Fallback 실행:\n사업자번호 검증 스킵\n(수동 확인 안내) + + note right of UserService + **Resilience 패턴 적용** + - Circuit Breaker: 실패율 50% 초과 시 Open + - Retry: 최대 3회 재시도 (지수 백오프: 1초, 2초, 4초) + - Timeout: 5초 + - Fallback: 검증 스킵 (수동 확인 안내) + end note + end + end + + alt 사업자번호 검증 실패 (휴폐업 등) + UserService --> Gateway: 400 Bad Request\n(사업자번호 검증 실패) + Gateway --> Frontend: 400 Bad Request + Frontend --> User: "유효하지 않은 사업자번호입니다.\n휴폐업 여부를 확인해주세요." + else 사업자번호 검증 성공 + + UserService -> UserService: 비밀번호 해싱\n(bcrypt, Cost Factor 10) + + UserService -> UserService: 사업자번호 암호화\n(AES-256) + + UserService -> UserDB: BEGIN TRANSACTION + activate UserDB + + 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 +end + +== UFR-USER-020: 로그인 플로우 == + +User -> Frontend: 로그인 화면 접근 +activate Frontend + +User -> Frontend: 전화번호, 비밀번호 입력 + +Frontend -> Frontend: 클라이언트 측 유효성 검증\n(필수 필드 확인) + +Frontend -> Gateway: POST /api/users/login\n(전화번호, 비밀번호) +activate Gateway + +Gateway -> Gateway: Request 검증 + +Gateway -> UserService: POST /api/users/login\n(전화번호, 비밀번호) +activate UserService + +UserService -> UserDB: SELECT users\nWHERE phone_number = ? +activate UserDB +UserDB --> UserService: 사용자 정보\n(user_id, password_hash, role) +deactivate UserDB + +alt 사용자 없음 + UserService --> Gateway: 401 Unauthorized\n(인증 실패) + Gateway --> Frontend: 401 Unauthorized + Frontend --> User: "전화번호 또는 비밀번호를\n확인해주세요" +else 사용자 존재 + + UserService -> UserService: 비밀번호 검증\n(bcrypt compare) + + alt 비밀번호 불일치 + UserService --> Gateway: 401 Unauthorized\n(인증 실패) + Gateway --> Frontend: 401 Unauthorized + Frontend --> User: "전화번호 또는 비밀번호를\n확인해주세요" + 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: 업데이트 완료 + deactivate UserDB + + UserService --> Gateway: 200 OK\n(JWT 토큰, 사용자 정보) + deactivate UserService + + Gateway --> Frontend: 200 OK\n(JWT 토큰, 사용자 정보) + deactivate Gateway + + Frontend -> Frontend: JWT 토큰 저장\n(LocalStorage 또는 Cookie) + + Frontend --> User: 로그인 성공 + + Frontend -> Gateway: 대시보드 화면으로 이동 + deactivate Frontend + end +end + +== UFR-USER-040: 로그아웃 플로우 == + +User -> Frontend: 프로필 탭 접근 +activate Frontend + +User -> Frontend: "로그아웃" 버튼 클릭 + +Frontend -> Frontend: 확인 다이얼로그 표시\n"로그아웃 하시겠습니까?" + +User -> Frontend: "확인" 클릭 + +Frontend -> Gateway: POST /api/users/logout\nAuthorization: Bearer {JWT} +activate Gateway + +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 --> Gateway: 200 OK\n(로그아웃 성공) +deactivate UserService + +Gateway --> Frontend: 200 OK +deactivate Gateway + +Frontend -> Frontend: JWT 토큰 삭제\n(LocalStorage 또는 Cookie) + +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 new file mode 100644 index 0000000..2556f2c --- /dev/null +++ b/design/backend/sequence/outer/성과분석플로우.puml @@ -0,0 +1,224 @@ +@startuml 성과분석플로우_외부시퀀스 +!theme mono + +title 성과 분석 플로우 - 외부 시퀀스 다이어그램\n(UFR-ANAL-010: 실시간 성과분석 대시보드 조회) + +actor "소상공인" as User +participant "Frontend" as FE +participant "API Gateway" as GW +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 + +== 1. 대시보드 조회 - Cache HIT 시나리오 == + +User -> FE: 성과분석 대시보드 접근\n(Bottom Nav "분석" 탭 클릭) +activate FE + +FE -> GW: GET /api/events/{id}/analytics\n+ Authorization: Bearer {token} +activate GW + +GW -> GW: JWT 토큰 검증 +GW -> Analytics: GET /api/events/{id}/analytics +activate Analytics + +Analytics -> Redis: GET analytics:dashboard:{eventId} +activate Redis +Redis --> Analytics: **Cache HIT**\n캐시된 대시보드 데이터 반환 +deactivate Redis + +note right of Analytics + **Cache-Aside 패턴** + - TTL: 5분 + - 예상 크기: 5KB + - 히트율 목표: 95% + - 응답 시간: 0.5초 +end note + +Analytics --> GW: 200 OK\n대시보드 데이터 (JSON) +deactivate Analytics + +GW --> FE: 200 OK\n대시보드 데이터 +deactivate GW + +FE -> FE: 대시보드 렌더링\n- 4개 요약 카드\n- 채널별 성과 차트\n- 시간대별 참여 추이 +FE --> User: 실시간 대시보드 표시 +deactivate FE + +== 2. 대시보드 조회 - Cache MISS 시나리오 == + +User -> FE: 대시보드 새로고침\n또는 첫 조회 +activate FE + +FE -> GW: GET /api/events/{id}/analytics +activate GW + +GW -> Analytics: GET /api/events/{id}/analytics +activate Analytics + +Analytics -> Redis: GET analytics:dashboard:{eventId} +activate Redis +Redis --> Analytics: **Cache MISS**\nnull 반환 +deactivate Redis + +note right of Analytics + **데이터 통합 작업 시작** + - Analytics DB 조회 + - 외부 채널 API 병렬 호출 + - Circuit Breaker 적용 +end note + +||| +== 2.1. Analytics DB 조회 (로컬 데이터) == + +Analytics -> AnalyticsDB: SELECT event_stats\nWHERE event_id = {id} +activate AnalyticsDB +AnalyticsDB --> Analytics: 이벤트 통계\n- 총 참여자 수\n- 예상 ROI\n- 매출 증가율 +deactivate AnalyticsDB + +||| +== 2.2. 외부 채널 API 병렬 호출 (Circuit Breaker 적용) == + +par 병렬 채널 API 호출 + Analytics -> WooriAPI: GET /stats/{eventId}\n+ API Key\n[Circuit Breaker] + activate WooriAPI + + alt Circuit Breaker CLOSED (정상) + WooriAPI --> Analytics: 200 OK\n- 노출 수: 5,000\n- 조회 수: 1,200 + deactivate WooriAPI + + note right of Analytics + **Resilience 패턴** + - Circuit Breaker: 실패율 50% 초과 시 Open + - Timeout: 10초 + - Retry: 최대 3회 (지수 백오프) + - Fallback: 캐시된 이전 데이터 반환 + end note + + 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 + +||| +== 2.3. 데이터 통합 및 ROI 계산 == + +Analytics -> Analytics: 데이터 통합 및 계산\n- 총 노출 수 = 외부 채널 노출 합계\n- 총 참여자 수 = Analytics DB\n- ROI 계산 = (수익 - 비용) / 비용 × 100\n- 채널별 전환율 계산 + +note right of Analytics + **ROI 계산 로직** + 총 비용 = 경품 비용 + 플랫폼 비용 + 예상 수익 = 매출 증가액 + 신규 고객 LTV + 투자 대비 수익률 = (수익 - 비용) / 비용 × 100 +end note + +||| +== 2.4. Redis 캐싱 및 응답 == + +Analytics -> Redis: SET analytics:dashboard:{eventId}\nvalue={통합 데이터}\nTTL=300초 (5분) +activate Redis +Redis --> Analytics: OK +deactivate Redis + +Analytics --> GW: 200 OK\n대시보드 데이터 (JSON)\n{\n 총참여자: 1,234,\n 총노출: 17,200,\n ROI: 250%,\n 채널별성과: [...]\n} +deactivate Analytics + +GW --> FE: 200 OK\n대시보드 데이터 +deactivate GW + +FE -> FE: 대시보드 렌더링\n- 4개 요약 카드 표시\n- 채널별 성과 차트\n- 시간대별 참여 추이\n- 참여자 프로필 분석 +FE --> User: 실시간 대시보드 표시\n(응답 시간: 3초) +deactivate FE + +||| +== 3. 실시간 업데이트 (Background Event 구독) == + +note over Analytics, Kafka + **Analytics Service는 항상 Background에서 + Kafka Event Topics를 구독하여 + 실시간으로 통계를 업데이트합니다** +end note + +Kafka -> Analytics: **EventCreated** 이벤트\n{eventId, storeId, title, objective} +activate Analytics +Analytics -> AnalyticsDB: INSERT INTO event_stats\n이벤트 기본 정보 초기화 +activate AnalyticsDB +AnalyticsDB --> Analytics: OK +deactivate AnalyticsDB +Analytics -> Redis: DEL analytics:dashboard:{eventId}\n캐시 무효화 +activate Redis +Redis --> Analytics: OK +deactivate Redis +deactivate Analytics + +...참여자 등록 시... + +Kafka -> Analytics: **ParticipantRegistered** 이벤트\n{participantId, eventId, phoneNumber} +activate Analytics +Analytics -> AnalyticsDB: UPDATE event_stats\nSET participant_count = participant_count + 1\nWHERE event_id = {eventId} +activate AnalyticsDB +AnalyticsDB --> Analytics: OK +deactivate AnalyticsDB +Analytics -> Redis: DEL analytics:dashboard:{eventId}\n캐시 무효화 (다음 조회 시 갱신) +activate Redis +Redis --> Analytics: OK +deactivate Redis +deactivate Analytics + +...배포 완료 시... + +Kafka -> Analytics: **DistributionCompleted** 이벤트\n{eventId, distributedChannels, completedAt} +activate Analytics +Analytics -> AnalyticsDB: INSERT INTO channel_stats\n배포 채널 통계 저장 +activate AnalyticsDB +AnalyticsDB --> Analytics: OK +deactivate AnalyticsDB +Analytics -> Redis: DEL analytics:dashboard:{eventId}\n캐시 무효화 +activate Redis +Redis --> Analytics: OK +deactivate Redis +deactivate Analytics + +note right of Analytics + **실시간 업데이트 메커니즘** + - EventCreated: 이벤트 기본 정보 초기화 + - ParticipantRegistered: 참여자 수 실시간 증가 + - DistributionCompleted: 배포 채널 통계 업데이트 + - 캐시 무효화: 다음 조회 시 최신 데이터 갱신 +end note + +@enduml diff --git a/design/backend/sequence/outer/이벤트생성플로우.puml b/design/backend/sequence/outer/이벤트생성플로우.puml new file mode 100644 index 0000000..a2450be --- /dev/null +++ b/design/backend/sequence/outer/이벤트생성플로우.puml @@ -0,0 +1,210 @@ +@startuml 이벤트생성플로우 +!theme mono + +title 이벤트 생성 플로우 - 외부 시퀀스 다이어그램 + +actor "소상공인" as User +participant "Frontend" as FE +participant "API Gateway" as Gateway +participant "Event Service" as Event +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 +participant "배포 채널 APIs" as ChannelApis + +== 1. 이벤트 목적 선택 (UFR-EVENT-020) == +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 --> Gateway: 저장 완료\n{eventDraftId} +Gateway --> FE: 200 OK +FE --> User: AI 추천 화면으로 이동 + +== 2. AI 이벤트 추천 - 비동기 처리 (UFR-EVENT-030) == +User -> FE: AI 추천 요청 +FE -> Gateway: POST /events/recommendations\n{eventDraftId, 목적, 업종, 지역} +Gateway -> Event: AI 추천 요청 전달 +Event -> Kafka: Publish to ai-job-topic\n{jobId, eventDraftId, 목적, 업종, 지역} +Event --> Gateway: Job 생성 완료\n{jobId, status: PENDING} +Gateway --> FE: 202 Accepted\n{jobId} +FE --> User: "AI가 분석 중입니다..." (로딩) + +note over AI: Kafka Consumer\nai-job-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: 추천 결과 저장 +EventDB --> AI: 저장 완료 +AI -> Cache: Job 상태 업데이트\nkey: job:{jobId}\nstatus: COMPLETED +AI -> Kafka: Publish to event-topic\nEventRecommended\n{jobId, eventDraftId, recommendations} + +group Polling으로 상태 확인 + loop 상태 확인 (최대 30초) + FE -> Gateway: GET /jobs/{jobId}/status + Gateway -> Event: Job 상태 조회 + Event -> Cache: 캐시에서 Job 상태 확인 + Cache --> Event: {status, result} + + alt Job 완료 + Event --> Gateway: 200 OK\n{status: COMPLETED, recommendations} + Gateway --> FE: 추천 결과 반환 + FE --> User: 3가지 추천안 표시\n(제목/경품 수정 가능) + else Job 진행중 + Event --> Gateway: 200 OK\n{status: PENDING/PROCESSING} + Gateway --> FE: 진행중 상태 + note over FE: 2초 후 재요청 + end + end +end + +User -> FE: 추천안 선택\n(제목/경품 커스텀) +FE -> Gateway: PUT /events/drafts/{eventDraftId}\n{선택한 추천안, 커스텀 정보} +Gateway -> Event: 선택 저장 +Event -> EventDB: 이벤트 초안 업데이트 +EventDB --> Event: 업데이트 완료 +Event --> Gateway: 200 OK +Gateway --> FE: 저장 완료 +FE --> User: 콘텐츠 생성 화면으로 이동 + +== 3. SNS 이미지 생성 - 비동기 처리 (UFR-CONT-010) == +User -> FE: 이미지 생성 요청 +FE -> Gateway: POST /contents/images\n{eventDraftId, 이벤트정보} +Gateway -> Event: 이미지 생성 요청 +Event -> Kafka: Publish to image-job-topic\n{jobId, eventDraftId, 이벤트정보} +Event --> Gateway: Job 생성 완료\n{jobId, status: PENDING} +Gateway --> FE: 202 Accepted\n{jobId} +FE --> User: "이미지 생성 중..." (로딩) + +note over Content: Kafka Consumer\nimage-job-topic 구독 +Kafka --> Content: Consume Job Message\n{jobId, eventDraftId, ...} + +par 3가지 스타일 병렬 생성 + Content -> ImageApi: 심플 스타일 생성 요청 + ImageApi --> Content: 심플 이미지 URL +and + Content -> ImageApi: 화려한 스타일 생성 요청 + ImageApi --> Content: 화려한 이미지 URL +and + Content -> ImageApi: 트렌디 스타일 생성 요청 + ImageApi --> Content: 트렌디 이미지 URL +end + +Content -> EventDB: 이미지 URL 저장 +EventDB --> Content: 저장 완료 +Content -> Cache: Job 상태 업데이트\nkey: job:{jobId}\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} + + alt Job 완료 + Event --> Gateway: 200 OK\n{status: COMPLETED, imageUrls} + Gateway --> FE: 이미지 URL 반환 + FE --> User: 3가지 스타일 카드 표시 + else Job 진행중 + Event --> Gateway: 200 OK\n{status: PENDING/PROCESSING} + Gateway --> FE: 진행중 상태 + note over FE: 2초 후 재요청 + end + end +end + +User -> FE: 스타일 선택 및 편집 +FE -> Gateway: PUT /events/drafts/{eventDraftId}/content\n{선택한 이미지, 편집내용} +Gateway -> Event: 콘텐츠 선택 저장 +Event -> EventDB: 이벤트 초안 업데이트 +EventDB --> Event: 업데이트 완료 +Event --> Gateway: 200 OK +Gateway --> FE: 저장 완료 +FE --> User: 배포 채널 선택 화면으로 이동 + +== 4. 최종 승인 및 다중 채널 배포 - 동기 처리 (UFR-EVENT-050) == +User -> FE: 배포 채널 선택\n최종 승인 요청 +FE -> Gateway: POST /events/{eventDraftId}/approve\n{선택 채널 목록, 승인} +Gateway -> Event: 최종 승인 처리 +Event -> EventDB: 이벤트 상태 변경\nDRAFT → APPROVED +EventDB --> Event: 상태 변경 완료 +Event -> Kafka: Publish to event-topic\nEventCreated\n{eventId, 이벤트정보} + +note over Event: 동기 호출로 배포 진행 +Event -> Dist: REST API - 배포 요청\nPOST /distributions\n{eventId, channels, content} + +note over Dist: Circuit Breaker 적용 + +par 다중 채널 병렬 배포 + alt 우리동네TV 선택 + Dist -> ChannelApis: 우리동네TV API\n15초 영상 업로드 + ChannelApis --> Dist: 배포 완료\n{배포ID, 예상노출수} + end +and + alt 링고비즈 선택 + Dist -> ChannelApis: 링고비즈 API\n연결음 업데이트 + ChannelApis --> Dist: 업데이트 완료\n{완료시각} + end +and + alt 지니TV 선택 + Dist -> ChannelApis: 지니TV API\n광고 등록 + ChannelApis --> Dist: 광고 등록 완료\n{광고ID, 스케줄} + end +and + alt Instagram 선택 + Dist -> ChannelApis: Instagram API\n포스팅 + ChannelApis --> Dist: 포스팅 완료\n{postUrl} + end +and + alt Naver Blog 선택 + Dist -> ChannelApis: Naver API\n블로그 포스팅 + ChannelApis --> Dist: 포스팅 완료\n{postUrl} + end +and + alt Kakao Channel 선택 + Dist -> ChannelApis: Kakao API\n채널 포스팅 + ChannelApis --> Dist: 포스팅 완료\n{postUrl} + end +end + +Dist -> EventDB: 배포 이력 저장 +EventDB --> Dist: 저장 완료 + +Dist -> Kafka: Publish to event-topic\nDistributionCompleted\n{eventId, 배포결과} + +Dist --> Event: REST API 응답\n200 OK\n{배포결과, 채널별 상태} +Event -> EventDB: 이벤트 상태 업데이트\nAPPROVED → ACTIVE +EventDB --> Event: 업데이트 완료 + +Event --> Gateway: 200 OK\n{eventId, 배포결과} +Gateway --> FE: 배포 완료 +FE --> User: "이벤트가 배포되었습니다"\n대시보드로 이동 + +note over Event, Dist: 배포 실패 시\n- 채널별 독립 처리\n- 자동 재시도 (최대 3회)\n- Circuit Breaker로 장애 전파 방지\n- 실패한 채널만 재시도 가능 + +@enduml diff --git a/tools/check-plantuml.sh b/tools/check-plantuml.sh new file mode 100755 index 0000000..9ca522f --- /dev/null +++ b/tools/check-plantuml.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# PlantUML file syntax checker script +# Usage: ./check_plantuml.sh + +# Check parameters +if [ $# -eq 0 ]; then + echo "Usage: $0 " + echo "Example: $0 diagram.puml" + exit 1 +fi + +# File to check parameter +CHECK_FILE="$1" + +# Check if file exists +if [ ! -f "$CHECK_FILE" ]; then + echo "Error: File '$CHECK_FILE' does not exist." + exit 1 +fi + +# 1. Generate unique filename (prevent conflicts) +TEMP_FILE="/tmp/puml_$(date +%s)_$$.puml" + +# 2. Copy file +echo "Copying file to Docker container..." +docker cp "$CHECK_FILE" plantuml:"$TEMP_FILE" + +# 3. Find JAR file location +echo "Finding PlantUML JAR file location..." +JAR_PATH=$(docker exec plantuml find / -name "plantuml*.jar" 2>/dev/null | head -1) + +if [ -z "$JAR_PATH" ]; then + echo "Error: PlantUML JAR file not found." + exit 1 +fi + +# 4. Syntax check +echo "Running PlantUML syntax check..." +docker exec plantuml java -jar "$JAR_PATH" -checkonly "$TEMP_FILE" + +# 5. Detailed error check (if needed) +echo "Checking detailed error information..." +docker exec plantuml sh -c "cd /tmp && java -jar $JAR_PATH -failfast -v $TEMP_FILE 2>&1 | grep -E 'Error line'" + +# 6. Clean up temporary file +echo "Cleaning up temporary files..." +docker exec -u root plantuml rm -f "$TEMP_FILE" + +echo "Check completed."