TA 역할 Spring 경계 smoke #3
2 changed files with 116 additions and 0 deletions
|
|
@ -0,0 +1,3 @@
|
||||||
|
# runtime-role-matrix-live-20260714101723-v7-ta-001-attempt-1-run-140dc870c325
|
||||||
|
|
||||||
|
Forge 이슈 작업 브랜치 `forge/runtime-role-matrix-live-20260714101723-v7-ta-001-attempt-1-run-140dc870c325`.
|
||||||
113
docs/adr/ADR-001-spring-boundary-architecture.md
Normal file
113
docs/adr/ADR-001-spring-boundary-architecture.md
Normal file
|
|
@ -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` 필드로 문제 유형 추적
|
||||||
|
- 트랜잭션 경과 시간 로깅으로 성능 병목 탐지
|
||||||
|
- 계층 간 호출 횟수 카운터로 불필요한 조회 감지
|
||||||
Loading…
Add table
Add a link
Reference in a new issue