# 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) 상속 │ │ - @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