From 48d28a2098105a9f54652c7c452f2c28b0bb691d Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 09:25:50 +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(role-ta-live-v5-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-001-spring-boundary-contracts-error-tx.md | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 docs/adr/ADR-001-spring-boundary-contracts-error-tx.md diff --git a/docs/adr/ADR-001-spring-boundary-contracts-error-tx.md b/docs/adr/ADR-001-spring-boundary-contracts-error-tx.md new file mode 100644 index 0000000..ff156a1 --- /dev/null +++ b/docs/adr/ADR-001-spring-boundary-contracts-error-tx.md @@ -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` 모듈 범위에 적용 +- 향후 마이크로서비스 분할 시 조정 가능