TA 역할 Spring 경계 smoke (role-ta-live-v5-001)
All checks were successful
ci / test (pull_request) Successful in 5s

This commit is contained in:
forge-bot 2026-07-14 09:25:50 +00:00
parent a715e432f4
commit 48d28a2098

View file

@ -0,0 +1,162 @@
# ADR-001: Spring 전환 경계, 계약, 오류 및 트랜잭션 결정
**날짜:** 2025-07-14
**상태:** 수락됨
**결정자:** TA 아키텍트
---
## 컨텍스트
runtime-role-matrix-live 프로젝트는 기존 레거시 런타임에서 Spring Boot 기반 런타임으로 전환 중이다. 전환 과정에서 다음 사항에 대한 명확한 경계와 계약이 필요하다:
- Spring 컴포넌트와 비-Spring 레거시 간의 경계
- 컴포넌트 간 계약(인터페이스) 정의
- 예외 처리 정책
- 트랜잭션 전파 및 경계 관리
---
## 결정
### 1. Spring 경계 (Boundary)
| 영역 | 경계 전략 | 설명 |
|------|-----------|------|
| **도입점** | `@SpringBootApplication` 메인 클래스 | 외부 요청의 단일 진입점 |
| **내부 컴포넌트** | `@Configuration` + `@Bean` | Spring 컨테이너 관리 대상 |
| **외부 의존성** | `Adapter` 패턴 | 비-Spring 라이브러리를 Spring Bean으로 감싸기 |
| **레거시 통합** | `@Qualifier` 주입 | Bean 이름으로 명시적 의존성 해결 |
**경계 원칙:**
- Spring 컨테이너 내부에서만 `@Autowired`, `@Inject` 사용
- 컨테이너 외부 레거시 코드는 `ApplicationContext`에서 Bean 조회 후 전달
- 순환 참조 방지: 생성자 주입 우선, `@Lazy` 활용
### 2. 계약 (Contract)
**인터페이스 계층:**
```
┌─────────────────────────────────────┐
│ Presentation Layer (@Controller) │
│ DTO / Request / Response │
└─────────────────┬───────────────────┘
┌─────────────────▼───────────────────┐
│ Application Layer (@Service) │
│ Service Interface │
└─────────────────┬───────────────────┘
┌─────────────────▼───────────────────┐
│ Domain Layer (Entity) │
│ Domain Model │
└─────────────────┬───────────────────┘
┌─────────────────▼───────────────────┐
│ Infrastructure (@Repository) │
│ Repository Interface │
└─────────────────────────────────────┘
```
**계약 규칙:**
- 각 계층은 하위 계층 인터페이스에만 의존 (의존성 역전 원칙)
- `Service``Repository` 계약: JPA `JpaRepository` 확장 또는 커스텀 인터페이스
- `Controller``Service` 계약: 명시적 서비스 인터페이스 정의
- DTO는 불변(immutable) 객체로 설계, `record` 또는 `final` 클래스 사용
### 3. 오류 처리 (Error Handling)
| 계층 | 처리 방식 | 구현 |
|------|-----------|------|
| **도메인** | 도메인 예외 (`DomainException`) | 비즈니스 규칙 위반 시 발생 |
| **인프라** | 데이터 접근 예외 (`DataAccessException`) | Spring의 추상화 예외 활용 |
| **애플리케이션** | 애플리케이션 예외 (`ApplicationException`) | 도메인 예외 감싸기, 로깅 |
| **프레젠테이션** | `@ControllerAdvice` | 일관된 HTTP 응답 반환 |
**예외 계층 구조:**
```
RuntimeException
├── DomainException (비즈니스 로직 오류)
├── ApplicationException (애플리케이션 수준 오류)
│ └── ResourceNotFoundException
│ └── InvalidStateException
└── InfrastructureException (외부 시스템 오류)
└── DataAccessException (Spring)
└── ExternalServiceException
```
**@ControllerAdvice 규칙:**
- `ErrorResponse` DTO 반환: `{ "code", "message", "timestamp", "path" }`
- HTTP 상태 매핑: 400(Bad Request), 404(Not Found), 409(Conflict), 500(Internal)
- 민감 정보 제외: 예외 스택 트레이스 클라이언트 노출 금지
- 로깅: `ERROR` 레벨, 요청 ID 포함
### 4. 트랜잭션 (Transaction)
| 시나리오 | 전파 정책 | 설명 |
|----------|-----------|------|
| **Service → Repository** | `REQUIRED` (기본) | 기존 트랜잭션 참여 또는 신규 생성 |
| **Service → Service** | `REQUIRED` | 같은 트랜잭션 내에서 실행 |
| **읽기 전용** | `readOnly=true` | SELECT 최적화 |
| **다중 데이터소스** | `REQUIRES_NEW` | 독립 트랜잭션 보장 |
| **비트랜잭션 작업** | `NOT_SUPPORTED` | 기존 트랜잭션 일시 중단 |
**트랜잭션 경계 규칙:**
- `@Transactional`은 public 메서드에만 적용 (프록시 제약)
- 클래스 레벨 `@Transactional`보다 메서드 레벨 우선
- 롤백: `RuntimeException`, `Error` 자동 롤백, 체크 예외는 명시적 `rollbackFor` 필요
- 격리 수준: `DEFAULT`(데이터소스 기본값) 유지, 필요 시 명시적 지정
**트랜잭션 순서도:**
```
[요청 수신]
[Controller] ──── 예외 ────▶ @ControllerAdvice ──▶ ErrorResponse
[Service @Transactional]
├── 성공 ──▶ [Repository] ──▶ Commit
├── DomainException ──▶ Rollback
└── ApplicationException ──▶ Rollback + 로깅
```
---
## 대안 검토
### 대안 A: 모든 코드를 한 번에 Spring 전환
- **단점:** 리스크 높음, 점진적 검증 불가
- **선택 안 함**
### 대안 B: 경계 없이 자유 주입
- **단점:** 순환 참조, 테스트 어려움
- **선택 안 함**
### 대안 C: 체크 예외 기반 트랜잭션
- **단점:** 명시적 롤백 선언 필요, 실수 가능성
- **선택 안 함**
---
## 결과 (Consequences)
** positif:**
- 명확한 계층 분리 → 유지보수성 향상
- 일관된 예외 처리 → 디버깅 용이
- 명시적 트랜잭션 경계 → 데이터 무결성 보장
- Spring 표준 패턴 적용 → 팀 역량 일관화
** 부정적:**
- 초기 학습 곡선 (ADR 숙지 필요)
- 레거시 통합 시 어댑터 추가 개발 필요
**제한:**
- 본 ADR은 `role-ta` 모듈 범위에 적용
- 향후 마이크로서비스 분할 시 조정 가능