Files
llm-wiki/raw/branch-notes/feature-transaction-concurrency-contract.md

46 KiB

title, source_type, status, branch, parent_branch, related_projects, governing_docs, tags, created, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256
title source_type status branch parent_branch related_projects governing_docs tags created target_merge status_label id kind project work_item inherits refines overrides depends_on contract_packet contract_packet_sha256
branch / feature-transaction-concurrency-contract branch-note raw feature-transaction-concurrency-contract
ca-skeleton
wiki/projects/ca-tmpl/transaction-boundary-abstraction
branch
ca-skeleton
transaction
concurrency
idempotency
2026-05-21 in-progress BR-CA-SKELETON-OPERATIONAL-CONTRACT-012 project-work-item ca-skeleton-operational-contract WI-CA-SKELETON-OPERATIONAL-CONTRACT-012
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-TRANSACTION-001@1
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-DATABASE-001@1
1 f8840ebc8c3775ace9287ef6e88d907803b2e8d13db1a9a4c1da841d1f3abc29

branch: feature-transaction-concurrency-contract

Layer: raw/branch-notes/ — transaction boundary와 concurrency 실패 계약을 정의합니다.

부모 (필수)

ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§14 Transaction/Concurrency) 의 결정/근거/금지 사항을 정제한다.

범위 정합 (2026-06-09 ground-truth 대조): TransactionPort abstraction 자체(inWrite/inRead/inNew, callback signature, @Transactional 금지 ArchUnit rule, inNew pool sizing)는 raw/branch-notes/feature-application-port-usecase-contract (D3/D11/D12) 가 SSOT 이며 이미 구현·검증 완료(Phase C2). 본 branch 는 그 위에 얹는 isolation 정책(D3) · lock-failure 분류 정책(D5) · idempotency 요구 정책(D6) · outbox trigger 정책(D7)소비자/정책 계층이다. D1/D2/D4 는 소비자 관점 재진술이며 원본 계약은 app-port branch 소유 (§Audit & Findings 참조).

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 1
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: transaction·concurrency failure fixture가 통과한다

상속한 프로젝트 결정

Decision Ref Project Summary Branch Application Source
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-TRANSACTION-001@1 application은 Spring transaction annotation 대신 TransactionPort 또는 TransactionalUseCaseRunner를 사용한다 Work Item 완료 조건에 적용 raw/project-notes/ca-skeleton-operational-contract
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-DATABASE-001@1 database는 PostgreSQL 16 단일 stack이다 Work Item 완료 조건에 적용 raw/project-notes/ca-skeleton-operational-contract

브랜치 지역 결정

기존 branch-local 결정은 아래 ## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.

Decision ID Decision Relation Supporting Claims Status

선언한 예외

Override ID Overrides Reason Approval Status

목표

운영 장애는 단순 DB unavailable보다 transaction boundary, lock, deadlock, duplicate command, retry 중복 write에서 자주 발생합니다. CA skeleton은 application use case 기준의 transaction/concurrency 규칙을 가져야 합니다.

  • 이슈:
  • PR:

범위

포함 범위

  • application use case transaction boundary.
  • read-only transaction 기준.
  • optimistic/pessimistic lock 실패 분류.
  • deadlock/lock timeout 분류.
  • duplicate command와 idempotent command 처리 기준.
  • retry 중복 write 방지 기준.
  • outbox pattern 도입 기준.

제외 범위

  • business transaction 상세 설계.
  • distributed transaction 구현.
  • event sourcing 기본 탑재.

근거 (필수, 최소 1개+)

본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 참조.

