diff --git a/.forge/role-ta-live-v2-001-attempt-1-run-d2b47353cc73.md b/.forge/role-ta-live-v2-001-attempt-1-run-d2b47353cc73.md deleted file mode 100644 index 7878a9f..0000000 --- a/.forge/role-ta-live-v2-001-attempt-1-run-d2b47353cc73.md +++ /dev/null @@ -1,3 +0,0 @@ -# role-ta-live-v2-001-attempt-1-run-d2b47353cc73 - -Forge 이슈 작업 브랜치 `forge/role-ta-live-v2-001-attempt-1-run-d2b47353cc73`. diff --git a/docs/adr/ADR-001-spring-boundary-contract.md b/docs/adr/ADR-001-spring-boundary-contract.md deleted file mode 100644 index dc8def0..0000000 --- a/docs/adr/ADR-001-spring-boundary-contract.md +++ /dev/null @@ -1,99 +0,0 @@ -# ADR-001: Spring 계층 경계, 오류 계약 및 트랜잭션 경계 정의 - -**날짜:** 2026-07-14 -**상태:** 수락됨 -**결정자:** TA 아키텍트 - ---- - -## Context (맥락) - -`runtime-role-matrix-live-202607141522-v2` 프로젝트는 Spring Boot 기반 멀티 모듈 애플리케이션이다. -현재 계층 간 책임 범위가 모호하고, 예외 처리 정책이 불명확하며, 트랜잭션 전파 규칙이 문서화되지 않아 -유지보수성과 일관성에 위험이 존재한다. - ---- - -## Decision (결정) - -### 1. Controller-Service-Repository 경계 - -| 계층 | 책임 | 허용 작업 | -|------|------|-----------| -| **Controller** | HTTP 요청/응답 변환, 입력 검증, HTTP 상태 코드 결정 | `@Valid`, `@RequestBody`, `@PathVariable` 파싱, Service 호출, `ResponseEntity` 반환 | -| **Service** | 비즈니스 로직, 도메인 규칙, 트랜잭션 경계 | Repository 호출, 도메인 객체 조작, 다른 Service 호출 (필요시), `@Transactional` 적용 | -| **Repository** | 데이터 접근 추상화, 쿼리 실행 | JPA `CrudRepository`/`JpaRepository` 메서드, `@Query`, `EntityManager` 직접 사용 | - -**금지 규칙:** -- Controller는 `@Transactional`을 직접 적용하지 않는다. -- Repository는 비즈니스 로직을 포함하지 않는다. -- Service 계층을 건너뛰고 Controller가 Repository를 직접 호출하지 않는다. - -### 2. 오류 계약 (Error Contract) - -모든 계층에서 발생하는 예외는 다음 규칙을 따른다. - -| 예외 유형 | 발생 계층 | HTTP 상태 | 응답 본문 필드 | -|----------|-----------|-----------|---------------| -| `ValidationException` | Controller | 400 | `{ "code": "VALIDATION_ERROR", "message": "...", "field": "..." }` | -| `ResourceNotFoundException` | Service/Repository | 404 | `{ "code": "NOT_FOUND", "message": "...", "resourceId": "..." }` | -| `BusinessRuleViolationException` | Service | 409 | `{ "code": "BUSINESS_RULE_VIOLATION", "message": "...", "rule": "..." }` | -| `DataAccessException` | Repository | 500 | `{ "code": "DATABASE_ERROR", "message": "Internal server error" }` | -| `UnexpectedException` | Any | 500 | `{ "code": "INTERNAL_ERROR", "message": "Internal server error" }` | - -**오류 계약 규칙:** -- ControllerAdvice가 모든 예외를 가로채고 일관된 JSON 구조로 변환한다. -- 내부 예외 메시지는 클라이언트에 노출하지 않는다 (로그에만 기록). -- 예외 계층 구조: `RuntimeException` → `BaseException` → 구체 예외 클래스. - -### 3. 트랜잭션 경계 - -| 시나리오 | 전파 방식 | 격리 수준 | 롤백 규칙 | -|---------|-----------|-----------|-----------| -| Service → Repository 호출 | `REQUIRED` (기본) | `READ_COMMITTED` (기본) | Checked 예외는 롤백하지 않음, Unchecked 예외는 롤백 | -| readOnly 트랜잭션 | `REQUIRED` + `readOnly=true` | `READ_COMMITTED` | 읽기 전용, 쓰기 시 예외 발생 | -| 다중 Service 호출 (같은 트랜잭션) | `REQUIRED` | 상속됨 | 부모 트랜잭션과 동일하게 처리 | -| 별도 트랜잭션 필요 | `REQUIRES_NEW` | 명시적 지정 | 독립 롤백 가능 | - -**트랜잭션 규칙:** -- `@Transactional`은 public 메서드에만 적용한다. -- 클래스 레벨 `@Transactional`보다 메서드 레벨이 우선한다. -- `readOnly=true`는 SELECT 성능 최적화를 위해 명시적으로 설정한다. - ---- - -## Alternatives (대안) - -### 대안 1: DTO 직접 전달 방식 -Controller가 Entity를 직접 Service에 전달하고 Service가 Entity를 직접 반환한다. -- **단점:** 계층 간 결합도 증가, 테스트 어려움, 도메인 오염 위험. -- **선택하지 않음** - -### 대안 2: 전역 예외 처리 미사용 -각 Controller에서 개별적으로 예외를 처리한다. -- **단점:** 코드 중복, 응답 형식 불일치, 유지보수 비용 증가. -- **선택하지 않음** - -### 대안 3: Programmatic Transaction -`TransactionTemplate`을 사용하여 명시적으로 트랜잭션을 관리한다. -- **단점:** 선언적 트랜잭션 대비 코드 복잡, 실수 가능성 증가. -- **선택하지 않음** - ---- - -## Consequences (결과) - -### 긍정적 결과 -- 계층별 책임이 명확해져 개발자 간 협업 효율 향상 -- 일관된 오류 응답으로 API 소비자가 예측 가능한 에러 처리 가능 -- 선언적 트랜잭션으로 데이터 무결성 보장 - -### 부정적 결과 -- DTO 변환 코드가 추가됨 (Mapper 또는 수동 변환) -- 예외 계층 구조 도입으로 초기 개발 시간 증가 -- `readOnly` 설정 누락 시 성능 최적화 미적용 - -### 모니터링 필요 항목 -- Controller 레벨 HTTP 5xx 에러율 -- Service 레벨 트랜잭션 롤백 빈도 -- Repository 레벨 쿼리 성능 (N+1 문제)