Files
llm-wiki/raw/company-tech-blogs/transaction-port-clean-ddd-spring-medium.md
T

10 KiB

title, source_type, url, archive_url, status, confidence, tags, related_branches, related_projects, created, last_reviewed
title source_type url archive_url status confidence tags related_branches related_projects created last_reviewed
Clean DDD Lessons: Transactions with Spring (UNIL engineering) company-tech-blog https://medium.com/unil-ci-software-engineering/clean-ddd-lessons-transactions-with-spring-e78324bfec9a raw medium
ca-transaction-boundary
transaction-port
hexagonal
clean-architecture
feature-application-port-usecase-contract
feature-transaction-concurrency-contract
ca-skeleton-operational-contract
2026-05-22 2026-05-27

Clean DDD Lessons: Transactions with Spring

Layer: raw/company-tech-blogs/ — UNIL CI Software Engineering (Medium) 의 원문 발췌·출처 기록. 대학 엔지니어링 팀이 Spring 의존을 application layer 밖으로 밀어내며 TransactionPort 패턴으로 전환한 사례. 검증된 요약은 /ingestwiki/concepts/에 별도 작성. 원본은 raw에 영구 보관.

Parent / 활용 branch (필수)

Branch 이 자료가 정당화하는 결정
raw/branch-notes/feature-application-port-usecase-contract output port 에 runInTransaction(Runnable) 형 메서드를 두는 ca-tmpl 결정의 reference 사례
raw/branch-notes/feature-transaction-concurrency-contract Topic 2 — Transaction Boundary 대안 비교에서 ca-tmpl 채택안 (TransactionPort) 의 동종 사례
raw/project-notes/ca-skeleton-operational-contract §14. Transaction / Concurrency Contract — TransactionPort 결정의 외부 동종 사례 근거 + §5. Exception Ownership Contract — presentation 분리

컨텍스트 / 왜 저장했는지

ca-tmpl 의 TransactionPort 결정에 대한 대안 1: TransactionPort abstraction (output port + TransactionTemplate) 의 실제 적용 사례. 대학 엔지니어링 팀이 Spring 의존을 application layer 밖으로 밀어내는 동일 결정을 한 사례.

출처 / Source

핵심 인용 / Key quotes (verbatim)

[§Annotation 권장] "Prefer to use javax.transaction.Transactional annotation when demarcating the methods of use cases for transactional processing with Spring — this isolates "Use Cases" layer from dependency on a framework (design-time), which is prohibited by CA."

[§Output port 도입 (2024-05-24 업데이트)] "We declare a method in the output port for our persistence adapter" that executes "provided {@linkplain Runnable} in a transaction configured with default propagation strategy and isolation level."

[§TransactionTemplate 구현] "transactionTemplate.executeWithoutResult(status -> runnable.run());"

[§Rollback 제어] "Control the conditions under which the transaction of a use case will be committed or rollback using try-catch blocks and org.springframework.transaction.interceptor.TransactionInterceptor."

[§Presentation 분리] "if a use case completes successfully its main logic (modifying the state of one or several domain entities), the overall state of the system must be consistent — even if presentation of the results (to the user) fails for some reason afterwards."

[§Presentation 분리] "Present result of successful execution of the use case outside transactional boundary."

[§Presentation 분리] "Do not let any errors in presentation logic affect the execution of a transaction."

Claims Extracted / 추출된 주장