Source 정당화하는 결정
raw/official-docs/postgres-transaction-isolation-official D3 — PostgreSQL READ COMMITTED 기본값 + statement/transaction-level snapshot 시맨틱 (#PG-ISO-C1~#PG-ISO-C6)
raw/official-docs/mysql-innodb-transaction-isolation-official D3 — MySQL InnoDB 기본값 = REPEATABLE READ (Postgres 와 상이) + consistent/locking read 시맨틱 (#MYSQL-ISO-C1~#MYSQL-ISO-C6)
raw/official-docs/spring-tx-management-reference D1 자체-호출 함정(#SPRING-TX-MGR-C5) + D4 propagation REQUIRED default(#SPRING-TX-MGR-C3) + isolation/readOnly/timeout 적용 범위(#SPRING-TX-MGR-C6)
raw/official-docs/spring-tx-propagation-required-new-nested-official D4 — REQUIRED/REQUIRES_NEW/NESTED propagation 정확한 시맨틱
raw/company-tech-blogs/transaction-port-clean-ddd-spring-medium D1 — UNIL의 동일 진화 경로 (2024-05, company-case-study)
raw/company-tech-blogs/transaction-port-vassilis-soum-github-readme D1 — TransactionPort 참고 구현 (company-case-study)
raw/official-docs/at-transactional-spring-official D1 — @Transactional 직접 부착 대안 + proxy self-invocation 함정
raw/company-tech-blogs/hexagonal-reflectoring-transactional-placement D1 — Hexagonal 표준 다수파 (@Transactional 직접 부착, company-case-study)
raw/official-docs/transaction-template-spring-official D2 — programmatic TransactionTemplate 권장 패턴
raw/official-docs/functional-tx-arrow-kt-resource-docs D1 대안 — Functional Resource monad (Arrow Kt)
raw/company-tech-blogs/custom-transaction-interceptor-catnipcoder D1 대안 — Custom TransactionInterceptor (AOP)
raw/company-tech-blogs/woowahan-hexagonal-multimodule 보완 — multi-module 분리

근거 자료가 raw에 아직 없다면 먼저 raw-source-template 또는 lecture-note-template 으로 raw에 등록한 뒤 여기서 링크.

외부 근거 / 대안 조사 (2026-05-22 — Topic 2; isolation 보강 2026-06-09)

본 branch의 transaction boundary + isolation + propagation 결정에 대한 외부 source 조사. 5종 대안 비교는 (예정) wiki/concepts/transaction-boundary-abstraction.md 참조.

TODO

TODO drained — 결정은 아래 표/결정 사항 참조.

진행 중 메모

  • transaction policy는 repository capability와 연결되어야 합니다.

결정 사항 (decisions)

  • 2026-05-21: transaction boundary는 application use case 기준으로 검토.
  • 2026-05-22: transaction abstraction의 SSOT는 feature-application-port-usecase-contract이며, 이 branch는 lock/isolation/retry/idempotency 분류를 소비자 관점에서 정의.
  • 2026-05-22: application package의 Spring @Transactional 직접 import는 금지. transaction 실행은 TransactionPort 또는 TransactionalUseCaseRunner 구현체를 통해 수행.
  • 2026-05-22: isolation level default = READ_COMMITTED (PostgreSQL/MySQL 양쪽 동일 의미). write-heavy use case는 명시 선언으로 REPEATABLE_READ/SERIALIZABLE 허용. 묵시적 vendor default 사용은 forbidden.
  • 2026-05-22: propagation default = REQUIRED 1택. REQUIRES_NEW는 outbox/audit row 분리 케이스에 한해 명시 선언 시만 허용. NESTED/NEVER 등 묵시 사용은 forbidden.
  • 2026-06-09 (정합 보강): TransactionalUseCaseRunner 는 미채택 대안 — 코드 미존재(§Audit DRIFT-1). isolation "PostgreSQL/MySQL 양쪽 동일 의미" 는 부정확 — 두 DB 기본값이 다름(Postgres=READ COMMITTED, MySQL InnoDB=REPEATABLE READ)이라서 명시 pin 이 필요하다는 것이 정확한 근거(§Audit DRIFT-2).

결정-근거 매핑

각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. company-tech-blog 는 company-case-study 로만 라벨 (official best practice 단정 금지). 선택 조건 열: "이 조건일 때 이 결정, 다른 조건이면 어떤 대안". 분기 없으면 N/A.

Decision ID Decision (요약) 선택 조건 (언제 이 결정 / 언제 대안) Supporting Claims Evidence Strength Open Risk
D1 transaction boundary 는 application use case 기준. application package 의 Spring @Transactional 직접 import 금지 — TransactionPort / TransactionalUseCaseRunner 구현체로만 실행 N/A (모든 application use case 항상) raw/official-docs/at-transactional-spring-official.md#AT-TX-C1 (concrete class 부착 권장), #AT-TX-C2 (interface annotation AspectJ silently ignored), #AT-TX-C5 (proxy self-invocation 함정), raw/official-docs/transaction-template-spring-official.md#TX-TMPL-C1, #TX-TMPL-C2 (programmatic callback 권장), raw/official-docs/spring-tx-management-reference.md#SPRING-TX-MGR-C2 (PlatformTransactionManager 는 SPI — application code 에서 직접 사용 + mock/stub 가능), #SPRING-TX-MGR-C5 (proxy mode default 에서 self-invocation 은 @Transactional 우회 — UseCase 외부 호출 강제 근거), raw/company-tech-blogs/transaction-port-clean-ddd-spring-medium.md, raw/company-tech-blogs/transaction-port-vassilis-soum-github-readme.md (company-case-study — UNIL 동일 진화 경로 + TransactionPort 참고 구현) official-vendor-doc (AT-TX-C1/C2/C5, TX-TMPL-C1/C2, SPRING-TX-MGR-C2/C5) + company-case-study (UNIL / Vassilis Soum) OWNERSHIP: TransactionPort + @Transactional 금지 ArchUnit rule 은 raw/branch-notes/feature-application-port-usecase-contract D3 가 SSOT 이며 이미 구현·검증 완료 (code: application-core/.../transaction/TransactionPort.java, app-bootstrap/.../CleanArchitectureTest.java L167-175 — 주석에 "raw/branch-notes/feature-application-port-usecase-contract D3"). 본 row 는 소비자 재진술. TransactionalUseCaseRunner 는 코드 미존재(§Audit DRIFT-1). Spring 공식은 @Transactional 함정만 명시 — clean/hexagonal 양립성 평가는 cited raw 범위 밖. TransactionPort 채택은 소수파. SPRING-TX-MGR-C5 는 AspectJ mode 동일 우회 의미 아님
D2 TransactionPort adapter 는 내부적으로 TransactionTemplate.execute(...) 사용 (programmatic 권장 패턴) N/A (adapter 구현 항상) raw/official-docs/transaction-template-spring-official.md#TX-TMPL-C1 (callback 접근법으로 boilerplate 제거), #TX-TMPL-C2 (Spring 팀 권장: imperative=TransactionTemplate, reactive=TransactionalOperator), #TX-TMPL-C3 (TransactionCallback + execute() 패턴), #TX-TMPL-C4 (setRollbackOnly() 명시적 rollback) official-vendor-doc OWNERSHIP: SpringTransactionPort (adapter-persistence) 가 모드별 TransactionTemplate 3개를 미리 빌드 — 코드 확인(actually-implemented), app-port branch 소유. 본 row 는 소비자 재진술. adapter 내부 self-invocation 함정(D1 #AT-TX-C5) 이 TransactionTemplate 경로에서 어떻게 처리되는지 별도 검증 필요
D3 isolation level default = READ_COMMITTED (명시 pin). write-heavy use case 는 명시 선언으로 REPEATABLE_READ/SERIALIZABLE 허용. 묵시적 vendor default 사용 forbidden write-heavy / read-consistency 필요 use case → 명시 REPEATABLE_READ/SERIALIZABLE; 그 외 모든 use case → READ_COMMITTED default. READ_UNCOMMITTED → forbidden raw/official-docs/postgres-transaction-isolation-official.md#PG-ISO-C1 (Postgres 기본 = READ COMMITTED), #PG-ISO-C2 (statement-level snapshot), #PG-ISO-C3 (REPEATABLE READ = tx-level snapshot), #PG-ISO-C4 (serialize 실패 에러), #PG-ISO-C5 (SERIALIZABLE = SSI), #PG-ISO-C6 (내부 3 레벨, READ UNCOMMITTED=READ COMMITTED); raw/official-docs/mysql-innodb-transaction-isolation-official.md#MYSQL-ISO-C1 (InnoDB 기본 = REPEATABLE READ), #MYSQL-ISO-C4 (READ COMMITTED = fresh snapshot per read), #MYSQL-ISO-C2/C3 (REPEATABLE READ snapshot + gap lock) official-vendor-doc (PostgreSQL + MySQL 공식) 두 vendor 기본값이 다름(Postgres READ COMMITTED vs MySQL InnoDB REPEATABLE READ)이 명시 pin 필요성의 근거. ca-tmpl Isolation enum 은 현재 READ_COMMITTED 단일값만 노출(code 확인) — REPEATABLE_READ/SERIALIZABLE 노출 + per-use-case 선택 메커니즘은 본 branch 미구현(planned). READ_COMMITTED 의 non-repeatable read/phantom 허용 trade-off 는 read-then-write use case 에서 lost-update 위험 (§구현 가이드 1)
D4 propagation default = REQUIRED 1택. REQUIRES_NEW 는 outbox/audit row 분리 명시 선언 시만. NESTED/NEVER 묵시 사용 forbidden 일반 use case → REQUIRED; outbox/audit row 분리 필요 → 명시 REQUIRES_NEW (inNew); NESTED/NEVER → forbidden raw/official-docs/spring-tx-management-reference.md#SPRING-TX-MGR-C3 (@Transactional default propagation = PROPAGATION_REQUIRED verbatim), #SPRING-TX-MGR-C6 (isolation/readOnly/timeout 은 REQUIRED/REQUIRES_NEW 한정), raw/official-docs/spring-tx-propagation-required-new-nested-official.md (REQUIRED/REQUIRES_NEW/NESTED 정확한 시맨틱) official-vendor-doc (Spring Framework Reference verbatim) OWNERSHIP: code 확인 — SpringTransactionPort inWrite/inRead=REQUIRED, inNew=REQUIRES_NEW (actually-implemented); inNew pool-sizing 공식은 app-port D12 소유. NESTED/NEVER 금지 자체는 ca-tmpl 내부 결정 — Spring 공식 prescribe 아님
D5 optimistic lock conflict 409 vs deadlock/timeout retryable by policy. all locks generic 500 금지 optimistic(@Version) 충돌 → 409 client non-retryable; deadlock(40P01)/serialization(40001) → retryable by policy; pessimistic lock → 명시 시만 raw/official-docs/postgres-transaction-isolation-official.md#PG-ISO-C4 (REPEATABLE READ serialize 실패 → 재시도), raw/official-docs/transaction-template-spring-official.md#TX-TMPL-C4 (명시적 rollback) + 위임: raw/branch-notes/feature-persistence-failure-baseline D6 (raw/official-docs/persistence-spring-dataaccessexception-hierarchy.md#SDA-EX-C5 optimistic locking failure 예시; SQLState 40001→DB_SERIALIZATION_FAILURE, 40P01→DB_DEADLOCK, 둘 다 category CONFLICT·retryable, 23505→DB_UNIQUE_VIOLATION) official-vendor-doc (transaction boundary) + cross-branch-delegation (persistence-failure-baseline D6 — exception→error-code 매핑 SSOT) 본 branch 는 정책(409 vs retryable) 만 소유 — exception→error-code 매핑은 persistence baseline 소유. error-codes.yaml 에 optimistic-lock 전용 code 부재(DB_SERIALIZATION_FAILURE/DB_DEADLOCK/DB_UNIQUE_VIOLATION 만) → optimistic @Version 충돌의 정확한 code 매핑은 registry gap(§구현 가이드 2). code 확인: @Version on WorkLogEntity (actually-implemented); pessimistic lock / lock-timeout 코드 NOT FOUND
D6 duplicate command → idempotency branch key scope. retryable write without idempotency forbidden 동일 idempotency key 재도착 → dedupe(sibling 소유); key 없는 mutating command 의 retryable write → forbidden(본 branch 정책) 위임: raw/branch-notes/feature-rate-limit-idempotency-contract D2 (key scope = (authenticatedPrincipal, idempotencyKey, useCaseName) triple + tenant), D3 (dedup 저장), D6 (TTL 24h), D7 (in-flight → 409 IDEMPOTENT_IN_FLIGHT), D8 (fingerprint mismatch → 422 IDEMPOTENT_REQUEST_MISMATCH). error-codes.yaml: IDEMPOTENT_IN_FLIGHT(409)·IDEMPOTENT_REQUEST_MISMATCH(422) owner_layer application cross-branch-delegation (rate-limit-idempotency 가 key scope/TTL/dedup/in-flight/mismatch 메커니즘 SSOT) 본 branch 는 "non-idempotent retryable write 금지" 정책만 소유 — idempotency 메커니즘은 sibling SSOT. code: @UseCaseCapability(idempotency=IDEMPOTENT|KEYED|NOT_IDEMPOTENT) enum 존재, KEYED 는 rate-limit merge 전까지 ArchUnit 으로 freeze. 어떤 use case 가 idempotency 선언을 요구하는지는 도메인 결정(§구현 가이드 3)
D7 outbox required for atomic external publish. DB commit then lossy publish 금지 external publish 필요 use case → outbox; internal-only domain event → outbox 불필요 위임: raw/branch-notes/feature-domain-event-outbox-contract D2 (transaction+publish atomicity = outbox default), D4 (SKIP LOCKED leadership), D9 (publisher claim tx = READ_COMMITTED + FOR UPDATE SKIP LOCKED) internal-cross-reference (outbox 메커니즘 SSOT = domain-event-outbox-contract) outbox 메커니즘 (SKIP LOCKED polling vs CDC) 의 근거는 raw/branch-notes/feature-domain-event-outbox-contract Decision Evidence Map 참조. D9 의 claim tx isolation(READ_COMMITTED) 이 본 branch D3 default 와 일치 — cross-vendor 일관성 확인 완료

Work Item Contract

각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 기준 작성으로 남아 있으면 이 branch는 완료로 보지 않습니다.

field required rule
Decision yes 구현자가 선택해야 하는 기본값
Allowed yes 허용되는 예외와 조건
Forbidden yes 절대 금지되는 구현/문서 상태
Required registry update conditional error/env/header/log/metric/capability 변경 시 필수
Required contract test yes 계약 위반 시 실패해야 하는 테스트
Failure condition yes review/build에서 실패로 판정할 상태
Canonical extraction target yes wiki/projects 승급 위치

판정 기준

구분 기준
Decision transaction execution은 application port abstraction으로 통과
Allowed read-only query는 readOnly mode만 선언 가능. infra implementation은 Spring transaction 사용 가능
Forbidden application use case의 direct @Transactional, hidden write transaction, idempotency 없는 retryable write
Required fields transaction mode, isolation exception 여부, retryable 여부, idempotency key scope
Failure condition transaction/capability/idempotency 선언 없이 write repository 접근이 가능하면 실패

Decisionized Work Items

item Decision Allowed Forbidden Required test
boundary application use case via TransactionPort infra adapter uses Spring tx direct application @Transactional forbidden import test
read-only query mode readOnly no transaction for pure in-memory query write in read-only use case read-only test
lock failures optimistic conflict vs retryable deadlock/timeout explicit pessimistic lock all locks generic 500 lock mapping test
duplicate command idempotency branch key scope non-idempotent command explicit conflict retryable write without idempotency duplicate write test
outbox required for atomic external publish internal-only domain event no outbox DB commit then lossy publish outbox atomicity test
isolation READ_COMMITTED default explicit REPEATABLE_READ/SERIALIZABLE for write-heavy vendor default 묵시 사용 isolation contract test
@Transactional propagation REQUIRED 명시된 REQUIRES_NEW (outbox/audit row 분리) NESTED/NEVER 묵시 사용 propagation contract test

구현 가이드

결정 이 "무엇" 이라면 본 §는 "어디에 어떻게" 의 사전 명세. 본 branch 의 고유 소유 결정(D3·D5·D6·D7) 만 in-scope. boundary/template/propagation 메커니즘(D1·D2·D4)은 feature-application-port-usecase-contract 가 SSOT 이므로 §구현 가이드에 명세하지 않고 §엣지·의존 + §Audit 에 위임 기록만 남긴다 (R3 OUT_OF_BRANCH_SCOPE).

code anchor 는 2026-06-09 ca-tmpl ground-truth grep 으로 확인. actually-implementedsrc/ 에서 확인된 것, 그 외는 planned.

1. Isolation level 선택 메커니즘 (D3 — 본 branch 핵심 소유)

Trace: D3 ← #PG-ISO-C1C6, #MYSQL-ISO-C1C4. 현재 code: application-core/.../transaction/Isolation.java = READ_COMMITTED 단일값(actually-implemented); adapter-persistence/.../transaction/SpringTransactionPort.java L76 = 3 template 모두 ISOLATION_READ_COMMITTED pin (actually-implemented).

  • UNSUPPORTED_IMPL_DECISION: REPEATABLE_READ/SERIALIZABLE 을 어떻게 노출 할지(① Isolation enum 확장 + TransactionPort.inWrite 에 isolation 파라미터 추가, ② @UseCaseCapability(isolation=...) 속성 추가, ③ 새 TransactionPort 오버로드) — vendor doc 은 어떤 레벨이 존재/무엇을 보장 하는지만 근거. ca-tmpl 노출 API 모양은 근거 없음. trade-off: capability 속성 = ArchUnit 정적 강제 가능하나 use-case 단위 coarse; 메서드 파라미터 = fine-grained 하나 런타임. 권고 기본값: ② capability 속성 (기존 transactionMode 와 동일한 정적 강제 경로 재사용).
  • 변경 파일 후보 (착수 시 헤매지 않도록): application-core/.../transaction/Isolation.java(enum 확장 — 현재 READ_COMMITTED 단일 상수), adapter-persistence/.../transaction/SpringTransactionPort.java(현재 3개 TransactionTemplateISOLATION_READ_COMMITTED 고정 pin → isolation 별 라우팅 필요), application-core/.../capability/UseCaseCapability.java(② 채택 시 속성 추가) + 대응 ArchUnit rule. 이 abstraction 은 app-port branch 가 SSOT 이므로 REPEATABLE_READ/SERIALIZABLE 실제 노출은 feature-application-port-usecase-contract 와 공동 PR 필요 — 그 전까지 호출 경로는 planned.
level 언제 Postgres 시맨틱 (claim) MySQL InnoDB 시맨틱 (claim) ca-tmpl 상태
READ_COMMITTED default (모든 use case) statement 시작 시점 snapshot (#PG-ISO-C2) 매 consistent read 마다 fresh snapshot (#MYSQL-ISO-C4) actually-implemented (enum + pin)
REPEATABLE_READ write-heavy / read 일관성 필요, 명시 tx 시작 snapshot 고정; write 충돌 시 serialize 에러 (#PG-ISO-C3,#PG-ISO-C4) tx 첫 read snapshot 재사용; locking read 시 gap/next-key lock (#MYSQL-ISO-C2,#MYSQL-ISO-C3) actually-implemented (enum 노출, 2026-06-09); call-path 라우팅은 planned (app-port 공동 PR)
SERIALIZABLE 최강 격리, 명시 SSI — anomaly 시 serialization failure (#PG-ISO-C5) autocommit=0 시 plain SELECT→FOR SHARE 묵시 변환 (#MYSQL-ISO-C6) actually-implemented (enum 노출, 2026-06-09); call-path 라우팅은 planned
READ_UNCOMMITTED forbidden 내부적으로 READ COMMITTED 로 매핑 (#PG-ISO-C6) (해당) forbidden (enum 제외)
  • 핵심 근거: Postgres 기본 = READ COMMITTED(#PG-ISO-C1), MySQL InnoDB 기본 = REPEATABLE READ(#MYSQL-ISO-C1) → 기본값이 vendor 마다 다름 → 묵시 vendor default 위임 시 동일 코드가 DB 따라 다른 격리 → 명시 pin 강제. 이것이 D3 forbidden 정책의 근거.

2. Lock-failure 분류 정책 (D5 — persistence baseline 소비)

Trace: D5 ← raw/branch-notes/feature-persistence-failure-baseline D6 (#SDA-EX-C5) + #PG-ISO-C4. 본 branch 는 분류 정책 만 소유; exception→error-code 매핑 은 persistence baseline 소유.

  • UNSUPPORTED_IMPL_DECISION: optimistic @Version 충돌의 정확한 error code — error-codes.yaml 에 optimistic 전용 code 부재(DB_SERIALIZATION_FAILURE/DB_DEADLOCK/DB_UNIQUE_VIOLATION 만, owner=persistence-baseline). 신규 OPTIMISTIC_LOCK_CONFLICT code 추가 vs 기존 generic CONFLICT 재사용 — registry 결정이며 owner_branch=persistence-baseline 이므로 본 branch 는 정책 요구만, code 신설은 persistence baseline 으로 이관(R3).
실패 유형 정책 (본 branch 소유) error-code 매핑 (persistence baseline 소유) code 확인
optimistic lock (@Version) 409, client non-retryable (registry gap — 신규 제안 필요) @Version on WorkLogEntity = actually-implemented. ⚠️ JPA @Version flush 시 OptimisticLockingFailureException 변환 경로는 persistence-baseline D6 #SDA-EX-C7(sql-error-codes.xml 매핑) needs-confirmation 해소 전까지 planned — integration test 로만 검증 가능
deadlock retryable by policy 40P01DB_DEADLOCK (CONFLICT, 409, retryable) error-codes.yaml = actually-implemented
serialization failure retryable by policy 40001DB_SERIALIZATION_FAILURE (CONFLICT, retryable) error-codes.yaml = actually-implemented
unique violation 충돌 (non-retryable) 23505DB_UNIQUE_VIOLATION (CONFLICT, non-retryable) error-codes.yaml = actually-implemented
pessimistic lock / lock-timeout 명시 선언 시만 (코드/registry 부재) planned (NOT FOUND in src/)
forbidden 모든 lock 실패를 generic 500 으로 뭉갬

3. Idempotency 요구 정책 (D6 — rate-limit-idempotency 소비)

Trace: D6 ← raw/branch-notes/feature-rate-limit-idempotency-contract D2/D3/D6/D7/D8. 본 branch 는 "non-idempotent retryable write 금지" 정책만 소유; key scope/TTL/dedup/in-flight/mismatch 메커니즘은 rate-limit branch SSOT.

  • UNSUPPORTED_IMPL_DECISION: 어떤 use case 가 idempotency 선언을 요구하는지 — 도메인 결정이며 ca-tmpl skeleton 이 prescribe 불가. 신규 use case 작성 시 @UseCaseCapability(idempotency=...) 선언을 ArchUnit 으로 강제하되 값 선택은 도메인 작성자. trade-off: 전 use case 강제 선언 = 누락 방지하나 NOT_IDEMPOTENT 보일러플레이트; 옵트인 = 가볍지만 누락 위험. 권고: 전 use case 선언 강제(기존 inbound_port_implementations_declare_capability rule 과 일치).
  • code 확인: @UseCaseCapability(idempotency = IDEMPOTENT | KEYED | NOT_IDEMPOTENT) enum = actually-implemented. KEYED 는 rate-limit merge 전까지 ArchUnit inbound_port_implementations_do_not_declare_keyed_idempotency 로 freeze (planned/의도적 차단).
  • 본 branch 책임: "retryable 로 분류된 write use case 가 idempotency 선언 없이 재시도 경로에 노출되면 실패" 계약 test (아래 §테스트 계약).

4. Outbox trigger 정책 (D7 — domain-event-outbox 소비)

Trace: D7 ← raw/branch-notes/feature-domain-event-outbox-contract D2/D9. 본 branch 는 "external publish 는 outbox 경유, DB commit 후 lossy publish 금지" trigger 정책만 소유; outbox 메커니즘(SKIP LOCKED/CDC)은 outbox branch SSOT.

  • outbox publisher claim transaction 이 READ_COMMITTED(outbox D9) 를 쓰므로 본 branch D3 default 와 일치 — isolation 일관성 확인됨.
  • planned — outbox 메커니즘 미구현(feature-domain-event-outbox-contract status=raw).

엣지·실패·의존

R4(깊이 게이트) 캡처용. 정상 경로 외 구현 중 부딪힐 실패/엣지/다른 계약 의존.

  • 실패·엣지 경로:
    • READ_COMMITTED 하 read-then-write use case → non-repeatable read/phantom 으로 lost update 위험(#PG-ISO-C2,#MYSQL-ISO-C4). 기대 동작: 명시 REPEATABLE_READ 선언 또는 SELECT ... FOR UPDATE(pessimistic) 로 보호. skeleton 은 위험만 문서화, 도메인 use case 가 선택.
    • REPEATABLE_READ/SERIALIZABLE 선택 시 serialization failure(Postgres "could not serialize access", #PG-ISO-C4/#PG-ISO-C5) → retryable. 기대 동작: 호출측 retry 정책 필요(현재 미구현 planned).
    • MySQL REPEATABLE_READ locking read 의 gap/next-key lock(#MYSQL-ISO-C3) → deadlock 빈도 증가. 기대 동작: D5 deadlock 분류(retryable) 로 흡수.
    • inNew(REQUIRES_NEW) 를 loop 내 호출 → connection pool 고갈(app-port D12 anti-pattern). 기대 동작: ArchUnit/리뷰로 차단(app-port 소유).
    • optimistic @Version 충돌이 generic 500 으로 뭉개짐 → D5 위반, 계약 test 실패.
    • REPEATABLE_READ/SERIALIZABLE serialization failure 재시도 ↔ D6 idempotency 충돌: serialization failure(#PG-ISO-C4) 의 retry 가 idempotency key 없는 mutating command 에서 발화하면 D6 "non-idempotent retryable write forbidden" 에 해당. 기대 동작: KEYED idempotency 선언된 use case 에 한해 재시도 허용 — NOT_IDEMPOTENT use case 의 REPEATABLE_READ/SERIALIZABLE 선언 + 자동 retry 는 사실상 forbidden.
  • 다른 계약 의존:

테스트 계약

  • write use case가 transaction 없이 repository write를 수행하면 실패.
  • application use case가 Spring transaction annotation을 직접 import하면 실패.
  • read-only use case가 write repository를 사용하면 실패.
  • optimistic lock 실패가 internal error로 뭉개지면 실패.
  • idempotent command 재시도 시 중복 row/write가 발생하면 실패.
  • TransactionPort 사용 use case에서 isolation을 명시하지 않은 채 vendor default에 위임하면 실패.
  • application use case의 @Transactional propagation이 NESTED 또는 NEVER로 명시되면 실패.

검증해야 할 주장

공식 문서 인용이 결정의 근거이지만, ca-tmpl 자체 구현에서의 동작은 별도 검증이 필요한 주장.

Claim Why uncertain How to verify Status
TransactionPort adapter 가 Spring bean 외부에서 호출되어 self-invocation 함정 (#AT-TX-C5) 회피 #AT-TX-C5 는 proxy mode 의 self-invocation 함정만 명시 — adapter call path 가 실제로 외부 호출인지 별도 보장 필요 adapter bean 호출 경로 trace + Spring AOP proxy 적용 여부 단언 integration test planned
application 패키지가 org.springframework.transaction.annotation.Transactional 또는 org.springframework.transaction.support.TransactionTemplate 을 import 하지 않음 cited raw 는 framework 의 권고만 보장 — ca-tmpl 내부 강제는 별도. code 확인: CleanArchitectureTest.application_does_not_use_spring_transactional_annotation (L167-175) = actually-implemented (app-port D3 소유) ArchUnit rule 존재 확인 완료; 의도적 위반 fixture 로 fail 검출은 ArchitectureViolationFixtureTest 에서 확인 locally-verified (app-port branch)
isolation level READ_COMMITTED 가 PostgreSQL 과 MySQL InnoDB 에서 ca-tmpl 이 가정한 시맨틱과 동일 동작 (D3) UNSUPPORTED 해소 — vendor doc verbatim 수집 완료. 단 "양쪽 동일 의미" 는 부정확: 기본값이 다름(Postgres READ COMMITTED #PG-ISO-C1 vs InnoDB REPEATABLE READ #MYSQL-ISO-C1). ca-tmpl 은 명시 pin 으로 vendor 차이 무력화 code 확인: SpringTransactionPortISOLATION_READ_COMMITTED pin (actually-implemented). 실 DB 에서 READ_COMMITTED 시맨틱(non-repeatable read 허용) 재현은 Testcontainers integration test 로 검증 필요 needs-confirmation (vendor 시맨틱 verified, ca-tmpl 실 DB 동작 미검증)
propagation REQUIRED 가 모든 ca-tmpl use case 의 default 시맨틱과 일치 (D4) UNSUPPORTED 해소#SPRING-TX-MGR-C3 (PROPAGATION_REQUIRED default verbatim) + spring-tx-propagation-required-new-nested-official 수집. code: inWrite/inRead=REQUIRED (actually-implemented) SpringTransactionPortTest 가 모드별 propagation 설정값 단언(app-port branch, locally-verified) locally-verified (app-port branch)
optimistic lock 실패가 application use case 에서 OptimisticLockingFailureException (또는 동등) 으로 식별되어 409 매핑 (D5) cited transaction raw 범위 밖 — persistence raw 의 #SDA-EX-C5 와 cross-reference. error-codes.yaml 에 optimistic 전용 code 부재(registry gap) integration test: @Version 충돌 시나리오에서 OptimisticLockingFailureException 발생 + handler 가 409 매핑 단언 planned
duplicate command idempotency 검증 (D6: 동일 idempotency key 로 retry 시 중복 row/write 없음) UNSUPPORTED 위임 — 메커니즘은 raw/branch-notes/feature-rate-limit-idempotency-contract D2/D7/D8 SSOT. 본 branch 는 "non-idempotent retryable write 금지" 정책만 contract test: 동일 idempotency key 로 5회 retry → DB row 1개만 생성 + 응답 동일 단언 (rate-limit branch 구현 후) planned
ArchUnit forbidden import test (application 의 @Transactional direct annotation) 가 실제 위반 검출 rule 정의 자체는 명확하지만 실제 적용 미검증. code 확인: rule + violation fixture 존재 ArchitectureViolationFixtureTest 가 의도된 위반 fixture 를 잡아냄 (app-port branch) locally-verified (app-port branch)
Vassilis Soum / UNIL TransactionPort 참고 구현 (D1 의 company-case-study) 이 ca-tmpl 환경에서 동작 보장 company-case-study 는 한 조직의 사례 — 우리 환경에서의 적합성 별도 검증 필요. code 확인: TransactionPort + SpringTransactionPort 실재(actually-implemented, app-port branch) 모든 use case 가 TransactionPort.inWrite/inRead/inNew(...) 경유 — SpringTransactionPortTest 통과 (app-port branch, locally-verified) locally-verified (app-port branch)

관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)

/coverage 가 채우는 생성물 — 손으로 유지하지 않는다. governing 문서(frontmatter governing_docs: wiki/projects/ca-tmpl/transaction-boundary-abstraction)가 요구하는 관심사를 이 브랜치가 빠짐없이 덮는지의 결과. 기준: rules/coverage-gate.md. 상태: covered-here(이 브랜치 결정) / delegated(다른 owner 브랜치) / missing(아무도 안 맡음 → Blocking).

관심사 상태 owner 심각도 근거
C1: TransactionPort abstraction (inWrite/inRead/inNew) + @Transactional 금지 ArchUnit rule delegated raw/branch-notes/feature-application-port-usecase-contract (D3/D11/D12) OK D1 Open Risk OWNERSHIP + §엣지·의존 링크
C2: SpringTransactionPort 내부 TransactionTemplate 사용 delegated raw/branch-notes/feature-application-port-usecase-contract (D3) OK D2 Open Risk OWNERSHIP + §엣지·의존 링크
C3: Isolation enum — READ_COMMITTED pin, READ_UNCOMMITTED forbidden covered-here D3 + §구현가이드 1; Isolation.java actually-implemented (code)
C4: Propagation 정책 — REQUIRED default, REQUIRES_NEW 조건, NESTED/NEVER forbidden delegated raw/branch-notes/feature-application-port-usecase-contract (D3/D12) OK D4 Open Risk OWNERSHIP; §엣지·의존 링크
C5: Isolation 선택 정책 — 명시 pin 강제, vendor default forbidden, REPEATABLE_READ/SERIALIZABLE 경로 covered-here D3 + §구현가이드 1 (UNSUPPORTED_IMPL_DECISION 3옵션 기록)
C6: Lock-failure 분류 정책 — optimistic 409, deadlock/serialization retryable, generic-500 forbidden covered-here D5 + §구현가이드 2; @Version WorkLogEntity actually-implemented (code)
C7: exception→error-code 매핑 (40001/40P01/optimistic @Version) delegated raw/branch-notes/feature-persistence-failure-baseline (D6) OK D5 위임 명시; error-codes.yaml DB_SERIALIZATION_FAILURE/DB_DEADLOCK actually-implemented (code)
C8: Idempotency 요구 정책 — non-idempotent retryable write 금지 covered-here D6 고유 소유; @UseCaseCapability(idempotency=...) actually-implemented (code)
C9: Idempotency 메커니즘 — key scope/TTL/dedup/in-flight/mismatch delegated raw/branch-notes/feature-rate-limit-idempotency-contract (D2/D3/D6/D7/D8) OK D6 위임 명시; IdempotencyExecutor/IdempotencyStoreAdapter actually-implemented (code)
C10: Outbox trigger 정책 — external publish outbox 경유, lossy publish 금지 covered-here D7 고유 소유
C11: Outbox 메커니즘 — SKIP LOCKED, at-least-once, publisher leadership delegated raw/branch-notes/feature-domain-event-outbox-contract (D2/D4/D9) OK D7 위임 명시; §엣지·의존 링크
C12: @UseCaseCapability(transactionMode/repositoryAccess) 어휘 + coherence ArchUnit rule delegated raw/branch-notes/feature-repository-access-permission-contract (D2/D11/D12) OK §엣지·의존 링크; capabilities.yaml TRANSACTION_REQUIRED owner 코드 확인

Audit & Findings (2026-06-09 ground-truth 대조)

ca-tmpl src/ + docs/registries/ + sibling branch-notes 대조로 발견한 drift/ownership. 사용자 작성 결정 영역은 자동 rewrite 하지 않고 정합 권고만 기록(CLAUDE.md §2 ground-truth 절차).

  • DRIFT-1 — TransactionalUseCaseRunner 미존재: D1·§결정사항·(이전)§외부근거 가 TransactionalUseCaseRunner 를 실행 경로로 언급하나, ca-tmpl src/ grep + governing doc transaction-boundary-abstraction L79 ("검토 후 미채택... 코드에 존재하지 않는다") 로 미채택 대안임을 확인. 권고: 실행 경로 표현에서 제거하고 "미채택 대안"으로만 유지. (§외부근거 대안 5 로 정정 기록함; D1 본문은 사용자 결정이라 verbatim 보존 + 본 finding 으로 정합 표시.)
  • DRIFT-2 — isolation "양쪽 동일 의미" 부정확: D3 의 "PostgreSQL/MySQL 양쪽 동일 의미" 는 vendor 공식과 불일치 — 기본값이 다름(Postgres=READ COMMITTED #PG-ISO-C1, MySQL InnoDB=REPEATABLE READ #MYSQL-ISO-C1). 정확한 명제: "명시 pin 하면 양쪽에서 READ COMMITTED 동작을 강제할 수 있고, 묵시 default 는 vendor 마다 달라 위험". D3 row/§결정사항 보강으로 정정 반영.
  • OWNERSHIP-1 — TransactionPort 계약은 app-port branch 소유: TransactionPort abstraction + @Transactional 금지 ArchUnit rule + propagation 모드는 raw/branch-notes/feature-application-port-usecase-contract (D3/D11/D12) 가 SSOT 이며 이미 구현·로컬검증 완료(CleanArchitectureTest L167-175 주석이 "raw/branch-notes/feature-application-port-usecase-contract D3" 로 귀속). 본 branch D1/D2/D4 는 소비자 재진술 — §구현 가이드에서 OUT_OF_BRANCH_SCOPE 로 정제(메커니즘 명세는 app-port 로 위임, 본 branch 는 isolation/lock/idempotency/outbox 정책만).
  • REGISTRY-GAP-1 — optimistic-lock 전용 error code 부재: error-codes.yaml 에 DB_SERIALIZATION_FAILURE/DB_DEADLOCK/DB_UNIQUE_VIOLATION 만 존재, optimistic @Version 충돌 전용 code 없음. D5 의 "optimistic→409" 매핑의 정확한 code 는 owner_branch=feature-persistence-failure-baseline 결정 영역 → 그 branch 로 신규 제안 이관 권고.

구현 진행 (2026-06-09 — Phase C2, 본 branch 고유 소유분)

위임분(D1/D2/D4 = app-port, C7 = persistence-baseline, C9 = rate-limit, C11 = outbox, C12 = repo-access, REGISTRY-GAP-1)은 구현 제외 — sibling SSOT 소유. 본 branch 고유 소유(C3/C5 isolation, C6 lock-policy)만 ca-tmpl 코드에 반영.

  • C3/C5 (D3) — actually-implemented: application-core/.../transaction/Isolation.java enum 을 READ_COMMITTED 단일값 → READ_COMMITTED / REPEATABLE_READ / SERIALIZABLE 3값으로 확장(app-port Isolation.java javadoc 이 본 contract 로 위임한 항목). READ_UNCOMMITTED 는 미선언(forbidden) 유지. call-path 라우팅(TransactionPort 시그니처/SpringTransactionPort isolation 별 라우팅)은 app-port 공동 PR 필요 → planned 유지, vocabulary 만 ship.
    • test: IsolationTest(app-core) — 3값 존재 + READ_UNCOMMITTED 미선언 검증.
    • test: SpringTransactionPortTest.every_mode_pins_an_explicit_isolation_never_the_vendor_default — 3 template 모두 ISOLATION_DEFAULT 아님(vendor default forbidden, D3 핵심 정책) 검증.
  • C6 (D5) — actually-implemented (정책 test): app-bootstrap/.../contract/LockFailureClassificationContractTest — deadlock/serialization = retryable CONFLICT, unique = non-retryable CONFLICT, DB conflict code 어느 것도 generic INTERNAL/500 아님(D5 forbidden "all locks generic 500") 검증 + REGISTRY-GAP-1(optimistic 전용 code 부재) 을 known-absent 로 pin. exception→code 매핑은 persistence-baseline 소유(소비만).
  • 검증: :application-core:test, :adapter-persistence:test, :app-bootstrap:test(ArchUnit 포함), verifyCleanArchitectureDependencies 전부 green (2026-06-09).
  • 제외(미구현, 의도적): REPEATABLE_READ/SERIALIZABLE call-path 라우팅(app-port 공동 PR), optimistic 전용 error code 신설(persistence-baseline), C8 non-idempotent-retryable-write 자동 금지(retry infra planned), C10 outbox trigger(outbox branch raw).

마주친 문제

  • 아직 없음.

묶음

본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.

근거 자료

오류 기록 (본 feature 작업 중 발생)

  • (없음 — 2026-06-09 C3/C5/C6 구현 시 빌드/테스트 에러 없음. raw/errors 파생 노트 not needed.)

면접 준비 (이 작업에서 나올 수 있는 면접 질문)

  • (후보, 미정제 — raw/interviews 파생 노트 not needed 현 시점) "isolation default 를 코드에서 명시 pin 하는 이유는?" → vendor 기본값 상이(Postgres READ COMMITTED vs MySQL InnoDB REPEATABLE READ), 묵시 위임 시 동일 코드가 DB 따라 다른 격리.

관련 일일 노트

이 브랜치를 작업한 날짜들. 양방향 nav 유지.

  • 2026-06-09 — Phase C2 본 branch 고유 소유분(C3/C5 isolation enum + vendor-default-forbidden test, C6 lock-failure 분류 정책 test) 구현. 위임분 제외. 전 verification green. 상세 §구현 진행 (2026-06-09).

완료 후 정리

  • PR 링크:
  • 리뷰 메모:
  • 머지 결과 / 배포 환경:
  • wiki 추출 대상 (verified만, wiki/projects/로만 추출):
    • actually-implemented 항목:
    • locally-verified 항목:
    • prod-verified 항목:
  • 추출하지 않을 항목 (planned / documented-only / abandoned):