내부 시퀀스 설계 완료

- 총 21개 PlantUML 파일 생성 (Meeting 10개, AI 6개, STT 2개, Notification 3개)
- 서브 에이전트를 활용한 병렬 설계로 효율성 극대화
- 모든 시나리오는 유저스토리 및 외부 시퀀스와 1:1 매칭
- Controller → Service → Repository 계층 구조 명확히 표현
- Redis Cache, Azure Event Hubs 등 인프라 컴포넌트 표시
- 동기(→)/비동기(-->) 구분 명확
- 외부 참여자 <<E>> 표시 적용
- PlantUML 문법 검사 및 오류 수정 완료 (13개 파일 수정)
- par/and 블록 문법 오류 수정
- return 형식 적용으로 참여자 없는 화살표 오류 해결

설계 특징:
- 캐시 전략: Cache-Aside 패턴, TTL 관리, 즉시 무효화
- 비동기 처리: Azure Event Hubs 기반 이벤트 구독
- 실시간 협업: WebSocket 기반 동기화, 변경 델타 전송
- 데이터 일관성: 버전 관리, 양방향 연결, 트랜잭션 처리

추가 파일:
- claude/sequence-inner-design.md: 내부시퀀스설계 가이드
- tools/check-plantuml.ps1: PlantUML 문법 검사 스크립트

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
kimjh 2025-10-22 15:57:51 +09:00
parent 6d84357215
commit 909025aa27
25 changed files with 4077 additions and 1 deletions

View File

@ -36,7 +36,9 @@
"Bash(mv:*)",
"Bash(cp:*)",
"mcp__sequential-thinking__sequentialthinking",
"Bash(awk '{print $1\"\"\"\": \"\"\"\"; system(\"\"\"\"sed -n \"\"\"\" $1\"\"\"\"p design/uiux/prototype/common.css\"\"\"\")}')"
"Bash(awk '{print $1\"\"\"\": \"\"\"\"; system(\"\"\"\"sed -n \"\"\"\" $1\"\"\"\"p design/uiux/prototype/common.css\"\"\"\")}')",
"Bash(git stash:*)",
"Bash(tools/check-mermaid.sh:*)"
],
"deny": [],
"ask": []

View File

@ -0,0 +1,76 @@
# 내부시퀀스설계 가이드
[요청사항]
- <작성원칙>을 준용하여 설계
- <작성순서>에 따라 설계
- [결과파일] 안내에 따라 파일 작성
[가이드]
<작성원칙>
- **유저스토리와 매칭**되어야 함. **불필요한 추가 설계 금지**
- **외부시퀀스설계서에서 설계한 플로우와 일치**해야 함
- UI/UX설계서의 '사용자 플로우'참조하여 설계
- 마이크로서비스 내부의 처리 흐름을 표시
- **각 서비스-시나리오별로 분리하여 각각 작성**
- 각 서비스별 주요 시나리오마다 독립적인 시퀀스 설계 수행
- 프론트엔드와 백엔드 책임 분리: 프론트엔드에서 할 수 있는 것은 백엔드로 요청 안하게 함
- 표현 요소
- **API 레이어**: 해당 시나리오의 모든 관련 엔드포인트
- **비즈니스 레이어**: Controller → Service → Domain 내부 플로우
- **데이터 레이어**: Repository, Cache, External API 접근
- **인프라 레이어**: 메시지 큐, 이벤트, 로깅 등
- 다이어그램 구성
- **참여자(Actor)**: Controller, Service, Repository, Cache, External API
- **생명선(Lifeline)**: 각 참여자의 활동 구간
- **메시지(Message)**: 동기(→)/비동기(-->) 호출 구분
- **활성화 박스**: 처리 중인 시간 구간 표시
- **노트**: 중요한 비즈니스 로직이나 기술적 고려사항 설명
- 참여자가 서비스 내부가 아닌 다른 마이크로 서비스, 외부시스템, 인프라 컴포넌트면 참여자 이름 끝에 '<<E>>'를 붙임
예) database "Redis Cache<<E>>" as cache
<작성순서>
- 준비:
- 유저스토리, UI/UX설계서, 외부시퀀스설계서 분석 및 이해
- "@analyze --play" 프로토타입이 있는 경우 웹브라우저에서 실행하여 서비스 이해
- 실행:
- <시나리오 분류 가이드>에 따라 각 서비스별로 시나리오 분류
- 내부시퀀스설계서 작성
- <병렬수행>가이드에 따라 동시 수행
- **PlantUML 스크립트 파일 생성 즉시 검사 실행**: 'PlantUML 문법 검사 가이드' 준용
- 검토:
- <작성원칙> 준수 검토
- 스쿼드 팀원 리뷰: 누락 및 개선 사항 검토
- 수정 사항 선택 및 반영
<시나리오 분류 가이드>
- 시나리오 식별 방법
- **유저스토리 기반**: 각 유저스토리를 기준으로 시나리오 도출
- **비즈니스 기능 단위**: 하나의 완전한 비즈니스 기능을 수행하는 단위로 분류
- 시나리오별 설계 원칙
- **단일 책임**: 하나의 시나리오는 하나의 명확한 비즈니스 목적을 가짐
- **완전성**: 해당 시나리오의 모든 API와 내부 처리를 포함
- **독립성**: 각 시나리오는 독립적으로 이해 가능해야 함
- **일관성**: 동일한 아키텍처 레이어 표현 방식 사용
- 시나리오 명명 규칙
- **케밥-케이스 사용**: entity action 형태. 한글로 작성 (예: 사용자 등록, 주문 처리)
- **동사형 액션**: 실제 수행하는 작업을 명확히 표현
- **일관된 용어**: 프로젝트 내에서 동일한 용어 사용
<병렬수행>
- **서브 에이전트를 활용한 병렬 작성 필수**
- 서비스별 독립적인 에이전트가 각 내부시퀀스설계를 동시에 작업
- 모든 설계 완료 후 전체 검증
[참고자료]
- 유저스토리
- UI/UX설계서
- 외부시퀀스설계서
- 프로토타입
[예시]
- 링크: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/samples/sample-시퀀스설계서(내부).puml
[결과파일]
- design/backend/sequence/inner/{서비스명}-{시나리오}.puml
- 서비스명은 영어로 시나리오명은 한글로 작성

View File

@ -0,0 +1,277 @@
# Meeting Service 내부 시퀀스 설계
Meeting Service의 내부 처리 흐름을 표현한 시퀀스 설계서 모음입니다.
## 📋 설계 개요
### 목적
- Meeting Service 내부의 Controller → Service → Repository 계층 구조 표현
- 각 시나리오별 비즈니스 로직 및 데이터 처리 흐름 상세화
- 캐시, 데이터베이스, 메시징 인프라와의 상호작용 명시
- 동기/비동기 처리 구분 명확화
### 설계 원칙
- **유저스토리 기반**: 모든 시나리오는 유저스토리와 1:1 매칭
- **외부 시퀀스 일치**: 외부 시퀀스 설계와 일관성 유지
- **계층 구조 명확화**: Controller, Service, Repository 역할 분리
- **인프라 표시**: DB, Cache, Event Hub 등 외부 참여자는 <<E>> 표시
- **비즈니스 로직 설명**: note를 통한 핵심 로직 설명
## 📂 시나리오별 설계 파일
### 1. 대시보드 조회
**파일**: `meeting-대시보드조회.puml`
**유저스토리**: AFR-USER-020
**주요 처리**:
- 사용자별 대시보드 정보 조회 (예정된 회의, Todo, 최근 회의록, 공유받은 회의록)
- Redis 캐시 우선 조회 (Cache-Aside 패턴)
- 통계 정보 계산 (예정 회의 수, Todo 완료율)
- 응답 데이터 TTL: 5분
**주요 컴포넌트**:
- DashboardController
- DashboardService
- MeetingRepository, TodoRepository, MinutesRepository
- Redis Cache, Meeting DB
---
### 2. 회의 예약
**파일**: `meeting-회의예약.puml`
**유저스토리**: UFR-MEET-010
**주요 처리**:
- 회의 정보 입력 검증 (제목, 날짜/시간, 참석자)
- 중복 회의 체크 (같은 시간대 회의 확인)
- 회의 및 참석자 정보 저장
- 캐시 저장 (회의 정보, 참석자 목록)
- 비동기 이벤트 발행 (MeetingCreated)
**주요 컴포넌트**:
- MeetingController
- MeetingService
- MeetingRepository, ParticipantRepository
- Meeting DB, Redis Cache
- Azure Event Hubs (비동기 알림)
---
### 3. 회의 시작
**파일**: `meeting-회의시작.puml`
**유저스토리**: UFR-MEET-030
**주요 처리**:
- 회의 시작 권한 검증 (생성자 또는 참석자)
- 회의 상태 확인 (SCHEDULED → IN_PROGRESS)
- 회의 세션 생성 (sessionId, startedAt)
- 회의록 초안 생성 (빈 회의록)
- 비동기 이벤트 발행 (MeetingStarted → STT 녹음 시작)
**주요 컴포넌트**:
- MeetingController
- MeetingService, SessionService
- MeetingRepository, SessionRepository
- Meeting DB, Redis Cache
- Azure Event Hubs (STT 연동)
---
### 4. 회의 종료
**파일**: `meeting-회의종료.puml`
**유저스토리**: UFR-MEET-040
**주요 처리**:
- 회의 종료 권한 검증 (생성자만)
- 회의 세션 종료 (endedAt 기록)
- 회의 상태 변경 (IN_PROGRESS → ENDED)
- 회의 통계 생성 (총 시간, 참석자 수, 발언 횟수)
- 회의록 상태 업데이트 (DRAFT)
- 비동기 이벤트 발행 (MeetingEnded → AI 최종 회의록 생성)
**주요 컴포넌트**:
- MeetingController
- MeetingService, SessionService, StatisticsService
- MeetingRepository, SessionRepository
- Meeting DB, Redis Cache
- Azure Event Hubs (AI 연동)
---
### 5. 회의록 확정
**파일**: `meeting-회의록확정.puml`
**유저스토리**: UFR-MEET-050
**주요 처리**:
- 회의록 확정 권한 검증 (생성자만)
- 필수 항목 검증 (제목, 참석자, 논의 내용, 결정 사항)
- 확정 버전 생성 (v1.0)
- 회의록 스냅샷 저장 (버전 관리)
- 회의록 상태 변경 (DRAFT → FINALIZED)
- 비동기 이벤트 발행 (MinutesFinalized → AI Todo 추출)
**주요 컴포넌트**:
- MinutesController
- MinutesService, ValidationService
- MinutesRepository
- Meeting DB, Redis Cache
- Azure Event Hubs (AI Todo 추출)
---
### 6. 회의록 상세 조회
**파일**: `meeting-회의록상세조회.puml`
**유저스토리**: UFR-MEET-047
**주요 처리**:
- 회의록 조회 권한 검증 (생성자, 참석자, 공유받은 사용자)
- Redis 캐시 우선 조회 (Cache-Aside 패턴)
- 회의록 기본 정보 + 참석자 + 섹션 + AI 요약 조회
- 관련 회의록 조회 (벡터 유사도 기반, 최대 3개)
- 조회 이력 기록
- 응답 데이터 TTL: 10분
**주요 컴포넌트**:
- MinutesController
- MinutesService, RelatedMinutesService
- MinutesRepository, SectionRepository
- Meeting DB, Redis Cache
---
### 7. 회의록 수정
**파일**: `meeting-회의록수정.puml`
**유저스토리**: UFR-MEET-055
**주요 처리**:
- 회의록 수정 권한 검증 (생성자만)
- 잠긴 섹션 수정 여부 확인
- 수정 이력 저장 (버전 관리)
- 회의록 제목 및 섹션 내용 수정
- 확정된 회의록은 DRAFT로 상태 변경
- 실시간 동기화 (WebSocket 브로드캐스트)
- 캐시 무효화
**주요 컴포넌트**:
- MinutesController
- MinutesService, VersionService, CollaborationService
- MinutesRepository, SectionRepository
- Meeting DB, Redis Cache
- WebSocket (실시간 협업)
---
### 8. 회의록 공유
**파일**: `meeting-회의록공유.puml`
**유저스토리**: UFR-MEET-060
**주요 처리**:
- 회의록 공유 권한 검증 (생성자만)
- 회의록 상태 확인 (FINALIZED만 공유 가능)
- 공유 링크 생성 (UUID 기반 토큰)
- 공유 대상 및 권한 설정 (READ_ONLY, COMMENT, EDIT)
- 다음 회의 일정 추출 및 캘린더 등록
- 비동기 이벤트 발행 (MinutesShared → 이메일 알림)
**주요 컴포넌트**:
- ShareController
- ShareService, CalendarService
- MinutesRepository, ShareRepository
- Meeting DB, Redis Cache
- Azure Event Hubs (알림 발송)
---
### 9. Todo 할당
**파일**: `meeting-Todo할당.puml`
**유저스토리**: UFR-TODO-010
**주요 처리**:
- Todo 정보 입력 검증 (내용, 담당자, 회의록 ID)
- 회의록 존재 확인
- Todo 생성 및 저장
- 회의록 섹션과 양방향 연결 (Todo 뱃지 추가)
- 마감일이 있는 경우 캘린더 이벤트 생성
- 비동기 이벤트 발행 (TodoAssigned → 담당자 알림)
- 캐시 무효화 (대시보드, 회의록)
**주요 컴포넌트**:
- TodoController
- TodoService, CalendarService
- TodoRepository, MinutesRepository
- Meeting DB, Redis Cache
- Azure Event Hubs (알림 발송)
---
### 10. Todo 완료 처리
**파일**: `meeting-Todo완료처리.puml`
**유저스토리**: UFR-TODO-030
**주요 처리**:
- Todo 완료 권한 검증 (담당자만)
- Todo 상태 변경 (IN_PROGRESS → COMPLETED)
- 회의록 섹션에 완료 상태 자동 반영
- 모든 Todo 완료 여부 확인
- 실시간 동기화 (WebSocket 브로드캐스트)
- 비동기 이벤트 발행 (TodoCompleted 또는 AllTodosCompleted)
- 캐시 무효화 (대시보드, 회의록)
**주요 컴포넌트**:
- TodoController
- TodoService, CollaborationService
- TodoRepository, MinutesRepository
- Meeting DB, Redis Cache
- WebSocket (실시간 협업)
- Azure Event Hubs (알림 발송)
---
## 🎯 설계 특징
### 1. 캐시 전략
- **Cache-Aside 패턴**: 캐시 우선 조회 후 DB 조회
- **TTL 설정**: 조회 데이터 5-10분, 회의 정보 10분
- **캐시 무효화**: 데이터 변경 시 즉시 삭제
### 2. 비동기 처리
- **이벤트 기반**: Azure Event Hubs를 통한 이벤트 발행
- **서비스 분리**: STT, AI, Notification 서비스와 비동기 통신
- **성능 최적화**: 장시간 작업은 비동기로 처리
### 3. 실시간 협업
- **WebSocket**: 회의록 수정 및 Todo 완료 시 실시간 동기화
- **변경 델타 전송**: 전체 데이터가 아닌 변경 부분만 전송
- **충돌 방지**: 버전 관리 및 잠금 기능
### 4. 권한 관리
- **역할 기반**: 생성자, 참석자, 공유받은 사용자 구분
- **작업별 권한**: 수정, 확정, 공유 등 작업별 권한 검증
- **섹션 잠금**: 검증 완료 섹션은 생성자만 잠금/해제
### 5. 데이터 일관성
- **버전 관리**: 회의록 수정 이력 및 스냅샷 저장
- **양방향 연결**: Todo와 회의록 섹션 간 양방향 링크
- **트랜잭션**: 관련 데이터는 동일 트랜잭션 내 처리
## 🔍 검증 사항
### PlantUML 문법
- ✅ `!theme mono` 사용
- ✅ 외부 참여자 `<<E>>` 표시
- ✅ 동기 호출 `→`, 비동기 호출 `-->`
- ✅ alt/loop/note 적절히 사용
### 유저스토리 일치
- ✅ 모든 시나리오가 유저스토리와 매칭
- ✅ 외부 시퀀스 설계와 일치
- ✅ UI/UX 설계의 사용자 플로우 반영
### 아키텍처 원칙
- ✅ 계층 구조 명확 (Controller → Service → Repository)
- ✅ 캐시 우선 전략 적용
- ✅ 비동기 처리 명확히 구분
- ✅ 비즈니스 로직 상세 설명
## 📝 다음 단계
1. **PlantUML 검증**: Docker 컨테이너 설정 후 문법 검사 실행
2. **클래스 설계**: 내부 시퀀스 기반 클래스 다이어그램 작성
3. **데이터 설계**: Repository 계층 기반 테이블 스키마 설계
4. **API 명세**: Controller 기반 OpenAPI 명세 작성
---
**작성일**: 2025-10-22
**작성자**: 길동 (아키텍트)
**버전**: v1.0

