Merge pull request 'TA 역할 Spring 경계 smoke' (#3) from forge/runtime-role-matrix-live-20260714112301-v11-ta-001-attempt-1-run-8946133a28af into main
This commit is contained in:
commit
340c5dc537
2 changed files with 203 additions and 0 deletions
200
docs/adr/ADR-001-spring-boundary-architecture.md
Normal file
200
docs/adr/ADR-001-spring-boundary-architecture.md
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
# ADR-001: Spring 경계 아키텍처 정의
|
||||
|
||||
## Context
|
||||
|
||||
runtime-role-matrix-live 프로젝트는 역할(Role) 기반 접근 제어 시스템을 구현한다. 현재 계층화 아키텍처의 명확한 경계가 정의되어 있지 않아 다음과 같은 문제가 발생한다.
|
||||
|
||||
- **응집도 부족**: Controller에서 비즈니스 로직 직접 수행
|
||||
- **결합도 증가**: Service 간 직접 의존으로 단위 테스트 어려움
|
||||
- **트랜잭션 범위 모호**: Repository 호출 시 트랜잭션 전파 정책 불명확
|
||||
- **오류 처리 불일치**: 각 계층별 예외 처리 방식 상이
|
||||
|
||||
### 현재 시스템 범위
|
||||
|
||||
| 계층 | 책임 |
|
||||
|------|------|
|
||||
| Controller | HTTP 요청/응답 변환, 입력 검증, 라우팅 |
|
||||
| Service | 비즈니스 로직, 트랜잭션 경계, 도메인 조율 |
|
||||
| Repository | 데이터 접근 추상화, 쿼리 실행 |
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. Controller-Service-Repository 경계 정의
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Controller Layer │
|
||||
│ - HTTP 요청 파라미터 바인딩 및 검증 (@Valid) │
|
||||
│ - HTTP 응답 변환 (DTO → ResponseEntity) │
|
||||
│ - 예외 → HTTP 상태码 매핑 │
|
||||
│ - 트랜잭션 경계에 참여하지 않음 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Service Layer │
|
||||
│ - @Transactional 메서드 단위 트랜잭션 경계 │
|
||||
│ - 비즈니스 규칙 및 도메인 로직 실행 │
|
||||
│ - 다중 Repository 조율 │
|
||||
│ - 도메인 객체 생성 및 상태 관리 │
|
||||
│ -Checked Exception → Unchecked Exception 변환 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Repository Layer │
|
||||
│ - JPA Repository (JpaRepository<T, ID>) 상속 │
|
||||
│ - @Query 기반 커스텀 쿼리 │
|
||||
│ - 도메인 엔티티 직접 반환 │
|
||||
│ - 트랜잭션 읽기 전용 (readOnly=true) 활용 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2. 오류 계약 (Error Contract)
|
||||
|
||||
#### 예외 계층 구조
|
||||
|
||||
```
|
||||
RuntimeException (java.lang)
|
||||
│
|
||||
├── RoleNotFoundException → HTTP 404
|
||||
├── RoleAlreadyExistsException → HTTP 409
|
||||
├── InvalidRoleStateException → HTTP 400
|
||||
└── PermissionDeniedException → HTTP 403
|
||||
```
|
||||
|
||||
#### 오류 응답 형식
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-07-14T11:23:01Z",
|
||||
"status": 404,
|
||||
"error": "Not Found",
|
||||
"code": "ROLE_NOT_FOUND",
|
||||
"message": "Role with id '123' does not exist",
|
||||
"path": "/api/v1/roles/123"
|
||||
}
|
||||
```
|
||||
|
||||
#### 전역 예외 처리 규칙
|
||||
|
||||
| 예외 유형 | HTTP 상태 | 응답 코드 |
|
||||
|-----------|-----------|-----------|
|
||||
| RoleNotFoundException | 404 | ROLE_NOT_FOUND |
|
||||
| RoleAlreadyExistsException | 409 | ROLE_ALREADY_EXISTS |
|
||||
| InvalidRoleStateException | 400 | INVALID_ROLE_STATE |
|
||||
| PermissionDeniedException | 403 | PERMISSION_DENIED |
|
||||
| MethodArgumentNotValidException | 400 | VALIDATION_ERROR |
|
||||
| 기타 RuntimeException | 500 | INTERNAL_ERROR |
|
||||
|
||||
### 3. 트랜잭션 경계 정책
|
||||
|
||||
#### 기본 원칙
|
||||
|
||||
| 작업 유형 | 전파 정책 | readOnly |
|
||||
|-----------|-----------|----------|
|
||||
| 조회 (Read) | REQUIRED | true |
|
||||
| 생성 (Create) | REQUIRED | false |
|
||||
| 수정 (Update) | REQUIRED | false |
|
||||
| 삭제 (Delete) | REQUIRED | false |
|
||||
|
||||
#### Service 클래스 설계
|
||||
|
||||
```java
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
public class RoleService {
|
||||
|
||||
@Transactional(readOnly = false)
|
||||
public Role createRole(CreateRoleRequest request) {
|
||||
// 비즈니스 로직
|
||||
}
|
||||
|
||||
@Transactional(readOnly = false)
|
||||
public Role updateRole(Long id, UpdateRoleRequest request) {
|
||||
// 비즈니스 로직
|
||||
}
|
||||
|
||||
public Role findById(Long id) {
|
||||
// readOnly=true 상속
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 격리 수준
|
||||
|
||||
- **기본값**: READ_COMMITTED
|
||||
- **필요 시**: @Transactional(isolation = Isolation.SERIALIZABLE)
|
||||
|
||||
---
|
||||
|
||||
## Alternatives
|
||||
|
||||
### 대안 1: Transactional死在 Controller
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@Transactional
|
||||
public class RoleController { ... }
|
||||
```
|
||||
|
||||
| 항목 | 결함 |
|
||||
|------|------|
|
||||
| 문제점 | HTTP 요청/응답 스레드와 트랜잭션 결합 |
|
||||
| 결과 | 롤백 시 응답 불가 상태 발생 가능 |
|
||||
|
||||
### 대안 2: Service 계층 생략 (Transaction Script)
|
||||
|
||||
```java
|
||||
@RestController
|
||||
public class RoleController {
|
||||
@Autowired RoleRepository repository;
|
||||
public Role create(...) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
| 항목 | 결함 |
|
||||
|------|------|
|
||||
| 문제점 | 복잡한 도메인 로직 축적 시 재사용 어려움 |
|
||||
| 결과 | Controller 비대화, 테스트 어려움 |
|
||||
|
||||
### 대안 3: Checked Exception 직접 전파
|
||||
|
||||
| 항목 | 결함 |
|
||||
|------|------|
|
||||
| 문제점 | 호출자에게 예외 처리 강제, 결합도 증가 |
|
||||
| 결과 | Service 교체 시 Caller 코드 수정 필요 |
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
### 긍정적 결과
|
||||
|
||||
- **단위 테스트 용이성**: Service를 순수 Java로 테스트 가능
|
||||
- **일관된 오류 처리**: 전역 @ControllerAdvice로 중앙화
|
||||
- **트랜잭션 명확성**: 메서드 단위 경계로 디버깅 용이
|
||||
- **유지보수성**: 계층별 책임 분리
|
||||
|
||||
### 부정적 결과
|
||||
|
||||
- **추가 코드 작성**: DTO, Exception, Mapper 클래스 증가
|
||||
- **학습 곡선**: 개발자별 아키텍처 이해 필요
|
||||
- **성능 오버헤드**: Proxy 기반 AOP 약간의 지연 (미미)
|
||||
|
||||
### 모니터링 필요 항목
|
||||
|
||||
- 트랜잭션 롤백 빈도
|
||||
- 예외 발생 패턴 (ROLE_NOT_FOUND 등)
|
||||
- Service 메서드 응답 시간
|
||||
|
||||
---
|
||||
|
||||
## 참고
|
||||
|
||||
- Java: 17+
|
||||
- Spring Boot: 3.2.x
|
||||
- JPA: Hibernate 6.x
|
||||
- 빌드 도구: Maven
|
||||
Loading…
Add table
Add a link
Reference in a new issue