From 0fdea049ce421a3598b9310d478a27555c70c41a Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 11:12:53 +0000 Subject: [PATCH 1/2] forge: open work branch for runtime-role-matrix-live-20260714111146-v10-ta-001-attempt-1-run-67f0ca150a2e --- ...ive-20260714111146-v10-ta-001-attempt-1-run-67f0ca150a2e.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .forge/runtime-role-matrix-live-20260714111146-v10-ta-001-attempt-1-run-67f0ca150a2e.md diff --git a/.forge/runtime-role-matrix-live-20260714111146-v10-ta-001-attempt-1-run-67f0ca150a2e.md b/.forge/runtime-role-matrix-live-20260714111146-v10-ta-001-attempt-1-run-67f0ca150a2e.md new file mode 100644 index 0000000..5ba28d6 --- /dev/null +++ b/.forge/runtime-role-matrix-live-20260714111146-v10-ta-001-attempt-1-run-67f0ca150a2e.md @@ -0,0 +1,3 @@ +# runtime-role-matrix-live-20260714111146-v10-ta-001-attempt-1-run-67f0ca150a2e + +Forge 이슈 작업 브랜치 `forge/runtime-role-matrix-live-20260714111146-v10-ta-001-attempt-1-run-67f0ca150a2e`. From 0bc26f2115be07961647d371529a4a10a12ae61a Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 11:13:04 +0000 Subject: [PATCH 2/2] =?UTF-8?q?TA=20=EC=97=AD=ED=95=A0=20Spring=20?= =?UTF-8?q?=EA=B2=BD=EA=B3=84=20smoke=20(runtime-role-matrix-live-20260714?= =?UTF-8?q?111146-v10-ta-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ADR-001-spring-boundary-architecture.md | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 docs/adr/ADR-001-spring-boundary-architecture.md diff --git a/docs/adr/ADR-001-spring-boundary-architecture.md b/docs/adr/ADR-001-spring-boundary-architecture.md new file mode 100644 index 0000000..c8434aa --- /dev/null +++ b/docs/adr/ADR-001-spring-boundary-architecture.md @@ -0,0 +1,137 @@ +# ADR-001: Spring 경계 아키텍처 및 오류 계약 + +## Context + +본 프로젝트(runtime-role-matrix-live)는 Spring Boot 기반의 REST API 서버로, 역할(Role) 기반 접근 제어 및 매트릭스 관리 기능을 제공한다. 현재 Controller, Service, Repository 계층 간 책임 분담이 명확하지 않고, 예외 처리 및 트랜잭션 경계가 일관되지 않아 유지보수성과 테스트 가능성이 저하되고 있다. + +### 현재 문제점 + +- Controller에서 비즈니스 로직 직접 수행 +- Service 계층의 트랜잭션 경계 불명확 +- 예외 처리 방식이 계층마다 상이 +- Repository 호출 시 구체적 예외가 Service까지 전파 + +## Decision + +### 1. 계층별 책임 정의 + +| 계층 | 책임 | +|------|------| +| **Controller** | HTTP 요청/응답 변환, 입력 검증, HTTP 상태 코드 결정, Service 호출 | +| **Service** | 비즈니스 로직 수행, 트랜잭션 경계 관리, 도메인 객체 조작, 예외 변환 | +| **Repository** | 데이터 접근 추상화, JPA Entity 관리, 쿼리 실행 | + +### 2. 오류 계약 (Error Contract) + +``` +Client Request + │ + ▼ +┌─────────────┐ +│ Controller │ ← @Valid, BindingResult 검증 +└──────┬──────┘ + │ BusinessException 또는 도메인 예외 + ▼ +┌─────────────┐ +│ Service │ ← 트랜잭션 경계, 예외 변환 +└──────┬──────┘ + │ DataAccessException (RuntimeException 래핑) + ▼ +┌─────────────┐ +│ Repository │ ← JPA/DB 접근 +└─────────────┘ +``` + +**예외 계층 구조:** + +| 예외 유형 | 발생 계층 | 처리 방식 | +|-----------|-----------|-----------| +| `MethodArgumentNotValidException` | Controller | 400 Bad Request, 필드 오류 목록 반환 | +| `BusinessException` | Service | 409 Conflict 또는 404 Not Found | +| `DataAccessException` | Repository | Service에서 `PersistenceException`으로 변환 → 500 Internal Server Error | +| `EntityNotFoundException` | Repository/Service | 404 Not Found | + +**표준 오류 응답 형식:** + +```json +{ + "timestamp": "2026-07-14T11:11:46Z", + "status": 400, + "error": "Bad Request", + "message": "Validation failed", + "path": "/api/v1/roles", + "details": [ + { "field": "name", "message": "must not be blank" } + ] +} +``` + +### 3. 트랜잭션 경계 + +| 작업 유형 | 트랜잭션 속성 | 전파 방식 | +|-----------|--------------|-----------| +| 조회 (Read) | `readOnly = true` | `REQUIRED` | +| 단일 생성/수정/삭제 | `readOnly = false` | `REQUIRED` | +| 다중 변경 (Batch) | `readOnly = false` | `REQUIRES_NEW` | + +**규칙:** +- Service 메서드가 트랜잭션 경계의 시작점 +- Controller에서 `@Transactional` 사용 금지 +- Repository는 항상 트랜잭션 내 실행 + +### 4. 의존성 방향 + +``` +Controller ──► Service ──► Repository + │ │ + └──► DTO/VO ◄──┘ +``` + +- Controller는 Service 인터페이스에만 의존 +- Service는 Repository 인터페이스에만 의존 +- Entity는 Repository → Service 방향으로만 이동 +- DTO/VO는 Controller ↔ Service 간 통신에 사용 + +## Alternatives + +### 대안 1: 모든 계층에서 예외 처리 + +| 장점 | 단점 | +|------|------| +| 세밀한 오류 제어 가능 | 예외 처리 코드 중복 | +| 계층별 맞춤 응답 가능 | 일관성 유지 어려움 | + +### 대안 2: @ControllerAdvice 단일화 + +| 장점 | 단점 | +|------|------| +| 예외 처리 중앙화 | 너무 많은 예외 유형 매핑 필요 | +| 응답 형식 일관성 | 디버깅 복잡도 증가 | + +### 선택: 계층별 예외 변환 + @ControllerAdvice + +Service 계층에서 도메인 예외로 변환하고, `@ControllerAdvice`에서 HTTP 응답으로 매핑하는 하이브리드 방식 채택. + +## Consequences + +### 긍정적 결과 + +- **단일 책임 원칙 준수**: 각 계층이 명확한 역할 수행 +- **테스트 용이성**: Mock 기반 단위 테스트 가능 +- **일관된 오류 응답**: API 소비자가 예측 가능한 에러 형식 수신 +- **트랜잭션 관리 용이**: Service 메서드 수준에서 트랜잭션 제어 + +### 부정적 결과 + +- **추가 코드 작성**: 예외 변환 클래스와 DTO 증가 +- **학습 곡선**: 개발자별 아키텍처 이해 필요 +- **트랜잭션 경계 설계 주의**: 잘못된 전파 설정 시 데이터 불일치 위험 + +### 추적 항목 + +| 항목 | 상태 | 비고 | +|------|------|------| +| 예외 계층 구조 구현 | Pending | BusinessException, PersistenceException 정의 | +| @ControllerAdvice 구성 | Pending | GlobalExceptionHandler | +| Service 트랜잭션 어노테이션 | Pending | @Transactional 적용 | +| 오류 응답 DTO 정의 | Pending | ErrorResponse, FieldErrorResponse |