View File

@ -0,0 +1,221 @@
@startuml
!theme mono
title AI Service 내부 시퀀스 - Todo자동추출
participant "TodoController" as Controller
participant "TodoExtractionService" as Service
participant "LLMClient" as LLM
participant "TodoRepository" as Repo
participant "MeetingServiceClient<<E>>" as MeetingClient
database "Azure OpenAI<<E>>" as OpenAI
database "PostgreSQL<<E>>" as DB
== MeetingEnded 이벤트 수신 ==
note over Controller
Azure Event Hubs로부터
MeetingEnded 이벤트 수신
(meetingId, userId, endTime)
end note
Controller -> Service: extractTodos(meetingId)
activate Service
== 최종 회의록 조회 ==
Service -> Repo: getFinalTranscript(meetingId)
activate Repo
Repo -> DB: SELECT content FROM ai_transcripts\nWHERE meeting_id = {meetingId}\nORDER BY created_at DESC LIMIT 1
activate DB
DB --> Repo: 최종 회의록 내용
deactivate DB
Repo --> Service: transcriptContent
deactivate Repo
Service -> Service: 참석자 정보 조회 준비
Service -> Repo: getMeetingParticipants(meetingId)
activate Repo
Repo -> DB: SELECT participants FROM meeting_context
activate DB
DB --> Repo: 참석자 목록
deactivate DB
Repo --> Service: participants
deactivate Repo
== LLM 기반 Todo 추출 ==
Service -> Service: Todo 추출 프롬프트 생성
note right
시스템 프롬프트:
- 역할: Todo 추출 전문가
- 지시사항: 액션 아이템 식별,
담당자 및 마감일 추출
사용자 프롬프트:
- 회의록 전체 내용
- 참석자 목록
- 추출 기준:
* "~하기로 함", "~까지 완료"
* "~담당", "제가 하겠습니다"
* 명령형 문장
end note
Service -> LLM: extractActionItems(prompt, transcript, participants)
activate LLM
LLM -> OpenAI: POST /chat/completions
activate OpenAI
note right
요청 파라미터:
- model: gpt-4o
- temperature: 0.2
- response_format: json_object
응답 형식:
{
"todos": [
{
"content": "Todo 내용",
"assignee": "담당자명",
"dueDate": "YYYY-MM-DD",
"priority": "HIGH|MEDIUM|LOW",
"section": "관련 섹션"
}
]
}
end note
OpenAI -> OpenAI: 회의록 분석
note right
1. 액션 아이템 키워드 탐지
2. 명령형 문장 분석
3. 담당자 식별
- 발언 내용 기반
- 직책/역할 기반
4. 마감일 추출
5. 우선순위 판단
end note
OpenAI --> LLM: Todo 목록 (JSON)
deactivate OpenAI
LLM --> Service: extractedTodos
deactivate LLM
== Todo 데이터 저장 및 검증 ==
Service -> Service: Todo 데이터 검증
note right
검증 항목:
- content 필수
- assignee가 참석자 목록에 있는지
- dueDate 형식 검증
- priority 유효성 검증
end note
loop 각 Todo 항목마다
Service -> Repo: saveTodo(meetingId, todoData)
activate Repo
Repo -> DB: INSERT INTO ai_extracted_todos
activate DB
note right
저장 데이터:
- meeting_id
- content
- assignee
- due_date
- priority
- section_reference
- status: PENDING
- extracted_at
end note
DB --> Repo: todoId
deactivate DB
Repo --> Service: todoId
deactivate Repo
end
== Meeting Service에 Todo 전송 ==
Service -> Service: Todo 목록 준비
note right
전송 데이터 구성:
- Todo 내용
- 담당자 정보
- 마감일
- 우선순위
- 관련 회의록 섹션 링크
end note
Service -> MeetingClient: POST /meetings/{meetingId}/todos
activate MeetingClient
note right
요청 바디:
{
"meetingId": "{meetingId}",
"todos": [
{
"content": "Todo 내용",
"assignee": "담당자",
"dueDate": "2025-01-30",
"priority": "HIGH",
"sectionReference": "결정사항 #3"
}
]
}
end note
MeetingClient -> MeetingClient: Todo 생성 및 할당 처리
note right
Meeting Service 내부 처리:
- Todo 테이블에 저장
- 회의록 섹션 링크 연결
- TodoCreated 이벤트 발행
- 담당자에게 알림 발송
end note
MeetingClient --> Service: 200 OK (Todo 생성 완료)
deactivate MeetingClient
== 처리 상태 업데이트 ==
Service -> Repo: updateExtractionStatus(meetingId, "COMPLETED")
activate Repo
Repo -> DB: UPDATE ai_task_status\nSET status = 'COMPLETED',\ncompleted_at = NOW()
activate DB
DB --> Repo: 업데이트 완료
deactivate DB
Repo --> Service: 완료
deactivate Repo
Service --> Controller: Todo 추출 완료
deactivate Service
Controller -> Controller: TodoExtractionCompleted 이벤트 발행 (내부 로깅)
note over Controller, DB
처리 시간:
- 회의록 조회: 100-200ms
- LLM Todo 추출: 3-5초
- 저장 처리: 200-500ms
- Meeting Service 전송: 500ms-1초
총 처리 시간: 약 4-7초
end note
@enduml

View File

@ -0,0 +1,244 @@
@startuml
!theme mono
title AI Service 내부 시퀀스 - 관련회의록연결
participant "RelationController" as Controller
participant "RelationService" as Service
participant "VectorService" as Vector
participant "LLMClient" as LLM
participant "RelationRepository" as Repo
database "Azure OpenAI<<E>>" as OpenAI
database "Vector DB<<E>>" as VectorDB
database "PostgreSQL<<E>>" as DB
== 회의록 생성 완료 이벤트 수신 ==
note over Controller
내부 이벤트 또는
TranscriptFinalized 이벤트 수신
(meetingId, transcriptId)
end note
Controller -> Service: findRelatedTranscripts(meetingId, transcriptId)
activate Service
== 현재 회의록 정보 조회 ==
Service -> Repo: getTranscriptInfo(transcriptId)
activate Repo
Repo -> DB: SELECT content, meeting_id, created_at\nFROM ai_transcripts\nWHERE id = {transcriptId}
activate DB
DB --> Repo: 회의록 정보
deactivate DB
Repo --> Service: transcriptContent, meetingInfo
deactivate Repo
== 주제 및 키워드 추출 ==
Service -> Service: 키워드 추출 프롬프트 생성
note right
프롬프트:
- 주요 주제 추출 (3-5개)
- 핵심 키워드 추출 (10-15개)
- 카테고리 분류
end note
Service -> LLM: extractTopicsAndKeywords(transcriptContent)
activate LLM
LLM -> OpenAI: POST /chat/completions
activate OpenAI
note right
요청:
- model: gpt-4o
- temperature: 0.2
- response_format: json_object
응답 형식:
{
"topics": ["주제1", "주제2", "주제3"],
"keywords": ["키워드1", "키워드2", ...],
"category": "카테고리"
}
end note
OpenAI --> LLM: 주제 및 키워드
deactivate OpenAI
LLM --> Service: topics, keywords, category
deactivate LLM
== 벡터 임베딩 생성 ==
Service -> Service: 검색 쿼리 구성
note right
쿼리 텍스트:
- 주요 주제
- 핵심 키워드
- 회의 요약
결합하여 검색용 텍스트 생성
end note
Service -> Vector: generateQueryEmbedding(queryText)
activate Vector
Vector -> OpenAI: POST /embeddings
activate OpenAI
note right
model: text-embedding-3-large
input: {queryText}
end note
OpenAI --> Vector: 쿼리 임베딩 벡터
deactivate OpenAI
Vector --> Service: queryEmbedding
deactivate Vector
== 벡터 유사도 검색 ==
Service -> Vector: searchSimilarTranscripts(queryEmbedding, meetingId, limit=20)
activate Vector
Vector -> VectorDB: 벡터 유사도 검색
activate VectorDB
note right
검색 조건:
- 코사인 유사도 기준
- 동일 폴더(프로젝트) 내
- 현재 회의록 제외
- 상위 20개 후보 조회
WHERE metadata.project_id = {projectId}
AND vector_id != {currentTranscriptId}
ORDER BY cosine_similarity DESC
LIMIT 20
end note
VectorDB --> Vector: 유사 회의록 목록 (벡터 유사도 점수 포함)
deactivate VectorDB
Vector --> Service: similarTranscripts (top 20)
deactivate Vector
== 관련도 점수 계산 및 필터링 ==
Service -> Service: 다중 기준으로 관련도 점수 재계산
note right
점수 계산 기준:
1. 벡터 유사도 (40%)
- 코사인 유사도 점수
2. 키워드 일치도 (30%)
- 공통 키워드 개수
- 가중치: 중요 키워드 우선
3. 참석자 중복도 (20%)
- 동일 참석자 비율
4. 시간적 연관성 (10%)
- 최근 회의 우선
- 분기별/월별 회의 패턴
최종 점수 = 가중 평균
end note
loop 각 후보 회의록마다
Service -> Repo: getTranscriptDetails(candidateId)
activate Repo
Repo -> DB: SELECT participants, keywords, created_at
activate DB
DB --> Repo: 상세 정보
deactivate DB
Repo --> Service: 참석자, 키워드, 날짜
deactivate Repo
Service -> Service: 관련도 점수 계산
note right
관련도 =
벡터유사도 * 0.4 +
키워드일치도 * 0.3 +
참석자중복도 * 0.2 +
시간연관성 * 0.1
end note
end
Service -> Service: 필터링 및 정렬
note right
필터링 기준:
- 관련도 70% 이상만 선택
- 관련도 점수순 정렬
- 상위 5개 선택
end note
== 관련 회의록 저장 ==
loop 선택된 상위 5개
Service -> Repo: saveRelatedTranscript(transcriptId, relatedId, score, keywords)
activate Repo
Repo -> DB: INSERT INTO related_transcripts
activate DB
note right
저장 데이터:
- transcript_id
- related_transcript_id
- relevance_score
- common_keywords (JSON)
- created_at
end note
DB --> Repo: 저장 완료
deactivate DB
Repo --> Service: 완료
deactivate Repo
end
== 응답 데이터 구성 ==
Service -> Service: 관련 회의록 정보 구성
note right
각 관련 회의록별 정보:
- 제목
- 날짜
- 참석자
- 관련도 점수 (%)
- 연관 키워드
- 링크
end note
Service --> Controller: 관련 회의록 목록 (top 5)
deactivate Service
Controller -> Controller: 회의록 상단에 "관련 회의록" 섹션 추가 준비
note over Controller, DB
처리 시간:
- 회의록 조회: 100-200ms
- LLM 주제 추출: 2-3초
- 벡터 임베딩: 500ms-1초
- 벡터 검색: 500ms-1초
- 관련도 계산: 1-2초 (20개)
- 저장 처리: 200-300ms
총 처리 시간: 약 5-8초
정책:
- 관련도 70% 이상만 자동 연결
- 최대 5개까지 표시
- 동일 폴더(프로젝트) 내에서만 검색
end note
@enduml

View File

