diff --git a/.forge/role-ta-live-v3-001-attempt-1-run-053b552c4cea.md b/.forge/role-ta-live-v3-001-attempt-1-run-053b552c4cea.md deleted file mode 100644 index 99259ea..0000000 --- a/.forge/role-ta-live-v3-001-attempt-1-run-053b552c4cea.md +++ /dev/null @@ -1,3 +0,0 @@ -# role-ta-live-v3-001-attempt-1-run-053b552c4cea - -Forge 이슈 작업 브랜치 `forge/role-ta-live-v3-001-attempt-1-run-053b552c4cea`. diff --git a/docs/adr/ADR-001-spring-architecture-boundaries.md b/docs/adr/ADR-001-spring-architecture-boundaries.md deleted file mode 100644 index 909ad0a..0000000 --- a/docs/adr/ADR-001-spring-architecture-boundaries.md +++ /dev/null @@ -1,140 +0,0 @@ -# ADR-001: Spring 계층 경계, 오류 계약 및 트랜잭션 경계 정의 - -## Context - -runtime-role-matrix-live-202607141522-v3 프로젝트에서 TA(Tech Architect) 역할의 역할 기반 접근 제어(RBAC)가 Spring Boot 기반으로 구현된다. Controller, Service, Repository 계층 간 책임 범위, 오류 처리 규약, 트랜잭션 전파 전략을 명확히 정의하지 않으면 다음과 같은 문제가 발생한다. - -- **책임 혼재**: Controller에서 비즈니스 로직 수행 또는 Repository 직접 호출로 결합도 증가 -- **일관성 없는 오류 처리**: 각 계층마다 다른 예외 타입/메시지 반환으로 클라이언트 혼란 -- **트랜잭션 경계 불명확**: 읽기 전용 쿼리에 불필요한 트랜잭션 오버헤드 또는 데이터 정합성 손실 -- **테스트 어려움**: 계층 간 경계 모호 시 단위 테스트 mocking 전략 수립 곤란 - -## Decision - -### 1. Controller-Service-Repository 경계 - -| 계층 | 책임 | 금지 사항 | -|------|------|-----------| -| **Controller** | HTTP 요청/응답 변환, 입력 검증(Bean Validation), 라우팅, 예외 매핑 | 비즈니스 로직 직접 구현, Repository 직접 호출, @Transactional 선언 | -| **Service** | 비즈니스 로직 수행, 도메인 객체 조합, 트랜잭션 경계 설정, 오류 변환 | HTTP 관련 코드(HttpServletRequest 등), 직접 JDBC/ORM 쿼리 실행 | -| **Repository** | 영속성 작업(DB CRUD), 쿼리 실행, JPA Entity 관리 | 비즈니스 로직, 다른 Repository 직접 호출, @Transactional 선언(상위 계층 위임) | - -### 2. 오류 계약 (Error Contract) - -모든 계층에서 발생하는 오류는 `RuntimeException` 계층 구조로 표준화한다. - -``` -BaseException (RuntimeException) -├── BusinessException → 사용자에게 의미 있는 메시지, HTTP 4xx 매핑 -│ ├── RoleNotFoundException -│ ├── DuplicateRoleException -│ └── InsufficientPermissionException -└── SystemException → 내부 오류, HTTP 5xx 매핑 - ├── DatabaseException - └── ExternalServiceException -``` - -**오류 응답 형식 (RFC 7807 Problem Details)**: - -```json -{ - "type": "https://api.example.com/errors/role-not-found", - "title": "Role Not Found", - "status": 404, - "detail": "ID가 'admin'인 역할이 존재하지 않습니다.", - "instance": "/api/v1/roles/admin", - "timestamp": "2026-07-14T15:22:00Z", - "traceId": "abc123" -} -``` - -**@ControllerAdvice 예외 매핑 규칙**: - -| 예외 타입 | HTTP 상태 코드 | 로깅 레벨 | -|----------|---------------|----------| -| BusinessException | 4xx (설정값) | WARN | -| SystemException | 500 | ERROR | -| ValidationException | 400 | WARN | -| 기타 예외 | 500 | ERROR | - -### 3. 트랜잭션 경계 - -| 작업 유형 | @Transactional 설정 | 전파 방식 | -|----------|---------------------|----------| -| **읽기 전용 조회** | `readOnly = true` | REQUIRED | -| **단일 쓰기 작업** | `readOnly = false` (기본) | REQUIRED | -| **복합 쓰기 작업** | `readOnly = false` | REQUIRED | -| **네스티드 읽기** | `readOnly = true` | NESTED (Savepoint) | -| **독립 읽기** | `readOnly = true` | REQUIRES_NEW | - -**트랜잭션 경계 위치**: Service 계층의 public 메서드에 선언한다. Controller에서 @Transactional 사용을 금지한다. - -**격리 수준 (Isolation Level)**: - -| 시나리오 | 격리 수준 | 선택 이유 | -|---------|----------|-----------| -| 역할 목록 조회 | READ_COMMITTED | 기본값, 동시성 성능 | -| 역할 할당/해제 | REPEATABLE_READ | 데이터 정합성 보장 | -| 설정 변경 | SERIALIZABLE | 극단적 정합성 필요 시 | - -**롤백 규칙**: Checked Exception은 기본적으로 롤백되지 않으므로, 명시적 롤백이 필요한 BusinessException에는 `@Transactional(rollbackFor = BusinessException.class)`를 적용한다. - -### 4. 패키지 구조 - -``` -com.example.runtimematrix -├── controller # REST API 엔드포인트 -│ └── RoleController.java -├── service # 비즈니스 로직 + 트랜잭션 -│ ├── RoleService.java -│ └── impl/ -├── repository # 영속성 접근 -│ ├── RoleRepository.java -│ └── custom/ -├── domain # 엔티티, 밸류 오브젝트 -│ ├── entity/ -│ └── vo/ -├── exception # 예외 계층 구조 -│ ├── BaseException.java -│ ├── BusinessException.java -│ └── SystemException.java -├── dto # 요청/응답 DTO -│ ├── request/ -│ └── response/ -└── config # 설정 클래스 -``` - -## Alternatives - -### 대안 1: Controller-Service-Repository 외에 DTO 변환 계층 추가 - -**선택하지 않은 이유**: 소규모 프로젝트에서 과도한 추상화로 복잡성 증가. DTO 변환은 Mapper 라이브러리(MapStruct) 또는 수동 변환으로 Service 내에서 처리한다. - -### 대안 2: 모든 예외를 RuntimeException으로 통일 - -**선택하지 않은 이유**: 예외 유형 구분 없이는 @ControllerAdvice에서 HTTP 상태 코드 매핑이 어려우며, 클라이언트에게 의미 있는 오류 피드백 제공 곤란. - -### 대안 3: 트랜잭션을 Controller에 선언 - -**선택하지 않은 이유**: HTTP 요청/응답 처리와 트랜잭션 관점 분리 필요. AOP 프록시 기반 트랜잭션은 public 메서드에만 적용되므로 Controller의 메서드 레벨 제어가 불완전하다. - -## Consequences - -### 긍정적 영향 - -- **단위 테스트 용이성**: Service를 MockRepository로 교체하여 독립 테스트 가능 -- **일관된 오류 처리**: 클라이언트가 예측 가능한 오류 응답 형식 수신 -- **트랜잭션 최적화**: 읽기 전용 쿼리에서 readOnly=true로 불필요한 쓰기 잠금 해제 -- **유지보수성**: 계층별 책임 명확화로 개발자 간 협업 효율 향상 - -### 부정적 영향 - -- **추가 클래스 생성**: 예외 계층 구조로 인한 다수의 예외 클래스 필요 -- **DTO 변환 코드**: Service 계층에서 Entity ↔ DTO 변환 로직 추가 -- **트랜잭션 경계 설계 필요**: 각 Service 메서드별 트랜잭션 설정 의사결정 필요 - -### 모니터링 및 검증 - -- 트랜잭션 경계 위반 시 `TransactionException` 발생으로 조기 감지 -- @ControllerAdvice 전역 예외 처리 로깅으로 오류 패턴 분석 가능 -- Integration Test에서 @Transactional(readOnly = true) 조회 후 수정 시도 시 예외 검증