Files
llm-wiki/raw/interviews/transaction-port-vs-spring-transactional.md

110 lines
11 KiB
Markdown

---
title: interview-prep / transaction-port-vs-spring-transactional
source_type: interview-prep
status: raw
related_branches: [feature-application-port-usecase-contract]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, transaction, hexagonal, spring, transactional, transaction-port]
created: 2026-05-28
status_label: collecting
---
# interview-prep: transaction-port-vs-spring-transactional
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트. 다듬어진 답변은 `/interviewize` 후 `wiki/interview/` 에 별도 작성.
## Parent / 부모
- [[raw/branch-notes/feature-application-port-usecase-contract]] — application 계층이 Spring `@Transactional` 을 직접 import 하지 않도록 `TransactionPort` 를 도입한 실 구현 (D3, Decisions 2026-05-28).
- [[raw/project-notes/ca-skeleton-operational-contract]] — ca-tmpl skeleton-wide operational contract.
## 질문 / Question
- 질문 원문: Spring 프로젝트에서 application 계층이 `@Transactional`_직접 부착하지 않고_ `TransactionPort` 같은 추상화로 감싸는 선택의 trade-off 를 설명해 보세요.
- 출처: 예상 질문 (실 면접 아님).
- 받은 날짜·맥락: 아직 없음. Clean Architecture / Hexagonal 패턴 경험 검증용 질문 후보.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상:
- Clean Architecture / Hexagonal 을 했다는 선언이 _framework leakage_ 차단 수준까지 갔는지 변별.
- `@Transactional`_self-invocation 함정_ 인지.
- 다수파 (`@Transactional` 직접) vs 소수파 (TransactionPort) 의 _둘 다 합리적_ 임을 인식하는지.
- 추상이 _enforce 되는_ 형태 (ArchUnit fitness function) 가 함께 있어야 의미가 있다는 점을 알고 있는지.
- 함정 / 흔히 빠지는 답변 패턴:
- "Clean Architecture 라서 추상화" 만 답하고 _구체 이득_ (testability, self-invocation 회피, Spring 의존 surface 축소) 을 설명 못함.
- "boilerplate 가 늘어서 안 쓰는 게 낫다" 만 답하고 다수파의 _proxy leak / self-invocation_ 위험 인식 못함.
- "TransactionPort 가 더 좋다" 같이 한쪽을 _우월_ 로 표현 — 실제로는 _맥락 의존_ 결정.
- 따라올 만한 후속 질문:
- 다수파 (`@Transactional` 직접) 입장이 _왜_ reasonable 한가요?
- `TransactionPort``REQUIRES_NEW` / `noRollbackFor` / `timeout` 까지 표현 가능해야 한다면 API 가 어떻게 커지나요?
- `TransactionTemplate` 기반 구현과 `@Transactional` AOP proxy 기반 구현 중 어느 쪽이 _self-invocation 함정_ 에서 자유롭나요? 왜요?
- `TransactionPort` 가 없으면 application 단위 테스트에서 transaction 동작을 어떻게 _모킹 / fake_ 합니까?
- `application-core` 의 Gradle 에서 `spring-tx`_제거_ 했는데, 왜요?
## 답변 재료 / Raw answer material
- 사실 1 (근거: `feature-application-port-usecase-contract.md` D3 + [[raw/company-tech-blogs/transaction-port-clean-ddd-spring-medium]] `UNIL-TX-C1` + [[raw/company-tech-blogs/transaction-port-vassilis-soum-github-readme]] `VSOUM-TX-C1`): application 계층은 outbound port (`TransactionPort`) 만 호출하고, Spring `@Transactional` 의 직접 import 는 forbidden. Spring 의존은 infrastructure 어댑터 (`SpringTransactionPort`) 에 격리.
- 사실 2 (근거: [[raw/official-docs/at-transactional-spring-official]] `AT-TX-C5`): Spring `@Transactional` AOP proxy 의 _self-invocation 함정_ — 같은 클래스의 메서드가 `this.otherMethod()` 형태로 호출되면 proxy 를 우회해서 transaction 이 적용되지 않음. `TransactionTemplate` 기반 추상화는 proxy 가 아니므로 영향 없음.
- 사실 3 (근거: [[raw/company-tech-blogs/hexagonal-reflectoring-transactional-placement]] `HEX-REFL-C1`, `HEX-REFL-C5`): Hexagonal 표준 _다수파_`@Transactional` 을 application service 에 직접 부착. 이유는 boilerplate 최소화 + Spring 공식 권고 (`AT-TX-C1`) 정합성. 다만 `HEX-REFL-C5` 는 negative claim — 저자가 자기 결정에 대한 명시적 정당화 없이 단순 채택.
- 사실 4 (근거: [[raw/official-docs/spring-tx-management-reference]] `SPRING-TX-MGR-C6`): `readOnly = true` 는 REQUIRED / REQUIRES_NEW propagation 에 _한정_ 해서 적용. ca-tmpl 의 `TransactionPort.inRead` 도 이 제약을 따라 REQUIRED + readOnly 로 구현.
- 사실 5 (근거: `feature-application-port-usecase-contract.md` Decisions 2026-05-28): `application-core` 의 Gradle 에서 `spring-tx`_제거_ 해서 `@Transactional` 어노테이션이 컴파일 classpath 자체에서 _reach 불가능_. ArchUnit rule 과 _belt+suspenders_.
- 사실 6 (근거: 동일 branch-note Decisions 2026-05-28 + 구현 결과): `SpringTransactionPort` 는 mode 별로 미리 빌드된 `TransactionTemplate` 인스턴스 3개 (`writeTemplate`, `readTemplate`, `requiresNewTemplate`) 를 보관해서 호출 시점의 mutation 없이 _동시성 안전_ 보장.
- 사실 7 (근거: 동일 branch-note Claims to Verify "application package의 ArchUnit rule" 행 → `actually-implemented`): `noClasses().that().resideInAPackage("..application..").should().dependOnClassesThat().haveFullyQualifiedName("org.springframework.transaction.annotation.Transactional")` 로 application 의 `@Transactional` import 자동 차단.
- 내가 직접 한 경험:
- ca-tmpl 의 `application-core``TransactionPort` 인터페이스 + 3 메서드 + `TransactionMode` / `Isolation` enum 정의.
- `adapter-persistence``SpringTransactionPort``TransactionTemplate` 기반으로 구현 + 4 unit test (`SpringTransactionPortTest`) — propagation / isolation / readOnly / rollback-on-exception.
- `sample-ticket` 의 aggregate Service (UserService / PostService) 의 모든 `@Transactional``tx.inRead` / `tx.inWrite` 로 일괄 치환. 동작 동등 + Spring 의존 surface 감소.
- ArchUnit rule 추가 후 `app-bootstrap` test classpath 가 `sample-ticket` 을 못 보아 vacuously 통과한 사례를 `testImplementation project(':sample-ticket')` 로 해결 — [[raw/errors/archunit-test-scope-sample-ticket-inclusion-2026-05-28]].
- 트레이드오프:
- **TransactionPort 추상의 _진짜 이득_** 은 testability 가 아니라 (`@Transactional` 메서드도 `@SpringBootTest` 로 테스트 가능) **Spring 의존을 단일 진입점 (`SpringTransactionPort`) 으로 좁히는 것**. Spring 업그레이드 / multi-tenant / multi-DB 시나리오에서 transaction 정책 변경 진입점이 한 클래스.
- **boilerplate 증가는 사실** — 메서드마다 `tx.inWrite(() -> { ... })` 한 단 추가. 작은 팀 / 단일 DB / 단일 transactionManager 환경에서는 다수파 (`@Transactional` 직접) 가 reasonable.
- **소수파 결정의 정당화** 는 _abstraction 자체의 testability 이득_ 이 아니라 _enforce 되는 추상_ 이 함께 있을 때만 성립. ArchUnit fitness function 없이 `TransactionPort` 만 두면 컨벤션이고, fitness function 이 `@Transactional` import 를 실패시키면 _drift 방지 메커니즘_.
- **`TransactionTemplate` vs `@Transactional` AOP proxy**: 후자는 `AT-TX-C5` 의 self-invocation 함정. 전자는 proxy 가 없어서 self-invocation 영향 없음. ca-tmpl 은 후자의 함정을 피하려고 전자 선택.
- 한계 / "이건 안 해봤다":
- `REQUIRES_NEW` 의 실 outbox / audit row 동작 통합 검증 _미수행_`feature-domain-event-outbox-contract` 로 위임.
- `TransactionPort``noRollbackFor` / `timeout` / `transactionManager` (multi-DB) 표현 _불가능_ — 의도적 한정. 필요 시 API 확장 결정.
- Hibernate session statistics 로 `readOnly` flush-mode 측정 PoC _미수행_ — Testcontainers 환경 후.
- prod 운영 검증 없음 — ca-tmpl 은 template repository.
## Sources / 근거
- [[raw/branch-notes/feature-application-port-usecase-contract]] — 결정 D1~D10 + 2026-05-28 구현 결과 + Claims to Verify status.
- [[raw/official-docs/at-transactional-spring-official]] — Spring `@Transactional` 공식 + self-invocation 함정 (`AT-TX-C1`, `AT-TX-C5`).
- [[raw/official-docs/spring-tx-management-reference]] — Spring transaction abstraction (`SPRING-TX-MGR-C3`, `SPRING-TX-MGR-C6`).
- [[raw/official-docs/transaction-template-spring-official]] — `TransactionTemplate` 공식 (`TX-TMPL-C1` ~ `TX-TMPL-C4`).
- [[raw/company-tech-blogs/transaction-port-clean-ddd-spring-medium]] — UNIL TransactionPort 진화 (`UNIL-TX-C1`, `UNIL-TX-C2`).
- [[raw/company-tech-blogs/transaction-port-vassilis-soum-github-readme]] — Vassilis Soum 참고 구현 (`VSOUM-TX-C1` ~ `VSOUM-TX-C4`).
- [[raw/company-tech-blogs/hexagonal-reflectoring-transactional-placement]] — `@Transactional` 직접 부착 다수파 (`HEX-REFL-C1`, `HEX-REFL-C5`).
- [[wiki/concepts/transaction-boundary-abstraction]] — 정제된 trade-off 개념 (canonical 후보, 아직 미생성).
## 미해결 / Unknown
- 모르는 것: `TransactionPort``noRollbackFor` / `timeout` 까지 표현 _해야_ 하는 시점은 언제인가. 현재는 의도적 한정이지만, 실 사업 도메인 들어오면 필요할 수 있음.
- 모르는 것: `TransactionTemplate` 기반 구현이 _완전히_ self-invocation 함정에서 자유로운지의 PoC (port 메서드가 다른 port 메서드 호출 시).
- 모르는 것: multi-DB / multi-tenant 시 `TransactionPort``transactionManager` 선택을 어떻게 표현할지.
- 확인 방법: `feature-domain-event-outbox-contract` (outbox / REQUIRES_NEW), self-invocation PoC, multi-DB 시나리오 추가 branch.
## 답변 경계 / Answer boundary
- 자신 있게 말할 수 있는 범위:
- ca-tmpl `application-core``TransactionPort` 인터페이스 정의 + `adapter-persistence``SpringTransactionPort` 구현 + 4 unit test + sample-ticket 마이그레이션의 _직접 수행_ 범위.
- 다수파 vs 소수파 trade-off 의 _양쪽 근거_ — 다수파의 boilerplate 이득, 소수파의 Spring 의존 surface 축소.
- `@Transactional` AOP proxy 의 self-invocation 함정 vs `TransactionTemplate` 의 직접 호출 차이.
- ArchUnit fitness function + Gradle `spring-tx` 제거의 belt+suspenders 패턴.
- "이 부분은 공식 문서를 다시 보고 답변드리겠습니다" 라고 해야 하는 부분:
- Spring `TransactionInterceptor` / `TransactionAttributeSource`_내부_ 동작 (안 깊이 봄).
- multi-DB 시 `PlatformTransactionManager` 선택의 _운영 best practice_ (안 해봄).
- Hibernate session 의 `readOnly` flush-mode 변경 _구체 동작_ (`needs-confirmation`, Testcontainers PoC 후).
- **절대 과장하지 말 것**:
- `TransactionPort` 가 다수파보다 _우월_ 하다는 식 금지 — _이 맥락 (template repository, 격리 우선)_ 까지만.
- prod 운영 검증 없음 — `locally-verified` 등급.
- `REQUIRES_NEW`_실 outbox 시나리오에서_ 동작 검증됐다고 표현 금지 — 단위 테스트의 `PROPAGATION_REQUIRES_NEW` 설정만 확인.
## Related / 관련
- 관련 면접 질문 (선행/후속): [[raw/interviews/clean-architecture-boundary-enforcement]] (자매 — ArchUnit 의 자동 검증 측면), [[raw/interviews/clean-architecture-module-blueprint]] (선행 — module 분리 자체).
- 영감을 받은 채용공고: (없음).
- 관련 블로그 글감: [[raw/blog-topics/transaction-port-abstraction-over-spring-transactional-2026-05-28]] (같은 주제의 글감).
- 답변 derive 후 위치: 생성 전. 후보 `wiki/interview/architecture/transaction-port-vs-spring-transactional.md`.