@ -0,0 +1,303 @@
@startuml
!theme mono
title AI Service 내부 시퀀스 - 맥락기반용어설명
participant "ExplanationController" as Controller
participant "TermExplanationService" as Service
participant "RAGService" as RAG
participant "VectorService" as Vector
participant "LLMClient" as LLM
participant "ExplanationRepository" as Repo
database "Azure OpenAI<<E>>" as OpenAI
database "Vector DB<<E>>" as VectorDB
database "Document Store<<E>>" as DocStore
database "PostgreSQL<<E>>" as DB
== 용어 설명 요청 수신 ==
note over Controller
요청 경로:
1. 용어 감지 후 자동 트리거 (비동기)
2. 사용자 클릭 시 API 요청
API: GET /api/ai/terms/{term}/explain
Query: meetingId, context
end note
Controller -> Service: explainTerm(term, meetingId, context)
activate Service
== 용어 정보 조회 ==
Service -> Repo: getTermInfo(term)
activate Repo
Repo -> DB: SELECT definition, category\nFROM term_dictionary\nWHERE term = {term}
activate DB
DB --> Repo: 기본 용어 정의
deactivate DB
Repo --> Service: basicDefinition, category
deactivate Repo
== 벡터 임베딩 생성 ==
Service -> Service: 검색 쿼리 구성
note right
검색 쿼리:
- 용어명
- 현재 회의 맥락
- 카테고리
예: "{term} {context} {category}"
end note
Service -> Vector: generateQueryEmbedding(queryText)
activate Vector
Vector -> OpenAI: POST /embeddings
activate OpenAI
note right
model: text-embedding-3-large
input: 검색 쿼리 텍스트
end note
OpenAI --> Vector: 쿼리 임베딩 벡터
deactivate OpenAI
Vector --> Service: queryEmbedding
deactivate Vector
== RAG 검색 수행 (병렬) ==
Service -> RAG: searchRelatedDocuments(queryEmbedding, term)
activate RAG
par "과거 회의록 검색"
RAG -> VectorDB: 벡터 유사도 검색 (회의록)
activate VectorDB
note right
검색 조건:
- 컬렉션: meeting_transcripts
- 필터: term 포함
- 유사도 threshold: 0.7
- 결과: top 3
end note
VectorDB --> RAG: 관련 회의록 (top 3)
deactivate VectorDB
and "사내 문서 검색"
RAG -> VectorDB: 벡터 유사도 검색 (문서)
activate VectorDB
note right
검색 조건:
- 컬렉션: company_documents
- 문서 유형: 위키, 매뉴얼, 보고서
- 유사도 threshold: 0.7
- 결과: top 2
end note
VectorDB --> RAG: 관련 사내 문서 (top 2)
deactivate VectorDB
and "업무 이력 검색"
RAG -> DocStore: 전체 텍스트 검색
activate DocStore
note right
검색 조건:
- 문서 유형: 프로젝트, 이메일
- 키워드: {term}
- 날짜: 최근 1년
- 결과: top 2
end note
DocStore --> RAG: 관련 업무 이력 (top 2)
deactivate DocStore
end
RAG -> RAG: 검색 결과 통합 및 정렬
note right
통합 결과:
- 과거 회의록 3개
- 사내 문서 2개
- 업무 이력 2개
관련도 점수순 정렬
end note
RAG --> Service: relatedDocuments (총 최대 7개)
deactivate RAG
alt 관련 문서를 찾지 못한 경우
Service -> Service: 기본 정의만 반환 준비
note right
응답:
- 용어 정의 (사전)
- "관련 정보를 찾을 수 없습니다"
- 전문가 요청 버튼
end note
Service --> Controller: 기본 정의 응답
deactivate Service
else 관련 문서 발견
== 맥락 기반 설명 생성 ==
Service -> Service: LLM 프롬프트 구성
note right
시스템 프롬프트:
- 역할: 용어 설명 전문가
- 지시사항:
* 기본 정의 제공
* 현재 회의 맥락에서의 의미
* 관련 프로젝트/이슈 연결
* 과거 사용 사례 요약
* 참조 출처 링크
사용자 프롬프트:
- 용어: {term}
- 기본 정의: {basicDefinition}
- 현재 회의 맥락: {context}
- 관련 문서: {relatedDocuments}
출력 형식:
{
"basicDefinition": "간단한 정의",
"contextualMeaning": "맥락 기반 설명",
"useCases": [사용 사례],
"relatedProjects": [프로젝트],
"pastDiscussions": [과거 논의],
"references": [출처 링크]
}
end note
Service -> LLM: generateContextualExplanation(prompt)
activate LLM
LLM -> OpenAI: POST /chat/completions
activate OpenAI
note right
요청 파라미터:
- model: gpt-4o
- temperature: 0.3
- max_tokens: 1500
- response_format: json_object
end note
OpenAI -> OpenAI: 맥락 기반 설명 생성
note right
처리 단계:
1. 검색된 문서 분석
2. 용어 사용 맥락 파악
3. 실제 사용 사례 추출
4. 프로젝트/이슈 연결
5. 과거 논의 요약
- 언제 논의되었는지
- 누가 사용했는지
- 어떻게 사용되었는지
6. 참조 출처 정리
end note
OpenAI --> LLM: 맥락 기반 설명 (JSON)
deactivate OpenAI
LLM --> Service: explanation
deactivate LLM
== 설명 저장 ==
Service -> Repo: saveExplanation(term, meetingId, explanation)
activate Repo
Repo -> DB: INSERT INTO term_explanations
activate DB
note right
저장 데이터:
- term
- meeting_id
- basic_definition
- contextual_meaning
- use_cases (JSON)
- related_projects (JSON)
- past_discussions (JSON)
- references (JSON)
- created_at
end note
DB --> Repo: explanationId
deactivate DB
Repo --> Service: explanationId
deactivate Repo
== 응답 데이터 구성 ==
Service -> Service: 최종 응답 구성
note right
응답 구조:
{
"term": "용어명",
"basicDefinition": "간단한 정의 (1-2문장)",
"contextualMeaning": "이 회의에서의 의미",
"useCases": [
"실제 사용 사례 1",
"실제 사용 사례 2"
],
"relatedProjects": [
{
"name": "프로젝트명",
"relevance": "연관성 설명"
}
],
"pastDiscussions": [
{
"date": "2024-12-15",
"participants": ["참석자1", "참석자2"],
"summary": "논의 요약",
"link": "회의록 링크"
}
],
"references": [
{
"title": "문서 제목",
"type": "위키|매뉴얼|회의록",
"link": "URL"
}
]
}
end note
Service --> Controller: 맥락 기반 설명 응답
deactivate Service
Controller --> Controller: 200 OK 응답 반환
note right
프론트엔드 표시:
- 툴팁 또는 사이드 패널
- 접을 수 있는 섹션별 표시
- 참조 링크 클릭 가능
end note
end
note over Controller, DB
처리 시간:
- 용어 정보 조회: 50-100ms
- 벡터 임베딩: 500ms-1초
- RAG 병렬 검색: 1-2초
- LLM 설명 생성: 3-5초
- 저장 처리: 100-200ms
총 처리 시간: 약 5-8초
차별화 포인트:
- 단순 용어 설명이 아닌
조직 내 실제 사용 맥락과 이력 제공
- 업무 지식이 없어도
실질적인 도움 제공
end note
@enduml

View File

@ -0,0 +1,242 @@
@startuml
!theme mono
title AI Service 내부 시퀀스 - 전문용어감지
participant "TermController" as Controller
participant "TermDetectionService" as Service
participant "LLMClient" as LLM
participant "TermRepository" as Repo
database "Azure OpenAI<<E>>" as OpenAI
database "PostgreSQL<<E>>" as DB
== 회의록 텍스트 실시간 분석 요청 ==
note over Controller
API 요청 (실시간 또는 배치):
POST /api/ai/terms/detect
Body: {
"meetingId": "{meetingId}",
"text": "회의록 텍스트"
}
end note
Controller -> Service: detectTerms(meetingId, text)
activate Service
== 용어 사전 조회 ==
par "조직별 용어 사전"
Service -> Repo: getOrganizationTerms(organizationId)
activate Repo
Repo -> DB: SELECT term, definition, category\nFROM term_dictionary\nWHERE org_id = {organizationId}
activate DB
DB --> Repo: 조직 전문용어 목록
deactivate DB
Repo --> Service: orgTerms
deactivate Repo
and "산업별 표준 용어"
Service -> Repo: getIndustryTerms(industry)
activate Repo
Repo -> DB: SELECT term, definition, category\nFROM standard_terms\nWHERE industry = {industry}
activate DB
DB --> Repo: 산업 표준용어 목록
deactivate DB
Repo --> Service: industryTerms
deactivate Repo
end
Service -> Service: 용어 사전 병합 및 준비
note right
용어 사전:
- 조직별 용어 (우선순위 높음)
- 산업별 표준 용어
- 기술 용어
end note
== LLM 기반 전문용어 감지 ==
Service -> Service: 용어 감지 프롬프트 생성
note right
시스템 프롬프트:
- 역할: 전문용어 감지 전문가
- 지시사항:
* 텍스트에서 전문용어 탐지
* 용어 사전과 비교
* 신뢰도 점수 계산
* 위치 정보 추출
사용자 프롬프트:
- 분석 대상 텍스트: {text}
- 용어 사전: {termDictionary}
응답 형식:
{
"detectedTerms": [
{
"term": "용어명",
"position": {line, offset},
"confidence": 0.0-1.0,
"category": "기술|업무|도메인"
}
]
}
end note
Service -> LLM: detectTechnicalTerms(prompt, text, termDictionary)
activate LLM
LLM -> OpenAI: POST /chat/completions
activate OpenAI
note right
요청 파라미터:
- model: gpt-4o
- temperature: 0.1
- response_format: json_object
end note
OpenAI -> OpenAI: 텍스트 분석 및 용어 감지
note right
처리 단계:
1. 텍스트 토큰화
2. 용어 사전과 매칭
3. 문맥 기반 용어 식별
4. 신뢰도 계산
- 정확한 매칭: 0.9-1.0
- 변형 매칭: 0.7-0.9
- 문맥 기반: 0.7-0.8
5. 위치 정보 추출
end note
OpenAI --> LLM: 감지된 용어 목록 (JSON)
deactivate OpenAI
LLM --> Service: detectedTerms
deactivate LLM
== 용어 필터링 및 검증 ==
Service -> Service: 신뢰도 기반 필터링
note right
필터링 기준:
- 신뢰도 70% 이상만 선택
- 중복 용어 제거
(첫 번째 출현만 유지)
- 카테고리별 분류
end note
loop 각 감지된 용어마다
Service -> Service: 용어 메타데이터 보강
note right
추가 정보:
- 용어 정의 (사전에서)
- 카테고리
- 사용 빈도
- 관련 문서 참조
end note
end
== 감지 결과 저장 ==
Service -> Repo: saveDetectedTerms(meetingId, detectedTerms)
activate Repo
loop 각 용어마다
Repo -> DB: INSERT INTO detected_terms
activate DB
note right
저장 데이터:
- meeting_id
- term
- position (JSON)
- confidence_score
- category
- detected_at
- status: DETECTED
end note
DB --> Repo: termId
deactivate DB
end
Repo --> Service: 저장 완료
deactivate Repo
== 하이라이트 정보 생성 ==
Service -> Service: 하이라이트 데이터 구성
note right
프론트엔드 전달 정보:
- 용어 위치 (줄 번호, 오프셋)
- 하이라이트 스타일
- 툴팁 텍스트
- 신뢰도 표시
end note
== 맥락 기반 설명 트리거 ==
Service -> Service: 용어 설명 생성 트리거
note right
비동기로 용어 설명 생성 시작
(UFR-RAG-020 연동)
각 감지된 용어에 대해:
- RAG 검색 수행
- 맥락 기반 설명 생성
end note
== 응답 반환 ==
Service -> Service: 응답 데이터 구성
note right
응답 데이터:
- detectedTerms: [
{
"term": "용어명",
"position": {line, offset},
"confidence": 0.85,
"category": "기술",
"highlight": true
}
]
- totalCount: 감지된 용어 수
- highlightInfo: 하이라이트 정보
end note
Service --> Controller: 감지 완료 응답
deactivate Service
Controller --> Controller: 200 OK 응답 반환
note right
프론트엔드 처리:
- 용어 하이라이트 표시
- 툴팁 준비
- 설명 로딩 중 표시
end note
note over Controller, DB
처리 시간:
- 용어 사전 조회: 100-200ms
- LLM 용어 감지: 2-4초
- 필터링 및 검증: 100-200ms
- 저장 처리: 200-300ms
총 처리 시간: 약 3-5초
정책:
- 신뢰도 70% 이상만 자동 감지
- 중복 용어는 첫 번째만 하이라이트
- 맥락 기반 설명은 비동기 생성
end note
@enduml

View File

@ -0,0 +1,224 @@
@startuml
!theme mono
title AI Service 내부 시퀀스 - 회의록개선
participant "ImproveController" as Controller
participant "ImproveService" as Service
participant "LLMClient" as LLM
participant "PromptTemplateService" as Template
participant "ImproveRepository" as Repo
database "Azure OpenAI<<E>>" as OpenAI
database "PostgreSQL<<E>>" as DB
== 회의록 개선 요청 수신 ==
note over Controller
API 요청:
POST /api/ai/transcripts/{meetingId}/improve
Body: {
"promptType": "1PAGE_SUMMARY",
"customPrompt": "optional"
}
end note
Controller -> Service: improveTranscript(meetingId, promptType, customPrompt)
activate Service
== 원본 회의록 조회 ==
Service -> Repo: getOriginalTranscript(meetingId)
activate Repo
Repo -> DB: SELECT content, version\nFROM ai_transcripts\nWHERE meeting_id = {meetingId}\nAND status = 'FINALIZED'\nORDER BY created_at DESC LIMIT 1
activate DB
DB --> Repo: 원본 회의록 내용
deactivate DB
Repo --> Service: originalContent, version
deactivate Repo
== 프롬프트 템플릿 선택 ==
Service -> Template: getPromptTemplate(promptType)
activate Template
Template -> Template: 프롬프트 유형별 템플릿 선택
note right
지원 프롬프트 유형:
1. 1PAGE_SUMMARY
- A4 1장 분량 요약
- 핵심 내용만 압축
2. CORE_SUMMARY
- 3-5개 핵심 포인트
- 불릿 포인트 형식
3. DETAILED_REPORT
- 시간순 상세 기록
- 타임스탬프 포함
4. DECISION_FOCUSED
- 의사결정 중심
- 결정 사항과 근거
5. ACTION_FOCUSED
- 액션 아이템 중심
- Todo와 담당자 강조
6. EXECUTIVE_REPORT
- 경영진 보고용
- 간결하고 임팩트 있게
7. CUSTOM
- 사용자 정의 프롬프트
end note
Template --> Service: promptTemplate
deactivate Template
== 프롬프트 생성 ==
Service -> Service: 최종 프롬프트 구성
note right
시스템 프롬프트:
- 역할: 회의록 개선 전문가
- 지시사항: {promptTemplate}
사용자 프롬프트:
- 원본 회의록: {originalContent}
- 개선 요구사항: {promptType}
- 추가 지시사항: {customPrompt}
출력 형식 지정:
- 구조화된 마크다운
- 적절한 헤딩과 섹션
- 가독성 최적화
end note
== LLM 기반 회의록 재구성 ==
Service -> LLM: regenerateTranscript(prompt, originalContent)
activate LLM
LLM -> OpenAI: POST /chat/completions
activate OpenAI
note right
요청 파라미터:
- model: gpt-4o
- temperature: 0.3
- max_tokens: 3000
- messages: [system, user]
end note
OpenAI -> OpenAI: 회의록 재구성
note right
처리 단계:
1. 원본 분석
2. 프롬프트 의도 파악
3. 중요도 기반 필터링
4. 형식에 맞춰 재배치
5. 불필요한 내용 제거
6. 스타일 조정
- 문체 변환
- 길이 조정
7. 구조화
end note
OpenAI --> LLM: 개선된 회의록
deactivate OpenAI
LLM --> Service: improvedContent
deactivate LLM
== 개선된 회의록 저장 ==
Service -> Service: 새 버전 생성
note right
버전 정보:
- base_version: 원본 버전 번호
- improvement_type: {promptType}
- version: 새 버전 번호
end note
Service -> Repo: saveImprovedTranscript(meetingId, improvedContent, metadata)
activate Repo
Repo -> DB: INSERT INTO ai_transcripts
activate DB
note right
저장 데이터:
- meeting_id
- content (개선된 내용)
- version (새 버전)
- base_version (원본 버전)
- improvement_type
- prompt_type
- custom_prompt
- created_at
- status: IMPROVED
end note
DB --> Repo: newTranscriptId
deactivate DB
Repo --> Service: transcriptId, version
deactivate Repo
== 버전 연결 저장 ==
Service -> Repo: linkVersions(originalId, improvedId)
activate Repo
Repo -> DB: INSERT INTO transcript_versions
activate DB
note right
버전 연결 정보:
- original_transcript_id
- improved_transcript_id
- improvement_type
- created_at
end note
DB --> Repo: 연결 완료
deactivate DB
Repo --> Service: 완료
deactivate Repo
== 응답 반환 ==
Service -> Service: 응답 데이터 구성
note right
응답 데이터:
- transcriptId (새 버전)
- version
- baseVersion
- improvementType
- content (개선된 내용)
- originalLink
- createdAt
end note
Service --> Controller: 개선 완료 응답
deactivate Service
Controller --> Controller: 200 OK 응답 반환
note over Controller, DB
처리 시간:
- 원본 조회: 100-200ms
- 템플릿 선택: 10-50ms
- LLM 재구성: 4-8초
- 저장 처리: 200-300ms
총 처리 시간: 약 5-9초
정책:
- 원본 회의록은 항상 보존
- 여러 버전 동시 생성 가능
- 버전 간 비교 기능 제공
end note
@enduml

