6.4 KiB
6.4 KiB
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계약: JPAJpaRepository확장 또는 커스텀 인터페이스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 규칙:
ErrorResponseDTO 반환:{ "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모듈 범위에 적용 - 향후 마이크로서비스 분할 시 조정 가능