From 29cbeea744bbdaf7f6e52bb96c083c972ee20b0b Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 08:00:06 +0000 Subject: [PATCH] =?UTF-8?q?TA=20=EC=97=AD=ED=95=A0=20Spring=20=EA=B2=BD?= =?UTF-8?q?=EA=B3=84=20smoke=20(role-ta-live-v2-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ADR-001-spring-architecture-boundaries.md | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 docs/adr/ADR-001-spring-architecture-boundaries.md diff --git a/docs/adr/ADR-001-spring-architecture-boundaries.md b/docs/adr/ADR-001-spring-architecture-boundaries.md new file mode 100644 index 0000000..30275f9 --- /dev/null +++ b/docs/adr/ADR-001-spring-architecture-boundaries.md @@ -0,0 +1,99 @@ +# ADR-001: Spring MVC 계층 경계 및 트랜잭션 정책 + +## Context + +runtime-role-matrix-live 프로젝트는 Spring Boot 기반 REST API 서버로, 역할 기반 접근 제어(RBAC) 메트릭스를 실시간 처리한다. 다중 개발자가 동시 개발 시 계층 간 책임 범위가 모호하여 중복 로직, 트랜잭션 누락, 일관되지 않은 오류 응답이 발생하는 문제가 있다. + +## Decision + +### 1. Controller-Service-Repository 경계 + +| 계층 | 책임 | 허용 의존성 | +|------|------|------------| +| **Controller** | HTTP 요청/응답 변환, 입력 검증, HTTP 상태 코드 결정 | Service 계층만 주입 | +| **Service** | 비즈니스 로직, 트랜잭션 경계, 도메인 조율 | Repository, Domain Entity, Value Object | +| **Repository** | 데이터 접근 추상화, JPA Entity 관리 | JPA Entity, EntityManager | + +**구체적 규칙:** +- Controller는 `@RequestBody` DTO만 수신하고, 직접 Entity를 반환하지 않는다. +- Service는 `@Transactional(readOnly = true)`를 기본으로 하고, 쓰기 작업 시 `readOnly = false` 명시한다. +- Repository는 `JpaRepository` 또는 `CrudRepository`를 확장하며, `@Query`로 네이티브 SQL을 최소화한다. +- 도메인 로직은 Service 계층에 위치하며, Controller에 절대 포함하지 않는다. + +### 2. 오류 계약 (Error Contract) + +모든 API 오류는 다음 구조로 일관되게 응답한다: + +```json +{ + "code": "ERR_ROLE_NOT_FOUND", + "message": "역할 ID 123을 찾을 수 없습니다", + "timestamp": "2026-07-14T15:22:00Z", + "path": "/api/v1/roles/123" +} +``` + +**오류 코드 체계:** + +| 접두사 | 의미 | HTTP 상태 | +|--------|------|-----------| +| `ERR_` | 비즈니스/시스템 오류 | 4xx, 5xx | +| `VALIDATION_` | 입력 검증 실패 | 400 | +| `AUTH_` | 인증/인가 오류 | 401, 403 | +| `CONFLICT_` | 리소스 충돌 | 409 | + +**구현 계약:** +- `GlobalExceptionHandler`가 모든 `RuntimeException` 하위 예외를 `@ExceptionHandler`로 처리한다. +- `BusinessException` 추상 클래스를 정의하고, 각 도메인 예외는 이를 확장한다. +- Controller에서 `try-catch`를 절대 사용하지 않는다. +- 예외 메시지는 사용자에게 직접 노출하지 않고, 내부 로그로만 기록한다. + +### 3. 트랜잭션 경계 + +| 시나리오 | 전파 방식 | 격리 수준 | +|----------|-----------|-----------| +| 읽기 전용 조회 | `REQUIRED`, `readOnly=true` | 기본값 (READ_COMMITTED) | +| 단일 엔티티 생성/수정 | `REQUIRED`, `readOnly=false` | 기본값 | +| 다중 테이블 변경 | `REQUIRES_NEW` | `SERIALIZABLE` (명시적 필요 시) | +| 외부 API 호출 포함 | `MANDATORY` (트랜잭션 없으면 예외) | - | + +**구체적 규칙:** +- `@Transactional`은 public 메서드에만 적용한다. +- 내부 메서드 호출(`this.method()`)은 프록시를 우회하므로, 별도 Bean으로 분리한다. +- 읽기 전용 트랜잭션에서 쓰기 시도 시 `InvalidDataAccessApiUsageException` 발생시킨다. +- 롤백은 `RuntimeException`, `DataAccessException`에 대해 자동 수행한다. + +### 4. 패키지 구조 + +``` +com.runtimematrix +├── controller # REST Controller, DTO +├── service # Business Logic, Transaction Boundary +├── repository # Data Access +├── domain # Entity, Value Object, Domain Event +├── exception # BusinessException hierarchy +├── config # Spring Configuration +└── dto # Request/Response DTO +``` + +## Alternatives + +| 대안 | 단점 | 선택하지 않은 이유 | +|------|------|-------------------| +| Controller에 트랜잭션 적용 | 테스트 어려움, 결합도 증가 | Service 계층이 자연스러운 트랜잭션 경계 | +| 예외를 직접 HTTP 응답에 매핑 | 오류 구조 불일치, 유지보수 어려움 | 중앙화된 ExceptionHandler로 일관성 확보 | +| Repository에 비즈니스 로직 포함 | 재사용성 저하, 테스트 어려움 | Service 계층에서 도메인 조율 | + +## Consequences + +**Positive:** +- 계층별 단위 테스트 용이 (Mock 기반) +- 오류 응답 일관성으로 클라이언트 처리 단순화 +- 트랜잭션 범위 명확화로 데이터 정합성 보장 + +**Negative:** +- DTO ↔ Entity 변환 코드 증가 (`MapStruct` 도입 권장) +- 다중 트랜잭션 시 `REQUIRES_NEW` 남용 시 성능 저하 가능 + +**Rollback Plan:** +- ADR 변경 시 기존 API 호환성을 위해 `@Deprecated` 어노테이션과 함께 2버전 마이그레이션 기간 운영