Claim ID Claim (이 자료가 직접 말하는 것) Evidence quote Strength Applies to Does not prove
UNIL-TX-C1 use case 메서드 트랜잭션 경계는 Spring @Transactional 이 아닌 javax.transaction.Transactional (framework-neutral) 을 우선 사용 — Use Cases 레이어를 framework 의존성에서 격리 [§Annotation 권장] "Prefer to use javax.transaction.Transactional annotation when demarcating the methods of use cases for transactional processing with Spring — this isolates "Use Cases" layer from dependency on a framework (design-time), which is prohibited by CA." company-case-study Clean Architecture + Spring 환경 use case 클래스 jakarta.transaction.Transactional 이 모든 Spring 버전에서 @Transactional 과 동일하게 작동한다는 뜻은 아님 — Spring 의 인터셉터 처리 여부는 별도
UNIL-TX-C2 후속 업데이트(2024-05-24) 에서는 persistence adapter 의 output port 에 Runnable 을 받는 트랜잭션 실행 메서드를 선언하고 adapter 가 TransactionTemplate 으로 구현하는 방식으로 전환 [§Output port 도입] "We declare a method in the output port for our persistence adapter" + "executes provided {@linkplain Runnable} in a transaction" + [§TransactionTemplate 구현] "transactionTemplate.executeWithoutResult(status -> runnable.run());" company-case-study application layer 가 framework annotation 도 import 하지 않으려는 hexagonal 케이스 nested transaction / propagation / isolation 의 전체 표현력을 Runnable 시그니처로 충분히 표현 가능한지는 본 인용 범위 밖
UNIL-TX-C3 use case 트랜잭션의 commit/rollback 조건은 try-catch 블록 + org.springframework.transaction.interceptor.TransactionInterceptor 조합으로 제어 가능 [§Rollback 제어] "Control the conditions under which the transaction of a use case will be committed or rollback using try-catch blocks and org.springframework.transaction.interceptor.TransactionInterceptor." company-case-study Spring TX 인프라 + use case 레벨 rollback 제어 Try.Failure / Either.Left 같은 functional 타입과의 통합 방법은 본 인용 범위 밖
UNIL-TX-C4 use case 의 핵심 로직이 성공하면 시스템 상태는 일관되어야 하며, 결과 presentation 의 실패가 트랜잭션을 롤백시켜서는 안 된다 — 따라서 presentation 은 트랜잭션 경계 에 위치 [§Presentation 분리] "if a use case completes successfully its main logic ... the overall state of the system must be consistent — even if presentation of the results (to the user) fails" + "Present result of successful execution of the use case outside transactional boundary." + "Do not let any errors in presentation logic affect the execution of a transaction." company-case-study application service + 결과 직렬화/응답 생성 분리 설계 "presentation" 의 정확한 경계 (HTTP 응답만? 로깅도? 이벤트 발행도?) 는 본 인용에서 모호

Usage Boundaries / 적용 경계

  • 이 자료가 직접 증명하는 것:
    • UNIL-TX-C1: framework-neutral annotation 선호 권고 (Use Cases isolation 목적)
    • UNIL-TX-C2: output port + Runnable + TransactionTemplate 패턴의 실제 코드 사례
    • UNIL-TX-C3: try-catch + TransactionInterceptor 로 rollback 조건 제어 가능성
    • UNIL-TX-C4: presentation 을 트랜잭션 밖으로 분리하는 명시적 권고
  • 이 자료가 증명하지 않는 것:
    • 이 패턴이 산업계 표준이라는 주장 (engineering-blog 수준 — 대학 팀 사례)
    • prod 환경에서 트랜잭션 안정성 측정값 (글에 측정 데이터 없음)
    • 모든 propagation/isolation 시나리오 (Runnable 시그니처로 표현 가능 여부)
  • 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
    • ca-tmpl 의 TransactionalUseCaseRunner 가 본 사례의 runInTransaction(Runnable) 보다 한 단계 더 abstraction 을 가짐 — 추가 abstraction 의 비용/이득 분석
    • nested transaction 이 필요한 use case 가 ca-tmpl 에 존재하는지 (있다면 Runnable 시그니처 불충분)
    • presentation 의 정확한 경계 정의 (ca-tmpl 의 controller/serializer 분리 정책과 일치 검증)

메모 / Notes (내 프로젝트 해석)

본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.

  • 적용 시나리오: hexagonal/clean architecture 에서 application(use case) layer 가 Spring @Transactional 직접 import 없이 트랜잭션 경계를 제어해야 할 때.
  • 장점:
    • application layer 가 org.springframework.transaction.* 의존성 0개. dependency rule 보존.
    • presentation 코드가 트랜잭션 안에 묶여 commit 이 지연되거나, 응답 직렬화 실패가 rollback 을 유발하는 문제를 차단.
    • mock 으로 port 갈아끼우면 단위 테스트에서 Spring context 부팅 없이 commit/rollback 시나리오 검증 가능.
  • 단점:
    • runInTransaction(Runnable) 형태가 nested transaction / propagation / isolation 표현력에서 @Transactional 속성 대비 빈약함. 옵션을 늘리면 port 가 다시 Spring 모양에 가까워짐.
    • 모든 use case 에 wrap 코드가 들어가서 시그니처 잡음 증가.
  • ca-tmpl(TransactionPort) 와의 차이: 거의 동일한 채택. ca-tmpl 의 TransactionalUseCaseRunner 는 use case 를 외부에서 감싸 자동으로 경계를 그리는 점에서 한 단계 더 abstraction layer 가 두꺼움.
  • testability 영향: ★ 상승 (Spring context-free 테스트 가능).
  • code 복잡도 영향: 중간 — port 인터페이스 추가, adapter 에서 TransactionTemplate 위임, use case 에서 port.runInTransaction { ... } 명시.