TA 역할 Spring 경계 smoke (role-ta-live-v4-001)
All checks were successful
ci / test (pull_request) Successful in 4s
All checks were successful
ci / test (pull_request) Successful in 4s
This commit is contained in:
parent
50acd05439
commit
bfe5561dc9
1 changed files with 152 additions and 0 deletions
152
docs/adr/ADR-001-spring-boundary-smoke.adoc
Normal file
152
docs/adr/ADR-001-spring-boundary-smoke.adoc
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
= ADR-001: TA 역할 Spring 경계 Smoke 테스트 설계
|
||||||
|
:doctype: architecture-decision-record
|
||||||
|
:status: accepted
|
||||||
|
:date: 2025-07-14
|
||||||
|
:deciders: TA
|
||||||
|
|
||||||
|
== Context
|
||||||
|
|
||||||
|
TA(Tech Architect) 역할은 Spring 기반 마이크로서비스 아키텍처에서 Controller-Service-Repository 경계의 명확한 분리와 오류 계약, 트랜잭션 경계를 정의해야 한다.
|
||||||
|
|
||||||
|
현재 시스템은 다음 요구사항을 만족해야 한다:
|
||||||
|
|
||||||
|
* **경계 명확성**: Controller는 외부 요청을 수신하고, Service는 비즈니스 로직을 수행하며, Repository는 데이터 접근을 담당한다.
|
||||||
|
* **오류 계약**: 각 계층 간 일관된 예외 처리와 오류 응답 구조를 보장한다.
|
||||||
|
* **트랜잭션 경계**: 데이터 일관성을 유지하면서 필요한 범위에서만 트랜잭션을 적용한다.
|
||||||
|
|
||||||
|
== Decision
|
||||||
|
|
||||||
|
=== 1. Controller-Service-Repository 경계 정의
|
||||||
|
|
||||||
|
[cols="1,2,3"]
|
||||||
|
|===
|
||||||
|
| 계층 | 책임 |Forbidden Dependencies
|
||||||
|
|
||||||
|
| `*Controller*` | HTTP 요청/응답 변환, 입력 검증, HTTP 상태 코드 관리 | Service 직접 호출 불가, Repository 직접 접근 금지
|
||||||
|
|
||||||
|
| `*Service*` | 비즈니스 로직 수행, 도메인 규칙 적용, 트랜잭션 관리 | Controller 직접 참조 불가, Web 관련 어노테이션 사용 금지
|
||||||
|
|
||||||
|
| `*Repository*` | 데이터 접근 추상화, JPA Entity 관리, 쿼리 실행 | 비즈니스 로직 포함 금지, HTTP 관련 코드 금지
|
||||||
|
|===
|
||||||
|
|
||||||
|
==== 경계 규칙
|
||||||
|
|
||||||
|
* **Controller → Service**: DTO를 통해 통신, Service 인터페이스 또는 구체 클래스를 직접 호출 가능
|
||||||
|
* **Service → Repository**: 도메인 Entity 또는 DTO를 전달, JPA Repository 인터페이스 호출
|
||||||
|
* **하위 계층 → 상위 계층**: 의존성 없음 (Repository는 Service를 모름, Service는 Controller를 모름)
|
||||||
|
|
||||||
|
=== 2. 오류 계약 정의
|
||||||
|
|
||||||
|
[cols="1,2,3"]
|
||||||
|
|===
|
||||||
|
| 오류 유형 | 발생 계층 | HTTP 응답
|
||||||
|
|
||||||
|
| `*ValidationException*` | Controller | 400 Bad Request + `{ "code": "VALIDATION_ERROR", "message": "..." }`
|
||||||
|
|
||||||
|
| `*ResourceNotFoundException*` | Service | 404 Not Found + `{ "code": "NOT_FOUND", "message": "..." }`
|
||||||
|
|
||||||
|
| `*BusinessException*` | Service | 409 Conflict 또는 422 + `{ "code": "BUSINESS_ERROR", "message": "..." }`
|
||||||
|
|
||||||
|
| `*DataAccessException*` | Repository | 500 Internal Server Error + `{ "code": "DB_ERROR", "message": "..." }`
|
||||||
|
|
||||||
|
| `*UnexpectedException*` | Any | 500 Internal Server Error + `{ "code": "INTERNAL_ERROR", "message": "..." }`
|
||||||
|
|===
|
||||||
|
|
||||||
|
==== 오류 계약 규칙
|
||||||
|
|
||||||
|
* 모든 예외는 `RuntimeException`을 기반으로 한다
|
||||||
|
* ControllerAdvice에서 전역 예외 처리를 수행한다
|
||||||
|
* 오류 응답은 `ErrorResponse` DTO로 통일한다
|
||||||
|
* 내부 예외 메시지는 로그에만 기록하고 클라이언트에는 노출하지 않는다
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
// ErrorResponse DTO 구조
|
||||||
|
public record ErrorResponse(
|
||||||
|
String code,
|
||||||
|
String message,
|
||||||
|
LocalDateTime timestamp,
|
||||||
|
String path
|
||||||
|
) {}
|
||||||
|
----
|
||||||
|
|
||||||
|
=== 3. 트랜잭션 경계 정의
|
||||||
|
|
||||||
|
[cols="1,2,3"]
|
||||||
|
|===
|
||||||
|
| 범위 | 적용 위치 | 전파 행동
|
||||||
|
|
||||||
|
| `*ReadOnly Transaction*` | Service 조회 메서드 | `readOnly = true`, `propagation = REQUIRED`
|
||||||
|
|
||||||
|
| `*Write Transaction*` | Service 변경 메서드 | `readOnly = false`, `propagation = REQUIRED`
|
||||||
|
|
||||||
|
| `*Nested Transaction*` | 복잡한业务流程 | `propagation = REQUIRES_NEW` (선택적)
|
||||||
|
|===
|
||||||
|
|
||||||
|
==== 트랜잭션 규칙
|
||||||
|
|
||||||
|
* **트랜잭션 시작점**: Service 계층의 public 메서드
|
||||||
|
* **트랜잭션 종료점**: Service 메서드 종료 시 자동 커밋 또는 롤백
|
||||||
|
* **Rollback 조건**: unchecked exception (`RuntimeException`) 발생 시 자동 롤백
|
||||||
|
* **Checked exception**: 명시적 `rollbackFor` 지정 필요
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
@Service
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public class MemberService {
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Member createMember(CreateMemberCommand command) {
|
||||||
|
// 비즈니스 로직
|
||||||
|
return memberRepository.save(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void updateMember(Long id, UpdateMemberCommand command) {
|
||||||
|
Member member = findByIdOrThrow(id);
|
||||||
|
member.update(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
== Alternatives
|
||||||
|
|
||||||
|
=== 대안 1: Controller에서 트랜잭션 관리
|
||||||
|
|
||||||
|
* **설명**: `@Transactional`을 Controller에 적용
|
||||||
|
* **단점**: HTTP 요청 스레드와 트랜잭션 수명이 불일치, Connection 유출 위험
|
||||||
|
* **채택 안 함**: Spring Best Practice 위반
|
||||||
|
|
||||||
|
=== 대안 2: 예외를 Service에서 직접 HTTP 응답으로 변환
|
||||||
|
|
||||||
|
* **설명**: Service에서 `ResponseEntity` 반환
|
||||||
|
* **단점**: Service가 Web 계층에 강결합, 단위 테스트 어려움
|
||||||
|
* **채택 안 함**: 계층 분리 원칙 위반
|
||||||
|
|
||||||
|
=== 대안 3: 모든 계층에서 예외 처리
|
||||||
|
|
||||||
|
* **설명**: 각 계층마다 try-catch로 예외 처리
|
||||||
|
* **단점**: 코드 중복, 일관성 없는 오류 응답
|
||||||
|
* **채택 안 함**: 비효율적이며 유지보수困难
|
||||||
|
|
||||||
|
== Consequences
|
||||||
|
|
||||||
|
=== 긍정적 Consequences
|
||||||
|
|
||||||
|
* **단일 책임 원칙 준수**: 각 계층이 명확한 역할을 담당하여 코드 가독성 향상
|
||||||
|
* **테스트 용이성**: 계층별 Mock을 통한 단위 테스트 용이
|
||||||
|
* **일관된 오류 처리**: 전역 예외 처리로 일관된 API 오류 응답 보장
|
||||||
|
* **트랜잭션 관리 용이**: Service 계층에서 집중 관리로 데이터 일관성 확보
|
||||||
|
|
||||||
|
=== 부정적 Consequences
|
||||||
|
|
||||||
|
* **DTO 증가**: 계층 간 통신을 위한 DTO 클래스 증가
|
||||||
|
* **추가 학습 곡선**: 개발자가 경계 규칙과 예외 계층 구조를 이해해야 함
|
||||||
|
* **잠재적 성능 오버헤드**: DTO 변환 과정에서의 약간의 오버헤드
|
||||||
|
|
||||||
|
=== 모니터링 및 검증
|
||||||
|
|
||||||
|
* **Smoke Test**: 각 계층 경계에서 정상/오류 흐름 검증
|
||||||
|
* **Integration Test**: Controller → Service → Repository 전체 흐름 검증
|
||||||
|
* **트랜잭션 검증**: 롤백 시 데이터 무결성 확인
|
||||||
Loading…
Add table
Add a link
Reference in a new issue