runtime-role-matrix-live-20.../docs/adr/ADR-001-spring-architecture-boundaries.md

4.4 KiB

ADR-001: Spring MVC 계층 경계 및 트랜잭션 정책

Context

runtime-role-matrix-live 프로젝트는 Spring Boot 기반 REST API 서버로, 역할 기반 접근 제어(RBAC) 메트릭스를 실시간 처리한다. 다중 개발자가 동시 개발 시 계층 간 책임 범위가 모호하여 중복 로직, 트랜잭션 누락, 일관되지 않은 오류 응답이 발생하는 문제가 있다.

Decision

1. Controller-Service-Repository 경계

계층 책임 허용 의존성
Controller HTTP 요청/응답 변환, 입력 검증, HTTP 상태 코드 결정 Service 계층만 주입
Service 비즈니스 로직, 트랜잭션 경계, 도메인 조율 Repository, Domain Entity, Value Object
Repository 데이터 접근 추상화, JPA Entity 관리 JPA Entity, EntityManager

구체적 규칙:

  • Controller는 @RequestBody DTO만 수신하고, 직접 Entity를 반환하지 않는다.
  • Service는 @Transactional(readOnly = true)를 기본으로 하고, 쓰기 작업 시 readOnly = false 명시한다.
  • Repository는 JpaRepository 또는 CrudRepository를 확장하며, @Query로 네이티브 SQL을 최소화한다.
  • 도메인 로직은 Service 계층에 위치하며, Controller에 절대 포함하지 않는다.

2. 오류 계약 (Error Contract)

모든 API 오류는 다음 구조로 일관되게 응답한다:

{
  "code": "ERR_ROLE_NOT_FOUND",
  "message": "역할 ID 123을 찾을 수 없습니다",
  "timestamp": "2026-07-14T15:22:00Z",
  "path": "/api/v1/roles/123"
}

오류 코드 체계:

접두사 의미 HTTP 상태
ERR_ 비즈니스/시스템 오류 4xx, 5xx
VALIDATION_ 입력 검증 실패 400
AUTH_ 인증/인가 오류 401, 403
CONFLICT_ 리소스 충돌 409

구현 계약:

  • GlobalExceptionHandler가 모든 RuntimeException 하위 예외를 @ExceptionHandler로 처리한다.
  • BusinessException 추상 클래스를 정의하고, 각 도메인 예외는 이를 확장한다.
  • Controller에서 try-catch를 절대 사용하지 않는다.
  • 예외 메시지는 사용자에게 직접 노출하지 않고, 내부 로그로만 기록한다.

3. 트랜잭션 경계

시나리오 전파 방식 격리 수준
읽기 전용 조회 REQUIRED, readOnly=true 기본값 (READ_COMMITTED)
단일 엔티티 생성/수정 REQUIRED, readOnly=false 기본값
다중 테이블 변경 REQUIRES_NEW SERIALIZABLE (명시적 필요 시)
외부 API 호출 포함 MANDATORY (트랜잭션 없으면 예외) -

구체적 규칙:

  • @Transactional은 public 메서드에만 적용한다.
  • 내부 메서드 호출(this.method())은 프록시를 우회하므로, 별도 Bean으로 분리한다.
  • 읽기 전용 트랜잭션에서 쓰기 시도 시 InvalidDataAccessApiUsageException 발생시킨다.
  • 롤백은 RuntimeException, DataAccessException에 대해 자동 수행한다.

4. 패키지 구조

com.runtimematrix
├── controller      # REST Controller, DTO
├── service         # Business Logic, Transaction Boundary
├── repository      # Data Access
├── domain          # Entity, Value Object, Domain Event
├── exception       # BusinessException hierarchy
├── config          # Spring Configuration
└── dto             # Request/Response DTO

Alternatives

대안 단점 선택하지 않은 이유
Controller에 트랜잭션 적용 테스트 어려움, 결합도 증가 Service 계층이 자연스러운 트랜잭션 경계
예외를 직접 HTTP 응답에 매핑 오류 구조 불일치, 유지보수 어려움 중앙화된 ExceptionHandler로 일관성 확보
Repository에 비즈니스 로직 포함 재사용성 저하, 테스트 어려움 Service 계층에서 도메인 조율

Consequences

Positive:

  • 계층별 단위 테스트 용이 (Mock 기반)
  • 오류 응답 일관성으로 클라이언트 처리 단순화
  • 트랜잭션 범위 명확화로 데이터 정합성 보장

Negative:

  • DTO ↔ Entity 변환 코드 증가 (MapStruct 도입 권장)
  • 다중 트랜잭션 시 REQUIRES_NEW 남용 시 성능 저하 가능

Rollback Plan:

  • ADR 변경 시 기존 API 호환성을 위해 @Deprecated 어노테이션과 함께 2버전 마이그레이션 기간 운영