From 24aa9dd3db8a9b74c901601f60f41433fc2270a4 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 10:18:28 +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(runtime-role-matrix-live-20260714101723-v7?= =?UTF-8?q?-ta-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ADR-001-spring-boundary-architecture.md | 113 ++++++++++++++++++ 1 file changed, 113 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..59b7cae --- /dev/null +++ b/docs/adr/ADR-001-spring-boundary-architecture.md @@ -0,0 +1,113 @@ +# ADR-001: Spring 경계 아키텍처 정의 + +**날짜**: 2026-07-14 +**상태**: 수락됨 +**결정자**: TA 아키텍트 + +--- + +## Context (배경) + +TA 역할 프로젝트는 역할(Role) 기반 접근 제어 시스템을 구현한다. 다중 계층 구조에서 Controller, Service, Repository 각 계층의 책임 범위, 오류 계약, 트랜잭션 경계를 명확히 정의하지 않으면 다음과 같은 문제가 발생한다. + +- **책임 혼재**: 비즈니스 로직이 Controller에 유출되거나, DB 접근 로직이 Service에 직접 작성 +- **일관성 없는 오류 처리**: 각 계층마다 다른 예외 타입과 HTTP 상태 코드를 반환 +- **트랜잭션 누락/과다**: 읽기 전용 쿼리에 불필요한 트랜잭션이 걸리거나, 다중 쓰기 작업이 원자성 없이 실행 +- **테스트 어려움**: 계층 간 결합으로 인해 단위 테스트가 불가능 + +--- + +## Decision (결정) + +### 1. 계층 책임 경계 + +| 계층 | 책임 | 포함 사항 | 금지 사항 | +|------|------|-----------|-----------| +| **Controller** | HTTP 요청/응답 변환, 입력 검증, 라우팅 | `@RequestMapping`, `@Valid`, `@RequestBody` 파싱, 응답 DTO 변환 | 비즈니스 로직 직접 실행, DB 접근, Service 메서드 직접 호출 없이 로직 처리 | +| **Service** | 비즈니스 로직, 트랜잭션 관리, 도메인 조율 | `@Transactional`, 도메인 객체 조작, 다중 Repository 호출, 정책 enforcement | HTTP 요청/응답 직접 처리, SQL 직접 작성, `@Entity` 직접 매핑 반환 | +| **Repository** | 데이터 접근 추상화, 쿼리 실행 | `JpaRepository` 확장, `@Query`, `EntityManager` 직접 사용, Specification 패턴 | 비즈니스 로직, 트랜잭션 경계 설정, 응답 형식 결정 | + +### 2. 오류 계약 (Error Contract) + +모든 계층에서 발생하는 예외는 **단일 예외 계층 구조**로 변환되어 Controller에서 일관된 HTTP 응답을 생성한다. + +``` +BaseException (추상) +├── BusinessException → HTTP 400 (잘못된 요청) +│ ├── RoleNotFoundException +│ ├── DuplicateRoleException +│ └── InvalidRoleStateException +├── AuthorizationException → HTTP 403 (권한 없음) +└── SystemException → HTTP 500 (서버 오류) + ├── DataAccessException + └── ExternalServiceException +``` + +**오류 응답 형식 (RFC 7807 Problem Details)**: +```json +{ + "type": "https://api.example.com/errors/role-not-found", + "title": "Role Not Found", + "status": 404, + "detail": "Role with id '123' does not exist", + "instance": "/api/v1/roles/123", + "timestamp": "2026-07-14T10:17:23Z" +} +``` + +**계약 규칙**: +- Service 계층은 `BusinessException` 하위 타입만 던진다 +- Repository 계층 예외는 Service 계층에서 `SystemException`으로 래핑한다 +- Controller는 `@ControllerAdvice`에서 전역 예외를 처리한다 +- 예외 메시지는 외부 노출용으로 **사용자 친화적**이어야 한다 + +### 3. 트랜잭션 경계 + +| 시나리오 | 전파 방식 | 격리 수준 | 읽기 전용 | +|----------|-----------|-----------|-----------| +| 단일 조회 (findById) | REQUIRED | DEFAULT | true | +| 목록 조회 (findAll) | REQUIRED | DEFAULT | true | +| 단일 생성 (save) | REQUIRED | DEFAULT | false | +| 벌크 업데이트 (bulk update) | REQUIRED | READ_COMMITTED | false | +| 다중 리포지토리 쓰기 | REQUIRED | READ_COMMITTED | false | +| 읽기 전용 조회 (통계/리포트) | REQUIRED_READ_ONLY | DEFAULT | true | + +**트랜잭션 롤백 규칙**: +- `RuntimeException`, `DataAccessException`은 자동 롤백 +- 검사 예외(`Checked Exception`)는 명시적 `rollbackFor` 지정 필요 +- 읽기 전용 트랜잭션에서 쓰기 시도 시 예외 발생 + +--- + +## Alternatives (대안) + +### 대안 1: Service에서 직접 예외 던지기 (현재 미선택) +- 각 Service 메서드가 다양한 예외 타입을 직접 던짐 +- **단점**: Controller에서 예외 타입별 분기 처리 필요, 일관성 유지 어려움 + +### 대안 2: 트랜잭션 없음 (수동 커밋) +- `TransactionTemplate`을 수동으로 사용 +- **단점**: 코드 복잡성 증가, 실수 가능성 높음 + +### 대안 3: Repository에서 비즈니스 로직 포함 +- **단점**: 데이터 접근과 비즈니스 로직 결합, 테스트 어려움, 재사용성 저하 + +--- + +## Consequences (결과) + +### 긍정적 결과 +- **단위 테스트 용이**: 각 계층이 명확히 분리되어 Mock 기반 테스트 가능 +- **유지보수성**: 오류 처리와 트랜잭션 정책이 한 곳에 집중 +- **일관성**: 모든 API가 동일한 오류 응답 형식 제공 +- **확장성**: 새 예외 타입 추가 시 `BaseException` 하위 클래스만 생성 + +### 부정적 결과 +- **초기 개발 시간**: 예외 계층 구조와 `@ControllerAdvice` 설정 필요 +- **학습 곡선**: 개발자가 계층 책임 경계와 트랜잭션 전파 규칙을 숙지해야 함 +- **오버엔지니어링 위험**: 소규모 프로젝트에서는 과한 추상화 가능성 + +### 모니터링 지표 +- 예외 발생 시 `type` 필드로 문제 유형 추적 +- 트랜잭션 경과 시간 로깅으로 성능 병목 탐지 +- 계층 간 호출 횟수 카운터로 불필요한 조회 감지