View File

@ -0,0 +1,185 @@
@startuml
!theme mono
title AI Service 내부 시퀀스 - 회의록자동작성
participant "TranscriptController" as Controller
participant "TranscriptService" as Service
participant "LLMClient" as LLM
participant "VectorService" as Vector
participant "TranscriptRepository" as Repo
database "Azure OpenAI<<E>>" as OpenAI
database "Vector DB<<E>>" as VectorDB
database "PostgreSQL<<E>>" as DB
== TranscriptReady 이벤트 수신 ==
note over Controller
Azure Event Hubs로부터
TranscriptReady 이벤트 수신
(meetingId, transcriptText, timestamp)
end note
Controller -> Service: processTranscript(meetingId, transcriptText, timestamp)
activate Service
Service -> Service: 회의 맥락 정보 조회 준비
note right
회의 제목, 참석자 정보,
이전 회의록 내용
end note
== 병렬 처리: 맥락 정보 수집 ==
par "회의 정보 조회"
Service -> Repo: getMeetingContext(meetingId)
activate Repo
Repo -> DB: SELECT meeting_info, participants
activate DB
DB --> Repo: 회의 정보 반환
deactivate DB
Repo --> Service: 회의 맥락 정보
deactivate Repo
and "이전 내용 조회"
Service -> Repo: getPreviousTranscripts(meetingId)
activate Repo
Repo -> DB: SELECT previous_content
activate DB
DB --> Repo: 이전 회의록
deactivate DB
Repo --> Service: 이전 내용
deactivate Repo
end
Service -> Service: 프롬프트 생성
note right
시스템 프롬프트:
- 역할: 회의록 작성 전문가
- 지시사항: 구어체→문어체 변환,
주제별 분류, 발언자별 정리
사용자 프롬프트:
- 회의 제목: {title}
- 참석자: {participants}
- 이전 내용: {previous}
- 현재 발언: {transcriptText}
end note
== LLM 기반 회의록 작성 ==
Service -> LLM: generateMinutes(prompt, meetingContext)
activate LLM
LLM -> OpenAI: POST /chat/completions
activate OpenAI
note right
요청 파라미터:
- model: gpt-4o
- temperature: 0.3
- max_tokens: 2000
- messages: [system, user]
end note
OpenAI -> OpenAI: 텍스트 분석 및 정리
note right
1. 주제별 분류
2. 발언자별 의견 정리
3. 중요 키워드 추출
4. 구어체→문어체 변환
5. 문법 교정
end note
OpenAI --> LLM: 정리된 회의록 내용
deactivate OpenAI
LLM --> Service: 회의록 초안
deactivate LLM
== 회의록 저장 ==
Service -> Service: 회의록 데이터 구조화
note right
구조화 항목:
- 논의 주제
- 발언자별 의견
- 결정 사항
- 보류 사항
- 요약문
end note
Service -> Repo: saveTranscriptDraft(meetingId, content, timestamp)
activate Repo
Repo -> DB: INSERT INTO ai_transcripts
activate DB
note right
저장 데이터:
- meeting_id
- content (JSON)
- generated_at
- version
- status: DRAFT
end note
DB --> Repo: 저장 완료
deactivate DB
Repo --> Service: transcriptId
deactivate Repo
== 벡터 임베딩 생성 (비동기) ==
Service -> Vector: createEmbedding(transcriptId, content)
activate Vector
Vector -> OpenAI: POST /embeddings
activate OpenAI
note right
model: text-embedding-3-large
input: 회의록 내용
end note
OpenAI --> Vector: 임베딩 벡터
deactivate OpenAI
Vector -> VectorDB: INSERT embedding
activate VectorDB
note right
vector_id: transcriptId
embedding: [float array]
metadata: {meetingId, timestamp}
end note
VectorDB --> Vector: 저장 완료
deactivate VectorDB
Vector --> Service: 임베딩 생성 완료
deactivate Vector
== TranscriptSummaryCreated 이벤트 발행 ==
Service -> Controller: 회의록 생성 완료 응답
deactivate Service
Controller -> Controller: TranscriptSummaryCreated 이벤트 발행
note right
이벤트 데이터:
- meetingId
- transcriptId
- content
- generatedAt
Partition Key: {meetingId}
Consumer: Meeting Service
end note
note over Controller, DB
처리 시간:
- 맥락 정보 조회: 100-200ms
- LLM 생성: 3-5초
- 저장 처리: 100-200ms
- 벡터 임베딩: 500ms-1초 (비동기)
총 처리 시간: 약 4-7초
end note
@enduml

View File

