From 72cef9b77ffc56f3811df584f46e6146a97e904e Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 10:34:16 +0000 Subject: [PATCH 1/2] forge: open work branch for runtime-role-matrix-live-20260714103305-v8-ta-001-attempt-1-run-a90451e9976a --- ...live-20260714103305-v8-ta-001-attempt-1-run-a90451e9976a.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .forge/runtime-role-matrix-live-20260714103305-v8-ta-001-attempt-1-run-a90451e9976a.md diff --git a/.forge/runtime-role-matrix-live-20260714103305-v8-ta-001-attempt-1-run-a90451e9976a.md b/.forge/runtime-role-matrix-live-20260714103305-v8-ta-001-attempt-1-run-a90451e9976a.md new file mode 100644 index 0000000..abd58a9 --- /dev/null +++ b/.forge/runtime-role-matrix-live-20260714103305-v8-ta-001-attempt-1-run-a90451e9976a.md @@ -0,0 +1,3 @@ +# runtime-role-matrix-live-20260714103305-v8-ta-001-attempt-1-run-a90451e9976a + +Forge 이슈 작업 브랜치 `forge/runtime-role-matrix-live-20260714103305-v8-ta-001-attempt-1-run-a90451e9976a`. From a9fdbcef086ee0cc8d330bb1590588fd48198a7b Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 10:34:29 +0000 Subject: [PATCH 2/2] =?UTF-8?q?TA=20=EC=97=AD=ED=95=A0=20Spring=20?= =?UTF-8?q?=EA=B2=BD=EA=B3=84=20smoke=20(runtime-role-matrix-live-20260714?= =?UTF-8?q?103305-v8-ta-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-spring-layered-architecture-boundaries.md | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 docs/adr/ADR-001-spring-layered-architecture-boundaries.md diff --git a/docs/adr/ADR-001-spring-layered-architecture-boundaries.md b/docs/adr/ADR-001-spring-layered-architecture-boundaries.md new file mode 100644 index 0000000..887ba43 --- /dev/null +++ b/docs/adr/ADR-001-spring-layered-architecture-boundaries.md @@ -0,0 +1,171 @@ +# ADR-001: Spring 계층형 아키텍처 경계 정의 + +## Context + +본 프로젝트(runtime-role-matrix-live)는 Spring Boot 기반 마이크로서비스로, 역할 기반 접근 제어(RBAC) 기능을 제공한다. 현재 계층 간 책임 분담이 명확하지 않아 다음 문제가 발생한다. + +- **Controller**: 요청 검증과 응답 형식화만 담당해야 하지만, 비즈니스 로직이 직접 포함됨 +- **Service**: 트랜잭션 경계가 불명확하여 데이터 일관성 문제 발생 가능 +- **Repository**: 도메인 로직과 데이터 접근 로직이 혼재됨 +- **오류 처리**: 각 계층에서 중구난방式的 예외 처리, 일관된 오류 계약 부재 + +### 기술 스택 + +- Java 17+ +- Spring Boot 3.x +- Spring Data JPA +- Spring Web (REST API) + +--- + +## Decision + +### 1. Controller-Service-Repository 경계 정의 + +| 계층 | 책임 | 포함 사항 | 미포함 사항 | +|------|------|-----------|-------------| +| **Controller** | HTTP 요청/응답 변환, 입력 검증, 라우팅 | `@RestController`, `@RequestMapping`, `@Valid`, DTO 변환, HTTP 상태 코드 결정 | 비즈니스 로직, DB 접근, 트랜잭션 관리 | +| **Service** | 비즈니스 로직, 트랜잭션 경계, 도메인 조율 | `@Service`, `@Transactional`, 도메인 객체 조작, 다중 Repository 호출, 오류 계약 정의 | HTTP 프로토콜 이해, 직접 HTTP 응답 | +| **Repository** | 데이터 접근 추상화, 쿼리 실행 | `@Repository`, `@JpaRepository`, 커스텀 쿼리, 엔티티 매핑 | 비즈니스 로직, 서비스 호출 | + +### 2. 오류 계약 (Error Contract) + +#### 예외 계층 구조 + +``` +BaseException (추상) +├── BusinessException → 사용자에게 의미 있는 오류 (400 Bad Request) +│ ├── RoleNotFoundException +│ ├── DuplicateRoleException +│ └── PermissionDeniedException +└── SystemException → 시스템 내부 오류 (500 Internal Server Error) + ├── DatabaseException + └── ExternalServiceException +``` + +#### 오류 응답 형식 (RFC 7807 Problem Details) + +```json +{ + "type": "https://api.example.com/errors/role-not-found", + "title": "Role Not Found", + "status": 404, + "detail": "Role with id '123' does not exist", + "instance": "/api/v1/roles/123", + "timestamp": "2026-07-14T10:30:00Z", + "traceId": "abc123" +} +``` + +#### 계층별 오류 처리 규칙 + +| 계층 | 예외 발생 시 | 처리 방식 | +|------|-------------|-----------| +| Repository | DB 오류 발생 | `DataAccessException` 래핑하여 Service에 전달 | +| Service | 비즈니스 규칙 위반 | `BusinessException` 발생, 트랜잭션 롤백 | +| Controller | Service 예외 포착 | `@ControllerAdvice`에서 `ProblemDetail` 응답 생성 | + +### 3. 트랜잭션 경계 + +#### 트랜잭션 전파 정책 + +| 시나리오 | 전파 방식 | 설명 | +|---------|----------|------| +| Service → Repository | `REQUIRED` (기본값) | 기존 트랜잭션 참여 또는 새 트랜잭션 생성 | +| 읽기 전용 연산 | `readOnly = true` | 성능 최적화, Hibernate flush mode AUTO | +| 다중 데이터 소스 | `REQUIRES_NEW` | 독립 트랜잭션 필요 시 | + +#### 트랜잭션 경계 설정 규칙 + +```java +// Service 계층에서 트랜잭션 시작 +@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) +public RoleResponse createRole(CreateRoleRequest request) { + // 트랜잭션 경계 내: 모든 DB 연산 포함 + validateRoleName(request.getName()); + Role role = roleRepository.save(toEntity(request)); + permissionRepository.saveAll(toPermissions(role, request.getPermissions())); + return toResponse(role); +} + +// 읽기 전용 트랜잭션 +@Transactional(readOnly = true) +public RoleResponse getRole(Long id) { + return roleRepository.findById(id) + .map(this::toResponse) + .orElseThrow(() -> new RoleNotFoundException(id)); +} +``` + +#### 트랜잭션 격리 수준 + +| 격리 수준 | 사용 시나리오 | 주의사항 | +|----------|--------------|----------| +| `READ_COMMITTED` | 기본값, 대부분의 경우 |Dirty Read 방지 | +| `REPEATABLE_READ` | 동일 트랜잭션 내 일관성 필요 시 | 성능 저하 고려 | +| `SERIALIZABLE` | 극단적 일관성 필요 시 | 동시성 심각히 저하, 피해야 함 | + +--- + +## Alternatives + +### 대안 1: Controller에서 직접 Service 호출, Service에서 직접 예외 변환 + +**장점**: +- 단순한 구조, 소규모 프로젝트에 적합 + +**단점**: +- Service가 HTTP 상태 코드에 종속됨 (관심사 분리 위반) +- 오류 처리 로직 중복 가능성 높음 +- 테스트 어려움 + +### 대안 2: 모든 예외를 RuntimeException으로 통일 + +**장점**: +- 예외 타입 단순화 + +**단점**: +- 오류 유형 구분 불가, 적절한 HTTP 상태 코드 매핑 어려움 +- 클라이언트에게 의미 있는 오류 정보 제공 불가 + +### 대안 3: CQRS 패턴 적용 + +**장점**: +- 읽기/쓰기 분리による 성능 최적화 +- 복잡한 도메인에 적합 + +**단점**: +- 초기 구축 비용 높음 +- 본 프로젝트 규모에는 과도한 설계 + +--- + +## Consequences + +### 긍정적 결과 + +- **단일 책임 원칙 준수**: 각 계층이 명확한 책임만 담당 +- **테스트 용이성**: Mock을 통한 단위 테스트 간결화 +- **일관된 오류 처리**: API 소비자에게 예측 가능한 오류 응답 +- **트랜잭션 보장**: 데이터 일관성 확보, 롤백 규칙 명확 +- **유지보수성 향상**: 변경 영향 범위 제한적 + +### 부정적 결과 + +- **추가 코드 작성**: DTO, Mapper, Exception 클래스 증가 +- **학습 곡선**: 팀원들의 계층 경계 규칙 숙지 필요 +- **성능 오버헤드**: 트랜잭션 관리, AOP 프록시 생성 비용 (미미) + +### 모니터링 필요 사항 + +- 트랜잭션 롤백 빈도 +- BusinessException 발생 패턴 +- API 응답 시간 (Controller → Service 경계) + +--- + +## 참고 자료 + +- [Spring Transaction Management](https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction) +- [RFC 7807 Problem Details for HTTP APIs](https://tools.ietf.org/html/rfc7807) +- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)