--- 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`.