runtime-role-matrix-live-20.../docs/adr/ADR-001-spring-boundary-architecture.md
2026-07-14 10:59:45 +00:00

10 KiB

ADR-001: Spring 경계 아키텍처 정의

Context

runtime-role-matrix-live 프로젝트는 역할(Role) 기반 접근 제어 시스템을 구현한다. 다중 계층 구조에서 Controller, Service, Repository 간의 책임 분리와 오류 처리, 트랜잭션 관리가 명확히 정의되지 않아 다음 문제가 발생한다.

문제점 영향
Controller에서 비즈니스 로직 직접 실행 단일 책임 원칙 위반, 테스트 어려움
Service에서 unchecked exception 무분별한 전파 일관된 오류 응답 불가
Repository에서 트랜잭션 경계 불분명 데이터 정합성 위험
각 계층 간 계약(contract) 부재 API 스펙 변경 시 파급 효과 예측 불가

Decision

1. Controller-Service-Repository 경계

┌─────────────────────────────────────────────────────────────┐
│  Controller Layer                                           │
│  - HTTP 요청/응답 처리                                        │
│  - 입력 검증 (DTO 변환, Bean Validation)                      │
│  - HTTP 상태 코드 결정                                        │
│  - Service 호출 및 결과 매핑                                  │
│  - 예외를 HTTP 응답으로 변환                                   │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  Service Layer                                              │
│  - 비즈니스 로직 수행                                         │
│  - 도메인 객체 조작                                           │
│  - 트랜잭션 경계 설정 (@Transactional)                        │
│  - Repository 호출                                           │
│  - 도메인 예외을 ServiceException으로 변환                     │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  Repository Layer                                           │
│  - 데이터 접근 (JPA Repository)                               │
│  - 엔티티 ↔ 도메인 객체 변환                                   │
│  - 순수 데이터 조작만 담당                                     │
│  - 예외는 그대로 전파 (DataAccessException)                   │
└─────────────────────────────────────────────────────────────┘

책임 매트릭스

책임 Controller Service Repository
HTTP 파라미터 바인딩
Bean Validation △ (도메인 검증)
비즈니스 로직
트랜잭션 관리
데이터 접근
DTO ↔ Entity 변환 △ (도메인 변환)
예외 → HTTP 응답

2. 오류 계약 (Error Contract)

예외 계층 구조

RuntimeException
├── ServiceException (체크 예외, 비즈니스 오류)
│   ├── RoleNotFoundException
│   ├── RoleAlreadyExistsException
│   └── PermissionDeniedException
└── DataAccessException (Spring, unchecked)

ServiceException 스펙

필드 타입 필수 설명
code String 오류 코드 (e.g., "ROLE_NOT_FOUND")
message String 사용자에게 표시할 메시지
details Map 추가 메타데이터
timestamp Instant 발생 시각

HTTP 상태 코드 매핑

예외 HTTP 상태 이유
RoleNotFoundException 404 리소스 없음
RoleAlreadyExistsException 409 리소스 충돌
PermissionDeniedException 403 권한 없음
ValidationException 400 잘못된 요청
ServiceException (기타) 500 내부 서버 오류

오류 응답 형식 (RFC 7807 Problem Details)

{
  "type": "https://api.example.com/errors/role-not-found",
  "title": "Role Not Found",
  "status": 404,
  "code": "ROLE_NOT_FOUND",
  "message": "ID가 'admin'인 역할을 찾을 수 없습니다.",
  "details": {
    "roleId": "admin",
    "timestamp": "2026-07-14T10:58:18Z"
  }
}

3. 트랜잭션 경계

트랜잭션 전파 정책

시나리오 전파 방식 설명
Service → Repository REQUIRED (기본) 기존 트랜잭션 참여 또는 신규 생성
Service → Service (내부 호출) REQUIRED 같은 트랜잭션 내에서 실행
readOnly 조회 readOnly=true 성능 최적화,Dirty checking 비활성화
쓰기 작업 readOnly=false (기본) 기본값, 명시적 지정 불필요

