runtime-role-matrix-live-20.../docs/adr/ADR-001-spring-boundary-contracts-error-tx.md
forge-bot 48d28a2098
All checks were successful
ci / test (pull_request) Successful in 5s
TA 역할 Spring 경계 smoke (role-ta-live-v5-001)
2026-07-14 09:25:50 +00:00

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         │
└─────────────────────────────────────┘

계약 규칙:

  • 각 계층은 하위 계층 인터페이스에만 의존 (의존성 역전 원칙)
  • ServiceRepository 계약: JPA JpaRepository 확장 또는 커스텀 인터페이스
  • ControllerService 계약: 명시적 서비스 인터페이스 정의
  • 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 모듈 범위에 적용
  • 향후 마이크로서비스 분할 시 조정 가능