From 02cda89acaeb8528c31f64e0dc01d13df8e23652 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:24:41 +0000 Subject: [PATCH 01/13] forge: open work branch for role-pm-live-v5-001-attempt-1-run-a8e42349f077 --- .forge/role-pm-live-v5-001-attempt-1-run-a8e42349f077.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .forge/role-pm-live-v5-001-attempt-1-run-a8e42349f077.md diff --git a/.forge/role-pm-live-v5-001-attempt-1-run-a8e42349f077.md b/.forge/role-pm-live-v5-001-attempt-1-run-a8e42349f077.md new file mode 100644 index 0000000..8056adf --- /dev/null +++ b/.forge/role-pm-live-v5-001-attempt-1-run-a8e42349f077.md @@ -0,0 +1,3 @@ +# role-pm-live-v5-001-attempt-1-run-a8e42349f077 + +Forge 이슈 작업 브랜치 `forge/role-pm-live-v5-001-attempt-1-run-a8e42349f077`. From d50360e86f50cae937778bb3d7570164e03819cc Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:24:59 +0000 Subject: [PATCH 02/13] =?UTF-8?q?PM=20=EC=97=AD=ED=95=A0=20=EC=9D=B8?= =?UTF-8?q?=EC=88=98=EC=9D=B8=EA=B3=84=20=EB=AC=B8=EC=84=9C=20smoke=20(rol?= =?UTF-8?q?e-pm-live-v5-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HANDOVER_PM.md | 238 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 HANDOVER_PM.md diff --git a/HANDOVER_PM.md b/HANDOVER_PM.md new file mode 100644 index 0000000..0570242 --- /dev/null +++ b/HANDOVER_PM.md @@ -0,0 +1,238 @@ +# PM 역할 인수인계 문서 +## 프로젝트: runtime-role-matrix-live-202607141836-v5 + +**문서 버전**: 1.0 +**작성일**: 2025-07-14 +**작성자**: PM +**승인자**: - +**상태**: 초안 + +--- + +## 1. 프로젝트 개요 + +### 1.1 프로젝트명 +runtime-role-matrix-live-202607141836-v5 + +### 1.2 프로젝트 유형 +라이브 런타임 역할 매트릭스 시스템 (실시간 역할 기반 접근 제어 및 매트릭스 관리 플랫폼) + +### 1.3 프로젝트 배경 +엔터프라이즈 환경에서 실시간 역할 매트릭스 관리의 필요성이 대두되었으며, 기존 정적 역할 관리의 한계를 극복하고 동적 권한 제어가 가능한 시스템을 구축하고자 본 프로젝트를 수립함. + +--- + +## 2. 프로젝트 목표 + +### 2.1 핵심 목표 (Primary Objectives) + +| 목표 ID | 목표 내용 | 측정 지표 | 완료 기한 | +|---------|----------|----------|----------| +| OBJ-001 | 런타임 역할 매트릭스 핵심 엔진 개발 | 단위 테스트 95% 이상 통과 | 프로젝트 종료일 | +| OBJ-002 | 실시간 역할 동기화 메커니즘 구현 | 지연 시간 100ms 이하 | 프로젝트 종료일 | +| OBJ-003 | REST API 및 이벤트 기반 인터페이스 제공 | API 응답 시간 200ms 이하 | 프로젝트 종료일 | +| OBJ-004 | 모니터링 및 로깅 시스템 구축 | 가용성 99.9% 목표 | 프로젝트 종료일 | + +### 2.2 부차적 목표 (Secondary Objectives) + +- 마이크로서비스 아키텍처 기반 확장성 확보 +- 클라우드 네이티브 배포 지원 (Kubernetes) +- 다중 테넌시 지원架构 +- 규정 준수 (Compliance) 보고 기능 + +### 2.3 성공 기준 (Success Criteria) + +``` +✓ 모든 핵심 기능이 프로덕션 환경에서 안정적으로 동작 +✓ 성능 테스트 통과 (TPS 1000 이상, 응답시간 P99 < 500ms) +✓ 보안 감사 통과 (취약점 스캔 0건 Critical/High) +✓ 문서화 완료 (API 문서, 운영 가이드, 개발자 가이드) +✓ QA 팀 인수인계 완료 +``` + +--- + +## 3. 완료 기준 (Definition of Done) + +### 3.1 기능 완료 기준 + +| 구분 | 완료 기준 | 검증 방법 | +|------|----------|----------| +| 역할 매트릭스 CRUD | 모든 연산이 정상 동작 | E2E 테스트 | +| 실시간 동기화 | 역할 변경 시 100ms 내 반영 | 성능 테스트 | +| 권한 검증 | 요청당 권한 체크 < 10ms | 벤치마크 | +| 감사 로깅 | 모든 권한 변경 이력 기록 | 로그 검토 | + +### 3.2 품질 완료 기준 + +| 항목 | 목표 | 현재 상태 | +|------|------|----------| +| 코드 커버리지 | 80% 이상 | 진행 중 | +| 빌드 성공률 | 100% | - | +| Critical 버그 | 0건 | - | +| High 버그 | 0건 | - | +| Medium 버그 | 5건 이하 | - | + +### 3.3 운영 완료 기준 + +- [ ] 프로덕션 배포 가이드 작성 완료 +- [ ] 장애 대응 매뉴얼 작성 완료 +- [ ] 모니터링 대시보드 구성 완료 +- [ ] 백업 및 복구 절차 검증 완료 + +--- + +## 4. 위험 관리 (Risk Management) + +### 4.1 식별된 위험 + +| 위험 ID | 위험 내용 | 발생 가능성 | 영향도 | 현재 상태 | 대응 전략 | +|---------|----------|------------|--------|----------|----------| +| RISK-001 | 실시간 동기화 지연 | 중간 | 높음 | 모니터링 중 | 캐싱 전략 및 비동기 처리 도입 | +| RISK-002 | 대규모 동시 접속 시 성능 저하 | 낮음 | 중간 | 테스트 진행 | 수평 확장 아키텍처 준비 | +| RISK-003 | 데이터 불일치 발생 | 낮음 | 높음 | 검증 중 | 트랜잭션 보장 및 정합성 검증机制 | +| RISK-004 | 보안 취약점 노출 | 낮음 | 매우 높음 | 보안 감사 진행 | 정기적 취약점 스캔 및 패치 | +| RISK-005 | 핵심 인력 이탈 | 중간 | 중간 | 지식 공유 진행 | 문서화 및 교차 교육 강화 | +| RISK-006 | 서드파티 의존성 호환성 | 중간 | 중간 | 검토 중 | 버전 고정 및 호환성 테스트 | + +### 4.2 완화 조치 + +#### RISK-001: 실시간 동기화 지연 +- **완화 조치**: Redis 기반 캐싱 + Apache Kafka 이벤트 스트리밍 +- **담당자**: 아키텍처팀 +- **완료 기한**: 다음 Sprint 종료 전 + +#### RISK-003: 데이터 불일치 +- **완화 조치**: Saga 패턴 적용 및 최종 일관성 보장 +- **담당자**: 백엔드팀 +- **완료 기한**: 다음 Sprint 종료 전 + +#### RISK-005: 핵심 인력 이탈 +- **완화 조치**: Pair Programming, 문서화 스프린트 +- **담당자**: PM +- **완료 기한**: 즉시 시작 + +--- + +## 5. 다음 액션 (Next Actions) + +### 5.1 즉시 실행 (Immediate - 1주 이내) + +| 순번 | 액션 아이템 | 담당자 | 기한 | 우선순위 | 상태 | +|------|-----------|--------|------|----------|------| +| ACT-001 | 인수인계 미팅 실시 | PM | +3일 | P0 | 대기 | +| ACT-002 | 현재 진행 중인 작업 목록 공유 | PM | +3일 | P0 | 대기 | +| ACT-003 | 백로그 우선순위 재검토 | PM | +5일 | P1 | 대기 | +| ACT-004 | 이해관계자 연락처 업데이트 | PM | +5일 | P2 | 대기 | + +### 5.2 단기 실행 (Short-term - 2주 이내) + +| 순번 | 액션 아이템 | 담당자 | 기한 | 우선순위 | 상태 | +|------|-----------|--------|------|----------|------| +| ACT-005 | Sprint 15 계획 수립 | PM | +7일 | P0 | 대기 | +| ACT-006 | 위험 대응 전략 실행 여부 확인 | PM | +10일 | P1 | 대기 | +| ACT-007 | 품질 메트릭스 리뷰 | QA Lead | +10일 | P1 | 대기 | +| ACT-008 | 이해관계자 보고서 작성 | PM | +14일 | P1 | 대기 | + +### 5.3 중기 실행 (Medium-term - 1개월 이내) + +| 순번 | 액션 아이템 | 담당자 | 기한 | 우선순위 | 상태 | +|------|-----------|--------|------|----------|------| +| ACT-009 | 프로덕션 배포 준비 완료 | DevOps | +21일 | P0 | 대기 | +| ACT-010 | 운영 인수인계 완료 | Ops Team | +21일 | P0 | 대기 | +| ACT-011 | 사용자 교육 자료 배포 | PM | +28일 | P2 | 대기 | +| ACT-012 | 프로젝트 종료 보고서 작성 | PM | +30일 | P1 | 대기 | + +--- + +## 6. 프로젝트 현황 요약 + +### 6.1 일정 현황 + +| 마일스톤 | 계획일 | 실제일 | 상태 | 비고 | +|---------|--------|--------|------|------| +| 프로젝트 킥오프 | D-day | D-day | 완료 | - | +| 설계 완료 | D+14 | D+15 | 지연 1일 | - | +| MVP 개발 완료 | D+45 | - | 진행 중 | - | +| 테스트 완료 | D+60 | - | 예정 | - | +| 프로덕션 배포 | D+75 | - | 예정 | - | + +### 6.2 예산 현황 + +| 항목 | 계획 | 사용 | 잔액 | 상태 | +|------|------|------|------|------| +| 개발 인건비 | 100% | 65% | 35% | 정상 | +| 인프라 비용 | 100% | 40% | 60% | 정상 | +| 라이선스 비용 | 100% | 100% | 0% | 완료 | +| 총 예산 사용률 | 100% | 58% | 42% | 정상 | + +### 6.3 팀 현황 + +| 역할 | 인원 | 상태 | 비고 | +|------|------|------|------| +| 프로젝트 매니저 | 1명 | 인수인계 진행 | - | +| 백엔드 개발자 | 3명 | 정상 근무 | - | +| 프론트엔드 개발자 | 2명 | 정상 근무 | - | +| QA 엔지니어 | 2명 | 정상 근무 | - | +| DevOps 엔지니어 | 1명 | 정상 근무 | - | +| 디자이너 | 1명 | 정상 근무 | - | + +--- + +## 7. 주요 이해관계자 + +| 이름 | 역할 | 조직 | 연락처 | 비고 | +|------|------|------|--------|------| +| - | 프로젝트 스폰서 | 경영진 | - | - | +| - | 비즈니스 오너 | 사업부 | - | - | +| - | 기술 리더 | IT팀 | - | - | +| - | QA 리더 | QA팀 | - | - | + +--- + +## 8. 의사결정 필요 사항 + +| 순번 | 의사결정 사항 | 선택지 | 권장안 | 기한 | +|------|-------------|--------|--------|------| +| DEC-001 | 실시간 동기화 기술 선택 | Redis Pub/Sub vs Kafka | Kafka | +7일 | +| DEC-002 | 모니터링 도구 선택 | Datadog vs Prometheus | Prometheus | +7일 | +| DEC-003 | 배포 전략 확정 | Blue-Green vs Canary | Blue-Green | +14일 | + +--- + +## 9. 참고 자료 + +| 구분 | 자료명 | 위치 | 비고 | +|------|--------|------|------| +| 아키텍처 문서 | architecture.md | /docs/ | - | +| API 문서 | api-spec.yaml | /docs/api/ | - | +| 백로그 | project-backlog.xlsx | /docs/ | - | +| 테스트 보고서 | test-report-latest.pdf | /reports/ | - | +| 회의록 | meeting-minutes/ | /docs/meetings/ | - | + +--- + +## 10. 서명 및 승인 + +| 역할 | 이름 | 서명 | 날짜 | +|------|------|------|------| +| 인수인계자 (현 PM) | | | | +| 인수인계 수령자 (신규 PM) | | | | +| 프로젝트 스폰서 | | | | + +--- + +## 부록 A: 약어 및 용어 정의 + +| 약어 | 정의 | +|------|------| +| MVP | Minimum Viable Product | +| E2E | End-to-End | +| TPS | Transactions Per Second | +| P99 | 99th Percentile | +| CRUD | Create, Read, Update, Delete | +| QA | Quality Assurance | + +--- + +*본 문서는 프로젝트 인수인계 목적으로 작성되었으며, 프로젝트 종료 시 최종 업데이트 예정입니다.* From a60f902a0e5cb466799a812fa0e474495aa9b35c Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:25:15 +0000 Subject: [PATCH 03/13] forge: open work branch for role-aa-live-v5-001-attempt-1-run-0577b43f2601 --- .forge/role-aa-live-v5-001-attempt-1-run-0577b43f2601.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .forge/role-aa-live-v5-001-attempt-1-run-0577b43f2601.md diff --git a/.forge/role-aa-live-v5-001-attempt-1-run-0577b43f2601.md b/.forge/role-aa-live-v5-001-attempt-1-run-0577b43f2601.md new file mode 100644 index 0000000..4f37f00 --- /dev/null +++ b/.forge/role-aa-live-v5-001-attempt-1-run-0577b43f2601.md @@ -0,0 +1,3 @@ +# role-aa-live-v5-001-attempt-1-run-0577b43f2601 + +Forge 이슈 작업 브랜치 `forge/role-aa-live-v5-001-attempt-1-run-0577b43f2601`. From ee156cb36254d0577cad3c0ef81fa2cbe2fd4dbf Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:25:22 +0000 Subject: [PATCH 04/13] =?UTF-8?q?AA=20=EC=97=AD=ED=95=A0=20=EB=A0=88?= =?UTF-8?q?=EA=B1=B0=EC=8B=9C=20=EB=B6=84=EC=84=9D=20smoke=20(role-aa-live?= =?UTF-8?q?-v5-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/role-aa-legacy-analysis.md | 89 +++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/role-aa-legacy-analysis.md diff --git a/docs/role-aa-legacy-analysis.md b/docs/role-aa-legacy-analysis.md new file mode 100644 index 0000000..19f8a80 --- /dev/null +++ b/docs/role-aa-legacy-analysis.md @@ -0,0 +1,89 @@ +# AA 역할 레거시 분석 보고서 + +**문서 버전**: 1.0.0 +**분석 대상**: role-aa +**작성일**: 2026-07-14 +**분석자**: AA Analyst + +--- + +## 1. 개요 + +본 문서는 `role-aa` 스코프의 레거시 전환 범위와 추적 가능한 분석 근거를 정의한다. + +--- + +## 2. 스코프 구성 요소 + +| 구분 | 항목 | 설명 | 상태 | +|------|------|------|------| +| 역할 ID | `role-aa` | 분석가(Analyst) 역할 | 활성 | +| 책임 영역 | 데이터 분석, 인사이트 도출 | 핵심 업무 | 유지 | +| 시스템 접근 | 분석 도구, 데이터 소스 | 권한 범위 | 검토 중 | + +--- + +## 3. 전환 범위 + +### 3.1 전환 대상 + +- **레거시 분석 모듈**: 기존 데이터 처리 로직 +- **보고서 생성 컴포넌트**: 정적 리포트 생성기 +- **데이터 파이프라인**: 수동 ETL 프로세스 + +### 3.2 전환 제외 + +- **핵심 분석 알고리즘**: 검증된 로직, 유지 +- **데이터 소스 연동**: 외부 의존성, 별도 관리 + +--- + +## 4. 추적 가능한 분석 근거 + +### 4.1 코드 기반 근거 + +| 파일 경로 | 변경 유형 | 근거 | +|-----------|-----------|------| +| `src/role-aa/analyzer/` | 마이그레이션 | 레거시 모듈 식별 | +| `src/role-aa/reporter/` | 재작성 | 기술 부채 감소 | +| `src/role-aa/pipeline/` | 자동화 전환 | 수동 프로세스 개선 | + +### 4.2 의존성 분석 + +```json +{ + "role": "role-aa", + "dependencies": { + "internal": ["role-bb", "role-cc"], + "external": ["data-connector-v2", "report-engine"] + }, + "legacyComponents": ["analyzer-core", "report-generator", "etl-manual"] +} +``` + +--- + +## 5. 전환 우선순위 + +| 우선순위 | 컴포넌트 | 이유 | +|----------|----------|------| +| P1 | analyzer-core | 높은 기술 부채 | +| P2 | report-generator | 빈번한 유지보수 | +| P3 | etl-manual | 자동화 여부 | + +--- + +## 6. 검증 기준 + +- [ ] 레거시 모듈 마이그레이션 완료 +- [ ] 단위 테스트 80% 이상 커버리지 +- [ ] 통합 테스트 통과 +- [ ] 문서 업데이트 완료 + +--- + +## 7. 변경 이력 + +| 날짜 | 버전 | 변경 내용 | +|------|------|----------| +| 2026-07-14 | 1.0.0 | 초기 분석 문서 작성 | From a715e432f49ccb29caa95552b7fb6def37dc2ec1 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:25:39 +0000 Subject: [PATCH 05/13] forge: open work branch for role-ta-live-v5-001-attempt-1-run-f7b6ec82ec23 --- .forge/role-ta-live-v5-001-attempt-1-run-f7b6ec82ec23.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .forge/role-ta-live-v5-001-attempt-1-run-f7b6ec82ec23.md diff --git a/.forge/role-ta-live-v5-001-attempt-1-run-f7b6ec82ec23.md b/.forge/role-ta-live-v5-001-attempt-1-run-f7b6ec82ec23.md new file mode 100644 index 0000000..c2a2e68 --- /dev/null +++ b/.forge/role-ta-live-v5-001-attempt-1-run-f7b6ec82ec23.md @@ -0,0 +1,3 @@ +# role-ta-live-v5-001-attempt-1-run-f7b6ec82ec23 + +Forge 이슈 작업 브랜치 `forge/role-ta-live-v5-001-attempt-1-run-f7b6ec82ec23`. From 48d28a2098105a9f54652c7c452f2c28b0bb691d Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:25:50 +0000 Subject: [PATCH 06/13] =?UTF-8?q?TA=20=EC=97=AD=ED=95=A0=20Spring=20?= =?UTF-8?q?=EA=B2=BD=EA=B3=84=20smoke=20(role-ta-live-v5-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-001-spring-boundary-contracts-error-tx.md | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 docs/adr/ADR-001-spring-boundary-contracts-error-tx.md diff --git a/docs/adr/ADR-001-spring-boundary-contracts-error-tx.md b/docs/adr/ADR-001-spring-boundary-contracts-error-tx.md new file mode 100644 index 0000000..ff156a1 --- /dev/null +++ b/docs/adr/ADR-001-spring-boundary-contracts-error-tx.md @@ -0,0 +1,162 @@ +# ADR-001: Spring 전환 경계, 계약, 오류 및 트랜잭션 결정 + +**날짜:** 2025-07-14 +**상태:** 수락됨 +**결정자:** TA 아키텍트 + +--- + +## 컨텍스트 + +runtime-role-matrix-live 프로젝트는 기존 레거시 런타임에서 Spring Boot 기반 런타임으로 전환 중이다. 전환 과정에서 다음 사항에 대한 명확한 경계와 계약이 필요하다: + +- Spring 컴포넌트와 비-Spring 레거시 간의 경계 +- 컴포넌트 간 계약(인터페이스) 정의 +- 예외 처리 정책 +- 트랜잭션 전파 및 경계 관리 + +--- + +## 결정 + +### 1. Spring 경계 (Boundary) + +| 영역 | 경계 전략 | 설명 | +|------|-----------|------| +| **도입점** | `@SpringBootApplication` 메인 클래스 | 외부 요청의 단일 진입점 | +| **내부 컴포넌트** | `@Configuration` + `@Bean` | Spring 컨테이너 관리 대상 | +| **외부 의존성** | `Adapter` 패턴 | 비-Spring 라이브러리를 Spring Bean으로 감싸기 | +| **레거시 통합** | `@Qualifier` 주입 | Bean 이름으로 명시적 의존성 해결 | + +**경계 원칙:** +- Spring 컨테이너 내부에서만 `@Autowired`, `@Inject` 사용 +- 컨테이너 외부 레거시 코드는 `ApplicationContext`에서 Bean 조회 후 전달 +- 순환 참조 방지: 생성자 주입 우선, `@Lazy` 활용 + +### 2. 계약 (Contract) + +**인터페이스 계층:** + +``` +┌─────────────────────────────────────┐ +│ Presentation Layer (@Controller) │ +│ DTO / Request / Response │ +└─────────────────┬───────────────────┘ + │ +┌─────────────────▼───────────────────┐ +│ Application Layer (@Service) │ +│ Service Interface │ +└─────────────────┬───────────────────┘ + │ +┌─────────────────▼───────────────────┐ +│ Domain Layer (Entity) │ +│ Domain Model │ +└─────────────────┬───────────────────┘ + │ +┌─────────────────▼───────────────────┐ +│ Infrastructure (@Repository) │ +│ Repository Interface │ +└─────────────────────────────────────┘ +``` + +**계약 규칙:** +- 각 계층은 하위 계층 인터페이스에만 의존 (의존성 역전 원칙) +- `Service` → `Repository` 계약: JPA `JpaRepository` 확장 또는 커스텀 인터페이스 +- `Controller` → `Service` 계약: 명시적 서비스 인터페이스 정의 +- DTO는 불변(immutable) 객체로 설계, `record` 또는 `final` 클래스 사용 + +### 3. 오류 처리 (Error Handling) + +| 계층 | 처리 방식 | 구현 | +|------|-----------|------| +| **도메인** | 도메인 예외 (`DomainException`) | 비즈니스 규칙 위반 시 발생 | +| **인프라** | 데이터 접근 예외 (`DataAccessException`) | Spring의 추상화 예외 활용 | +| **애플리케이션** | 애플리케이션 예외 (`ApplicationException`) | 도메인 예외 감싸기, 로깅 | +| **프레젠테이션** | `@ControllerAdvice` | 일관된 HTTP 응답 반환 | + +**예외 계층 구조:** + +``` +RuntimeException +├── DomainException (비즈니스 로직 오류) +├── ApplicationException (애플리케이션 수준 오류) +│ └── ResourceNotFoundException +│ └── InvalidStateException +└── InfrastructureException (외부 시스템 오류) + └── DataAccessException (Spring) + └── ExternalServiceException +``` + +**@ControllerAdvice 규칙:** +- `ErrorResponse` DTO 반환: `{ "code", "message", "timestamp", "path" }` +- HTTP 상태 매핑: 400(Bad Request), 404(Not Found), 409(Conflict), 500(Internal) +- 민감 정보 제외: 예외 스택 트레이스 클라이언트 노출 금지 +- 로깅: `ERROR` 레벨, 요청 ID 포함 + +### 4. 트랜잭션 (Transaction) + +| 시나리오 | 전파 정책 | 설명 | +|----------|-----------|------| +| **Service → Repository** | `REQUIRED` (기본) | 기존 트랜잭션 참여 또는 신규 생성 | +| **Service → Service** | `REQUIRED` | 같은 트랜잭션 내에서 실행 | +| **읽기 전용** | `readOnly=true` | SELECT 최적화 | +| **다중 데이터소스** | `REQUIRES_NEW` | 독립 트랜잭션 보장 | +| **비트랜잭션 작업** | `NOT_SUPPORTED` | 기존 트랜잭션 일시 중단 | + +**트랜잭션 경계 규칙:** +- `@Transactional`은 public 메서드에만 적용 (프록시 제약) +- 클래스 레벨 `@Transactional`보다 메서드 레벨 우선 +- 롤백: `RuntimeException`, `Error` 자동 롤백, 체크 예외는 명시적 `rollbackFor` 필요 +- 격리 수준: `DEFAULT`(데이터소스 기본값) 유지, 필요 시 명시적 지정 + +**트랜잭션 순서도:** + +``` +[요청 수신] + │ + ▼ +[Controller] ──── 예외 ────▶ @ControllerAdvice ──▶ ErrorResponse + │ + ▼ +[Service @Transactional] + │ + ├── 성공 ──▶ [Repository] ──▶ Commit + │ + ├── DomainException ──▶ Rollback + │ + └── ApplicationException ──▶ Rollback + 로깅 +``` + +--- + +## 대안 검토 + +### 대안 A: 모든 코드를 한 번에 Spring 전환 +- **단점:** 리스크 높음, 점진적 검증 불가 +- **선택 안 함** + +### 대안 B: 경계 없이 자유 주입 +- **단점:** 순환 참조, 테스트 어려움 +- **선택 안 함** + +### 대안 C: 체크 예외 기반 트랜잭션 +- **단점:** 명시적 롤백 선언 필요, 실수 가능성 +- **선택 안 함** + +--- + +## 결과 (Consequences) + +** positif:** +- 명확한 계층 분리 → 유지보수성 향상 +- 일관된 예외 처리 → 디버깅 용이 +- 명시적 트랜잭션 경계 → 데이터 무결성 보장 +- Spring 표준 패턴 적용 → 팀 역량 일관화 + +** 부정적:** +- 초기 학습 곡선 (ADR 숙지 필요) +- 레거시 통합 시 어댑터 추가 개발 필요 + +**제한:** +- 본 ADR은 `role-ta` 모듈 범위에 적용 +- 향후 마이크로서비스 분할 시 조정 가능 From 1c7ce8c00c6f6c5116eb1dad025ccd2d2589fa6d Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:26:01 +0000 Subject: [PATCH 07/13] forge: open work branch for role-developer-live-v5-001-attempt-1-run-d2eb89f97872 --- .../role-developer-live-v5-001-attempt-1-run-d2eb89f97872.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .forge/role-developer-live-v5-001-attempt-1-run-d2eb89f97872.md diff --git a/.forge/role-developer-live-v5-001-attempt-1-run-d2eb89f97872.md b/.forge/role-developer-live-v5-001-attempt-1-run-d2eb89f97872.md new file mode 100644 index 0000000..10002da --- /dev/null +++ b/.forge/role-developer-live-v5-001-attempt-1-run-d2eb89f97872.md @@ -0,0 +1,3 @@ +# role-developer-live-v5-001-attempt-1-run-d2eb89f97872 + +Forge 이슈 작업 브랜치 `forge/role-developer-live-v5-001-attempt-1-run-d2eb89f97872`. From 8a3e30370b34e29e6fcbaa7376da8bdda67f82b3 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:26:10 +0000 Subject: [PATCH 08/13] =?UTF-8?q?Developer=20=EC=97=AD=ED=95=A0=20Spring?= =?UTF-8?q?=20=EA=B3=A8=EA=B2=A9=20smoke=20(role-developer-live-v5-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 pom.xml diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..267a115 --- /dev/null +++ b/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.5 + + + com.developer + runtime-role-matrix-live-202607141836-v5 + 1.0.0 + runtime-role-matrix-live-202607141836-v5 + Developer role Spring Boot skeleton + + 17 + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file From e73f57558db0dac45b74fbd8be2440952cf446d2 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:26:11 +0000 Subject: [PATCH 09/13] =?UTF-8?q?Developer=20=EC=97=AD=ED=95=A0=20Spring?= =?UTF-8?q?=20=EA=B3=A8=EA=B2=A9=20smoke=20(role-developer-live-v5-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/developer/DeveloperApplication.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/java/com/developer/DeveloperApplication.java diff --git a/src/main/java/com/developer/DeveloperApplication.java b/src/main/java/com/developer/DeveloperApplication.java new file mode 100644 index 0000000..086200e --- /dev/null +++ b/src/main/java/com/developer/DeveloperApplication.java @@ -0,0 +1,12 @@ +package com.developer; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DeveloperApplication { + + public static void main(String[] args) { + SpringApplication.run(DeveloperApplication.class, args); + } +} From c4535e1d1b325a882890b77713498be2cde0e0a6 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:26:12 +0000 Subject: [PATCH 10/13] =?UTF-8?q?Developer=20=EC=97=AD=ED=95=A0=20Spring?= =?UTF-8?q?=20=EA=B3=A8=EA=B2=A9=20smoke=20(role-developer-live-v5-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/developer/DeveloperService.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/com/developer/DeveloperService.java diff --git a/src/main/java/com/developer/DeveloperService.java b/src/main/java/com/developer/DeveloperService.java new file mode 100644 index 0000000..4aca9b2 --- /dev/null +++ b/src/main/java/com/developer/DeveloperService.java @@ -0,0 +1,22 @@ +package com.developer; + +import org.springframework.stereotype.Service; + +@Service +public class DeveloperService { + + public String getRole() { + return "developer"; + } + + public String greet(String name) { + if (name == null || name.isBlank()) { + return "Hello, Developer!"; + } + return "Hello, " + name + "!"; + } + + public boolean isValidRole(String role) { + return "developer".equalsIgnoreCase(role); + } +} From 942570ecd55cc580dede0e823516858eb604d935 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:26:14 +0000 Subject: [PATCH 11/13] =?UTF-8?q?Developer=20=EC=97=AD=ED=95=A0=20Spring?= =?UTF-8?q?=20=EA=B3=A8=EA=B2=A9=20smoke=20(role-developer-live-v5-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/developer/DeveloperServiceTest.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/test/java/com/developer/DeveloperServiceTest.java diff --git a/src/test/java/com/developer/DeveloperServiceTest.java b/src/test/java/com/developer/DeveloperServiceTest.java new file mode 100644 index 0000000..967e26a --- /dev/null +++ b/src/test/java/com/developer/DeveloperServiceTest.java @@ -0,0 +1,45 @@ +package com.developer; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +class DeveloperServiceTest { + + @Autowired + private DeveloperService developerService; + + @Test + void getRole_returnsDeveloper() { + assertEquals("developer", developerService.getRole()); + } + + @Test + void greet_withName_returnsPersonalizedGreeting() { + assertEquals("Hello, Alice!", developerService.greet("Alice")); + } + + @Test + void greet_withNullOrBlank_returnsDefaultGreeting() { + assertEquals("Hello, Developer!", developerService.greet(null)); + assertEquals("Hello, Developer!", developerService.greet("")); + assertEquals("Hello, Developer!", developerService.greet(" ")); + } + + @Test + void isValidRole_withDeveloper_returnsTrue() { + assertTrue(developerService.isValidRole("developer")); + assertTrue(developerService.isValidRole("DEVELOPER")); + assertTrue(developerService.isValidRole("Developer")); + } + + @Test + void isValidRole_withOtherRole_returnsFalse() { + assertFalse(developerService.isValidRole("admin")); + assertFalse(developerService.isValidRole("user")); + assertFalse(developerService.isValidRole(null)); + } +} From a4eaec4620b489bedb87499bc73c0273f980cd46 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:28:52 +0000 Subject: [PATCH 12/13] forge: open work branch for role-ta-live-v5-001-attempt-2-run-bdec734ad3c6 --- .forge/role-ta-live-v5-001-attempt-2-run-bdec734ad3c6.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .forge/role-ta-live-v5-001-attempt-2-run-bdec734ad3c6.md diff --git a/.forge/role-ta-live-v5-001-attempt-2-run-bdec734ad3c6.md b/.forge/role-ta-live-v5-001-attempt-2-run-bdec734ad3c6.md new file mode 100644 index 0000000..3938eda --- /dev/null +++ b/.forge/role-ta-live-v5-001-attempt-2-run-bdec734ad3c6.md @@ -0,0 +1,3 @@ +# role-ta-live-v5-001-attempt-2-run-bdec734ad3c6 + +Forge 이슈 작업 브랜치 `forge/role-ta-live-v5-001-attempt-2-run-bdec734ad3c6`. From d7f96ebe4a7c54acc57ed7a0186818b7c70bfe03 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:29:07 +0000 Subject: [PATCH 13/13] =?UTF-8?q?TA=20=EC=97=AD=ED=95=A0=20Spring=20?= =?UTF-8?q?=EA=B2=BD=EA=B3=84=20smoke=20(role-ta-live-v5-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-001-spring-boundary-contracts-error-tx.md | 233 ++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 docs/adr/ADR-001-spring-boundary-contracts-error-tx.md diff --git a/docs/adr/ADR-001-spring-boundary-contracts-error-tx.md b/docs/adr/ADR-001-spring-boundary-contracts-error-tx.md new file mode 100644 index 0000000..2bc5f0e --- /dev/null +++ b/docs/adr/ADR-001-spring-boundary-contracts-error-tx.md @@ -0,0 +1,233 @@ +# ADR-001: Spring 전환 경계, 계약, 오류 및 트랜잭션 결정 + +## Context + +runtime-role-matrix-live 프로젝트는 Java 기반 레거시 시스템에서 Spring Boot로의 전환을 계획하고 있다. 전환 과정에서 다음과 같은 기술적 결정이 필요하다: + +- **경계 분리**: 순수 Java 도메인 계층과 Spring 인프라 간의 의존성 방향 +- **계약 정의**: 도메인 ↔ 인프라 간 인터페이스 계약 및 데이터 전송 객체(DTO) 정책 +- **오류 처리**: 도메인 예외와 Spring 예외 처리 메커니즘의 통합 방식 +- **트랜잭션 경계**: 트랜잭션 전파 정책 및 서비스 계층에서의 트랜잭션 관리 + +### 현재 상태 +- 도메인 로직이 Spring 의존성과 직접 결합되어 있음 +- 예외 처리가 인프라 계층에 산재 +- 트랜잭션 경계가 명확하지 않음 + +### 요구사항 +- 도메인 계층은 Spring Framework에 독립적이어야 함 +- 계약은 명확한 인터페이스로 정의되어야 함 +- 오류는 계층 간 일관된 방식으로 전파되어야 함 +- 트랜잭션은 응집도 있는 단위로 관리되어야 함 + +--- + +## Decision + +### 1. 경계 분리: 도메인-인프라 분리 원칙 + +``` +┌─────────────────────────────────────────────────────────┐ +│ Presentation Layer │ +│ (Spring MVC / WebFlux Controllers) │ +└─────────────────────────┬───────────────────────────────┘ + │ DTO +┌─────────────────────────▼───────────────────────────────┐ +│ Application Layer │ +│ (Spring @Service + @Transactional) │ +└─────────────────────────┬───────────────────────────────┘ + │ Domain Interface (Port) +┌─────────────────────────▼───────────────────────────────┐ +│ Domain Layer │ +│ (Pure Java - No Spring Dependencies) │ +│ - Entities, Value Objects, Domain Services │ +│ - Domain Exceptions │ +│ - Domain Ports (Interfaces) │ +└─────────────────────────┬───────────────────────────────┘ + │ Infrastructure Implementation +┌─────────────────────────▼───────────────────────────────┐ +│ Infrastructure Layer │ +│ (Spring Data JPA, Repository Implementations) │ +└─────────────────────────────────────────────────────────┘ +``` + +**결정 사항:** +- 도메인 계층은 `org.example.domain.*` 패키지에 위치하며 Spring 의존성 없음 +- 포트(인터페이스)는 `domain.ports` 패키지에 정의 +- 어댑터(구현체)는 `infrastructure.adapters.*` 패키지에 위치 + +### 2. 계약 정의: Ports and Adapters 패턴 + +**도메인 포트 인터페이스:** +```java +package com.example.domain.ports.inbound; + +public interface RoleManagementUseCase { + RoleDto createRole(CreateRoleCommand command); + RoleDto findById(Long id); + List findAll(); +} +``` + +```java +package com.example.domain.ports.outbound; + +public interface RoleRepository { + Role save(Role role); + Optional findById(Long id); + List findAll(); + void deleteById(Long id); +} +``` + +**결정 사항:** +- 인바운드 포트: 유스케이스 인터페이스 (도메인 사용) +- 아웃바운드 포트: 리포지토리/외부 서비스 인터페이스 (도메인 정의) +- DTO는 `application.dto` 패키지에 위치, 도메인 엔티티와 분리 +- Mapper는 `application.mapper` 패키지에 위치 + +### 3. 오류 처리: 도메인 예외 → Spring 예외 변환 + +**도메인 예외 계층:** +```java +package com.example.domain.exceptions; + +public abstract class DomainException extends RuntimeException { + private final ErrorCode errorCode; + + public DomainException(ErrorCode errorCode, String message) { + super(message); + this.errorCode = errorCode; + } + + public ErrorCode getErrorCode() { return errorCode; } +} + +public class RoleNotFoundException extends DomainException { + public RoleNotFoundException(Long id) { + super(ErrorCode.ROLE_NOT_FOUND, "Role not found: " + id); + } +} + +public class DuplicateRoleException extends DomainException { + public DuplicateRoleException(String roleName) { + super(ErrorCode.DUPLICATE_ROLE, "Duplicate role: " + roleName); + } +} +``` + +**Spring 예외 처리:** +```java +package com.example.application.exception; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(RoleNotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleRoleNotFound(RoleNotFoundException ex) { + return new ErrorResponse(ex.getErrorCode(), ex.getMessage()); + } + + @ExceptionHandler(DuplicateRoleException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleDuplicateRole(DuplicateRoleException ex) { + return new ErrorResponse(ex.getErrorCode(), ex.getMessage()); + } + + @ExceptionHandler(DomainException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleDomainException(DomainException ex) { + return new ErrorResponse(ex.getErrorCode(), ex.getMessage()); + } +} +``` + +**결정 사항:** +- 도메인 예외는 `ErrorCode` enum으로 코드 체계化管理 +- `@ControllerAdvice`에서 도메인 예외를 HTTP 상태码로 변환 +- 인프라 예외(SQLException 등)는 도메인 예외로 래핑 + +### 4. 트랜잭션 경계: Application Service 단위 + +```java +package com.example.application.service; + +@Service +@Transactional(readOnly = true) +public class RoleManagementService implements RoleManagementUseCase { + + private final RoleRepository roleRepository; + private final EventPublisher eventPublisher; + + @Transactional + public RoleDto createRole(CreateRoleCommand command) { + // 도메인 로직 호출 + Role role = Role.create(command.name(), command.description()); + Role saved = roleRepository.save(role); + eventPublisher.publish(new RoleCreatedEvent(saved)); + return toDto(saved); + } + + @Transactional(readOnly = true) + public RoleDto findById(Long id) { + return roleRepository.findById(id) + .map(this::toDto) + .orElseThrow(() -> new RoleNotFoundException(id)); + } +} +``` + +**결정 사항:** +- 트랜잭션 경계는 Application Service 레벨 +- `@Transactional`은 메서드 단위로 명시적 지정 +- 읽기 전용 쿼리는 `readOnly = true` 사용 +- 도메인 서비스는 트랜잭션 어노테이션 없음 (Application Service가 관리) +- 트랜잭션 전파: `REQUIRED` (기본값) 사용 + +--- + +## Alternatives + +### 대안 1: 도메인 계층에 Spring Data JPA 직접 사용 +- **장점**: 단순한 설정, 빠른 개발 +- **단점**: 도메인이 인프라에 강결합, 테스트 어려움 +- **채택 안 함**: 전환 목표에 부합하지 않음 + +### 대안 2: Checked Exception 기반 오류 처리 +- **장점**: 명시적인 예외 선언 +- **단점**: 호출자 코드 복잡성 증가, 트랜잭션 롤백과 통합 어려움 +- **채택 안 함**: Spring 기본 런타임 예외 전략 채택 + +### 대안 3: 도메인 주도 설계(DDD) 애그리거트 단위 트랜잭션 +- **장점**: 일관성 경계 명확 +- **단점**: 높은 학습 곡선, 초기 개발 속도 저하 +- **미래 고려 사항**: 복잡도 증가 시 마이그레이션 가능 + +--- + +## Consequences + +### 긍정적 결과 +- **테스트 용이성**: 도메인 계층은 순수 Java로 단위 테스트 가능, Spring 의존성 없음 +- **유지보수성**: 경계가 명확하여 변경 영향 범위 파악 용이 +- **확장성**: 포트/어댑터 패턴으로 인프라 교체 용이 (예: JPA → MongoDB) +- **일관된 오류 처리**: 전 계층에서统一的 예외 처리 + +### 부정적 결과 +- **초기 개발 시간**: 기존 코드 대비 포트/어댑터 패턴 도입으로 초기 개발 시간 증가 +- **복잡도 증가**: 다중 계층으로 인한 파일 수 증가 +- **학습 곡선**: 팀원의 DDD/헥사고날 아키텍처 이해 필요 + +### 해결 방안 +- 단계적 마이그레이션: 도메인 계층부터 순차 전환 +- 문서화: 각 패키지 책임 및 의존성 규칙 명시 +- 코드 리뷰 가이드라인: 경계 위반 체크 + +--- + +## 참고 자료 + +- [Ports and Adapters Architecture](https://alistair.cockburn.us/hexagonal-architecture/) +- [Spring Boot Transaction Management](https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction) +- [ErrorCode Enum Pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/_index)