diff --git a/docs/adr/ADR-001-spring-architecture-boundaries.md b/docs/adr/ADR-001-spring-architecture-boundaries.md new file mode 100644 index 0000000..be26926 --- /dev/null +++ b/docs/adr/ADR-001-spring-architecture-boundaries.md @@ -0,0 +1,131 @@ +# ADR-001: Spring Architecture Boundaries + +## Status +Accepted + +## Context + +### 프로젝트 배경 +runtime-role-matrix-live-202607141522 프로젝트는 역할 기반 접근 제어(RBAC) 매트릭스를 실시간으로 관리하는 시스템이다. Spring Boot 기반으로 구축되며, 다중 클라이언트 환경에서 일관된 아키텍처 패턴이 필요하다. + +### 문제 정의 +1. **Controller-Service-Repository 경계 모호**: 각 계층의 책임이 명확하지 않아 코드의 응집도 감소 및 결합도 증가 +2. **오류 계약 부재**: 예외 처리 전략이 통일되지 않아 일관되지 않은 API 응답 생성 +3. **트랜잭션 경계 불명확**: 서비스 계층에서 트랜잭션 관리 방식이 표준화되지 않아 데이터 일관성 위험 + +### 기술 환경 +- Spring Boot 3.2.x / Java 17 / Jakarta EE 10 +- Spring Data JPA / Spring Web (REST API) + +--- + +## Decision + +### 1. Controller-Service-Repository 경계 정의 + +| 계층 | 책임 | 금지 사항 | +|------|------|----------| +| **Controller** | HTTP 요청/응답, 입력 검증(@Valid), ResponseEntity 반환 | 비즈니스 로직, DB 접근, @Transactional | +| **Service** | 비즈니스 로직, @Transactional 관리, 도메인 객체 조작 | HttpServletRequest/Response 접근, 응답 형식 직접 생성 | +| **Repository** | DB 접근, 쿼리 실행, Entity 관리 | 비즈니스 로직, Service 호출 | + +```java +// Controller 예시 +@RestController @RequiredArgsConstructor +public class RoleMatrixController { + private final RoleMatrixService service; + @PostMapping @Valid @RequestBody RoleCreateRequest req + public ResponseEntity> createRole(req) { + return ResponseEntity.status(CREATED).body(ApiResponse.success(service.createRole(req))); + } +} + +// Service 예시 +@Service @RequiredArgsConstructor @Transactional(readOnly = true) +public class RoleMatrixService { + private final RoleRepository roleRepository; + @Transactional public RoleResponse createRole(RoleCreateRequest req) { + Role role = Role.create(req.getName(), req.getDescription()); + return RoleResponse.from(roleRepository.save(role)); + } +} + +// Repository 예시 +@Repository +public interface RoleRepository extends JpaRepository { + Optional findByName(String name); + @Query("SELECT r FROM Role r LEFT JOIN FETCH r.permissions WHERE r.id = :id") + Optional findByIdWithPermissions(@Param("id") Long id); +} +``` + +### 2. 오류 계약 정의 + +**예외 계층**: `BaseException` → `BusinessException` / `SystemException` + +**오류 응답 표준 형식**: +```json +{"success": false, "error": {"code": "ROLE_NOT_FOUND", "message": "요청한 역할을 찾을 수 없습니다."}, "timestamp": "..."} +``` + +| 예외 유형 | HTTP 상태 | 용도 | +|----------|-----------|------| +| BusinessException | 4xx | 클라이언트 오류 (not found, duplicate, denied) | +| SystemException | 5xx | 서버 오류 (database, external service) | + +```java +@RestControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler(BusinessException.class) + public ResponseEntity> handleBusiness(BusinessException ex) { + return ResponseEntity.status(ex.getErrorCode().getHttpStatus()) + .body(ApiResponse.error(ex.getErrorCode())); + } + @ExceptionHandler(Exception.class) + public ResponseEntity> handleGeneric(Exception ex) { + return ResponseEntity.status(INTERNAL_SERVER_ERROR) + .body(ApiResponse.error(ErrorCode.INTERNAL_SERVER_ERROR)); + } +} +``` + +### 3. 트랜잭션 경계 정의 + +| 상황 | 설정 | 설명 | +|------|------|------| +| 기본 읽기 | `@Transactional(readOnly = true)` | 성능 최적화, Dirty Checking 비활성화 | +| 쓰기 작업 | `@Transactional` | 변경 감지 활성화 | +| 전파 정책 | REQUIRED (기본) | 기존 트랜잭션 참여 또는 신규 생성 | +| 격리 수준 | READ_COMMITTED | 기본값 | +| 롤백 조건 | unchecked exception | RuntimeException 자동 롤백 | + +**규칙**: 트랜잭션 시작/종료점은 Service Layer public 메서드. 다중 Repository 호출 시同一 트랜잭션에서 실행. + +--- + +## Alternatives + +| 대안 | 장점 | 단점 | 미선택 이유 | +|------|------|------|------------| +| Transaction Script | 단순, 직관적 | 복잡도 증가 시 유지보수 어려움 | 확장성 부족 | +| DDD 패턴 | 복잡 도메인 캡슐화 | 학습 곡선 높음, 과도한 추상화 | 현재 규모에서 과도한 복잡성 | +| Checked Exception | 컴파일 타임 강제 | 코드 복잡도 증가, 트랜잭션 충돌 | Spring 예외 처리와 불일치 | +| Repository DTO 반환 | 즉시 변환 가능 | 계층 결합, 테스트 어려움 | 책임 분리 위반 | + +--- + +## Consequences + +### 긍정적 결과 +- **명확한 책임 분리**: 계층별 집중으로 가독성 향상 +- **일관된 오류 처리**: 표준화된 예외/응답으로 API 일관성 확보 +- **테스트 용이성**: 계층별 Mock 가능 +- **트랜잭션 안전성**: 명확한 경계로 데이터 일관성 보장 + +### 부정적 결과 +- **초기 설정 비용**: 예외 계층, DTO 등 부가 코드 증가 +- **추가 추상화**: 간단 CRUD도 Service 경유 필요 + +### 재검토 조건 +- 도메인 복잡도大幅 증가 시 DDD 패턴 도입 검토 +- 성능 병목 발생 시 쿼리 최적화