runtime-role-matrix-live-20.../docs/adr/ADR-001-spring-boundary-architecture.md
2026-07-14 11:13:04 +00:00

4.8 KiB

ADR-001: Spring 경계 아키텍처 및 오류 계약

Context

본 프로젝트(runtime-role-matrix-live)는 Spring Boot 기반의 REST API 서버로, 역할(Role) 기반 접근 제어 및 매트릭스 관리 기능을 제공한다. 현재 Controller, Service, Repository 계층 간 책임 분담이 명확하지 않고, 예외 처리 및 트랜잭션 경계가 일관되지 않아 유지보수성과 테스트 가능성이 저하되고 있다.

현재 문제점

  • Controller에서 비즈니스 로직 직접 수행
  • Service 계층의 트랜잭션 경계 불명확
  • 예외 처리 방식이 계층마다 상이
  • Repository 호출 시 구체적 예외가 Service까지 전파

Decision

1. 계층별 책임 정의

계층 책임
Controller HTTP 요청/응답 변환, 입력 검증, HTTP 상태 코드 결정, Service 호출
Service 비즈니스 로직 수행, 트랜잭션 경계 관리, 도메인 객체 조작, 예외 변환
Repository 데이터 접근 추상화, JPA Entity 관리, 쿼리 실행

2. 오류 계약 (Error Contract)

Client Request
      │
      ▼
┌─────────────┐
│ Controller  │ ← @Valid, BindingResult 검증
└──────┬──────┘
       │ BusinessException 또는 도메인 예외
       ▼
┌─────────────┐
│  Service    │ ← 트랜잭션 경계, 예외 변환
└──────┬──────┘
       │ DataAccessException (RuntimeException 래핑)
       ▼
┌─────────────┐
│ Repository  │ ← JPA/DB 접근
└─────────────┘

예외 계층 구조:

예외 유형 발생 계층 처리 방식
MethodArgumentNotValidException Controller 400 Bad Request, 필드 오류 목록 반환
BusinessException Service 409 Conflict 또는 404 Not Found
DataAccessException Repository Service에서 PersistenceException으로 변환 → 500 Internal Server Error
EntityNotFoundException Repository/Service 404 Not Found

표준 오류 응답 형식:

{
  "timestamp": "2026-07-14T11:11:46Z",
  "status": 400,
  "error": "Bad Request",
  "message": "Validation failed",
  "path": "/api/v1/roles",
  "details": [
    { "field": "name", "message": "must not be blank" }
  ]
}

3. 트랜잭션 경계

작업 유형 트랜잭션 속성 전파 방식
조회 (Read) readOnly = true REQUIRED
단일 생성/수정/삭제 readOnly = false REQUIRED
다중 변경 (Batch) readOnly = false REQUIRES_NEW

규칙:

  • Service 메서드가 트랜잭션 경계의 시작점
  • Controller에서 @Transactional 사용 금지
  • Repository는 항상 트랜잭션 내 실행

4. 의존성 방향

Controller ──► Service ──► Repository
    │              │
    └──► DTO/VO ◄──┘
  • Controller는 Service 인터페이스에만 의존
  • Service는 Repository 인터페이스에만 의존
  • Entity는 Repository → Service 방향으로만 이동
  • DTO/VO는 Controller ↔ Service 간 통신에 사용

Alternatives

대안 1: 모든 계층에서 예외 처리

장점 단점
세밀한 오류 제어 가능 예외 처리 코드 중복
계층별 맞춤 응답 가능 일관성 유지 어려움

대안 2: @ControllerAdvice 단일화

장점 단점
예외 처리 중앙화 너무 많은 예외 유형 매핑 필요
응답 형식 일관성 디버깅 복잡도 증가

선택: 계층별 예외 변환 + @ControllerAdvice

Service 계층에서 도메인 예외로 변환하고, @ControllerAdvice에서 HTTP 응답으로 매핑하는 하이브리드 방식 채택.

Consequences

긍정적 결과

  • 단일 책임 원칙 준수: 각 계층이 명확한 역할 수행
  • 테스트 용이성: Mock 기반 단위 테스트 가능
  • 일관된 오류 응답: API 소비자가 예측 가능한 에러 형식 수신
  • 트랜잭션 관리 용이: Service 메서드 수준에서 트랜잭션 제어

부정적 결과

  • 추가 코드 작성: 예외 변환 클래스와 DTO 증가
  • 학습 곡선: 개발자별 아키텍처 이해 필요
  • 트랜잭션 경계 설계 주의: 잘못된 전파 설정 시 데이터 불일치 위험

추적 항목

항목 상태 비고
예외 계층 구조 구현 Pending BusinessException, PersistenceException 정의
@ControllerAdvice 구성 Pending GlobalExceptionHandler
Service 트랜잭션 어노테이션 Pending @Transactional 적용
오류 응답 DTO 정의 Pending ErrorResponse, FieldErrorResponse