From a60f902a0e5cb466799a812fa0e474495aa9b35c Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:25:15 +0000 Subject: [PATCH 1/9] 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 2/9] =?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 1c7ce8c00c6f6c5116eb1dad025ccd2d2589fa6d Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:26:01 +0000 Subject: [PATCH 3/9] 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 4/9] =?UTF-8?q?Developer=20=EC=97=AD=ED=95=A0=20Spring=20?= =?UTF-8?q?=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 5/9] =?UTF-8?q?Developer=20=EC=97=AD=ED=95=A0=20Spring=20?= =?UTF-8?q?=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 6/9] =?UTF-8?q?Developer=20=EC=97=AD=ED=95=A0=20Spring=20?= =?UTF-8?q?=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 7/9] =?UTF-8?q?Developer=20=EC=97=AD=ED=95=A0=20Spring=20?= =?UTF-8?q?=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 8/9] 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 9/9] =?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)