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

111 lines
10 KiB
Markdown

---
title: "Clean DDD Lessons: Transactions with Spring (UNIL engineering)"
source_type: company-tech-blog
url: https://medium.com/unil-ci-software-engineering/clean-ddd-lessons-transactions-with-spring-e78324bfec9a
archive_url:
status: raw
confidence: medium
tags: [ca-transaction-boundary, transaction-port, hexagonal, clean-architecture]
related_branches: [feature-application-port-usecase-contract, feature-transaction-concurrency-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Clean DDD Lessons: Transactions with Spring
> Layer: `raw/company-tech-blogs/` — UNIL CI Software Engineering (Medium) 의 **원문 발췌·출처 기록**. 대학 엔지니어링 팀이 Spring 의존을 application layer 밖으로 밀어내며 TransactionPort 패턴으로 전환한 사례.
> 검증된 요약은 `/ingest` 후 `wiki/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
- 원본 URL: https://medium.com/unil-ci-software-engineering/clean-ddd-lessons-transactions-with-spring-e78324bfec9a
- 아카이브 URL: (미수집)
- 저자 / 조직: UNIL CI Software Engineering (스위스 로잔대학교 엔지니어링 팀 기술블로그)
- 발행일: 2024 (최종 업데이트 2024-05-24)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / 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 { ... }` 명시.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/hexagonal-reflectoring-transactional-placement]] (baseline `@Transactional` direct)
- [[raw/company-tech-blogs/transaction-port-vassilis-soum-github-readme]] (functional 통합 변형)
- [[raw/company-tech-blogs/woowahan-hexagonal-multimodule]] (multi-module 보완)
- 인용하는 branch:
- [[raw/branch-notes/feature-application-port-usecase-contract]]
- [[raw/branch-notes/feature-transaction-concurrency-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§14, §5)
- 인용한 wiki 요약: (미작성)
- 대안 그룹: **Topic 2 — Transaction Boundary** (대안 5종: TransactionPort / @Transactional direct / TransactionTemplate / Functional monad / Custom AOP)
- 본 source 의 위치: ca-tmpl 채택안 baseline (TransactionPort abstraction)