설계서 전면 업데이트: RAG Service 문서화 및 불필요 설계 정리

## 주요 변경사항

### 1. RAG Service 독립 서비스 문서화
- RAG Service OpenAPI 명세 작성 (9개 API)
  - Terms APIs: 용어 검색, 조회, 맥락 기반 설명 (3개)
  - Documents APIs: 관련 문서 검색, 통계 (2개)
  - Minutes APIs: 회의록 벡터 검색, 연관 검색 (4개)
- 기술 스택: Python 3.11+, FastAPI, PostgreSQL+pgvector, Azure AI Search
- 성능 요구사항 명시 (용어 검색 <500ms, 설명 생성 <3초)

### 2. 불필요한 설계서 삭제 (10개 파일, 27% 감소)
- AI Service (3개): 결정사항제안, 논의사항제안, 회의록개선
- Meeting Service (5개): 실시간수정동기화, 충돌해결, Todo완료처리, Todo할당, 리마인더발송
- Notification Service (2개): Todo알림발송, 초대알림발송

### 3. API 설계서 업데이트 (v2.0 → v2.1)
- 마이크로서비스: 5개 → 6개 (RAG Service 추가)
- 총 API 개수: 47개 → 56개 (+9개)
- AI Service 주요 특징 업데이트
  - RAG Service 연동 명시
  - 삭제된 Suggestion API 제거
  - 차별화 포인트: 맥락 기반 용어 설명, 하이브리드 검색 강조
- RAG Service 섹션 완전 신규 작성
- 통계 및 문서 이력 업데이트

### 4. 내부 시퀀스 다이어그램 업데이트 (2개)
- ai-전문용어감지.puml: RAG Service API 호출 방식 명시
- ai-맥락기반용어설명.puml: RAG Service API 호출 방식 명시

### 5. 문서화
- 설계서 업데이트 요약 문서 작성 (claudedocs/설계서_업데이트_요약.md)
- 전체 변경 사항, 영향 분석, 다음 단계 작업 명시

