mirror of
https://github.com/hwanny1128/HGZero.git
synced 2026-06-13 07:09:09 +00:00
설계서 전면 업데이트: 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:
@@ -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
|
||||
Reference in New Issue
Block a user