트랜잭션 경계 설정 규칙

  1. 트랜잭션 시작점: Service 계층의 public 메서드
  2. 트랜잭션 종료점: Service 메서드 종료 시 commit, 예외 발생 시 rollback
  3. Controller에서 @Transactional 금지: HTTP 요청 스레드와 트랜잭션 바인딩 분리
  4. Repository에서 @Transactional 금지: 데이터 접근만 담당

트랜잭션 시퀀스 다이어그램

sequenceDiagram
    participant C as Controller
    participant S as Service
    participant R as Repository
    participant DB as Database

    C->>+S: createRole(dto)
    S->>S: @Transactional 시작
    S->>+R: existsByRoleId(id)
    R->>+DB: SELECT
    DB-->-R: 결과
    R-->-S: false
    alt 역할 존재 시
        S-->>C: 예외 발생 (RoleAlreadyExistsException)
    else 역할 미존재 시
        S->>+R: save(entity)
        R->>+DB: INSERT
        DB-->-R: 저장된 엔티티
        R-->-S: 저장된 엔티티
        S-->>S: @Transactional 커밋
        S-->>-C: 생성된 역할 DTO
    end

    Note over S,DB: 예외 발생 시 자동 Rollback

외부 시스템 연동 시 트랜잭션 처리

┌──────────────────────────────────────────────────────────────┐
│  @Transactional                                              │
│  ┌────────────────┐  ┌────────────────┐  ┌────────────────┐ │
│  │  DB 저장       │  │  메시지 발송    │  │  외부 API 호출  │ │
│  │  (트랜잭션 참여)│  │  (로컬 트랜잭션)│  │  (비트랜잭션)   │ │
│  └────────────────┘  └────────────────┘  └────────────────┘ │
│                                                              │
│  실패 시: DB 저장만 롤백, 메시지/외부API는 별도 보상 처리 필요   │
└──────────────────────────────────────────────────────────────┘

Alternatives

대안 1: Controller에서 직접 Repository 호출

항목 내용
장점 간단한 CRUD에 코드량 감소
단점 비즈니스 로직 분산, 테스트 어려움, 트랜잭션 관리 불가
채택 여부 불채택 - 확장성 및 유지보수성 저하

대안 2: Service 계층 없이 도메인 객체에 비즈니스 로직 포함

항목 내용
장점 도메인 주도 설계(DDD) 접근, 객체지향적
단점 도메인 객체가 프레임워크 의존성 발생, 테스트 복잡
채택 여부 불채택 - 현재 프로젝트 규모에서 과도한 복잡성

대안 3: 전역 예외 처리 (@ControllerAdvice)만 사용, 계층별 예외 변환 없음

항목 내용
장점 구현 단순화
단점 예외 처리 로직 중앙화되어 단일 책임 위반, 테스트 어려움
채택 여부 불채택 - 계층별 명확한 오류 계약 필요

Consequences

긍정적 결과

  • 단일 책임 원칙 준수: 각 계층이 명확한 역할을 담당
  • 테스트 용이성: Mock을 통한 단위 테스트 가능
  • 일관된 오류 처리: RFC 7807 표준 준수, 예측 가능한 API 응답
  • 트랜잭션 관리 명확성: Service 계층에서 트랜잭션 경계 집중 관리
  • 유지보수성 향상: 변경 시 파급 효과 최소화

부정적 결과 (적용 부담)

  • 코드량 증가: DTO 변환, 예외 변환 로직 추가
  • 학습 곡선: 개발자 역량 요구사항 상승
  • 추가 의존성: 예외 계층 구조 관리 필요

모니터링 및 검증

지표 측정 방법
계층 분리 준수율 코드 리뷰 시 Controller에 비즈니스 로직 존재 여부 체크
예외 처리 일관성 @ControllerAdvice 로그 분석
트랜잭션 커밋/롤백 비율 트랜잭션 로그 모니터링

마이그레이션 계획

  1. 기존 코드를 점진적으로 리팩토링 (하위 호환 유지)
  2. 새로운 기능은 ADR 규칙 즉시 적용
  3. 공통 예외 클래스를 exception 패키지에 배치
  4. DTO 클래스를 dto 패키지에 배치