Spring 경계와 계약 아키텍처 정의 #3
1 changed files with 119 additions and 0 deletions
119
docs/adr/ADR-003-transaction-boundary.md
Normal file
119
docs/adr/ADR-003-transaction-boundary.md
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
# ADR-003: 트랜잭션 경계(Transaction Boundary) 정의
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Spring에서 트랜잭션 경계 설정 방식에 따라:
|
||||||
|
- 데이터 무결성 보장
|
||||||
|
- 성능 최적화
|
||||||
|
- 격리 수준(Isolation Level) 제어
|
||||||
|
|
||||||
|
를 적절히 balancing해야 한다.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
### 1. 트랜잭션 전파 정책
|
||||||
|
|
||||||
|
| 전파 유형 | 사용 시점 |
|
||||||
|
|----------|----------|
|
||||||
|
| `REQUIRED` (기본값) | 대부분의 Service 메서드 |
|
||||||
|
| `REQUIRES_NEW` | 독립적인 작업 단위 (로깅, 알림) |
|
||||||
|
| `NESTED` | 저장점(savepoint) 기반 부분 롤백 |
|
||||||
|
| `SUPPORTS` | 읽기 전용 조회 (트랜잭션 없으면 자동 읽기 전용) |
|
||||||
|
|
||||||
|
### 2. 격리 수준(Isolation Level)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||||
|
```
|
||||||
|
|
||||||
|
| 격리 수준 | 더티 리드 | 반복 불가능 읽기 | 팬텀 읽기 |
|
||||||
|
|----------|----------|-----------------|----------|
|
||||||
|
| READ_UNCOMMITTED | 가능 | 가능 | 가능 |
|
||||||
|
| READ_COMMITTED | 불가 | 가능 | 가능 |
|
||||||
|
| REPEATABLE_READ | 불가 | 불가 | 가능 |
|
||||||
|
| SERIALIZABLE | 불가 | 불가 | 불가 |
|
||||||
|
|
||||||
|
**결정:** `READ_COMMITTED`를 기본값으로 사용
|
||||||
|
- 대부분의 비즈니스 시나리오에 적합
|
||||||
|
- 동시성 성능과 일관성의 균형
|
||||||
|
|
||||||
|
### 3. 읽기 전용 트랜잭션
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<Role> getAllRoles() {
|
||||||
|
return roleRepository.findAll();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**적용 규칙:**
|
||||||
|
- 데이터 조회 전용 Service 메서드에 적용
|
||||||
|
- JPA: Hibernate flush mode를 MANUAL로 설정하여 최적화
|
||||||
|
- JDBC: 읽기 전용 커넥션 힌트 제공
|
||||||
|
|
||||||
|
### 4. 트랜잭션 경계 위치
|
||||||
|
|
||||||
|
```
|
||||||
|
[Controller]
|
||||||
|
↓
|
||||||
|
[Service Layer] ← ★ 트랜잭션 경계
|
||||||
|
↓
|
||||||
|
[Repository Layer]
|
||||||
|
↓
|
||||||
|
[Database]
|
||||||
|
```
|
||||||
|
|
||||||
|
**규칙:**
|
||||||
|
- 트랜잭션은 Service 계층에서 시작
|
||||||
|
- Controller에서 @Transactional 사용 금지
|
||||||
|
- Repository에서 @Transactional 사용 금지
|
||||||
|
|
||||||
|
### 5. 롤백 정책
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void createRole(RoleRequest request) {
|
||||||
|
// unchecked exception (RuntimeException): 자동 롤백
|
||||||
|
// checked exception: rollbackFor 명시 필요 시 사용
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**결정:**
|
||||||
|
- 기본값(RuntimeException 및 하위 클래스 자동 롤백) 유지
|
||||||
|
- 비즈니스 예외는 모두 RuntimeException 상속
|
||||||
|
|
||||||
|
### 6. 트랜잭션 타임아웃
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Transactional(timeout = 30) // 30초
|
||||||
|
```
|
||||||
|
|
||||||
|
**적용 규칙:**
|
||||||
|
- 대량 데이터 처리 배치 작업에만 명시적 타임아웃 설정
|
||||||
|
- 일반 API 요청은 기본값(INFINITE) 유지
|
||||||
|
|
||||||
|
## Alternatives
|
||||||
|
|
||||||
|
### 대안 1: Programmatic Transaction
|
||||||
|
- TransactionTemplate 사용
|
||||||
|
- 단점: 코드 복잡성 증가, AOP 이점 상실
|
||||||
|
- 채택하지 않음 (선언적 트랜잭션 채택)
|
||||||
|
|
||||||
|
### 대안 2: Controller 레벨 트랜잭션
|
||||||
|
- 단점: HTTP 요청 단위로 전체 트랜잭션 시야 과도
|
||||||
|
- 채택하지 않음
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
**Positive:**
|
||||||
|
- Service 메서드 단위로 명확한 트랜잭션 경계
|
||||||
|
- 격리 수준 및 전파 정책 세밀한 제어 가능
|
||||||
|
- 읽기 전용 최적화 활용 가능
|
||||||
|
|
||||||
|
**Negative:**
|
||||||
|
- 잘못된 전파 설정 시 예상 외 동작 가능
|
||||||
|
- 다중 데이터소스 환경에서 복잡성 증가
|
||||||
|
|
||||||
|
**Mitigation:**
|
||||||
|
- 전파 정책 사용 시 주석으로 의도 명시
|
||||||
|
- Integration Test에서 트랜잭션 동작 검증
|
||||||
Loading…
Add table
Add a link
Reference in a new issue