@ -0,0 +1,179 @@
@startuml meeting-Todo완료처리
!theme mono
title Meeting Service - Todo완료처리 내부 시퀀스
participant "TodoController" as Controller
participant "TodoService" as Service
participant "TodoRepository" as TodoRepo
participant "MinutesRepository" as MinutesRepo
participant "CollaborationService" as CollabService
database "Meeting DB<<E>>" as DB
database "Redis Cache<<E>>" as Cache
queue "Azure Event Hubs<<E>>" as EventHub
participant "WebSocket<<E>>" as WebSocket
[-> Controller: PATCH /todos/{todoId}/complete
activate Controller
note over Controller
경로 변수: todoId
사용자 정보: userId, userName, email
end note
Controller -> Controller: todoId 유효성 검증
Controller -> Service: completeTodo(todoId, userId)
activate Service
' Todo 정보 조회
Service -> TodoRepo: findById(todoId)
activate TodoRepo
TodoRepo -> DB: SELECT * FROM todos WHERE id = ?
activate DB
DB --> TodoRepo: Todo 정보
deactivate DB
TodoRepo --> Service: Todo
deactivate TodoRepo
note over Service
비즈니스 규칙 검증:
- Todo 존재 확인
- 완료 권한 확인 (담당자만)
- 상태 확인 (이미 완료된 경우 처리)
end note
Service -> Service: Todo 존재 확인
Service -> Service: 완료 권한 검증\n(담당자만 가능)
alt 권한 없음
Service --> Controller: 403 Forbidden\n담당자만 완료 가능
return 403 Forbidden
else 권한 있음
alt Todo가 이미 완료됨
Service --> Controller: 409 Conflict\n이미 완료된 Todo
return 409 Conflict
else 완료 처리 가능
' 완료 확인 다이얼로그 (프론트엔드에서 처리됨)
' Todo 완료 처리
Service -> TodoRepo: markAsCompleted(todoId, userId)
activate TodoRepo
TodoRepo -> DB: UPDATE todos\nSET status = 'COMPLETED',\n completedAt = NOW(),\n completedBy = ?\nWHERE id = ?
activate DB
DB --> TodoRepo: 업데이트 완료
deactivate DB
TodoRepo --> Service: 업데이트 성공
deactivate TodoRepo
note over Service
회의록 실시간 반영:
- 관련 회의록 섹션 자동 업데이트
- 완료 표시 추가
- 완료 시간 및 완료자 정보 기록
end note
' 회의록 섹션 업데이트
Service -> MinutesRepo: updateTodoStatus(todoId, "COMPLETED")
activate MinutesRepo
MinutesRepo -> DB: UPDATE minutes_sections\nSET todoStatuses = JSON_SET(todoStatuses,\n CONCAT('$."', ?, '"'),\n JSON_OBJECT(\n 'status', 'COMPLETED',\n 'completedAt', NOW(),\n 'completedBy', ?\n )\n)\nWHERE JSON_CONTAINS(todoIds, CAST(? AS JSON))
activate DB
DB --> MinutesRepo: 업데이트 완료
deactivate DB
MinutesRepo --> Service: 업데이트 성공
deactivate MinutesRepo
' 회의록의 모든 Todo 완료 여부 확인
Service -> TodoRepo: countPendingTodos(minutesId)
activate TodoRepo
TodoRepo -> DB: SELECT COUNT(*) FROM todos\nWHERE minutesId = ?\nAND status != 'COMPLETED'
activate DB
DB --> TodoRepo: 미완료 Todo 개수
deactivate DB
TodoRepo --> Service: int pendingCount
deactivate TodoRepo
' 캐시 무효화
Service -> Cache: DELETE dashboard:{assigneeId}
activate Cache
Cache --> Service: 삭제 완료
deactivate Cache
Service -> Cache: DELETE minutes:detail:{minutesId}
activate Cache
Cache --> Service: 삭제 완료
deactivate Cache
note over Service
실시간 협업:
- WebSocket으로 회의록 업데이트 전송
- 모든 참석자에게 완료 상태 동기화
end note
' 실시간 동기화
Service -> CollabService: broadcastTodoUpdate(minutesId, todoId, status)
activate CollabService
note over CollabService
WebSocket 메시지 형식:
{
"type": "TODO_COMPLETED",
"todoId": "uuid",
"minutesId": "uuid",
"completedBy": {
"userId": "...",
"userName": "..."
},
"completedAt": "...",
"timestamp": "..."
}
end note
CollabService -> WebSocket: broadcast to room:{minutesId}
activate WebSocket
WebSocket --> CollabService: 전송 완료
deactivate WebSocket
CollabService --> Service: 동기화 완료
deactivate CollabService
note over Service
비동기 이벤트 발행:
- 완료 알림 발송
- 모든 Todo 완료 시 전체 완료 알림
end note
alt 모든 Todo 완료됨
Service -> EventHub: publish(AllTodosCompleted)\n{\n minutesId, meetingId,\n completedAt, totalTodos\n}
activate EventHub
EventHub --> Service: 발행 완료
deactivate EventHub
else 일부 Todo만 완료
Service -> EventHub: publish(TodoCompleted)\n{\n todoId, minutesId,\n completedBy, completedAt\n}
activate EventHub
EventHub --> Service: 발행 완료
deactivate EventHub
end
Service --> Controller: TodoCompleteResponse
deactivate Service
note over Controller
응답 데이터:
{
"todoId": "uuid",
"status": "COMPLETED",
"completedAt": "2025-01-24T10:00:00",
"completedBy": "userId",
"minutesId": "uuid",
"allTodosCompleted": true/false
}
end note
return 200 OK\nTodoCompleteResponse
end
end
deactivate Controller
@enduml

View File

@ -0,0 +1,158 @@
@startuml meeting-Todo할당
!theme mono
title Meeting Service - Todo할당 내부 시퀀스
participant "TodoController" as Controller
participant "TodoService" as Service
participant "TodoRepository" as TodoRepo
participant "MinutesRepository" as MinutesRepo
participant "CalendarService" as CalendarService
database "Meeting DB<<E>>" as DB
database "Redis Cache<<E>>" as Cache
queue "Azure Event Hubs<<E>>" as EventHub
[-> Controller: POST /todos
activate Controller
note over Controller
요청 데이터:
{
"content": "Todo 내용",
"assignee": "user@example.com",
"dueDate": "2025-01-30",
"priority": "HIGH" | "MEDIUM" | "LOW",
"minutesId": "uuid",
"sectionId": "uuid" // 회의록 섹션 위치
}
사용자 정보: userId, userName, email
end note
Controller -> Controller: 입력 검증\n- content 필수\n- assignee 필수\n- minutesId 필수
Controller -> Service: createTodo(request, userId)
activate Service
note over Service
비즈니스 규칙:
- Todo 내용 최대 500자
- 마감일은 현재보다 미래여야 함
- 회의록 존재 확인
- 담당자 유효성 검증
end note
' 회의록 존재 확인
Service -> MinutesRepo: findById(minutesId)
activate MinutesRepo
MinutesRepo -> DB: SELECT * FROM minutes WHERE id = ?
activate DB
DB --> MinutesRepo: 회의록 정보
deactivate DB
MinutesRepo --> Service: Minutes
deactivate MinutesRepo
Service -> Service: 회의록 존재 확인
' Todo 생성
Service -> Service: Todo 엔티티 생성\n- todoId (UUID)\n- 상태: IN_PROGRESS\n- 생성 정보
Service -> TodoRepo: save(todo)
activate TodoRepo
TodoRepo -> DB: INSERT INTO todos\n(id, content, assignee, dueDate,\npriority, status, minutesId, sectionId,\ncreatedBy, createdAt)
activate DB
DB --> TodoRepo: Todo 저장 완료
deactivate DB
TodoRepo --> Service: Todo
deactivate TodoRepo
note over Service
회의록 양방향 연결:
- 회의록 섹션에 Todo 뱃지 추가
- Todo에서 회의록 섹션으로 링크
end note
' 회의록 섹션에 Todo 연결
Service -> MinutesRepo: linkTodoToSection(sectionId, todoId)
activate MinutesRepo
MinutesRepo -> DB: UPDATE minutes_sections\nSET todoIds = JSON_ARRAY_APPEND(todoIds, '$', ?)\nWHERE id = ?
activate DB
DB --> MinutesRepo: 업데이트 완료
deactivate DB
MinutesRepo --> Service: 연결 성공
deactivate MinutesRepo
' 마감일이 있는 경우 캘린더 연동
alt 마감일 설정됨
Service -> CalendarService: createTodoEvent(todo)
activate CalendarService
note over CalendarService
캘린더 이벤트 생성:
- 제목: Todo 내용
- 일시: 마감일
- 참석자: 담당자
- 리마인더: 마감 3일 전
end note
CalendarService -> CalendarService: 캘린더 이벤트 생성
CalendarService --> Service: 이벤트 ID
deactivate CalendarService
Service -> TodoRepo: updateCalendarEventId(todoId, eventId)
activate TodoRepo
TodoRepo -> DB: UPDATE todos\nSET calendarEventId = ?\nWHERE id = ?
activate DB
DB --> TodoRepo: 업데이트 완료
deactivate DB
TodoRepo --> Service: 업데이트 성공
deactivate TodoRepo
end
' 캐시 무효화
Service -> Cache: DELETE dashboard:{assigneeId}
activate Cache
Cache --> Service: 삭제 완료
deactivate Cache
Service -> Cache: DELETE minutes:detail:{minutesId}
activate Cache
Cache --> Service: 삭제 완료
deactivate Cache
note over Service
비동기 이벤트 발행:
- 담당자에게 즉시 알림 발송
- 회의록 실시간 업데이트 (WebSocket)
- 캘린더 초대 발송
end note
' 이벤트 발행
Service -> EventHub: publish(TodoAssigned)\n{\n todoId, content, assignee,\n dueDate, minutesId, sectionId,\n assignedBy, calendarEventId\n}
activate EventHub
EventHub --> Service: 발행 완료
deactivate EventHub
Service --> Controller: TodoResponse
deactivate Service
note over Controller
응답 데이터:
{
"todoId": "uuid",
"content": "Todo 내용",
"assignee": "user@example.com",
"dueDate": "2025-01-30",
"priority": "HIGH",
"status": "IN_PROGRESS",
"minutesId": "uuid",
"sectionId": "uuid",
"calendarEventId": "...",
"createdAt": "2025-01-23T16:45:00"
}
end note
return 201 Created\nTodoResponse
deactivate Controller
@enduml

View File

@ -0,0 +1,140 @@
@startuml meeting-대시보드조회
!theme mono
title Meeting Service - 대시보드조회 내부 시퀀스
participant "DashboardController" as Controller
participant "DashboardService" as Service
participant "MeetingRepository" as MeetingRepo
participant "TodoRepository" as TodoRepo
participant "MinutesRepository" as MinutesRepo
database "Redis Cache<<E>>" as Cache
database "Meeting DB<<E>>" as DB
[-> Controller: GET /dashboard
activate Controller
note over Controller
사용자 정보는 헤더에서 추출
(userId, userName, email)
end note
Controller -> Service: getDashboardData(userId)
activate Service
' 캐시 조회
Service -> Cache: GET dashboard:{userId}
activate Cache
Cache --> Service: 캐시 조회 결과
deactivate Cache
alt Cache Hit
Service --> Service: 캐시 데이터 반환
else Cache Miss
' 예정된 회의 조회
Service -> MeetingRepo: findUpcomingMeetings(userId)
activate MeetingRepo
MeetingRepo -> DB: SELECT meetings\nWHERE userId = ? AND status = 'SCHEDULED'\nAND startTime > NOW()\nORDER BY startTime LIMIT 3
activate DB
DB --> MeetingRepo: 예정된 회의 목록
deactivate DB
MeetingRepo --> Service: List<Meeting>
deactivate MeetingRepo
' 진행 중 Todo 조회
Service -> TodoRepo: findActiveTodos(userId)
activate TodoRepo
TodoRepo -> DB: SELECT todos\nWHERE assignee = ? AND status = 'IN_PROGRESS'\nORDER BY dueDate LIMIT 3
activate DB
DB --> TodoRepo: 진행 중 Todo 목록
deactivate DB
TodoRepo --> Service: List<Todo>
deactivate TodoRepo
' 최근 회의록 조회
Service -> MinutesRepo: findRecentMinutes(userId)
activate MinutesRepo
MinutesRepo -> DB: SELECT minutes\nWHERE creatorId = ?\nORDER BY createdAt DESC LIMIT 3
activate DB
DB --> MinutesRepo: 최근 회의록 목록
deactivate DB
MinutesRepo --> Service: List<Minutes>
deactivate MinutesRepo
' 공유받은 회의록 조회
Service -> MinutesRepo: findSharedMinutes(userId)
activate MinutesRepo
MinutesRepo -> DB: SELECT minutes m JOIN shared_minutes sm\nON m.id = sm.minutesId\nWHERE sm.sharedWith = ?\nORDER BY sm.sharedAt DESC LIMIT 3
activate DB
DB --> MinutesRepo: 공유받은 회의록 목록
deactivate DB
MinutesRepo --> Service: List<Minutes>
deactivate MinutesRepo
' 통계 정보 조회
Service -> MeetingRepo: countUpcomingMeetings(userId)
activate MeetingRepo
MeetingRepo -> DB: SELECT COUNT(*)\nFROM meetings\nWHERE userId = ? AND status = 'SCHEDULED'
activate DB
DB --> MeetingRepo: 예정된 회의 개수
deactivate DB
MeetingRepo --> Service: int count
deactivate MeetingRepo
Service -> TodoRepo: countActiveTodos(userId)
activate TodoRepo
TodoRepo -> DB: SELECT COUNT(*)\nFROM todos\nWHERE assignee = ? AND status = 'IN_PROGRESS'
activate DB
DB --> TodoRepo: 진행 중 Todo 개수
deactivate DB
TodoRepo --> Service: int count
deactivate TodoRepo
Service -> TodoRepo: calculateTodoCompletionRate(userId)
activate TodoRepo
TodoRepo -> DB: SELECT\n (COUNT(CASE WHEN status='COMPLETED' THEN 1 END) * 100.0 /\n COUNT(*)) as rate\nFROM todos WHERE assignee = ?
activate DB
DB --> TodoRepo: Todo 완료율
deactivate DB
TodoRepo --> Service: double rate
deactivate TodoRepo
note over Service
비즈니스 로직:
- 데이터 조합 및 정제
- DTO 변환
- 통계 계산
end note
Service -> Service: 대시보드 데이터 조합
' 캐시 저장
Service -> Cache: SET dashboard:{userId}\n(TTL: 5분)
activate Cache
Cache --> Service: 캐시 저장 완료
deactivate Cache
end
Service --> Controller: DashboardResponse
deactivate Service
note over Controller
응답 데이터 구조:
{
"upcomingMeetings": [...],
"activeTodos": [...],
"recentMinutes": [...],
"sharedMinutes": [...],
"statistics": {
"upcomingMeetingsCount": n,
"activeTodosCount": n,
"todoCompletionRate": n
}
}
end note
return 200 OK\nDashboardResponse
deactivate Controller
@enduml

View File

@ -0,0 +1,153 @@
@startuml meeting-회의록공유
!theme mono
title Meeting Service - 회의록공유 내부 시퀀스
participant "ShareController" as Controller
participant "ShareService" as Service
participant "MinutesRepository" as MinutesRepo
participant "ShareRepository" as ShareRepo
participant "CalendarService" as CalendarService
database "Meeting DB<<E>>" as DB
database "Redis Cache<<E>>" as Cache
queue "Azure Event Hubs<<E>>" as EventHub
[-> Controller: POST /minutes/{minutesId}/share
activate Controller
note over Controller
경로 변수: minutesId
요청 데이터:
{
"sharedWith": ["user1@example.com", "user2@example.com"],
"permission": "READ_ONLY" | "COMMENT" | "EDIT",
"shareMethod": "EMAIL" | "LINK",
"linkOptions": {
"expiresIn": 7, // days
"requirePassword": true,
"password": "..."
}
}
사용자 정보: userId, userName, email
end note
Controller -> Controller: 입력 검증\n- sharedWith 최소 1명\n- permission 유효성
Controller -> Service: shareMinutes(minutesId, request, userId)
activate Service
' 회의록 정보 조회
Service -> MinutesRepo: findById(minutesId)
activate MinutesRepo
MinutesRepo -> DB: SELECT * FROM minutes WHERE id = ?
activate DB
DB --> MinutesRepo: 회의록 정보
deactivate DB
MinutesRepo --> Service: Minutes
deactivate MinutesRepo
note over Service
비즈니스 규칙 검증:
- 회의록 존재 확인
- 공유 권한 확인 (생성자만)
- 회의록 상태 확인 (FINALIZED만 공유 가능)
end note
Service -> Service: 회의록 존재 확인
Service -> Service: 공유 권한 검증\n(생성자만 가능)
Service -> Service: 회의록 상태 확인
alt 회의록이 확정되지 않음
Service --> Controller: 400 Bad Request\n확정된 회의록만 공유 가능
return 400 Bad Request
else 공유 가능
' 공유 링크 생성
Service -> Service: 고유 공유 링크 생성\n(UUID 기반 토큰)
Service -> ShareRepo: createShareLink(minutesId, token, options)
activate ShareRepo
ShareRepo -> DB: INSERT INTO share_links\n(minutesId, token, permission,\nexpiresAt, requirePassword, passwordHash,\ncreatedBy, createdAt)
activate DB
DB --> ShareRepo: 링크 생성 완료
deactivate DB
ShareRepo --> Service: ShareLink
deactivate ShareRepo
' 공유 대상 저장
loop 각 공유 대상마다
Service -> ShareRepo: addSharedUser(minutesId, userEmail, permission)
activate ShareRepo
ShareRepo -> DB: INSERT INTO shared_minutes\n(minutesId, sharedWith, permission,\nsharedBy, sharedAt)
activate DB
DB --> ShareRepo: 공유 정보 저장 완료
deactivate DB
ShareRepo --> Service: 저장 성공
deactivate ShareRepo
end
' 회의록에서 다음 회의 일정 언급 확인
Service -> Service: 다음 회의 일정 추출\n(회의록 내용 분석)
alt 다음 회의 일정 언급됨
Service -> CalendarService: createCalendarEvent(meetingInfo)
activate CalendarService
note over CalendarService
캘린더 이벤트 생성:
- 제목: 다음 회의 제목
- 일시: 추출된 일시
- 참석자: 공유 대상자
end note
CalendarService -> CalendarService: 캘린더 이벤트 생성
CalendarService --> Service: 이벤트 ID
deactivate CalendarService
end
' 캐시 무효화
Service -> Cache: DELETE minutes:detail:{minutesId}
activate Cache
Cache --> Service: 삭제 완료
deactivate Cache
note over Service
비동기 이벤트 발행:
- 공유 대상자에게 이메일 발송
- 공유 알림 발송
- 캘린더 초대 발송 (일정 있는 경우)
end note
' 이벤트 발행
Service -> EventHub: publish(MinutesShared)\n{\n minutesId, shareLink,\n sharedWith, permission,\n sharedBy, nextMeetingEvent\n}
activate EventHub
EventHub --> Service: 발행 완료
deactivate EventHub
Service --> Controller: ShareResponse
deactivate Service
note over Controller
응답 데이터:
{
"minutesId": "uuid",
"shareLink": "https://.../share/{token}",
"sharedWith": [...],
"permission": "READ_ONLY",
"expiresAt": "2025-01-30T00:00:00",
"sharedAt": "2025-01-23T16:30:00",
"nextMeetingEvent": {
"eventId": "...",
"title": "...",
"startTime": "..."
}
}
end note
return 200 OK\nShareResponse
end
deactivate Controller
@enduml

View File

@ -0,0 +1,182 @@
@startuml meeting-회의록상세조회
!theme mono
title Meeting Service - 회의록상세조회 내부 시퀀스
participant "MinutesController" as Controller
participant "MinutesService" as Service
participant "MinutesRepository" as MinutesRepo
participant "SectionRepository" as SectionRepo
participant "RelatedMinutesService" as RelatedService
database "Meeting DB<<E>>" as DB
database "Redis Cache<<E>>" as Cache
[-> Controller: GET /minutes/{minutesId}
activate Controller
note over Controller
경로 변수: minutesId
사용자 정보: userId, userName, email
end note
Controller -> Controller: minutesId 유효성 검증
Controller -> Service: getMinutesDetail(minutesId, userId)
activate Service
' 캐시 조회
Service -> Cache: GET minutes:detail:{minutesId}
activate Cache
Cache --> Service: 캐시 조회 결과
deactivate Cache
alt Cache Hit
Service -> Service: 권한 확인\n(캐시 데이터에서)
alt 권한 없음
Service --> Controller: 403 Forbidden\n조회 권한 없음
return 403 Forbidden
else 권한 있음
Service --> Controller: MinutesDetailResponse\n(캐시 데이터)
return 200 OK
end
else Cache Miss
' 회의록 기본 정보 조회
Service -> MinutesRepo: findByIdWithMeeting(minutesId)
activate MinutesRepo
MinutesRepo -> DB: SELECT m.*, mt.*\nFROM minutes m\nJOIN meetings mt ON m.meetingId = mt.id\nWHERE m.id = ?
activate DB
DB --> MinutesRepo: 회의록 및 회의 정보
deactivate DB
MinutesRepo --> Service: Minutes + Meeting
deactivate MinutesRepo
note over Service
비즈니스 규칙 검증:
- 회의록 존재 확인
- 조회 권한 확인
* 생성자
* 참석자
* 공유받은 사용자
end note
Service -> Service: 회의록 존재 확인
' 권한 확인
Service -> MinutesRepo: hasAccessPermission(minutesId, userId)
activate MinutesRepo
MinutesRepo -> DB: SELECT COUNT(*) FROM (\n SELECT 1 FROM minutes WHERE id = ? AND creatorId = ?\n UNION\n SELECT 1 FROM participants p JOIN meetings m\n ON p.meetingId = m.id JOIN minutes mi\n ON mi.meetingId = m.id\n WHERE mi.id = ? AND p.userId = ?\n UNION\n SELECT 1 FROM shared_minutes\n WHERE minutesId = ? AND sharedWith = ?\n) AS access
activate DB
DB --> MinutesRepo: 권한 확인 결과
deactivate DB
MinutesRepo --> Service: boolean hasAccess
deactivate MinutesRepo
alt 권한 없음
Service --> Controller: 403 Forbidden\n조회 권한 없음
return 403 Forbidden
else 권한 있음
' 참석자 목록 조회
Service -> MinutesRepo: findParticipants(minutesId)
activate MinutesRepo
MinutesRepo -> DB: SELECT p.*, u.name, u.email\nFROM participants p\nJOIN meetings m ON p.meetingId = m.id\nJOIN minutes mi ON mi.meetingId = m.id\nJOIN users u ON p.userId = u.id\nWHERE mi.id = ?
activate DB
DB --> MinutesRepo: 참석자 목록
deactivate DB
MinutesRepo --> Service: List<Participant>
deactivate MinutesRepo
' 섹션별 상세 내용 조회
Service -> SectionRepo: findSectionsByMinutesId(minutesId)
activate SectionRepo
SectionRepo -> DB: SELECT * FROM minutes_sections\nWHERE minutesId = ?\nORDER BY sectionOrder
activate DB
DB --> SectionRepo: 섹션 목록
deactivate DB
SectionRepo --> Service: List<Section>
deactivate SectionRepo
' AI 요약 정보 조회
Service -> SectionRepo: findAISummaries(minutesId)
activate SectionRepo
SectionRepo -> DB: SELECT * FROM ai_summaries\nWHERE minutesId = ?\nORDER BY sectionId
activate DB
DB --> SectionRepo: AI 요약 목록
deactivate DB
SectionRepo --> Service: List<AISummary>
deactivate SectionRepo
' 관련 회의록 조회
Service -> RelatedService: findRelatedMinutes(minutesId, limit: 3)
activate RelatedService
note over RelatedService
관련 회의록 검색 로직:
- 벡터 유사도 기반 검색 (AI 서비스 호출)
- 관련도 70% 이상
- 최대 3개
end note
RelatedService -> DB: SELECT * FROM related_minutes\nWHERE minutesId = ?\nAND similarityScore >= 0.7\nORDER BY similarityScore DESC\nLIMIT 3
activate DB
DB --> RelatedService: 관련 회의록 목록
deactivate DB
RelatedService --> Service: List<RelatedMinutes>
deactivate RelatedService
' 조회 이력 기록
Service -> MinutesRepo: recordViewHistory(minutesId, userId)
activate MinutesRepo
MinutesRepo -> DB: INSERT INTO view_history\n(minutesId, userId, viewedAt)\nVALUES (?, ?, NOW())
activate DB
DB --> MinutesRepo: 기록 완료
deactivate DB
MinutesRepo --> Service: 기록 성공
deactivate MinutesRepo
note over Service
데이터 조합:
- 기본 정보
- 참석자 목록
- 섹션별 내용
- AI 요약
- 관련 회의록
- 권한 정보 (수정 가능 여부)
end note
Service -> Service: 상세 응답 DTO 구성
' 캐시 저장
Service -> Cache: SET minutes:detail:{minutesId}\n(TTL: 10분)
activate Cache
Cache --> Service: 캐싱 완료
deactivate Cache
Service --> Controller: MinutesDetailResponse
deactivate Service
note over Controller
응답 데이터:
{
"minutesId": "uuid",
"meeting": {...},
"participants": [...],
"sections": [...],
"aiSummaries": [...],
"relatedMinutes": [...],
"permissions": {
"canEdit": true/false,
"canShare": true/false
},
"status": "FINALIZED",
"version": "v1.0"
}
end note
return 200 OK\nMinutesDetailResponse
end
end
deactivate Controller
@enduml

View File

@ -0,0 +1,191 @@
@startuml meeting-회의록수정
!theme mono
title Meeting Service - 회의록수정 내부 시퀀스
participant "MinutesController" as Controller
participant "MinutesService" as Service
participant "MinutesRepository" as MinutesRepo
participant "SectionRepository" as SectionRepo
participant "VersionService" as VersionService
participant "CollaborationService" as CollabService
database "Meeting DB<<E>>" as DB
database "Redis Cache<<E>>" as Cache
participant "WebSocket<<E>>" as WebSocket
[-> Controller: PATCH /minutes/{minutesId}
activate Controller
note over Controller
경로 변수: minutesId
요청 데이터:
{
"title": "수정된 제목",
"sections": [
{
"sectionId": "uuid",
"content": "수정된 내용",
"aiSummary": "수정된 요약"
}
]
}
사용자 정보: userId, userName, email
end note
Controller -> Controller: 입력 검증
Controller -> Service: updateMinutes(minutesId, request, userId)
activate Service
' 회의록 정보 조회
Service -> MinutesRepo: findById(minutesId)
activate MinutesRepo
MinutesRepo -> DB: SELECT * FROM minutes WHERE id = ?
activate DB
DB --> MinutesRepo: 회의록 정보
deactivate DB
MinutesRepo --> Service: Minutes
deactivate MinutesRepo
note over Service
비즈니스 규칙 검증:
- 회의록 존재 확인
- 수정 권한 확인 (생성자만)
- 잠긴 섹션 수정 시도 검증
- 확정된 회의록은 DRAFT로 상태 변경
end note
Service -> Service: 회의록 존재 확인
Service -> Service: 수정 권한 검증\n(생성자만 가능)
alt 권한 없음
Service --> Controller: 403 Forbidden\n수정 권한 없음
return 403 Forbidden
else 권한 있음
' 잠긴 섹션 확인
Service -> SectionRepo: findLockedSections(minutesId)
activate SectionRepo
SectionRepo -> DB: SELECT * FROM minutes_sections\nWHERE minutesId = ?\nAND isLocked = true
activate DB
DB --> SectionRepo: 잠긴 섹션 목록
deactivate DB
SectionRepo --> Service: List<Section>
deactivate SectionRepo
Service -> Service: 잠긴 섹션 수정 여부 검증
alt 잠긴 섹션 수정 시도
Service --> Controller: 400 Bad Request\n잠긴 섹션은 수정 불가
return 400 Bad Request
else 수정 가능
' 수정 이력 저장 (버전 관리)
Service -> VersionService: createModificationHistory(minutesId, userId, changes)
activate VersionService
VersionService -> DB: INSERT INTO modification_history\n(minutesId, modifiedBy, modifiedAt,\nchangeType, oldValue, newValue)
activate DB
DB --> VersionService: 이력 저장 완료
deactivate DB
VersionService --> Service: 저장 성공
deactivate VersionService
' 회의록 제목 수정
alt 제목 변경됨
Service -> MinutesRepo: updateTitle(minutesId, newTitle)
activate MinutesRepo
MinutesRepo -> DB: UPDATE minutes\nSET title = ?,\n updatedAt = NOW(),\n updatedBy = ?\nWHERE id = ?
activate DB
DB --> MinutesRepo: 업데이트 완료
deactivate DB
MinutesRepo --> Service: 업데이트 성공
deactivate MinutesRepo
end
' 섹션별 내용 수정
loop 각 섹션마다
Service -> SectionRepo: updateSection(sectionId, content, aiSummary)
activate SectionRepo
SectionRepo -> DB: UPDATE minutes_sections\nSET content = ?,\n aiSummary = ?,\n updatedAt = NOW(),\n updatedBy = ?\nWHERE id = ?
activate DB
DB --> SectionRepo: 업데이트 완료
deactivate DB
SectionRepo --> Service: 업데이트 성공
deactivate SectionRepo
end
' 회의록 상태 변경 (확정 → 작성중)
alt 확정된 회의록 수정
Service -> MinutesRepo: updateStatus(minutesId, "DRAFT")
activate MinutesRepo
MinutesRepo -> DB: UPDATE minutes\nSET status = 'DRAFT'\nWHERE id = ?\nAND status = 'FINALIZED'
activate DB
DB --> MinutesRepo: 업데이트 완료
deactivate DB
MinutesRepo --> Service: 상태 변경 완료
deactivate MinutesRepo
end
' 캐시 무효화
Service -> Cache: DELETE minutes:detail:{minutesId}
activate Cache
Cache --> Service: 삭제 완료
deactivate Cache
Service -> Cache: DELETE minutes:info:{minutesId}
activate Cache
Cache --> Service: 삭제 완료
deactivate Cache
note over Service
실시간 협업:
- WebSocket을 통해 참석자에게 수정 사항 전송
- 수정 델타만 전송 (전체 내용 아님)
- 수정자 정보 포함
end note
' 실시간 동기화
Service -> CollabService: broadcastUpdate(minutesId, changes, userId)
activate CollabService
note over CollabService
WebSocket 메시지 형식:
{
"type": "MINUTES_UPDATED",
"minutesId": "uuid",
"changes": [...],
"modifiedBy": {
"userId": "...",
"userName": "..."
},
"timestamp": "..."
}
end note
CollabService -> WebSocket: broadcast to room:{minutesId}
activate WebSocket
WebSocket --> CollabService: 전송 완료
deactivate WebSocket
CollabService --> Service: 동기화 완료
deactivate CollabService
Service --> Controller: MinutesUpdateResponse
deactivate Service
note over Controller
응답 데이터:
{
"minutesId": "uuid",
"status": "DRAFT",
"updatedAt": "2025-01-23T16:00:00",
"updatedBy": "userId",
"version": "v1.1"
}
end note
return 200 OK\nMinutesUpdateResponse
end
end
deactivate Controller
@enduml

View File

@ -0,0 +1,148 @@
@startuml meeting-회의록확정
!theme mono
title Meeting Service - 회의록확정 내부 시퀀스
participant "MinutesController" as Controller
participant "MinutesService" as Service
participant "ValidationService" as ValidationService
participant "MinutesRepository" as MinutesRepo
participant "TodoService" as TodoService
database "Meeting DB<<E>>" as DB
database "Redis Cache<<E>>" as Cache
queue "Azure Event Hubs<<E>>" as EventHub
[-> Controller: POST /minutes/{minutesId}/finalize
activate Controller
note over Controller
경로 변수: minutesId
사용자 정보: userId, userName, email
end note
Controller -> Controller: minutesId 유효성 검증
Controller -> Service: finalizeMinutes(minutesId, userId)
activate Service
' 회의록 정보 조회
Service -> MinutesRepo: findById(minutesId)
activate MinutesRepo
MinutesRepo -> DB: SELECT * FROM minutes WHERE id = ?
activate DB
DB --> MinutesRepo: 회의록 정보
deactivate DB
MinutesRepo --> Service: Minutes
deactivate MinutesRepo
note over Service
비즈니스 규칙 검증:
- 회의록이 존재하는지 확인
- 확정 권한 확인 (생성자만)
- 회의록 상태 확인 (DRAFT만 확정 가능)
- 필수 항목 검증
end note
Service -> Service: 권한 검증\n(생성자만 확정 가능)
Service -> Service: 회의록 상태 확인
alt 회의록이 이미 확정됨
Service --> Controller: 409 Conflict\n이미 확정된 회의록
return 409 Conflict
else 확정 가능
note over Service
필수 항목 검증:
- 회의 제목
- 참석자 목록
- 주요 논의 내용
- 결정 사항
end note
' 필수 항목 검증
Service -> ValidationService: validateRequiredFields(minutes)
activate ValidationService
ValidationService -> ValidationService: 제목 존재 확인
ValidationService -> ValidationService: 참석자 목록 확인
ValidationService -> ValidationService: 논의 내용 확인
ValidationService -> ValidationService: 결정 사항 확인
alt 필수 항목 누락
ValidationService --> Service: ValidationException\n누락된 항목 목록
Service --> Controller: 400 Bad Request\n필수 항목 누락
return 400 Bad Request
else 검증 통과
ValidationService --> Service: 검증 성공
deactivate ValidationService
' 버전 생성
Service -> Service: 확정 버전 번호 생성\n(v1.0)
' 회의록 상태 업데이트
Service -> MinutesRepo: finalize(minutesId, version)
activate MinutesRepo
MinutesRepo -> DB: UPDATE minutes\nSET status = 'FINALIZED',\n version = ?,\n finalizedAt = NOW(),\n finalizedBy = ?\nWHERE id = ?
activate DB
DB --> MinutesRepo: 업데이트 완료
deactivate DB
MinutesRepo --> Service: 업데이트 성공
deactivate MinutesRepo
' 회의록 스냅샷 저장 (버전 관리)
Service -> MinutesRepo: saveSnapshot(minutesId, version, content)
activate MinutesRepo
MinutesRepo -> DB: INSERT INTO minutes_snapshots\n(minutesId, version, content, createdAt)
activate DB
DB --> MinutesRepo: 스냅샷 저장 완료
deactivate DB
MinutesRepo --> Service: 저장 성공
deactivate MinutesRepo
' 캐시 무효화
Service -> Cache: DELETE minutes:info:{minutesId}
activate Cache
Cache --> Service: 삭제 완료
deactivate Cache
note over Service
비동기 처리:
- AI가 Todo 항목 자동 추출
- 회의록 공유 가능 상태로 전환
- 참석자에게 확정 알림
end note
' AI Todo 추출 이벤트 발행
Service -> EventHub: publish(MinutesFinalized)\n{\n minutesId, meetingId,\n finalizedAt, content,\n participants\n}
activate EventHub
EventHub --> Service: 발행 완료
deactivate EventHub
' Todo 자동 추출 (비동기 응답 대기 없음)
note over Service
AI 서비스가 MinutesFinalized 이벤트를 구독하여
Todo 자동 추출 및 할당 수행
추출된 Todo는 별도 이벤트로 전달됨
end note
Service --> Controller: MinutesFinalizeResponse
deactivate Service
note over Controller
응답 데이터:
{
"minutesId": "uuid",
"status": "FINALIZED",
"version": "v1.0",
"finalizedAt": "2025-01-23T15:30:00",
"finalizedBy": "userId"
}
end note
return 200 OK\nMinutesFinalizeResponse
end
end
deactivate Controller
@enduml

View File

@ -0,0 +1,148 @@
@startuml meeting-회의시작
!theme mono
title Meeting Service - 회의시작 내부 시퀀스
participant "MeetingController" as Controller
participant "MeetingService" as Service
participant "SessionService" as SessionService
participant "MeetingRepository" as MeetingRepo
participant "SessionRepository" as SessionRepo
database "Meeting DB<<E>>" as DB
database "Redis Cache<<E>>" as Cache
queue "Azure Event Hubs<<E>>" as EventHub
[-> Controller: POST /meetings/{meetingId}/start
activate Controller
note over Controller
경로 변수: meetingId
사용자 정보: userId, userName, email
end note
Controller -> Controller: meetingId 유효성 검증
Controller -> Service: startMeeting(meetingId, userId)
activate Service
' 회의 정보 조회
Service -> Cache: GET meeting:info:{meetingId}
activate Cache
Cache --> Service: 캐시 조회 결과
deactivate Cache
alt Cache Miss
Service -> MeetingRepo: findById(meetingId)
activate MeetingRepo
MeetingRepo -> DB: SELECT * FROM meetings\nWHERE id = ?
activate DB
DB --> MeetingRepo: 회의 정보
deactivate DB
MeetingRepo --> Service: Meeting
deactivate MeetingRepo
Service -> Cache: SET meeting:info:{meetingId}
activate Cache
Cache --> Service: 캐싱 완료
deactivate Cache
end
note over Service
비즈니스 규칙 검증:
- 회의가 존재하는지 확인
- 회의 시작 권한 확인 (생성자만)
- 회의 상태 확인 (SCHEDULED만 시작 가능)
- 회의 시작 시간 10분 전부터 가능
end note
Service -> Service: 권한 검증\n(생성자 또는 참석자)
Service -> Service: 회의 상태 확인
alt 회의가 이미 진행 중
Service --> Controller: 409 Conflict\n이미 진행 중인 회의
return 409 Conflict
else 시작 가능
Service -> Service: 회의 세션 생성
' 세션 저장
Service -> SessionRepo: createSession(meetingId, userId)
activate SessionRepo
note over SessionRepo
세션 정보:
- sessionId (UUID)
- meetingId
- startedBy (userId)
- startedAt (현재시각)
- status: ACTIVE
end note
SessionRepo -> DB: INSERT INTO meeting_sessions\n(id, meetingId, startedBy, startedAt, status)
activate DB
DB --> SessionRepo: 세션 생성 완료
deactivate DB
SessionRepo --> Service: Session
deactivate SessionRepo
' 회의 상태 업데이트
Service -> MeetingRepo: updateStatus(meetingId, "IN_PROGRESS")
activate MeetingRepo
MeetingRepo -> DB: UPDATE meetings\nSET status = 'IN_PROGRESS',\n actualStartTime = NOW()\nWHERE id = ?
activate DB
DB --> MeetingRepo: 업데이트 완료
deactivate DB
MeetingRepo --> Service: 업데이트 성공
deactivate MeetingRepo
' 캐시 무효화
Service -> Cache: DELETE meeting:info:{meetingId}
activate Cache
Cache --> Service: 삭제 완료
deactivate Cache
' 회의록 초안 생성 (빈 회의록)
Service -> Service: 회의록 초안 생성
Service -> MeetingRepo: createMinutesDraft(meetingId, sessionId)
activate MeetingRepo
MeetingRepo -> DB: INSERT INTO minutes\n(id, meetingId, sessionId, status, createdAt)\nVALUES (?, ?, ?, 'DRAFT', NOW())
activate DB
DB --> MeetingRepo: 회의록 생성 완료
deactivate DB
MeetingRepo --> Service: Minutes
deactivate MeetingRepo
note over Service
비동기 이벤트 발행:
- STT 서비스에 녹음 시작 요청
- 참석자에게 회의 시작 알림
- 실시간 협업 WebSocket 준비
end note
' 이벤트 발행
Service -> EventHub: publish(MeetingStarted)\n{\n meetingId, sessionId,\n startedAt, participants\n}
activate EventHub
EventHub --> Service: 발행 완료
deactivate EventHub
Service --> Controller: SessionResponse
deactivate Service
note over Controller
응답 데이터:
{
"sessionId": "uuid",
"meetingId": "uuid",
"status": "IN_PROGRESS",
"startedAt": "2025-01-23T14:00:00",
"minutesId": "uuid"
}
end note
return 201 Created\nSessionResponse
end
deactivate Controller
@enduml

View File

@ -0,0 +1,123 @@
@startuml meeting-회의예약
!theme mono
title Meeting Service - 회의예약 내부 시퀀스
participant "MeetingController" as Controller
participant "MeetingService" as Service
participant "MeetingRepository" as Repo
participant "ParticipantRepository" as ParticipantRepo
database "Meeting DB<<E>>" as DB
database "Redis Cache<<E>>" as Cache
queue "Azure Event Hubs<<E>>" as EventHub
[-> Controller: POST /meetings
activate Controller
note over Controller
요청 데이터:
{
"title": "회의 제목",
"startTime": "2025-01-23T14:00:00",
"endTime": "2025-01-23T15:00:00",
"location": "회의실 A",
"participants": ["user1@example.com", ...]
}
사용자 정보: userId, userName, email
end note
Controller -> Controller: 입력 검증\n- 제목 필수 (최대 100자)\n- 날짜/시간 필수\n- 참석자 최소 1명
Controller -> Service: createMeeting(request, userId)
activate Service
note over Service
비즈니스 규칙 검증:
- 회의 시간 유효성 (시작 < 종료)
- 중복 회의 체크
- 참석자 유효성 검증
end note
Service -> Service: 회의 시간 유효성 검사
Service -> Repo: checkConflictingMeetings(userId, startTime, endTime)
activate Repo
Repo -> DB: SELECT COUNT(*) FROM meetings\nWHERE creatorId = ?\nAND status = 'SCHEDULED'\nAND (startTime <= ? AND endTime >= ?)
activate DB
DB --> Repo: 중복 회의 개수
deactivate DB
Repo --> Service: int count
deactivate Repo
alt 중복 회의 존재
Service --> Controller: 409 Conflict\n중복된 회의 시간
return 409 Conflict
else 중복 없음
Service -> Service: Meeting 엔티티 생성\n- 회의 ID 생성\n- 상태: SCHEDULED\n- 생성자 정보 설정
' 회의 정보 저장
Service -> Repo: save(meeting)
activate Repo
Repo -> DB: INSERT INTO meetings\n(id, title, startTime, endTime,\nlocation, status, creatorId, createdAt)
activate DB
DB --> Repo: 회의 저장 완료
deactivate DB
Repo --> Service: Meeting
deactivate Repo
' 참석자 저장
Service -> ParticipantRepo: saveAll(participants)
activate ParticipantRepo
ParticipantRepo -> DB: INSERT INTO participants\n(meetingId, email, role, status)
activate DB
DB --> ParticipantRepo: 참석자 저장 완료
deactivate DB
ParticipantRepo --> Service: List<Participant>
deactivate ParticipantRepo
' 캐시 저장
Service -> Cache: SET meeting:info:{meetingId}\n(TTL: 10분)
activate Cache
Cache --> Service: 캐싱 완료
deactivate Cache
Service -> Cache: SET meeting:participants:{meetingId}\n(TTL: 10분)
activate Cache
Cache --> Service: 캐싱 완료
deactivate Cache
note over Service
비동기 이벤트 발행:
- 초대 이메일 발송
- 캘린더 등록
- 리마인더 스케줄링
end note
' 이벤트 발행
Service -> EventHub: publish(MeetingCreated)\n{\n meetingId, title, startTime,\n participants, creatorInfo\n}
activate EventHub
EventHub --> Service: 발행 완료
deactivate EventHub
Service --> Controller: MeetingResponse
deactivate Service
note over Controller
응답 데이터:
{
"meetingId": "uuid",
"title": "회의 제목",
"startTime": "...",
"endTime": "...",
"location": "...",
"participants": [...],
"status": "SCHEDULED"
}
end note
return 201 Created\nMeetingResponse
end
deactivate Controller
@enduml

View File

@ -0,0 +1,162 @@
@startuml meeting-회의종료
!theme mono
title Meeting Service - 회의종료 내부 시퀀스
participant "MeetingController" as Controller
participant "MeetingService" as Service
participant "SessionService" as SessionService
participant "MeetingRepository" as MeetingRepo
participant "SessionRepository" as SessionRepo
participant "StatisticsService" as StatService
database "Meeting DB<<E>>" as DB
database "Redis Cache<<E>>" as Cache
queue "Azure Event Hubs<<E>>" as EventHub
[-> Controller: POST /meetings/{meetingId}/end
activate Controller
note over Controller
경로 변수: meetingId
사용자 정보: userId, userName, email
end note
Controller -> Controller: meetingId 유효성 검증
Controller -> Service: endMeeting(meetingId, userId)
activate Service
' 회의 정보 조회
Service -> MeetingRepo: findById(meetingId)
activate MeetingRepo
MeetingRepo -> DB: SELECT * FROM meetings WHERE id = ?
activate DB
DB --> MeetingRepo: 회의 정보
deactivate DB
MeetingRepo --> Service: Meeting
deactivate MeetingRepo
note over Service
비즈니스 규칙 검증:
- 회의가 존재하는지 확인
- 회의 종료 권한 확인 (생성자만)
- 회의 상태 확인 (IN_PROGRESS만 종료 가능)
end note
Service -> Service: 권한 검증\n(생성자만 종료 가능)
Service -> Service: 회의 상태 확인
alt 회의가 진행 중이 아님
Service --> Controller: 409 Conflict\n진행 중인 회의가 아님
return 409 Conflict
else 종료 가능
' 세션 정보 조회
Service -> SessionRepo: findActiveSession(meetingId)
activate SessionRepo
SessionRepo -> DB: SELECT * FROM meeting_sessions\nWHERE meetingId = ?\nAND status = 'ACTIVE'
activate DB
DB --> SessionRepo: 세션 정보
deactivate DB
SessionRepo --> Service: Session
deactivate SessionRepo
' 세션 종료
Service -> SessionRepo: endSession(sessionId)
activate SessionRepo
SessionRepo -> DB: UPDATE meeting_sessions\nSET status = 'ENDED',\n endedAt = NOW()\nWHERE id = ?
activate DB
DB --> SessionRepo: 업데이트 완료
deactivate DB
SessionRepo --> Service: 종료 성공
deactivate SessionRepo
' 회의 상태 업데이트
Service -> MeetingRepo: updateStatus(meetingId, "ENDED")
activate MeetingRepo
MeetingRepo -> DB: UPDATE meetings\nSET status = 'ENDED',\n actualEndTime = NOW()\nWHERE id = ?
activate DB
DB --> MeetingRepo: 업데이트 완료
deactivate DB
MeetingRepo --> Service: 업데이트 성공
deactivate MeetingRepo
note over Service
회의 통계 생성:
- 회의 총 시간
- 참석자 수
- 발언 횟수 (STT 데이터 기반)
- 주요 키워드
end note
' 회의 통계 생성
Service -> StatService: generateMeetingStatistics(sessionId)
activate StatService
StatService -> DB: SELECT\n COUNT(DISTINCT speakerId) as speakerCount,\n COUNT(*) as utteranceCount,\n TIMESTAMPDIFF(MINUTE, startedAt, endedAt) as duration\nFROM transcripts WHERE sessionId = ?
activate DB
DB --> StatService: 통계 데이터
deactivate DB
StatService -> DB: INSERT INTO meeting_statistics\n(meetingId, sessionId, duration,\nparticipantCount, utteranceCount, createdAt)
activate DB
DB --> StatService: 통계 저장 완료
deactivate DB
StatService --> Service: Statistics
deactivate StatService
' 회의록 상태 업데이트
Service -> MeetingRepo: updateMinutesStatus(meetingId, "DRAFT")
activate MeetingRepo
MeetingRepo -> DB: UPDATE minutes\nSET status = 'DRAFT',\n endedAt = NOW()\nWHERE meetingId = ?\nAND status = 'IN_PROGRESS'
activate DB
DB --> MeetingRepo: 업데이트 완료
deactivate DB
MeetingRepo --> Service: 업데이트 성공
deactivate MeetingRepo
' 캐시 무효화
Service -> Cache: DELETE meeting:info:{meetingId}
activate Cache
Cache --> Service: 삭제 완료
deactivate Cache
note over Service
비동기 이벤트 발행:
- STT 서비스에 녹음 중지 요청
- AI 서비스에 최종 회의록 생성 요청
- 참석자에게 회의 종료 알림
end note
' 이벤트 발행
Service -> EventHub: publish(MeetingEnded)\n{\n meetingId, sessionId,\n endedAt, statistics,\n minutesId\n}
activate EventHub
EventHub --> Service: 발행 완료
deactivate EventHub
Service --> Controller: MeetingEndResponse
deactivate Service
note over Controller
응답 데이터:
{
"meetingId": "uuid",
"sessionId": "uuid",
"status": "ENDED",
"endedAt": "2025-01-23T15:00:00",
"statistics": {
"duration": 60,
"participantCount": 5,
"utteranceCount": 120
},
"minutesId": "uuid"
}
end note
return 200 OK\nMeetingEndResponse
end
deactivate Controller
@enduml

View File

@ -0,0 +1,147 @@
@startuml
!theme mono
title Notification Service - Todo알림발송 내부 시퀀스
participant "NotificationController" as Controller
participant "NotificationService" as Service
participant "EmailTemplateService" as TemplateService
participant "NotificationRepository" as Repository
participant "EmailClient" as EmailClient
database "Notification DB" as DB
queue "Azure Event Hubs<<E>>" as EventHub
participant "Email Service<<E>>" as EmailService
== TodoAssigned 이벤트 수신 ==
EventHub -> Controller: TodoAssigned 이벤트 수신
activate Controller
note right
이벤트 데이터:
- todoId
- meetingId
- 담당자 (userId, userName, email)
- Todo 내용
- 마감일
- 우선순위
- 회의록 링크
end note
Controller -> Service: sendTodoNotification(todoId, todoData)
activate Service
== 알림 기록 생성 ==
Service -> Repository: createNotification(todoId, "TODO_ASSIGNED", assignee)
activate Repository
Repository -> DB: INSERT INTO notifications\n(notification_id, todo_id,\ntype='TODO_ASSIGNED',\nstatus='PENDING',\nrecipients,\ncreated_at)
activate DB
DB --> Repository: notificationId 반환
deactivate DB
Repository --> Service: NotificationEntity 반환
deactivate Repository
== 이메일 템플릿 생성 ==
Service -> TemplateService: generateTodoEmail(todoData)
activate TemplateService
TemplateService -> TemplateService: 템플릿 로드
note right
템플릿 정보:
- 제목: "[TODO 할당] {Todo 내용}"
- 내용: Todo 상세 + 회의록 링크
- 우선순위 뱃지 표시
end note
TemplateService -> TemplateService: 데이터 바인딩
note right
바인딩 데이터:
- Todo 내용
- 마감일
- 우선순위
- 회의 제목
- 회의록 링크 (해당 섹션)
- Todo 관리 페이지 링크
end note
TemplateService --> Service: EmailContent 반환
deactivate TemplateService
== 이메일 발송 ==
Service -> EmailClient: sendEmail(assignee.email, emailContent)
activate EmailClient
EmailClient -> EmailService: SMTP 이메일 발송
activate EmailService
EmailService --> EmailClient: 발송 결과
deactivate EmailService
alt 발송 성공
EmailClient --> Service: SUCCESS
Service -> Repository: updateNotificationStatus(notificationId, "SENT")
activate Repository
Repository -> DB: UPDATE notifications\nSET status='SENT',\nsent_at=NOW()\nWHERE notification_id='{notificationId}'
activate DB
DB --> Repository: 업데이트 완료
deactivate DB
Repository --> Service: 완료
deactivate Repository
else 발송 실패
EmailClient --> Service: FAILED (errorMessage)
Service -> Repository: updateNotificationStatus(notificationId, "FAILED")
activate Repository
Repository -> DB: UPDATE notifications\nSET status='FAILED',\nerror_message='{errorMessage}'\nWHERE notification_id='{notificationId}'
activate DB
DB --> Repository: 업데이트 완료
deactivate DB
Repository --> Service: 완료
deactivate Repository
Service -> Service: 재시도 큐에 추가
end
deactivate EmailClient
Service --> Controller: NotificationResponse\n(notificationId, status)
deactivate Service
Controller --> EventHub: TodoNotificationSent 이벤트 발행\n(todoId, notificationId, status)
deactivate Controller
== Todo 마감일 3일 전 리마인더 (스케줄링) ==
note over Service, EmailService
별도 스케줄링 작업:
- 마감일 3일 전 자동 리마인더
- 실행 주기: 1일 1회
- 대상: 미완료 Todo
- 템플릿: "[리마인더] Todo 마감 3일 전"
end note
note over Controller, EmailService
처리 시간:
- 알림 기록 생성: ~100ms
- 템플릿 생성: ~200ms
- 이메일 발송: ~500ms
- 총 처리 시간: ~800ms
재시도 정책:
- 최대 3회 재시도
- 재시도 간격: 5분, 15분, 30분
Todo 알림 유형:
1. 할당 알림 (즉시)
2. 마감일 3일 전 리마인더
3. 마감일 1일 전 리마인더
4. 마감일 당일 리마인더
end note
@enduml

View File

@ -0,0 +1,158 @@
@startuml
!theme mono
title Notification Service - 리마인더발송 내부 시퀀스
participant "SchedulerJob" as Scheduler
participant "ReminderService" as Service
participant "EmailTemplateService" as TemplateService
participant "NotificationRepository" as Repository
participant "EmailClient" as EmailClient
database "Notification DB" as DB
participant "Email Service<<E>>" as EmailService
== 스케줄링된 작업 실행 (회의 시작 30분 전) ==
Scheduler -> Scheduler: 30분 전 알림 대상 회의 조회
activate Scheduler
note right
조회 조건:
- 회의 시작 시간 - 30분 = NOW
- 회의 상태 = 예약됨
- 리마인더 미발송
end note
Scheduler -> Service: sendMeetingReminders(meetingList)
activate Service
loop 각 회의별
Service -> Repository: checkReminderSent(meetingId)
activate Repository
Repository -> DB: SELECT * FROM notifications\nWHERE meeting_id='{meetingId}'\nAND type='REMINDER'
activate DB
DB --> Repository: 조회 결과
deactivate DB
Repository --> Service: 발송 여부 확인
deactivate Repository
alt 이미 발송됨
Service -> Service: 스킵
else 미발송
== 리마인더 알림 생성 ==
Service -> Repository: createNotification(meetingId, "REMINDER", participants)
activate Repository
Repository -> DB: INSERT INTO notifications\n(notification_id, meeting_id,\ntype='REMINDER',\nstatus='PENDING',\nrecipients,\ncreated_at)
activate DB
DB --> Repository: notificationId 반환
deactivate DB
Repository --> Service: NotificationEntity 반환
deactivate Repository
== 이메일 템플릿 생성 ==
Service -> TemplateService: generateReminderEmail(meetingData)
activate TemplateService
TemplateService -> TemplateService: 템플릿 로드
note right
템플릿 정보:
- 제목: "[리마인더] {회의 제목} - 30분 후 시작"
- 내용: 회의 정보 + 참여 링크
- 긴급도: 높음
end note
TemplateService -> TemplateService: 데이터 바인딩
note right
바인딩 데이터:
- 회의 제목
- 시작 시간 (30분 후)
- 장소
- 회의 참여 링크
- 준비 사항 (있는 경우)
end note
TemplateService --> Service: EmailContent 반환
deactivate TemplateService
== 참석자별 이메일 발송 ==
loop 각 참석자별
Service -> EmailClient: sendEmail(recipient, emailContent)
activate EmailClient
EmailClient -> EmailService: SMTP 이메일 발송
activate EmailService
EmailService --> EmailClient: 발송 결과
deactivate EmailService
alt 발송 성공
EmailClient --> Service: SUCCESS
Service -> Repository: updateRecipientStatus(notificationId, recipient, "SENT")
activate Repository
Repository -> DB: UPDATE notification_recipients\nSET status='SENT', sent_at=NOW()
activate DB
DB --> Repository: 업데이트 완료
deactivate DB
Repository --> Service: 완료
deactivate Repository
else 발송 실패
EmailClient --> Service: FAILED
Service -> Repository: updateRecipientStatus(notificationId, recipient, "FAILED")
activate Repository
Repository -> DB: UPDATE notification_recipients\nSET status='FAILED'
activate DB
DB --> Repository: 업데이트 완료
deactivate DB
Repository --> Service: 완료
deactivate Repository
Service -> Service: 재시도 큐에 추가
end
deactivate EmailClient
end
== 알림 상태 업데이트 ==
Service -> Repository: updateNotificationStatus(notificationId, finalStatus)
activate Repository
Repository -> DB: UPDATE notifications\nSET status='{finalStatus}',\ncompleted_at=NOW(),\nsent_count={sentCount},\nfailed_count={failedCount}
activate DB
DB --> Repository: 업데이트 완료
deactivate DB
Repository --> Service: 완료
deactivate Repository
end
end
Service --> Scheduler: 전체 리마인더 발송 완료\n(총 발송 건수, 성공/실패 통계)
deactivate Service
Scheduler -> Scheduler: 다음 스케줄 대기
deactivate Scheduler
note over Scheduler, EmailService
스케줄링 정책:
- 실행 주기: 1분마다
- 대상: 30분 후 시작 회의
- 중복 발송 방지: DB 체크
처리 시간:
- 대상 회의 조회: ~200ms
- 이메일 발송 (per recipient): ~500ms
- 총 처리 시간: 회의 및 참석자 수에 비례
end note
@enduml

View File

@ -0,0 +1,145 @@
@startuml
!theme mono
title Notification Service - 초대알림발송 내부 시퀀스
participant "NotificationController" as Controller
participant "NotificationService" as Service
participant "EmailTemplateService" as TemplateService
participant "NotificationRepository" as Repository
participant "EmailClient" as EmailClient
database "Notification DB" as DB
queue "Azure Event Hubs<<E>>" as EventHub
participant "Email Service<<E>>" as EmailService
== MeetingCreated 이벤트 수신 ==
EventHub -> Controller: MeetingCreated 이벤트 수신
activate Controller
note right
이벤트 데이터:
- meetingId
- 제목
- 일시
- 장소
- 참석자 목록 (이메일)
- 생성자 정보
end note
Controller -> Service: sendMeetingInvitation(meetingId, meetingData)
activate Service
== 알림 기록 생성 ==
Service -> Repository: createNotification(meetingId, "INVITATION", participants)
activate Repository
Repository -> DB: INSERT INTO notifications\n(notification_id, meeting_id,\ntype='INVITATION',\nstatus='PENDING',\nrecipients,\ncreated_at)
activate DB
DB --> Repository: notificationId 반환
deactivate DB
Repository --> Service: NotificationEntity 반환
deactivate Repository
== 이메일 템플릿 생성 ==
Service -> TemplateService: generateInvitationEmail(meetingData)
activate TemplateService
TemplateService -> TemplateService: 템플릿 로드
note right
템플릿 정보:
- 제목: "[회의 초대] {회의 제목}"
- 내용: 회의 정보 + 참여 링크
- CTA 버튼: "회의 참석하기"
end note
TemplateService -> TemplateService: 데이터 바인딩
note right
바인딩 데이터:
- 회의 제목
- 날짜/시간
- 장소
- 생성자 이름
- 회의 참여 링크
- 캘린더 추가 링크
end note
TemplateService --> Service: EmailContent 반환\n(subject, htmlBody, plainTextBody)
deactivate TemplateService
== 참석자별 이메일 발송 (병렬 처리) ==
loop 각 참석자별
Service -> EmailClient: sendEmail(recipient, emailContent)
activate EmailClient
EmailClient -> EmailService: SMTP 이메일 발송
activate EmailService
EmailService --> EmailClient: 발송 결과
deactivate EmailService
alt 발송 성공
EmailClient --> Service: SUCCESS
Service -> Repository: updateRecipientStatus(notificationId, recipient, "SENT")
activate Repository
Repository -> DB: UPDATE notification_recipients\nSET status='SENT', sent_at=NOW()\nWHERE notification_id='{notificationId}'\nAND recipient='{email}'
activate DB
DB --> Repository: 업데이트 완료
deactivate DB
Repository --> Service: 완료
deactivate Repository
else 발송 실패
EmailClient --> Service: FAILED (errorMessage)
Service -> Repository: updateRecipientStatus(notificationId, recipient, "FAILED")
activate Repository
Repository -> DB: UPDATE notification_recipients\nSET status='FAILED',\nerror_message='{errorMessage}'\nWHERE notification_id='{notificationId}'\nAND recipient='{email}'
activate DB
DB --> Repository: 업데이트 완료
deactivate DB
Repository --> Service: 완료
deactivate Repository
Service -> Service: 재시도 큐에 추가\n(최대 3회 재시도)
end
deactivate EmailClient
end
== 전체 알림 상태 업데이트 ==
Service -> Repository: updateNotificationStatus(notificationId, finalStatus)
activate Repository
Repository -> DB: UPDATE notifications\nSET status='{finalStatus}',\ncompleted_at=NOW(),\nsent_count={sentCount},\nfailed_count={failedCount}\nWHERE notification_id='{notificationId}'
activate DB
DB --> Repository: 업데이트 완료
deactivate DB
Repository --> Service: 완료
deactivate Repository
Service --> Controller: NotificationResponse\n(notificationId, status, sentCount, failedCount)
deactivate Service
Controller --> EventHub: InvitationSent 이벤트 발행\n(meetingId, notificationId, status)
deactivate Controller
note over Controller, EmailService
처리 시간:
- 알림 기록 생성: ~100ms
- 템플릿 생성: ~200ms
- 이메일 발송 (per recipient): ~500ms
- 총 처리 시간: 참석자 수에 비례 (병렬 처리)
재시도 정책:
- 최대 3회 재시도
- 재시도 간격: 5분, 15분, 30분
end note
@enduml

View File

@ -0,0 +1,87 @@
@startuml
!theme mono
title STT Service - 음성녹음시작 내부 시퀀스
participant "RecordingController" as Controller
participant "RecordingService" as Service
participant "RecordingRepository" as Repository
participant "AzureSpeechClient" as AzureClient
database "STT DB" as DB
database "Azure Blob Storage<<E>>" as BlobStorage
queue "Azure Event Hubs<<E>>" as EventHub
== MeetingStarted 이벤트 수신 ==
EventHub -> Controller: MeetingStarted 이벤트 수신\n(meetingId, sessionId)
activate Controller
Controller -> Service: prepareRecording(meetingId, sessionId)
activate Service
Service -> Service: 녹음 세션 검증
note right
- 중복 녹음 방지 체크
- meetingId 유효성 검증
end note
Service -> Repository: createRecording(meetingId, sessionId)
activate Repository
Repository -> DB: INSERT INTO recordings\n(recording_id, meeting_id, session_id,\nstatus='READY', created_at)
activate DB
DB --> Repository: recordingId 반환
deactivate DB
Repository --> Service: RecordingEntity 반환
deactivate Repository
== Azure Speech Service 초기화 ==
Service -> AzureClient: initializeRecognizer(recordingId, sessionId)
activate AzureClient
AzureClient -> AzureClient: 음성 인식기 설정
note right
- 언어: ko-KR
- 샘플레이트: 16kHz
- 화자 식별 활성화
- 실시간 스트리밍 모드
end note
AzureClient -> BlobStorage: 녹음 파일 저장 경로 생성\n(path: recordings/{meetingId}/{sessionId}.wav)
activate BlobStorage
BlobStorage --> AzureClient: 저장 경로 URL 반환
deactivate BlobStorage
AzureClient --> Service: RecognizerConfig 반환
deactivate AzureClient
== 녹음 상태 업데이트 ==
Service -> Repository: updateRecordingStatus(recordingId, "RECORDING")
activate Repository
Repository -> DB: UPDATE recordings\nSET status='RECORDING',\nstarted_at=NOW(),\nstorage_path='{blobUrl}'\nWHERE recording_id='{recordingId}'
activate DB
DB --> Repository: 업데이트 완료
deactivate DB
Repository --> Service: 업데이트 완료
deactivate Repository
Service --> Controller: RecordingResponse(recordingId, status, storagePath)
deactivate Service
Controller --> EventHub: RecordingStarted 이벤트 발행\n(recordingId, meetingId, status)
deactivate Controller
note over Controller, EventHub
처리 시간:
- DB 녹음 생성: ~100ms
- Azure 인식기 초기화: ~500ms
- Blob 경로 생성: ~200ms
- 총 처리 시간: ~800ms
end note
@enduml

View File

@ -0,0 +1,115 @@
@startuml
!theme mono
title STT Service - 음성텍스트변환 내부 시퀀스
participant "Frontend<<E>>" as Frontend
participant "TranscriptController" as Controller
participant "TranscriptService" as Service
participant "RecordingRepository" as RecordingRepo
participant "TranscriptRepository" as TranscriptRepo
participant "AzureSpeechClient" as AzureClient
database "STT DB" as DB
database "Azure Blob Storage<<E>>" as BlobStorage
queue "Azure Event Hubs<<E>>" as EventHub
== 음성 데이터 스트리밍 수신 (5초 간격 배치) ==
Frontend -> Controller: POST /api/transcripts/stream\n(audioData, recordingId, timestamp)
activate Controller
Controller -> Service: processAudioStream(audioData, recordingId)
activate Service
== 음성 인식 처리 ==
Service -> AzureClient: recognizeAudio(audioData)
activate AzureClient
AzureClient -> AzureClient: 음성 인식 수행
note right
- 실시간 STT 처리
- 화자 식별 (Speaker Diarization)
- 타임스탬프 자동 기록
- 신뢰도 점수 계산
end note
AzureClient -> BlobStorage: 음성 파일 저장\n(chunk 단위 저장)
activate BlobStorage
BlobStorage --> AzureClient: 저장 완료
deactivate BlobStorage
AzureClient --> Service: RecognitionResult\n(text, speakerId, confidence, timestamp)
deactivate AzureClient
== 정확도 검증 및 처리 ==
Service -> Service: 정확도 점수 검증
note right
confidence >= 60%: 정상 처리
confidence < 60%: 경고 플래그 설정
end note
== 변환 결과 저장 ==
Service -> TranscriptRepo: createTranscript(recordingId, text, metadata)
activate TranscriptRepo
TranscriptRepo -> DB: INSERT INTO transcripts\n(transcript_id, recording_id, speaker_id,\ntext, confidence, timestamp, warning_flag,\ncreated_at)
activate DB
DB --> TranscriptRepo: transcriptId 반환
deactivate DB
TranscriptRepo --> Service: TranscriptEntity 반환
deactivate TranscriptRepo
== 화자 정보 업데이트 ==
Service -> RecordingRepo: updateSpeakerInfo(recordingId, speakerId)
activate RecordingRepo
RecordingRepo -> DB: INSERT INTO speakers\n(recording_id, speaker_id, segment_count)\nON CONFLICT UPDATE segment_count
activate DB
DB --> RecordingRepo: 업데이트 완료
deactivate DB
RecordingRepo --> Service: 완료
deactivate RecordingRepo
== 이벤트 발행 ==
Service -> EventHub: TranscriptReady 이벤트 발행
activate EventHub
note right of EventHub
이벤트 데이터:
- transcriptId
- recordingId
- meetingId
- text
- speakerId
- timestamp
- confidence
end note
EventHub --> Service: 발행 완료
deactivate EventHub
Service --> Controller: TranscriptResponse\n(transcriptId, text, confidence, warningFlag)
deactivate Service
Controller --> Frontend: 200 OK\n(transcriptId, text, speakerId, timestamp, confidence)
deactivate Controller
note over Frontend, EventHub
처리 시간:
- Azure STT 처리: 1-3초
- DB 저장: ~100ms
- Event 발행: ~50ms
- 총 처리 시간: 1-4초
정확도 경고:
- 60% 미만: 수동 수정 권장
- 60-80%: 검토 권장
- 80% 이상: 정상
end note
@enduml

66
tools/check-plantuml.ps1 Normal file
View File

@ -0,0 +1,66 @@
param(
[Parameter(Mandatory=$false)]
[string]$FilePath = "C:\home\workspace\tripgen\design\backend\system\azure-physical-architecture.txt"
)
Write-Host "=== PlantUML Syntax Checker ===" -ForegroundColor Cyan
Write-Host "Target file: $FilePath" -ForegroundColor Yellow
# Check if file exists
if (-not (Test-Path $FilePath)) {
Write-Host "❌ File not found: $FilePath" -ForegroundColor Red
exit 1
}
# Execute directly in PowerShell
$timestamp = Get-Date -Format 'yyyyMMddHHmmss'
$tempFile = "/tmp/puml_$timestamp.puml"
# Copy file
Write-Host "`n1. Copying file..." -ForegroundColor Gray
Write-Host " Temporary file: $tempFile"
docker cp $FilePath "plantuml:$tempFile"
if ($LASTEXITCODE -ne 0) {
Write-Host "❌ File copy failed" -ForegroundColor Red
exit 1
}
Write-Host " ✅ Copy completed" -ForegroundColor Green
# Find JAR file path
Write-Host "`n2. Looking for PlantUML JAR file..." -ForegroundColor Gray
$JAR_PATH = docker exec plantuml sh -c "find / -name 'plantuml*.jar' 2>/dev/null | head -1"
Write-Host " JAR path: $JAR_PATH"
Write-Host " ✅ JAR file confirmed" -ForegroundColor Green
# Syntax check
Write-Host "`n3. Running syntax check..." -ForegroundColor Gray
$syntaxOutput = docker exec plantuml sh -c "java -jar $JAR_PATH -checkonly $tempFile 2>&1"
if ($LASTEXITCODE -eq 0) {
Write-Host "`n✅ Syntax check passed!" -ForegroundColor Green
Write-Host " No syntax errors found in the diagram." -ForegroundColor Green
} else {
Write-Host "`n❌ Syntax errors detected!" -ForegroundColor Red
Write-Host "Error details:" -ForegroundColor Red
Write-Host $syntaxOutput -ForegroundColor Yellow
# Detailed error check
Write-Host "`nAnalyzing detailed errors..." -ForegroundColor Yellow
$detailError = docker exec plantuml sh -c "java -jar $JAR_PATH -failfast -v $tempFile 2>&1"
$errorLines = $detailError | Select-String "Error line"
if ($errorLines) {
Write-Host "`n📍 Error locations:" -ForegroundColor Magenta
$errorLines | ForEach-Object {
Write-Host " $($_.Line)" -ForegroundColor Red
}
}
}
# Clean up temporary file
Write-Host "`n4. Cleaning up temporary files..." -ForegroundColor Gray
docker exec plantuml sh -c "rm -f $tempFile" 2>$null
Write-Host " ✅ Cleanup completed" -ForegroundColor Green
Write-Host "`n=== Check completed ===" -ForegroundColor Cyan