## 영향 분석
- 설계서 파일: 37개 → 27개 (10개 삭제)
- 유저스토리 커버리지: 28개 유저스토리 100% 반영
- 서비스 아키텍처: AI Service와 RAG Service 분리로 독립성 향상

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
yabo0812
2025-10-29 17:51:57 +09:00
parent fc0c8c28d3
commit d2f396cffb
15 changed files with 1088 additions and 1686 deletions
@@ -1,250 +0,0 @@
@startuml
!theme mono
title AI Service 내부 시퀀스 - 결정사항제안
participant "SuggestionController" as Controller
participant "DecisionSuggestionService" as Service
participant "LLMClient" as LLM
participant "TranscriptRepository" as TranscriptRepo
database "Azure OpenAI<<E>>" as OpenAI
database "Redis Cache<<E>>" as Cache
database "PostgreSQL<<E>>" as DB
== 실시간 결정사항 제안 요청 ==
note over Controller
TranscriptService로부터 호출
(회의록 자동작성 프로세스 내부)
end note
Controller -> Service: suggestDecisions(meetingId, transcriptText)
activate Service
== 회의 맥락 조회 ==
Service -> TranscriptRepo: getMeetingContext(meetingId)
activate TranscriptRepo
TranscriptRepo -> DB: 회의 맥락 조회\n(회의정보, 참석자)
activate DB
DB --> TranscriptRepo: 회의 정보
deactivate DB
TranscriptRepo --> Service: meetingContext
deactivate TranscriptRepo
Service -> Cache: GET decisions:{meetingId}
activate Cache
note right
이전에 감지한 결정사항 조회
(중복 제거용)
end note
Cache --> Service: previousDecisions
deactivate Cache
== LLM 기반 결정사항 패턴 감지 ==
Service -> Service: 결정사항 감지 프롬프트 생성
note right
시스템 프롬프트:
- 역할: 결정사항 추출 전문가
- 목표: 대화에서 결정 패턴 감지
결정 패턴 예시:
- "~하기로 했습니다"
- "~로 결정했습니다"
- "~하는 것으로 합의했습니다"
- "~로 진행하겠습니다"
- "~은 이렇게 처리하겠습니다"
사용자 프롬프트:
- 회의 참석자: {participants}
- 이미 감지한 결정: {previousDecisions}
- 현재 대화 내용: {transcriptText}
지시사항:
- 위 패턴이 포함된 문장 찾기
- 결정 내용 구조화
- 결정자/참여자 식별
- 결정 카테고리 분류
- 신뢰도 점수 계산
응답 형식:
{
"decisions": [
{
"content": "결정 내용",
"category": "기술|일정|리소스|정책|기타",
"decisionMaker": "결정자 이름",
"participants": ["참여자1", "참여자2"],
"confidence": 0.0-1.0,
"extractedFrom": "원문 발췌",
"context": "결정 배경"
}
]
}
end note
Service -> LLM: detectDecisionPatterns(prompt)
activate LLM
LLM -> OpenAI: POST /chat/completions
activate OpenAI
note right
요청 파라미터:
- model: gpt-4o
- temperature: 0.2
(정확한 패턴 감지 위해 낮은 값)
- response_format: json_object
- max_tokens: 1500
end note
OpenAI -> OpenAI: 대화 텍스트 분석
note right
처리 단계:
1. 문장별로 결정 패턴 검사
2. "하기로 함" 등 키워드 탐지
3. 결정 내용 추출 및 정리
4. 발언자 식별 (누가 결정했나)
5. 결정 맥락 파악
6. 신뢰도 계산
- 명확한 결정 표현: 0.9-1.0
- 암묵적 합의: 0.7-0.9
- 추정: 0.5-0.7
7. 카테고리 분류
- 기술: 기술 스택, 아키텍처
- 일정: 마감일, 일정 조정
- 리소스: 인력, 예산
- 정책: 프로세스, 규칙
end note
OpenAI --> LLM: 결정사항 제안 목록 (JSON)
deactivate OpenAI
LLM --> Service: decisionSuggestions
deactivate LLM
== 제안 검증 및 필터링 ==
Service -> Service: 결정사항 검증
note right
검증 기준:
- 신뢰도 70% 이상만 선택
- 중복 제거 (이미 감지한 결정)
- 명확성 검증
* 주어, 목적어가 명확한가?
* 결정 내용이 구체적인가?
- 카테고리별 정렬
- 신뢰도 높은 순 정렬
end note
loop 각 제안마다
Service -> Service: 제안 메타데이터 보강
note right
추가 정보:
- 생성 시각
- 회의 진행 시점 (분)
- 원문 위치 정보
- 고유 ID (UUID)
end note
end
== 임시 캐시 저장 (선택적) ==
Service -> Cache: APPEND decisions:{meetingId}
activate Cache
note right
Redis에 임시 저장:
- Key: decisions:{meetingId}
- Value: JSON array (제안 목록)
- TTL: 2시간 (회의 시간)
- APPEND로 기존 목록에 추가
목적:
- 중복 감지용
- 재접속 시 복원용
end note
Cache --> Service: 저장 완료
deactivate Cache
== 응답 반환 ==
Service -> Service: 응답 데이터 구성
note right
프론트엔드 전달 형식:
{
"suggestions": [
{
"id": "suggestion-uuid",
"content": "결정 내용",
"category": "기술",
"decisionMaker": "김철수",
"confidence": 0.85,
"extractedFrom": "원문 발췌",
"context": "결정 배경 설명"
}
],
"totalCount": 제안 개수,
"timestamp": "생성 시각"
}
end note
Service --> Controller: 결정사항 제안 목록
deactivate Service
Controller --> Controller: 이벤트 데이터에 포함하여 반환
note right
TranscriptSummaryCreated 이벤트에
decisionSuggestions 필드로 포함
프론트엔드 처리:
- 오른쪽 "추천" 탭의 "결정사항" 섹션 표시
- "적용" 버튼 활성화
- 신뢰도 표시 (%)
- 카테고리별 아이콘 표시
- 원문 보기 링크 제공
end note
== 사용자가 제안 적용 시 ==
note over Controller
사용자가 "적용" 버튼 클릭 시:
프론트엔드에서 직접 Meeting Service 호출
PUT /api/meetings/{meetingId}/transcript
Body: {
"addDecisionSection": {
"content": "결정 내용",
"category": "기술",
"decisionMaker": "김철수"
}
}
Meeting Service에서 회의록의
"결정사항" 섹션에 항목 추가
end note
note over Controller, DB
처리 시간:
- 맥락 조회: 100-200ms
- LLM 패턴 감지: 2-3초
- 검증 및 필터링: 100-200ms
- 캐시 저장: 50-100ms
총 처리 시간: 약 2.5-3.5초
특징:
- DB 영구 저장 없음 (임시 데이터)
- Redis 캐시만 활용
* 중복 감지용
* 재접속 복원용
- 프론트엔드 메모리에서 관리
- "적용" 시에만 회의록에 반영
end note
@enduml
@@ -1,231 +0,0 @@
@startuml
!theme mono
title AI Service 내부 시퀀스 - 논의사항제안
participant "SuggestionController" as Controller
participant "DiscussionSuggestionService" as Service
participant "LLMClient" as LLM
participant "TranscriptRepository" as TranscriptRepo
database "Azure OpenAI<<E>>" as OpenAI
database "Redis Cache<<E>>" as Cache
database "PostgreSQL<<E>>" as DB
== 실시간 논의사항 제안 요청 ==
note over Controller
TranscriptService로부터 호출
(회의록 자동작성 프로세스 내부)
end note
Controller -> Service: suggestDiscussionTopics(meetingId, transcriptText)
activate Service
== 회의 맥락 정보 조회 ==
Service -> TranscriptRepo: getMeetingContext(meetingId)
activate TranscriptRepo
TranscriptRepo -> DB: 회의 맥락 정보 조회\n(회의정보, 안건, 참석자)
activate DB
DB --> TranscriptRepo: 회의 정보
deactivate DB
TranscriptRepo --> Service: meetingContext
deactivate TranscriptRepo
Service -> TranscriptRepo: getPreviousDiscussions(meetingId)
activate TranscriptRepo
TranscriptRepo -> DB: 이미 논의한 주제 조회\n(회의ID 기준)
activate DB
DB --> TranscriptRepo: 이미 논의한 주제 목록
deactivate DB
TranscriptRepo --> Service: discussedTopics
deactivate TranscriptRepo
== LLM 기반 논의사항 제안 생성 ==
Service -> Service: 제안 프롬프트 생성
note right
시스템 프롬프트:
- 역할: 회의 퍼실리테이터
- 목표: 회의 안건 대비 빠진 논의 찾기
사용자 프롬프트:
- 회의 안건: {agenda}
- 이미 논의한 주제: {discussedTopics}
- 현재 대화 내용: {transcriptText}
- 참석자 정보: {participants}
지시사항:
- 안건에 있지만 아직 안 다룬 항목 찾기
- 대화 흐름상 빠진 중요 논의 식별
- 추가하면 좋을 주제 제안
- 우선순위 부여
응답 형식:
{
"suggestions": [
{
"topic": "논의 주제",
"reason": "제안 이유",
"priority": "HIGH|MEDIUM|LOW",
"relatedAgenda": "관련 안건 항목",
"estimatedTime": 분 단위 예상 시간
}
]
}
end note
Service -> LLM: generateDiscussionSuggestions(prompt)
activate LLM
LLM -> OpenAI: POST /chat/completions
activate OpenAI
note right
요청 파라미터:
- model: gpt-4o
- temperature: 0.4
- response_format: json_object
- max_tokens: 1500
end note
OpenAI -> OpenAI: 회의 맥락 분석
note right
분석 단계:
1. 안건 항목별 진행 상황 체크
2. 이미 논의한 주제와 비교
3. 현재 대화 맥락 이해
4. 빠진 중요 논의 식별
5. 추가 제안 생성
6. 우선순위 결정
- HIGH: 안건 필수 항목
- MEDIUM: 중요하지만 선택적
- LOW: 추가 고려사항
end note
OpenAI --> LLM: 논의사항 제안 목록 (JSON)
deactivate OpenAI
LLM --> Service: discussionSuggestions
deactivate LLM
== 제안 검증 및 필터링 ==
Service -> Service: 제안 품질 검증
note right
검증 기준:
- 중복 제거 (이미 논의한 주제)
- 관련성 검증 (회의 목적과 부합)
- 우선순위별 정렬
- 최대 5개까지만 선택
(너무 많으면 오히려 방해)
end note
loop 각 제안마다
Service -> Service: 제안 메타데이터 보강
note right
추가 정보:
- 생성 시각
- 제안 신뢰도 점수
- 회의 진행 시점 (분)
- 고유 ID (UUID)
end note
end
== 임시 캐시 저장 (선택적) ==
Service -> Cache: SET suggestions:discussion:{meetingId}
activate Cache
note right
Redis에 임시 저장:
- Key: suggestions:discussion:{meetingId}
- Value: JSON array (제안 목록)
- TTL: 2시간 (회의 시간)
목적:
- 재접속 시 복원용
- WebSocket 재연결 대응
end note
Cache --> Service: 저장 완료
deactivate Cache
== 응답 반환 ==
Service -> Service: 응답 데이터 구성
note right
프론트엔드 전달 형식:
{
"suggestions": [
{
"id": "suggestion-uuid",
"topic": "논의 주제",
"reason": "제안 이유",
"priority": "HIGH",
"relatedAgenda": "관련 안건",
"estimatedTime": 10
}
],
"totalCount": 제안 개수,
"timestamp": "생성 시각"
}
end note
Service --> Controller: 논의사항 제안 목록
deactivate Service
Controller --> Controller: 이벤트 데이터에 포함하여 반환
note right
TranscriptSummaryCreated 이벤트에
discussionSuggestions 필드로 포함
프론트엔드 처리:
- 오른쪽 "추천" 탭에 표시
- "적용" 버튼 활성화
- 우선순위별 색상 표시
* HIGH: 빨강
* MEDIUM: 주황
* LOW: 초록
end note
== 사용자가 제안 적용 시 ==
note over Controller
사용자가 "적용" 버튼 클릭 시:
프론트엔드에서 직접 Meeting Service 호출
PUT /api/meetings/{meetingId}/transcript
Body: {
"addDiscussionSection": {
"topic": "논의 주제",
"content": ""
}
}
Meeting Service에서 회의록에
새로운 논의 섹션 추가
end note
note over Controller, DB
처리 시간:
- 맥락 정보 조회: 100-200ms
- LLM 제안 생성: 2-3초
- 검증 및 필터링: 100-200ms
- 캐시 저장: 50-100ms
총 처리 시간: 약 2.5-3.5초
특징:
- DB 영구 저장 없음 (임시 데이터)
- Redis 캐시만 활용 (재접속 복원용)
- 프론트엔드 메모리에서 관리
- "적용" 시에만 회의록에 반영
end note
@enduml
@@ -30,10 +30,17 @@ activate Service
== 용어 정보 조회 ==
note over Service
**구현 방식**: AI Service → RAG Service API 호출
GET /api/rag/terms/{termId}
- PostgreSQL + pgvector에서 조회
- Redis 캐싱 적용
end note
Service -> Repo: getTermInfo(term)
activate Repo
Repo -> DB: 용어 정보 조회\n(용어사전에서 정의 및 카테고리)
Repo -> DB: 용어 정보 조회\n(용어사전에서 정의 및 카테고리)\n**실제: RAG Service API 호출**
activate DB
DB --> Repo: 기본 용어 정의
@@ -26,11 +26,19 @@ activate Service
== 용어 사전 조회 ==
note over Service
**구현 방식**: AI Service → RAG Service API 호출
POST /api/rag/terms/search
- 하이브리드 검색 (키워드 + 벡터)
- PostgreSQL + pgvector
- Redis 캐싱
end note
par "조직별 용어 사전"
Service -> Repo: getOrganizationTerms(organizationId)
activate Repo
Repo -> DB: 조직 전문용어 조회\n(조직ID 기준, 용어/정의/카테고리)
Repo -> DB: 조직 전문용어 조회\n(조직ID 기준, 용어/정의/카테고리)\n**실제: RAG Service API 호출**
activate DB
DB --> Repo: 조직 전문용어 목록
@@ -43,7 +51,7 @@ else
Service -> Repo: getIndustryTerms(industry)
activate Repo
Repo -> DB: 산업 표준용어 조회\n(산업분류 기준, 용어/정의/카테고리)
Repo -> DB: 산업 표준용어 조회\n(산업분류 기준, 용어/정의/카테고리)\n**실제: RAG Service API 호출**
activate DB
DB --> Repo: 산업 표준용어 목록
@@ -1,167 +0,0 @@
@startuml
!theme mono
title AI Service 내부 시퀀스 - 섹션AI요약재생성
participant "SectionController" as Controller
participant "SectionSummaryService" as Service
participant "LLMClient" as LLM
participant "SectionRepository" as Repo
database "Azure OpenAI<<E>>" as OpenAI
database "PostgreSQL<<E>>" as DB
== 섹션 AI 요약 재생성 요청 수신 ==
note over Controller
API 요청:
POST /api/ai/sections/{sectionId}/regenerate-summary
Body: {
"sectionContent": "**논의 사항:**\n- AI 기반...",
"meetingId": "550e8400-..."
}
end note
Controller -> Service: regenerateSummary(sectionId, sectionContent, meetingId)
activate Service
== 회의 맥락 조회 (선택적) ==
Service -> Repo: getMeetingContext(meetingId)
activate Repo
Repo -> DB: 회의 정보 조회\n- 회의 제목\n- 참석자\n- 안건
activate DB
DB --> Repo: 회의 맥락 정보
deactivate DB
Repo --> Service: meetingContext
deactivate Repo
note right of Service
회의 맥락을 통해
더 정확한 요약 생성
예: "신규 프로젝트 킥오프"
→ 기술/일정 중심 요약
end note
== 프롬프트 생성 ==
Service -> Service: 요약 프롬프트 구성
note right
시스템 프롬프트:
- 역할: 회의록 섹션 요약 전문가
- 목표: 핵심 내용을 2-3문장으로 압축
- 스타일: 명확하고 간결한 문체
사용자 프롬프트:
- 회의 맥락: {meetingContext}
- 섹션 내용: {sectionContent}
요구사항:
- 2-3문장으로 요약
- 논의사항과 결정사항 구분
- 핵심 키워드 포함
- 불필요한 세부사항 제외
end note
== LLM 기반 요약 생성 ==
Service -> LLM: generateSummary(prompt, sectionContent)
activate LLM
LLM -> OpenAI: POST /chat/completions
activate OpenAI
note right
요청 파라미터:
- model: gpt-4o
- temperature: 0.3
- max_tokens: 200
- messages: [system, user]
end note
OpenAI -> OpenAI: 섹션 내용 분석 및 요약
note right
처리 단계:
1. 섹션 내용 파싱
- 논의사항 추출
- 결정사항 추출
- 보류사항 추출
2. 핵심 내용 식별
- 중요도 평가
- 키워드 추출
3. 요약 생성
- 2-3문장으로 압축
- 논의→결정 흐름 반영
- 명확한 문장 구성
4. 품질 검증
- 길이 확인 (150자 이내)
- 핵심 누락 여부 확인
end note
OpenAI --> LLM: 생성된 AI 요약
deactivate OpenAI
LLM --> Service: summaryText
deactivate LLM
== 생성된 요약 저장 (선택적) ==
Service -> Repo: saveSectionSummary(sectionId, summaryText)
activate Repo
Repo -> DB: AI 요약 저장
activate DB
note right
저장 데이터:
- section_id
- summary_text
- generated_at
- model: "gpt-4o"
- token_usage
end note
DB --> Repo: 저장 완료
deactivate DB
Repo --> Service: 완료
deactivate Repo
== 응답 반환 ==
Service -> Service: 응답 데이터 구성
note right
응답 데이터:
- summary: "AI 기반 회의록 자동화..."
- generatedAt: "2025-01-23T11:00:00Z"
end note
Service --> Controller: 요약 생성 완료 응답
deactivate Service
Controller --> Controller: 200 OK 응답 반환
note over Controller, DB
처리 시간:
- 회의 맥락 조회: 50-100ms
- 프롬프트 구성: 10-20ms
- LLM 요약 생성: 2-4초
- 저장 처리: 50-100ms
총 처리 시간: 약 2-5초
정책:
- 섹션 내용이 변경되면 요약도 재생성
- 이전 요약은 이력으로 보관
- 사용자는 생성된 요약을 수정 가능
- 수정된 요약은 AI 재생성 가능
처리량:
- max_tokens: 200 (요약은 짧음)
- 비용 효율적 (전체 회의록 대비)
end note
@enduml
@@ -1,203 +0,0 @@
@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: Todo 정보 조회
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담당자만 완료 가능
note right
에러 응답 형식:
{
"error": {
"code": "INSUFFICIENT_PERMISSION",
"message": "Todo 완료 권한이 없습니다",
"details": "담당자만 Todo를 완료할 수 있습니다",
"timestamp": "2025-10-23T12:00:00Z",
"path": "/api/todos/{todoId}/complete"
}
}
end note
return 403 Forbidden
else 권한 있음
alt Todo가 이미 완료됨
Service --> Controller: 409 Conflict\n이미 완료된 Todo
note right
에러 응답 형식:
{
"error": {
"code": "TODO_ALREADY_COMPLETED",
"message": "이미 완료된 Todo입니다",
"details": "해당 Todo는 이미 완료 처리되었습니다",
"timestamp": "2025-10-23T12:00:00Z",
"path": "/api/todos/{todoId}/complete"
}
}
end note
return 409 Conflict
else 완료 처리 가능
' 완료 확인 다이얼로그 (프론트엔드에서 처리됨)
' Todo 완료 처리
Service -> TodoRepo: markAsCompleted(todoId, userId)
activate TodoRepo
TodoRepo -> DB: Todo 완료 상태 업데이트
activate DB
DB --> TodoRepo: 업데이트 완료
deactivate DB
TodoRepo --> Service: 업데이트 성공
deactivate TodoRepo
note over Service
회의록 실시간 반영:
- 관련 회의록 섹션 자동 업데이트
- 완료 표시 추가
- 완료 시간 및 완료자 정보 기록
end note
' 회의록 섹션 업데이트
Service -> MinutesRepo: updateTodoStatus(todoId, "COMPLETED")
activate MinutesRepo
MinutesRepo -> DB: 회의록 섹션의 Todo 상태 업데이트
activate DB
DB --> MinutesRepo: 업데이트 완료
deactivate DB
MinutesRepo --> Service: 업데이트 성공
deactivate MinutesRepo
' 회의록의 모든 Todo 완료 여부 확인
Service -> TodoRepo: countPendingTodos(minutesId)
activate TodoRepo
TodoRepo -> DB: 미완료 Todo 개수 조회
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
@@ -1,158 +0,0 @@
@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: 회의록 정보 조회
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: Todo 정보 저장
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: 회의록 섹션에 Todo 연결
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: 캘린더 이벤트 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
@@ -1,87 +0,0 @@
@startuml
!theme mono
title 실시간 수정 동기화 내부 시퀀스
participant "WebSocket<<E>>" as WebSocket
participant "CollaborationController" as Controller
participant "CollaborationService" as Service
participant "TranscriptService" as TranscriptService
participant "OperationalTransform" as OT
database "Redis Cache<<E>>" as Cache
queue "Event Hub<<E>>" as EventHub
WebSocket -> Controller: onMessage(editOperation)
activate Controller
Controller -> Service: processEdit(meetingId, operation, userId)
activate Service
Service -> Cache: get(meeting:{id}:session)
activate Cache
note right of Cache
활성 세션 정보:
- 참여 사용자 목록
- 현재 문서 버전
- 락 정보
end note
Cache --> Service: sessionData
deactivate Cache
Service -> OT: transform(operation, concurrentOps)
activate OT
note right of OT
Operational Transform:
- 동시 편집 충돌 해결
- 작업 순서 정렬
- 일관성 보장
end note
OT --> Service: transformedOp
deactivate OT
Service -> TranscriptService: applyOperation(meetingId, transformedOp)
activate TranscriptService
TranscriptService -> TranscriptService: updateContent()
note right of TranscriptService
내용 업데이트:
- 버전 증가
- 변경 사항 적용
- 임시 저장
end note
TranscriptService --> Service: updatedVersion
deactivate TranscriptService
Service -> Cache: SET meeting:{id}:version\n(TTL: 1시간)
activate Cache
note right of Cache
세션 버전 정보 캐싱:
- TTL: 1시간
- 버전 정보 업데이트
- 최신 상태 유지
end note
Cache --> Service: OK
deactivate Cache
Service ->> EventHub: publish(EditOperationEvent)
activate EventHub
note right of EventHub
다른 참여자에게 전파:
- WebSocket 브로드캐스트
- 실시간 동기화
end note
deactivate EventHub
Service --> Controller: SyncResponse
deactivate Service
Controller --> WebSocket: broadcast(editOperation)
deactivate Controller
note over WebSocket
다른 클라이언트에게
실시간 전송
end note
@enduml
@@ -1,92 +0,0 @@
@startuml
!theme mono
title 충돌 해결 내부 시퀀스
participant "WebSocket<<E>>" as WebSocket
participant "CollaborationController" as Controller
participant "CollaborationService" as Service
participant "ConflictResolver" as Resolver
participant "TranscriptService" as TranscriptService
database "Redis Cache<<E>>" as Cache
queue "Event Hub<<E>>" as EventHub
WebSocket -> Controller: onConflict(conflictData)
activate Controller
Controller -> Service: resolveConflict(meetingId, conflictData)
activate Service
Service -> Cache: get(meeting:{id}:conflicts)
activate Cache
note right of Cache
충돌 목록 조회:
- 발생 시간
- 관련 사용자
- 충돌 영역
end note
Cache --> Service: conflictList
deactivate Cache
Service -> Resolver: analyzeConflict(conflictData)
activate Resolver
Resolver -> Resolver: detectConflictType()
note right of Resolver
충돌 유형 분석:
- 동일 위치 수정
- 삭제-수정 충돌
- 순서 변경 충돌
end note
Resolver -> Resolver: applyStrategy()
note right of Resolver
해결 전략:
- 자동 병합 (단순 충돌)
- 최신 우선 (시간 기반)
- 수동 해결 필요 (복잡)
end note
Resolver --> Service: resolutionResult
deactivate Resolver
alt auto-resolved
Service -> TranscriptService: applyResolution(meetingId, resolution)
activate TranscriptService
TranscriptService --> Service: mergedContent
deactivate TranscriptService
Service -> Cache: del(meeting:{id}:conflicts)
activate Cache
Cache --> Service: OK
deactivate Cache
else manual-required
Service -> Cache: SET meeting:{id}:conflicts\n(TTL: 1시간)
activate Cache
note right of Cache
충돌 정보 캐싱:
- TTL: 1시간
- 충돌 정보 저장
- 수동 해결 대기
end note
Cache --> Service: OK
deactivate Cache
end
Service ->> EventHub: publish(ConflictResolvedEvent)
activate EventHub
note right of EventHub
이벤트 발행:
- 자동 해결: 동기화
- 수동 필요: 알림
end note
deactivate EventHub
Service --> Controller: ResolutionResponse
deactivate Service
Controller --> WebSocket: send(resolution)
deactivate Controller
@enduml
@@ -1,147 +0,0 @@
@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: 알림 정보 생성\n(알림ID, TodoID, 유형, 상태, 수신자, 생성일시)
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: 알림 상태 업데이트\n(상태=발송완료, 발송일시=현재시각)
activate DB
DB --> Repository: 업데이트 완료
deactivate DB
Repository --> Service: 완료
deactivate Repository
else 발송 실패
EmailClient --> Service: FAILED (errorMessage)
Service -> Repository: updateNotificationStatus(notificationId, "FAILED")
activate Repository
Repository -> DB: 알림 상태 업데이트\n(상태=발송실패, 오류메시지=에러내용)
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
@@ -1,158 +0,0 @@
@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: 리마인더 알림 조회\n(회의ID, 유형='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: 리마인더 알림 생성\n(알림ID, 회의ID, 유형, 상태, 수신자, 생성일시)
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: 수신자별 알림 상태 업데이트\n(상태='발송완료', 발송일시=현재시각)
activate DB
DB --> Repository: 업데이트 완료
deactivate DB
Repository --> Service: 완료
deactivate Repository
else 발송 실패
EmailClient --> Service: FAILED
Service -> Repository: updateRecipientStatus(notificationId, recipient, "FAILED")
activate Repository
Repository -> DB: 수신자별 알림 상태 업데이트\n(상태='발송실패')
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: 알림 최종 상태 업데이트\n(상태, 완료일시, 발송건수, 실패건수)
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
@@ -1,145 +0,0 @@
@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: 초대 알림 생성\n(알림ID, 회의ID, 유형, 상태, 수신자, 생성일시)
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: 수신자별 알림 상태 업데이트\n(상태='발송완료', 발송일시=현재시각)
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: 수신자별 알림 상태 업데이트\n(상태='발송실패', 오류메시지=에러내용)
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: 알림 최종 상태 업데이트\n(상태, 완료일시, 발송건수, 실패건수)
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