init: llm-wiki-haness 하네스 설계

This commit is contained in:
DongHyeonka
2026-07-24 14:21:35 +09:00
parent 42bf3db4fd
commit 6c53ded9cb
2436 changed files with 194486 additions and 1 deletions
@@ -0,0 +1,117 @@
---
title: Togglz · FF4J — Java feature toggle library 비교 (adapter on/off 대안)
source_type: company-tech-blog
url: https://www.togglz.org/
archive_url:
related_branches: [feature-integration-adapter-templates, feature-env-driven-runtime-configuration]
related_projects: [ca-skeleton-operational-contract]
tags: [ca-tmpl, adapter, feature-toggle, togglz, ff4j, alternative]
status: raw
confidence: medium
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Togglz · FF4J — Java feature toggle 라이브러리
> Layer: `raw/company-tech-blogs/` — OSS feature toggle 라이브러리 자체 소개 페이지 (Togglz `togglz.org`, FF4J `ff4j.github.io`) verbatim.
> ca-tmpl `feature-integration-adapter-templates` branch 의 **대안 5** (runtime-time feature toggle library) 비교 근거.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 별도 작성.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-integration-adapter-templates]] | adapter on/off 의 startup-time toggle 채택 시, runtime-time feature toggle 라이브러리 (Togglz/FF4J) 와의 시맨틱 차이 명시 — adapter 자체 on/off ≠ adapter 내부 분기 |
| [[raw/branch-notes/feature-env-driven-runtime-configuration]] | env-driven runtime configuration (startup env flag) 가 default 인 이유: 외부 상태 저장 (DB/Redis/JCache) 의존 회피 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | Group I — Integration adapter templates 대안 비교 매트릭스 |
## 컨텍스트 / 왜 저장했는지
ca-tmpl `feature-integration-adapter-templates` branch의 **대안 5**. branch는 `@ConditionalOnProperty` 기반 startup-time toggle을 채택했음. Togglz / FF4J는 **runtime-time toggle** 라이브러리 → adapter 자체의 on/off가 아니라 adapter 호출 시점에 동적 분기가 필요할 때의 대안. 두 영역의 경계를 명확히 보존.
## 출처 / Source
- 원본 URL (Togglz): https://www.togglz.org/
- 원본 URL (FF4J): https://ff4j.github.io/
- 아카이브 URL: (미수집)
- 저자 / 조직: Togglz (Christian Kaltepoth, Apache 2.0 OSS) / FF4J (Cedrick Lunven 외, Apache 2.0 OSS)
- 발행일: rolling (라이브러리 공식 페이지)
- 마지막 확인일: 2026-05-27
- 신뢰도 주의: OSS 라이브러리 자체 소개 페이지로 자기 제품 마케팅 포함. 공식 best practice 로 인용 금지.
## 핵심 인용 / Key quotes (verbatim)
(Togglz `togglz.org` 메인 페이지)
> [§Togglz intro] "Togglz is an implementation of the Feature Toggles pattern for Java."
> [§Togglz intro] "Feature Toggles are a very common agile development practices in the context of continuous deployment and delivery."
> [§Togglz intro] "This allows you to enable or disable these features at application runtime, even for individual users."
(FF4J `ff4j.github.io` 메인 페이지)
> [§FF4J tagline] "Feature Flags for Java made Easy"
> [§FF4J runtime] "Enable. and disable features at runtime - no deployments. In your code implement multiple paths protected by dynamic predicates"
> [§FF4J strategies] "Implement custom predicates _(Strategy Pattern)_ to evaluate if a feature is enabled."
> [§FF4J strategies] "Some are provided out of the box: _White/Black lists_ ,_Time based_, _Expression based_."
> [§FF4J spring-boot] "Import ff4j-spring-boot-starter dependency in your microservices to get the web console and rest api working immediately."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| TOGGLZ-FF4J-C1 | Togglz 는 Java 용 Feature Toggles 패턴 구현체 | [§Togglz intro] "Togglz is an implementation of the Feature Toggles pattern for Java." | `company-case-study` | Java 애플리케이션의 feature toggle 도입 시 후보 라이브러리 | feature toggle 패턴 자체의 정의가 Togglz 만의 것이라는 뜻은 아님 (Fowler 의 일반 패턴) |
| TOGGLZ-FF4J-C2 | Togglz 는 application runtime 에서 feature 활성/비활성, 개별 user 단위 활성도 지원 | [§Togglz intro] "This allows you to enable or disable these features at application runtime, even for individual users." | `company-case-study` | runtime 동적 toggle 이 필요한 시나리오 | 개별 user 매칭 메커니즘 (username/role/percentage) 의 정확한 구현은 본 인용에 없음 — 별도 docs 확인 필요 |
| TOGGLZ-FF4J-C3 | FF4J 는 deployment 없이 runtime 에서 feature 활성/비활성 가능, dynamic predicate 로 다중 경로 보호 | [§FF4J runtime] "Enable. and disable features at runtime - no deployments. In your code implement multiple paths protected by dynamic predicates" | `company-case-study` | FF4J 도입 시 코드 안에 분기 경로 작성 | "no deployments" 가 모든 backend store 구성에서 보장된다는 뜻은 아님 — feature store 변경 자체는 별도 |
| TOGGLZ-FF4J-C4 | FF4J 는 Strategy Pattern 기반 custom predicate 를 지원하며 기본 제공 strategy 는 White/Black list, Time based, Expression based | [§FF4J strategies] "Implement custom predicates _(Strategy Pattern)_ to evaluate if a feature is enabled." + "Some are provided out of the box: _White/Black lists_ ,_Time based_, _Expression based_." | `company-case-study` | FF4J activation strategy 선택 시 | 위 3개 외 strategy (예: percentage rollout, geographic) 가 기본 제공되는지는 인용 범위 밖 |
| TOGGLZ-FF4J-C5 | FF4J 는 Spring Boot starter (`ff4j-spring-boot-starter`) 를 제공하며 import 시 web console + REST API 가 즉시 동작 | [§FF4J spring-boot] "Import ff4j-spring-boot-starter dependency in your microservices to get the web console and rest api working immediately." | `company-case-study` | Spring Boot 마이크로서비스에 FF4J 통합 시 | console/REST API 의 인증·인가 default 정책은 본 인용에 없음 — 운영 환경 노출 전 별도 확인 필요 |
| TOGGLZ-FF4J-C6 | (부재) Togglz 의 Spring Boot starter / activation strategy 상세는 메인 페이지 인용 범위 내에 명시 없음 | (부재 자체가 claim) | `needs-confirmation` | Togglz Spring Boot starter / activation strategy 정확한 동작 | Togglz 가 Spring Boot 를 지원 안 한다는 뜻 아님 — nav 메뉴에 "Spring Boot Starter" 링크는 존재하나 본 페이지 본문 인용 불가 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `TOGGLZ-FF4J-C1` ~ `C5`: Togglz/FF4J 의 자체 마케팅 문구 — Java runtime toggle, dynamic predicate, Spring Boot starter 존재
- **이 자료가 증명하지 않는 것**:
- `TOGGLZ-FF4J-C6`: Togglz 의 activation strategy 상세 / Spring Boot starter 동작
- Togglz/FF4J 의 실제 production 운영 사례 (사용자 자체 마케팅이라 self-attestation)
- Togglz/FF4J 가 LaunchDarkly / Unleash 보다 우수하다는 비교 결론
- ca-tmpl 의 `@ConditionalOnProperty` 가 Togglz/FF4J 보다 적합하다는 일반적 결론 (시맨틱 차이의 사례에만 한정)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 `env-keys.yaml` registry + `owner_branch` governance 를 Togglz/FF4J 의 enum/annotation 정의 모델과 어떻게 연결할지
- Togglz/FF4J 의 외부 feature store (DB/Redis/JCache) 가 ca-tmpl 의 "disabled adapter = bean 미등록" 원칙과 양립 가능한지
## 메모 / Notes (내 프로젝트 해석)
> 검증되지 않은 내 추론은 여기에 두지 말 것 — wiki source-summary 단계에서.
- 적용 시나리오: 같은 adapter는 항상 enabled이지만 그 안의 특정 동작 경로만 user / role / percentage 기반으로 분기해야 할 때.
- 장점:
- Spring Boot Starter 제공 (`togglz-spring-boot-starter`, `ff4j-spring-boot-starter`).
- runtime UI / REST console → product team이 backend 배포 없이 toggle 조작.
- activation strategy (Username, GradualActivation, ScheduleActivation, Custom predicate).
- role-based access (FF4J).
- 단점 / ca-tmpl 적용 시 한계:
- **adapter 자체 on/off에는 over-engineering**: branch는 disabled adapter가 ApplicationContext에 bean으로조차 등록되지 않는 것을 요구. Togglz/FF4J는 bean은 있고 호출 시 분기. 시맨틱이 다름.
- **외부 상태 저장 의존**: feature state를 DB / Redis / JCache에 저장 → adapter 비활성 시 의존성 늘어남 (모순).
- **registry governance 부재**: branch는 `env-keys.yaml` registry + `owner_branch` 강제. Togglz/FF4J는 toggle 정의가 enum/annotation + console에 분산. governance 레이어를 별도로 만들어야 함.
- LaunchDarkly/Unleash와 같은 "deploy ≠ release" 문제 영역. **adapter 통합 자체보다는 product feature flag 영역**.
- ca-tmpl 결정과의 매핑:
- branch Layer 1-2-3: adapter 자체의 on/off (startup-time decision). Togglz/FF4J의 영역 아님.
- 만약 adapter는 항상 on이고 그 안의 분기만 runtime toggle해야 한다면, Togglz/FF4J 또는 LaunchDarkly/Unleash가 후보. 단 ca-tmpl baseline에 포함시키지 않는 게 branch 결정과 정합 (registry/owner governance가 없으면 forbidden).
- 채택 시점 후보: 50+ active toggle, 또는 product team이 console UI로 직접 toggle을 운영해야 할 때. infra adapter on/off에는 부적합.
## Related / 관련
- 같은 주제 다른 raw: (미수집 — LaunchDarkly / Unleash 비교 자료 후보)
- 인용하는 branch:
- [[raw/branch-notes/feature-integration-adapter-templates]]
- [[raw/branch-notes/feature-env-driven-runtime-configuration]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (Group I)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,111 @@
---
title: GitHub REST API versioning — X-GitHub-Api-Version header
source_type: company-tech-blog
url: https://docs.github.com/en/rest/overview/api-versions
archive_url:
status: raw
confidence: high
tags: [ca-tmpl, api-versioning, deprecation, github, header-versioning]
related_branches: [feature-api-compatibility-deprecation-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# GitHub REST API versioning — `X-GitHub-Api-Version` header
> Layer: `raw/company-tech-blogs/` — GitHub 공식 REST API docs 원문 발췌. Stripe 와 같은 date-based versioning 이지만 **URL 이 아닌 헤더**로 전달하고 24개월 EOL 후 `410 Gone` 강제 종료를 채택한 변형.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-api-compatibility-deprecation-contract]] | API versioning 대안 평가 — header-based date versioning (대안 3) + 24개월 EOL + `410 Gone` 응답 코드의 catalog 도입 근거 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | API evolution & schema contract 의 외부 벤더 사례. EOL 응답 코드 catalog (RFC 8594 Sunset 후 `410 Gone`) 의 vendor 근거 |
## 컨텍스트 / 왜 저장했는지
ca-tmpl 이 검토한 대안 중 **GitHub REST API headers** 의 실제 운영 모델. Stripe 의 "freeze forever" 와 ca-tmpl 의 "90d window" 의 **중간 지점** (24개월 명시 EOL + `410 Gone` 강제 종료).
## 출처 / Source
- 원본 URL: https://docs.github.com/en/rest/overview/api-versions
- 아카이브 URL: (미수집)
- 저자 / 조직: GitHub (REST API docs)
- 발행일: 2022-11-28 첫 도입, 이후 dated releases (rolling)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§API versioning] "You should use the `X-GitHub-Api-Version` header to specify an API version."
> [§Default version] "Requests without the `X-GitHub-Api-Version` header will default to use the `2022-11-28` version."
> [§Version naming] "The API version name is based on the date when the API version was released."
> [§Breaking changes] "Breaking changes are changes that can potentially break an integration."
> [§Breaking changes — announcement] "Breaking changes will be released in a new API version. We will provide advance notice before releasing breaking changes."
> [§Closing down API version] "If you specify an API version that is no longer supported, you will receive a `410 Gone` response."
> [§Support window] "When a new REST API version is released, the previous API version will be supported for at least 24 more months."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| GH-APIV-C1 | API version 선택은 `X-GitHub-Api-Version` 요청 헤더로 지정한다 (URL path 가 아님) | [§API versioning] "You should use the `X-GitHub-Api-Version` header to specify an API version." | `official-vendor-doc` | GitHub REST API 의 모든 endpoint | URL 기반 versioning 이 더 나쁘다는 일반 명제가 아님 — GitHub 의 운영 선택일 뿐 |
| GH-APIV-C2 | 헤더 없는 요청은 default 로 `2022-11-28` 버전을 받는다 (헤더 미지정 시 명시적 default 적용) | [§Default version] "Requests without the `X-GitHub-Api-Version` header will default to use the `2022-11-28` version." | `official-vendor-doc` | GitHub REST API 요청 | "default 가 항상 최신" 이라는 뜻은 아님 — 고정된 dated default |
| GH-APIV-C3 | API 버전 이름은 **release 날짜** 기반 (예: `2022-11-28`) | [§Version naming] "The API version name is based on the date when the API version was released." | `official-vendor-doc` | GitHub REST API 버전 식별자 | semver / major bump 모델보다 우월하다는 뜻은 아님 — vendor 선택 |
| GH-APIV-C4 | Breaking change 는 **integration 을 깰 수 있는 변경**으로 정의되며, 구체적 예: operation 제거, parameter 제거/이름 변경, response field 제거/이름 변경, 새 required parameter 추가, optional → required 변경, type 변경, enum value 제거, 새 validation rule 추가, 인증/인가 요구 변경 | [§Breaking changes] "Breaking changes are changes that can potentially break an integration." + 항목 리스트: "Removing an entire operation", "Removing or renaming a parameter", "Removing or renaming a response field", "Adding a new required parameter", "Making a previously optional parameter required", "Changing the type of a parameter or response field", "Removing enum values", "Adding a new validation rule to an existing parameter", "Changing authentication or authorization requirements" | `official-vendor-doc` | GitHub 의 breaking change 정책 | 이 목록이 모든 API 의 breaking 정의에 일반적으로 적용된다는 뜻은 아님 — GitHub 의 선언 |
| GH-APIV-C5 | Breaking change 는 **새 API 버전으로 release** 되며, 사전 공지(advance notice) 가 원칙 (단, 보안/가용성 사유 시 즉시 적용 예외) | [§Breaking changes — announcement] "Breaking changes will be released in a new API version. We will provide advance notice before releasing breaking changes." | `official-vendor-doc` | GitHub REST API 의 breaking change 통보 | 사전 공지 기간 (며칠/주/개월) 의 구체적 SLA 는 본 인용에 없음 |
| GH-APIV-C6 | 지원 종료된 API version 요청은 **`410 Gone`** 응답을 받는다 | [§Closing down API version] "If you specify an API version that is no longer supported, you will receive a `410 Gone` response." | `official-vendor-doc` | EOL 된 GitHub REST API version 요청 | EOL 전 별도 Sunset / Deprecation 헤더의 발행 여부는 본 인용 범위 밖 |
| GH-APIV-C7 | 새 REST API version release 시 직전 version 은 **최소 24개월** 추가 지원 (지원 윈도우 명시) | [§Support window] "When a new REST API version is released, the previous API version will be supported for at least 24 more months." | `official-vendor-doc` | GitHub REST API 의 버전 lifecycle | 24개월 이 모든 API vendor 의 표준이라는 뜻은 아님. Stripe 무제한 / ca-tmpl 90일 등 vendor 별 다름 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `GH-APIV-C1` ~ `C3`: GitHub 의 header-based date versioning 메커니즘 (헤더 이름, default 버전, 명명 규칙)
- `GH-APIV-C4` ~ `C5`: breaking change 정의 + 사전 공지 원칙
- `GH-APIV-C6` ~ `C7`: EOL 시 `410 Gone` + 24개월 지원 윈도우
- **이 자료가 증명하지 않는 것**:
- header-based versioning 이 URL-based versioning 보다 일반적으로 우수하다는 명제
- 24개월 윈도우가 모든 enterprise API 의 표준이라는 일반화
- Sunset / Deprecation HTTP 헤더 (RFC 8594 / draft-deprecation-header) 와의 결합 방식 (본 페이지에는 명시 없음)
- 사전 공지의 정확한 lead time (days/weeks/months)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 이 internal-first 일 때 24개월 윈도우가 과한지 (현 결정: 90d public / 30d internal)
- `410 Gone` 응답을 ca-tmpl error code catalog 에 추가 시 client-side handling 패턴 (재시도 금지 vs 명시 마이그레이션 안내)
- GitHub 처럼 release notes + deprecation header 채널을 verification suite 로 강제할 수 있는지
## 메모 / Notes
> 검증되지 않은 내 해석은 wiki source-summary 단계에서 작성.
- Stripe vs GitHub vs ca-tmpl 비교:
| | Stripe | GitHub | ca-tmpl |
| --- | --- | --- | --- |
| 버전 식별 | `Stripe-Version` header + account pin | `X-GitHub-Api-Version` header | URL `/v1` + OpenAPI deprecated marker |
| EOL 정책 | 없음 (freeze forever) | next release 후 24개월 | 90d public / 30d internal |
| EOL 시 응답 | 영원히 정상 | `410 Gone` | (ca-tmpl 결정 안 됨) |
| breaking 단위 | dated release | dated release | per field/operation |
- ca-tmpl 보강 포인트 (해석, 미검증):
- **EOL 응답 코드** 가 catalog 에 빠져 있음. RFC 8594 Sunset 시점 후 `410 Gone` 을 default 응답 코드로 catalog 에 추가 후보.
- GitHub 처럼 advance notice 채널 (release notes, deprecation header) 을 verification suite 에서 강제할 수 있음.
- Trade-off (해석, 미검증):
- GitHub 모델 장점: URL 안정성. routing/cache 단순. version 은 헤더로만 분기.
- GitHub 모델 단점: URL 만 보고 어느 버전인지 모름 → 로그/메트릭에서 `X-GitHub-Api-Version` 을 항상 같이 기록해야 함.
- ca-tmpl 이 URL versioning 유지 시 internal-first 라 routing 단순. 외부 공개 시 GitHub 모델 검토 가치.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/api-versioning-stripe-date-based]] (대안 1: Stripe freeze forever)
- 인용하는 branch:
- [[raw/branch-notes/feature-api-compatibility-deprecation-contract]] (대안 3)
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (Group G-F — API evolution & schema)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,106 @@
---
title: Stripe API versioning — date-based rolling versions
source_type: company-tech-blog
url: https://stripe.com/blog/api-versioning
archive_url:
status: raw
confidence: high
tags: [ca-tmpl, api-versioning, deprecation, stripe, date-based, backward-compat]
related_branches: [feature-api-compatibility-deprecation-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Stripe API versioning — date-based rolling versions
> Layer: `raw/company-tech-blogs/` — Stripe 엔지니어링 블로그의 versioning 정책 원문 발췌.
> ca-tmpl 이 채택한 `90d public + 30d internal migration window + Sunset header` 결정의 **대안** (removal 없이 freeze) 평가용.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-api-compatibility-deprecation-contract]] | API versioning 대안 평가 — date-based "freeze forever" (대안 1) 비교. version change module 패턴이 ca-tmpl 의 compatibility adapter 와 유사한지 검토 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | API evolution & schema contract 의 외부 벤더 사례. freeze 모델 vs migration window 모델의 정책 차이 명문화 |
## 컨텍스트 / 왜 저장했는지
ca-tmpl 은 "deprecate → 90일 public / 30일 internal migration window → 제거" 를 default 로 두지만, Stripe 는 **field/endpoint 를 영구히 제거하지 않고 version pinning 으로 freeze** 하는 정반대 전략을 씀. 두 전략의 trade-off 를 비교하기 위해 보관.
## 출처 / Source
- 원본 URL: https://stripe.com/blog/api-versioning
- 아카이브 URL: (미수집)
- 저자 / 조직: Stripe Engineering (Brandur Leach 등)
- 발행일: 2017-08 (원문 게시), 이후 docs 로 이관
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§Version naming] "rolling versions that are named with the date they're released (for example, `2017-05-24`)"
> [§Account pinning] "The first time a user makes an API request, their account is automatically pinned to the most recent version available"
> [§Field stability] "Fields that were present before should stay present, and fields should always preserve their same type and name."
> [§API stability — analogy] "Like a connected power grid or water supply, after hooking it up, an API should run without interruption for as long as possible."
> [§Override] "Users can override the version of any single request by manually setting the `Stripe-Version` header, or upgrade their account's pinned version from Stripe's dashboard."
> [§Version change modules] "Version change modules keep older API versions abstracted out of core code paths. Developers can largely avoid thinking about them while they're building new products."
> [§Breaking changes — incremental] "Although backwards-incompatible, each one contains a small set of changes that make incremental upgrades relatively easy"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| STRIPE-APIV-C1 | API 버전 식별자는 **release 날짜** 기반 (예: `2017-05-24`) | [§Version naming] "rolling versions that are named with the date they're released (for example, `2017-05-24`)" | `company-case-study` | Stripe API versioning 정책 | date-based 가 semver 보다 일반적으로 우월하다는 뜻은 아님 — vendor 선택 |
| STRIPE-APIV-C2 | 사용자가 첫 API 요청 시 계정이 자동으로 **가장 최신 버전에 pin** 됨 (이후 명시 변경 전까지 유지) | [§Account pinning] "The first time a user makes an API request, their account is automatically pinned to the most recent version available" | `company-case-study` | Stripe 의 계정 단위 version pinning | 모든 SaaS 가 account-level pinning 을 채택해야 한다는 일반화 금지 |
| STRIPE-APIV-C3 | field 는 한 번 노출되면 **이름·타입 보존**, 제거하지 않음 (backward compatibility 정책) | [§Field stability] "Fields that were present before should stay present, and fields should always preserve their same type and name." | `company-case-study` | Stripe API 의 field lifecycle | 모든 vendor 가 field 를 영구 보존해야 한다는 뜻은 아님 — Stripe 의 정책적 약속 |
| STRIPE-APIV-C4 | Stripe 는 web API 안정성을 **연결된 power grid / water supply** 에 비유 — 한번 연결되면 가능한 한 오래 중단 없이 운영되어야 함 | [§API stability — analogy] "Like a connected power grid or water supply, after hooking it up, an API should run without interruption for as long as possible." | `company-case-study` | Stripe 의 API stability 철학 | 인용된 analogy 는 마케팅·철학 선언이지 기술적 명제 아님 — best practice 로 격상 금지 |
| STRIPE-APIV-C5 | 사용자는 `Stripe-Version` 헤더로 단일 요청 단위 override 가능, 또는 대시보드에서 self-directed 로 pinned version 업그레이드 가능 | [§Override] "Users can override the version of any single request by manually setting the `Stripe-Version` header, or upgrade their account's pinned version from Stripe's dashboard." | `company-case-study` | Stripe API 의 version override 메커니즘 | 헤더 + 대시보드 외 다른 채널 (API call, SDK config) 의 존재 여부는 본 인용 범위 밖 |
| STRIPE-APIV-C6 | Stripe 는 **version change modules** 로 옛 버전을 core code 와 격리, 신규 개발 시 옛 버전을 의식하지 않게 함 | [§Version change modules] "Version change modules keep older API versions abstracted out of core code paths. Developers can largely avoid thinking about them while they're building new products." | `company-case-study` | Stripe 내부 코드 구조 | version change module 구현 세부 (어디서 분기, 어떻게 테스트) 는 본 인용에 없음 |
| STRIPE-APIV-C7 | breaking change 는 작은 단위로 분산되어 dated release 에 묶임 — 점진적 upgrade 를 쉽게 하기 위함 | [§Breaking changes — incremental] "Although backwards-incompatible, each one contains a small set of changes that make incremental upgrades relatively easy" | `company-case-study` | Stripe 의 breaking change release 방식 | 작은 dated release 가 모든 API 에 적합하다는 뜻은 아님 — Stripe 의 throughput/리뷰 부담을 감당할 수 있어야 함 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `STRIPE-APIV-C1` ~ `C2`: date-based version 명명 + account 자동 pinning
- `STRIPE-APIV-C3`: field 영구 보존 정책 (이름·타입)
- `STRIPE-APIV-C5` ~ `C6`: 헤더 override + dashboard upgrade + version change module 격리
- `STRIPE-APIV-C7`: breaking change 의 작은 dated release 분산
- **이 자료가 증명하지 않는 것**:
- Stripe 가 **endpoint 전체** (path operation) 를 영구히 제거하지 않는다는 명시 — 인용은 field 보존만 직접 언급
- account pinning 의 expiry / 강제 마이그레이션 정책 (현 시점에 EOL 이 없다는 뜻인지)
- version change module 의 성능 비용 / 테스트 부담 정량 데이터
- Stripe 모델이 모든 SaaS 의 best practice 라는 명제 — `company-case-study` 강도, 격상 금지
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 이 internal-first 일 때 Stripe 모델 전면 도입은 과함 (외부 SDK consumer 가 거의 없음)
- ca-tmpl 의 compatibility adapter (legacy enum → 새 enum 매핑) 가 Stripe 의 version change module 아이디어와 유사한지 검증 (코드 비교 필요)
## 메모 / Notes
> 검증되지 않은 내 해석은 wiki source-summary 단계에서 작성.
- 버전 식별자: 날짜 (`2017-05-24` 형태). 의미적 major bump 없음.
- breaking change 처리: 작은 dated release 로 분산. major version jump 회피.
- ca-tmpl 결정과의 차이 (해석):
- **ca-tmpl**: deprecate marker + 90d window + 강제 removal. catalog 7행으로 분류.
- **Stripe**: 절대 removal 안 함. 모든 클라이언트는 자기가 pin 한 버전을 영원히 받음. version change 모듈이 core 에서 분기.
- Trade-off (해석, 미검증):
- Stripe 방식 장점: 외부 SDK·integrator 가 깨질 일이 거의 없음. PR 리뷰에서 breaking 여부 판정이 단순 (전부 새 dated version).
- Stripe 방식 단점: version change 모듈을 매번 작성·테스트해야 함. legacy 버전 유지비가 누적. 내부 도메인 모델까지 다중 표현을 안고 가야 함.
- ca-tmpl 방식 장점: 운영 부담 한정 (특히 internal-only API). breaking diff 를 CI 에서 깰 수 있음.
- ca-tmpl 방식 단점: 외부 컨슈머가 많을수록 migration window 합의 비용이 큼.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/api-versioning-github-rest-date-header]] (대안 3: GitHub header + 24개월 EOL + 410 Gone)
- 인용하는 branch:
- [[raw/branch-notes/feature-api-compatibility-deprecation-contract]] (대안 1)
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (Group G-F — API evolution & schema)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,80 @@
---
title: company-tech-blog / Hexagonal Architecture With Spring Boot — Arho Huttunen
source_type: company-tech-blog
url: https://www.arhohuttunen.com/hexagonal-architecture-spring-boot/
archive_url:
related_branches: [feature-persistence-auditing-contract]
related_projects: [ca-tmpl]
tags: [company-tech-blog, ca-tmpl, architecture, spring-boot, hexagonal, clean-architecture, domain-purity]
created: 2026-06-10
---
# company-tech-blog / Hexagonal Architecture With Spring Boot — Arho Huttunen
> Layer: `raw/` — 외부 자료(전문가 기술 블로그)의 **원문 발췌·출처 기록**.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 `source-summary-template` 형식으로 별도 작성. 원본은 raw에 영구 보관.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-persistence-auditing-contract]] | D2 (core mandate): Hexagonal / Clean Architecture 에서 JPA Entity 및 persistence 관심사는 persistence adapter 안에만 존재하고 domain model 과 분리해야 한다 — 따라서 audit 메타데이터(created_at / updated_at / created_by / updated_by)는 persistence-adapter JPA entity 또는 @MappedSuperclass 에 속하며, domain-core aggregate 를 오염시켜서는 안 된다. |
## 출처 / Source
- 원본 URL: https://www.arhohuttunen.com/hexagonal-architecture-spring-boot/
- 아카이브 URL: (미제공)
- 저자 / 조직: Arho Huttunen (개인 전문가 블로그)
- 발행일: 미확인 (URL에 날짜 없음)
- 마지막 확인일: 2026-06-10
## 왜 저장했는지 / Why archived
Hexagonal Architecture 에서 JPA Entity 를 domain model 과 분리해야 한다는 구체적 설계 패턴과 근거를 담고 있다. 특히 `OrderEntity` (JPA) vs `Order` (domain) 분리 패턴 및 persistence adapter 가 두 모델 사이의 mapping 을 전담한다는 내용은 `feature-persistence-auditing-contract` 의 D2 결정 — audit 메타데이터를 JPA entity 에만 두고 domain aggregate 를 오염시키지 않는다 — 을 직접 정당화한다.
## 핵심 인용 / Key quotes (verbatim, 3~5문장)
> [§Persistence Adapter / Secondary Adapters] "The `OrderEntity` itself holds the `jakarta.persistence` annotations for ORM."
> [§Persistence Adapter / Secondary Adapters] "A lot of applications pollute the domain model with such annotations. Here we have a clean separation of those concerns with the cost of having to do mapping between the models."
> [§Persistence Adapter / Secondary Adapters] "The `OrdersJpaAdapter` takes care of the translation between the domain and the JPA entities."
> [§Module Structure / Application Module] "the `coffeeshop-application` holds all the business logic and use cases of the application and does not depend on Spring Boot at all. In fact, the only dependencies it has are JUnit 5 and AssertJ."
> [§Transaction Management] "if we truly want to keep frameworks out of the application core, we can do better."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| C1 | JPA (`jakarta.persistence`) annotation 은 JPA entity class 에만 달고, domain model class 에는 달지 않는 것이 hexagonal architecture 의 관심사 분리 방식이다 | [§Secondary Adapters] "The `OrderEntity` itself holds the `jakarta.persistence` annotations for ORM." | `engineering-blog` | Spring Boot + JPA 기반 Hexagonal Architecture | JPA 이외의 persistence 기술(MongoDB, R2DBC 등)에서의 적용 방식 / Spring Data JPA 의 공식 권고 사항 |
| C2 | Domain model 에 JPA annotation 을 추가하는 것은 관심사 오염이며, 올바른 분리는 domain ↔ JPA entity 매핑 비용을 수반한다 | [§Secondary Adapters] "A lot of applications pollute the domain model with such annotations. Here we have a clean separation of those concerns with the cost of having to do mapping between the models." | `engineering-blog` | persistence 관심사를 domain model 과 분리하려는 모든 아키텍처 | 매핑 비용의 구체적 수치 / 도메인 오염이 실제 프로젝트에서 야기하는 장애 |
| C3 | Persistence adapter 가 domain 객체와 JPA entity 사이의 변환(translation)을 전담한다 | [§Secondary Adapters] "The `OrdersJpaAdapter` takes care of the translation between the domain and the JPA entities." | `engineering-blog` | Hexagonal Architecture 의 secondary adapter 구현 | MapStruct 등 특정 매핑 라이브러리의 사용 필수 여부 / 성능 특성 |
| C4 | Application (domain) 모듈은 Spring Boot 에 전혀 의존하지 않는 것이 가능하다 — 테스트 라이브러리 외 프레임워크 의존성 zero | [§Module Structure] "the `coffeeshop-application` holds all the business logic and use cases of the application and does not depend on Spring Boot at all. In fact, the only dependencies it has are JUnit 5 and AssertJ." | `engineering-blog` | Gradle multi-module 기반 Hexagonal Architecture | 모든 Spring Boot 프로젝트에서 이 모듈 분리가 강제된다는 것 / 성능·빌드 시간 영향 |
| C5 | @Transactional 과 같은 Spring 프레임워크 annotation 을 domain core 에 두는 것은 프레임워크 오염이며, 더 나은 방법(AOP aspect 활용 등)이 존재한다 | [§Transaction Management] "if we truly want to keep frameworks out of the application core, we can do better." | `engineering-blog` | Spring @Transactional 을 domain use case 에서 제거하고자 하는 설계 | Spring AOP aspect 방식이 모든 트랜잭션 경계 시나리오에서 동일하게 동작한다는 보장 |
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `C1C3`: Spring Boot + JPA 조합에서 JPA entity 와 domain model 을 분리하고 adapter 가 매핑을 담당하는 구체적 구현 패턴 (저자의 예제 코드 기반)
- `C4`: Gradle multi-module 분리로 application module 의 Spring Boot 무의존성이 달성 가능함
- `C5`: @Transactional 을 domain core 밖으로 이동하는 방향성
- 이 자료가 증명하지 않는 것:
- 이 패턴이 Spring 공식 권고 또는 best practice 임을 증명하지 않는다 (개인 블로그 — `engineering-blog` 등급)
- audit 메타데이터(`created_at`, `updated_by` 등)를 JPA entity 에 두어야 한다는 것을 직접 언급하지 않는다 (D2 결론은 C1–C3 를 도메인에 적용한 추론)
- `@MappedSuperclass` 또는 Spring Data JPA `@EnableJpaAuditing` 의 구체적 설정
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- ca-tmpl 에서 audit entity (`AuditableEntity` 또는 `BaseEntity`) 가 실제로 JPA layer 에만 존재하는지 코드 grep 으로 검증 필요
- domain aggregate(`Order`, `Member` 등)에 JPA annotation 이 없는지 ArchUnit rule 으로 강제 여부 확인
## 메모 / Notes
- 이 블로그는 저자(Arho Huttunen)의 개인 전문가 블로그로, 대기업 엔지니어링 블로그가 아니다. 사용자가 `company-tech-blog`로 지정했으나, claim strength 는 `engineering-blog` 로 분류했다 — `company-case-study` 보다 낮은 등급.
- 기술 블로그 단독으로는 "공식 best practice"로 인용 불가. D2 결정의 근거로 쓰되, Spring Data JPA 공식 문서(`official-vendor-doc` 등급)와 함께 병기하는 것이 권고됨.
- 저자가 제공하는 전체 예제 코드는 Codeberg 에 있다고 언급됨 (링크 미포함).
## Related / 관련
- 동일 주제 공식 문서: [[raw/official-docs/spring-data-jpa-enable-jpa-auditing-api]] — Spring Data JPA `@EnableJpaAuditing` 설정 계약
- 이 자료를 인용한 wiki 요약: (생성 전)
@@ -0,0 +1,104 @@
---
title: company-tech-blog / AWS IAM ARN Format — 계층적 리소스 식별자 구조 (case study)
source_type: company-tech-blog
url: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html
archive_url:
related_branches: [feature-resource-identifier-contract]
related_projects: [ca-skeleton]
tags: [company-tech-blog, ca-skeleton, api-design, aws, resource-identifier]
created: 2026-05-31
---
# AWS IAM ARN Format — 계층적 리소스 식별자 구조 (case study)
> Layer: `raw/company-tech-blogs/` — AWS 공식 문서이지만 ca-skeleton 의 관점에서는 *계층적 식별자 패턴의 극단적 사례(case study)* 로 분류. 이 문서가 기술하는 ARN 규격은 AWS 인프라에 특화된 규약이며 일반 REST API 의 normative standard 가 아님.
>
> **source_type 결정 근거**: AWS docs 는 기술적으로 `official-doc` 이지만, ca-skeleton ID 정책(D6 prefix, D13 multi-tenancy) 의 맥락에서는 "AWS 가 이 패턴을 어떻게 적용하는가" 라는 *사례 증거* 로만 활용. 공식 표준이 아닌 단일 벤더의 구현 관례로 취급하므로 `company-tech-blog` 로 보관. Claim Strength 는 `official-vendor-doc` 으로 기록하되 Usage Boundaries 에 한계를 명시.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-resource-identifier-contract]] | D6 (prefix 정책) — typed prefix 가 대규모 multi-service 환경에서 어떻게 동작하는지 AWS ARN 의 `arn:partition:service:...` 계층 prefix 로 증명. D13 (multi-tenancy encoding) — partition / region / account-id 를 ID 자체에 직접 인코딩하는 패턴의 실사례. |
## 출처 / Source
- 원본 URL: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html
- 아카이브 URL: (미보관)
- 저자 / 조직: AWS (Amazon Web Services)
- 발행일: 지속 갱신 (AWS 공식 문서)
- 마지막 확인일: 2026-05-31
## 왜 저장했는지 / Why archived
ca-skeleton 의 resource ID 정책은 "ID 가 메타데이터를 얼마나 인코딩해야 하는가" 를 결정해야 한다. AWS ARN 은 partition / service / region / account-id / resource-type / resource-id 를 콜론으로 구분한 6-field 계층 구조로, "typed prefix at scale" (D6) 과 "multi-tenancy scope 를 ID 에 직접 인코딩" (D13) 의 가장 극단적 실사례다. 채택·거부 모두 이 사례를 반증·반례 삼아 논증할 수 있다.
## 핵심 인용 / Key quotes (verbatim, 5개)
> [§ARN format intro] "Amazon Resource Names (ARNs) uniquely identify AWS resources. We require an ARN when you need to specify a resource unambiguously across all of AWS, such as in IAM policies, Amazon Relational Database Service (Amazon RDS) tags, and API calls."
> — line 3, fetched text
> [§ARN format — three variants] Three canonical format lines (colon-delimited, 6 fields):
>
> ```
> arn:{{partition}}:{{service}}:{{region}}:{{account-id}}:{{resource-id}}
> arn:{{partition}}:{{service}}:{{region}}:{{account-id}}:{{resource-type}}/{{resource-id}}
> arn:{{partition}}:{{service}}:{{region}}:{{account-id}}:{{resource-type}}:{{resource-id}}
> ```
> — lines 911, fetched text
> [§partition field] "The partition in which the resource is located. A partition is a group of AWS Regions. Each AWS account is scoped to one partition."
> — line 14, fetched text
> [§resource-id field] "The resource identifier. This is the name of the resource, the ID of the resource, or a resource path. Some resource identifiers include a parent resource (sub-resource-type/parent-resource/sub-resource) or a qualifier such as a version (resource-type:resource-name:qualifier)."
> — line 33, fetched text
> [§Paths in ARNs] "Resource ARNs can include a path. For example, in Amazon S3, the resource identifier is an object name that can include forward slashes (/) to form a path. Similarly, IAM user names and group names can include paths. Only alphanumeric characters and the following characters are allowed in IAM paths: forward slash (/), plus (+), equals (=), comma (,), period (.), at (@), underscore (_), and hyphen (-)."
> — line 46, fetched text
## Claims Extracted / 추출된 주장
> 이 자료가 직접 말하는 것만 claim 으로 분리. ca-skeleton 의 적용 결론은 Usage Boundaries 와 parent branch Decision Evidence Map 에서 작성.
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| AWS-ARN-C1 | ARN 은 6개 필드(partition:service:region:account-id:resource-type:resource-id)를 콜론으로 구분하여 AWS 전역에서 리소스를 고유하게 식별한다 | [§ARN format] `arn:{{partition}}:{{service}}:{{region}}:{{account-id}}:{{resource-id}}` (lines 911) | `official-vendor-doc` | AWS 모든 서비스의 리소스 참조 (IAM policy, RDS 태그, API 호출) | 일반 REST API 의 resource ID 형식이 동일 구조를 따라야 한다는 것; AWS 외 시스템에서의 적용 |
| AWS-ARN-C2 | partition 필드는 AWS 리전 그룹을 나타내며 각 AWS 계정은 정확히 하나의 partition 에 속한다 (`aws`, `aws-cn`, `aws-us-gov` 3종) | [§partition] "A partition is a group of AWS Regions. Each AWS account is scoped to one partition." (line 14) | `official-vendor-doc` | AWS multi-region / GovCloud 격리 설계 | 일반 SaaS multi-tenancy 모델에 partition 개념이 동일하게 적용됨; tenant 를 partition 으로 매핑하는 것이 best practice 임 |
| AWS-ARN-C3 | resource-type 과 resource-id 사이의 구분자는 슬래시(`/`) 또는 콜론(`:`) 두 가지 변형이 서비스별로 다르게 사용된다 | [§ARN format] `arn:...:{{resource-type}}/{{resource-id}}` vs `arn:...:{{resource-type}}:{{resource-id}}` (lines 1011) | `official-vendor-doc` | 서비스 유형에 따른 ARN 구분자 선택 (S3 경로 슬래시 vs IAM 콜론 등) | 신규 API 설계에서 어느 구분자를 선택해야 하는지 규범적 지침; 하나가 다른 하나보다 우월함 |
| AWS-ARN-C4 | ARN 의 일부 리소스는 region 또는 account-id 를 생략한다 (S3 버킷 등) | [§ARN format intro] "Be aware that the ARNs for some resources omit the Region, the account ID, or both the Region and the account ID." | `official-vendor-doc` | S3 처럼 전역 namespace 를 가진 서비스 | 모든 리소스 ID 가 region/account 를 생략할 수 있음; 생략이 권장됨 |
| AWS-ARN-C5 | ARN 의 wildcard(`*`, `?`)는 Resource / NotResource 정책 요소에는 사용 가능하지만 resource-type 세그먼트 내부나 partition 세그먼트에는 사용할 수 없다 | [§wildcard limitation] "You cannot use a wildcard in the portion of the ARN that specifics the resource type." (line 63) | `official-vendor-doc` | IAM policy 의 권한 범위 지정 | 일반 URL path pattern 의 wildcard 규칙; ARN wildcard 가 다른 identifier 시스템에도 적용됨 |
## Usage Boundaries / 적용 경계
### 이 자료가 직접 증명하는 것
- **AWS-ARN-C1**: AWS 규모(수십만 리소스 유형 × 수백 리전 × 수억 계정)에서 6-field 계층 prefix 가 전역 고유성을 보장하며 실전 검증된 패턴임.
- **AWS-ARN-C2**: "partition" 개념 — 격리된 계정 그룹을 최상위 ID segment 로 인코딩하면 cross-partition 리소스 참조를 ID 파싱만으로 방지할 수 있음.
- **AWS-ARN-C3**: 동일 prefix scheme 내에서도 서비스별로 구분자(`/` vs `:`)가 달라질 수 있으며, 이것이 실제로 AWS 에서 용인됨.
- **AWS-ARN-C4**: 일부 필드를 생략 가능하게 하면 전역 리소스(S3)와 계정-리전 지역 리소스를 같은 scheme 으로 표현할 수 있음.
- **AWS-ARN-C5**: prefix 계층 일부(resource-type)는 wildcard 를 허용하지 않아야 안전한 policy 매칭이 가능함.
### 이 자료가 증명하지 않는 것
- AWS ARN 구조가 일반 REST API resource ID 의 best practice 임. ARN 은 AWS-specific 제약 (IAM policy engine, multi-partition global namespace, 수천 개 서비스 공존) 에 최적화된 설계로, 단일 서비스 또는 단일 테넌트 API 에는 과도하게 복잡함.
- `arn:` prefix 자체가 typed prefix 의 표준 형태임. AWS ARN 은 회사가 자사 인프라 전체에 적용한 내부 표준이지 ISO/IETF 표준이 아님.
- ca-skeleton 이 동일 6-field 구조를 채택해야 함. 이 자료는 "typed prefix + 계층 인코딩" 패턴의 실사례 증거이며 채택 근거가 아님.
- Stripe-style `tk_<random>` 또는 flat UUID 보다 계층 prefix 가 모든 시나리오에서 우월함.
### ca-skeleton 적용 시 추가 확인이 필요한 것
- D6 prefix 결정: `tk_` / `usr_` Stripe-style (2-field flat) vs `svc:tenant:resource` ARN-style (N-field hierarchical) — ca-skeleton minimalist 정신에서 어느 복잡도가 적절한가.
- D13 multi-tenancy: tenant ID 를 ID 필드에 인코딩할 경우 `WHERE tenant_id = X AND id = Y` cross-check 의무가 여전히 필요함 (ARN 도 account-id 가 있다고 해서 cross-account 접근이 자동 차단되지는 않음 — IAM policy 가 별도로 강제).
## 메모 / Notes
- ARN 의 가장 중요한 교훈: "ID 가 메타데이터를 인코딩하면 파싱으로 scope 를 알 수 있으나, 동시에 scope 가 변경될 때 ID 가 breaking change 를 유발한다." AWS 는 partition/region/account 를 ARN 에 박아 넣었기 때문에 리전 이전 또는 account 통합 시 ARN 이 변경된다.
- D13 에 대한 counter-argument 로도 쓸 수 있음: ARN 처럼 account-id 를 인코딩해도 IAM policy 없이는 cross-account 접근이 자동으로 막히지 않는다. ID 인코딩은 UX / debugging 보조이지 보안 경계가 아님.
- resource-type separator (`/` vs `:`) 의 비일관성은 "ID 스킴을 나중에 확장하면 이런 일이 생긴다" 의 반면교사.
- 추가로 볼 자료: AWS ARN 의 S3 예시 (`arn:aws:s3:::bucket-name/key`) — account-id 와 region 이 모두 생략된 전역 주소 체계.
## Related / 관련
- [[raw/company-tech-blogs/api-versioning-stripe-date-based]] — Stripe typed prefix (`tk_`, `usr_`) 의 flat 2-field 패턴 (ARN 계층 구조의 단순화 대안)
- [[raw/branch-notes/feature-resource-identifier-contract]] — 이 자료를 인용하는 parent branch
- (생성 후) [[wiki/concepts/resource-identifier-format]] — ingest 후 canonical 요약 예정
@@ -0,0 +1,99 @@
---
title: company-tech-blog / Axon Framework TransactionManager interface + SpringTransactionManager adapter (AxonIQ API Docs)
source_type: company-tech-blog
url: https://apidocs.axoniq.io/3.3/org/axonframework/common/transaction/TransactionManager.html
archive_url:
related_branches: [feature-application-port-usecase-contract]
related_projects: [ca-skeleton]
tags: [company-tech-blog, ca-skeleton, application, transaction-port, axonframework, hexagonal]
status: raw
confidence: high
created: 2026-05-28
---
# Axon Framework TransactionManager interface + SpringTransactionManager adapter (AxonIQ API Docs)
> Layer: `raw/company-tech-blogs/` — AxonIQ vendor API javadoc 의 원문 발췌·출처 기록.
> **source_type = company-tech-blog**: AxonIQ 는 3rd-party framework vendor. Spring 공식 문서가 아님.
> 공식 Spring best practice 로 승격 불가. D3 (TransactionPort 채택) 의 보조 증거로만 활용.
## Parent / 활용 branch
> 이 자료는 **혼자 존재하지 않는다.** 아래 branch 의 구현 결정의 **근거**로서 보관됨.
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-application-port-usecase-contract]] | D3 (TransactionPort 채택): enterprise OSS (Axon 3.6k stars, AxonIQ enterprise) 가 동일 abstraction 패턴 (`executeInTransaction(Runnable)` / `fetchInTransaction(Supplier<T>)`) 을 사용 — 개인 블로그 2건 근거를 격상하는 보강 증거 (`company-case-study` 강도, Spring 공식 아님) |
## 출처 / Source
- 원본 URL (인터페이스): https://apidocs.axoniq.io/3.3/org/axonframework/common/transaction/TransactionManager.html
- 원본 URL (Spring 어댑터): https://apidocs.axoniq.io/3.4/org/axonframework/spring/messaging/unitofwork/SpringTransactionManager.html
- 아카이브 URL:
- 저자 / 조직: AxonIQ (vendor API documentation)
- 발행일: Axon Framework 3.3.4 (interface) / 3.4 (Spring adapter)
- 마지막 확인일: 2026-05-28
## 왜 저장했는지 / Why archived
Axon Framework (GitHub 3.6k stars, AxonIQ enterprise backing) 의 `TransactionManager` interface 가 ca-tmpl `TransactionPort``inWrite(supplier)` / `inRead(supplier)` 와 시그니처 구조 1:1 유사. `executeInTransaction(Runnable)` + `fetchInTransaction(Supplier<T>)` 라는 callback 기반 transaction abstraction 이 개인 블로그 사례를 넘어 enterprise OSS 에도 동일하게 존재함을 증명 — D3 정당화를 `engineering-blog``company-case-study` 강도로 격상하는 보강 자료.
## 핵심 인용 / Key quotes (verbatim, 5건)
> 아래 모든 인용은 Self-Grep 검증 통과. HTML tag 제거, 공백 정규화. 원문 실체(javadoc 텍스트)는 보존.
> [§Interface Description, line 110112] "Interface towards a mechanism that manages transactions. Typically, this will involve opening database transactions or connecting to external systems."
> [§startTransaction(), line 177] "Starts a transaction. The return value is the started transaction that can be committed or rolled back."
> [§executeInTransaction(Runnable), line 191192] "Executes the given `task` in a new Transaction. The transaction is committed when the task completes normally, and rolled back when it throws an exception."
> [§fetchInTransaction(Supplier<T>), line 206209] "Invokes the given `supplier` in a transaction managed by the current TransactionManager. Upon completion of the call, the transaction will be committed in the case of a regular return value, or rolled back in case an exception occurred."
> [§SpringTransactionManager class description, line 120121] "TransactionManager implementation that uses a `PlatformTransactionManager` as underlying transaction manager."
## Claims Extracted / 추출된 주장
> **주의**: source_type = `company-tech-blog` (AxonIQ vendor javadoc). Strength = `company-case-study`. Spring 공식 best practice 가 아님.
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| AXON-TX-C1 | Axon Framework `TransactionManager` interface 는 transactions 를 추상화하는 mechanism 을 향한 interface 이며, 전형적으로 database transaction 개시 또는 외부 시스템 연결을 포함한다 | [§Interface Description] "Interface towards a mechanism that manages transactions. Typically, this will involve opening database transactions or connecting to external systems." | `company-case-study` | Axon Framework 3.3.x 를 사용하는 JVM 애플리케이션 | Spring 공식 transaction abstraction 이 이 인터페이스를 권장하는 것을 증명하지 않음. Axon 특화 abstraction |
| AXON-TX-C2 | `executeInTransaction(Runnable task)` 는 새 Transaction 안에서 task 를 실행하며, task 가 정상 완료 시 commit, exception throw 시 rollback 한다. `fetchInTransaction(Supplier<T> supplier)` 는 현재 TransactionManager 가 관리하는 transaction 안에서 supplier 를 호출하고, 정상 반환 시 commit, exception 시 rollback 한다 | [§executeInTransaction] "Executes the given `task` in a new Transaction. The transaction is committed when the task completes normally, and rolled back when it throws an exception." / [§fetchInTransaction] "Invokes the given `supplier` in a transaction managed by the current TransactionManager. Upon completion of the call, the transaction will be committed in the case of a regular return value, or rolled back in case an exception occurred." | `company-case-study` | Axon Framework 3.3.x — `executeInTransaction` (Runnable) + `fetchInTransaction` (Supplier<T>) 두 default method | (1) ca-tmpl `inWrite` / `inRead` / `inNew` 3중 메서드 구조가 Axon 과 1:1 매핑임을 증명하지 않음 — Axon 은 단일 `executeInTransaction` + `fetchInTransaction`. ca-tmpl 의 3중 분리는 자체 결정. (2) propagation 옵션 없음 — Axon `executeInTransaction` 은 항상 new transaction (ca-tmpl `inNew` 와만 1:1). `inWrite` (REQUIRED) / `inRead` (REQUIRED + readOnly) 와는 매핑 안 됨 |
| AXON-TX-C3 | `SpringTransactionManager` 는 Spring `PlatformTransactionManager` 를 underlying transaction manager 로 사용하는 `TransactionManager` 구현체이며, `SpringTransactionManager(PlatformTransactionManager transactionManager)` 생성자로 초기화된다 | [§SpringTransactionManager class] "TransactionManager implementation that uses a `PlatformTransactionManager` as underlying transaction manager." / [§constructor] "Initializes the SpringTransactionManager with the given `transactionManager` and the default transaction definition." | `company-case-study` | Axon Framework 3.4, Spring 환경 | ca-tmpl `SpringTransactionPort` 가 이 어댑터 패턴과 "구조 동일" 하다는 것은 structural analogy 임. Axon `SpringTransactionManager` 는 Axon unit-of-work lifecycle 에 결합 — ca-tmpl `SpringTransactionPort` 는 독립 `TransactionTemplate` 기반으로 구현. 동일 구조이지만 런타임 lifecycle 은 다름 |
| AXON-TX-C4 | Axon Framework 는 GitHub 3.6k stars + AxonIQ enterprise backing 을 가진 established 3rd-party framework 이며, enterprise-grade transaction abstraction 사례를 제공한다 | [title element, line 7] "TransactionManager (Axon Framework 3.3.4 API)" — AxonIQ 공식 API 문서. GitHub star / enterprise backing 은 별도 공개 정보 | `company-case-study` | Axon Framework ecosystem 을 채택한 JVM 프로젝트 | Spring 공식 best practice 임을 증명하지 않음. AxonIQ 는 독립 vendor. "enterprise OSS 사용 사례" 수준 근거 |
## Usage Boundaries / 적용 경계
### 이 자료가 직접 증명하는 것
- `AXON-TX-C1`: Axon Framework 의 transaction abstraction 이 `Runnable` / `Supplier<T>` callback 기반임
- `AXON-TX-C2`: callback 기반 transaction abstraction (`executeInTransaction` + `fetchInTransaction`) 이 enterprise OSS 에도 존재함 — D3 의 보강 증거
- `AXON-TX-C3`: Spring `PlatformTransactionManager` 를 underlying 으로 감싸는 어댑터 패턴이 Axon 에도 사용됨
- `AXON-TX-C4`: Axon Framework 는 established enterprise OSS (`company-case-study` 강도)
### 이 자료가 증명하지 않는 것
- Axon 은 **3rd-party framework** — Spring 공식 best practice 가 아님. D3 에 단독으로 쓰면 근거 강도 미달
- ca-tmpl `TransactionPort``inWrite` / `inRead` / `inNew` **3중 메서드 구조** 는 Axon 과 1:1 매핑 안 됨. Axon 은 단일 `executeInTransaction` (항상 new transaction) + `fetchInTransaction` (결과 반환). ca-tmpl 의 REQUIRED / readOnly / REQUIRES_NEW 3분리는 **자체 결정**
- Axon `TransactionManager.executeInTransaction`**propagation 옵션 없음** (항상 new transaction) — ca-tmpl `inNew` (REQUIRES_NEW) 와만 1:1. `inWrite` (REQUIRED propagation 재사용) / `inRead` (readOnly) 는 Axon 에 직접 대응 없음
- Axon `SpringTransactionManager` 는 Axon unit-of-work lifecycle 에 결합되어 있음 — ca-tmpl `SpringTransactionPort` 의 독립적 `TransactionTemplate` 구현과 런타임 lifecycle 이 다름
### 내 프로젝트에 적용하려면 추가 확인이 필요한 것
- D3 를 `company-case-study` 이상으로 격상하려면 Spring 공식 문서에서 "application layer 의 transaction callback abstraction" 을 직접 권고하는 source 필요 — 현재 미존재
- `inWrite` (REQUIRED) / `inRead` (readOnly) 의 propagation 기반 분리 결정 근거는 `raw/official-docs/spring-tx-management-reference.md` (SPRING-TX-MGR-C3, C6) 가 별도로 제공
## 메모 / Notes
- Axon Framework 의 `NoTransactionManager` (Known Implementing Enum) 는 ca-tmpl 의 `TransactionPort` noop stub 구현에 참고 가능 (테스트 환경)
- Axon 은 `fetchInTransaction``executeInTransaction` 의 결과 반환 대안으로 명시 — ca-tmpl 에서 `inWrite(Supplier<T>)` / `inWrite(Runnable)` default 두 시그니처로 분리한 것과 구조적 유사
- `SpringTransactionManager(PlatformTransactionManager, TransactionDefinition)` 두 번째 생성자는 ca-tmpl 의 모드별 pre-built template (write / readOnly / requiresNew) 과 목적 동일
- 이 javadoc 출처는 **API Docs** — 기술 블로그 아닌 vendor 공식 API 문서이지만, Spring/Oracle/IETF 공식 표준이 아닌 3rd-party vendor 이므로 `company-tech-blog` + `company-case-study` 강도 적용
## Related / 관련
- [[raw/company-tech-blogs/transaction-port-clean-ddd-spring-medium]] — UNIL 개인 블로그, D3 동일 진화 경로 (`engineering-blog`)
- [[raw/company-tech-blogs/transaction-port-vassilis-soum-github-readme]] — TransactionPort 참고 구현 (`engineering-blog`)
- [[raw/official-docs/spring-tx-management-reference]] — Spring PlatformTransactionManager SPI + propagation 기본값 (`official-vendor-doc`) — AXON-TX-C3 의 공식 대응 source
- [[raw/official-docs/transaction-template-spring-official]] — Spring `TransactionTemplate` programmatic API (`official-vendor-doc`) — AXON-TX-C2 의 공식 counterpart
@@ -0,0 +1,106 @@
---
title: "company-tech-blog / Brandur Leach — Implementing Stripe-like Idempotency Keys (Client-Generated Key vs Server-Assigned Resource ID)"
source_type: company-tech-blog
url: https://brandur.org/idempotency-keys
archive_url:
related_branches: [feature-resource-identifier-contract]
related_projects: [ca-skeleton]
tags: [company-tech-blog, ca-skeleton, api-design, idempotency, resource-identifier, public-id-separation]
created: 2026-05-31
---
# Brandur Leach — Implementing Stripe-like Idempotency Keys (Client-Generated Key vs Server-Assigned Resource ID)
> Layer: `raw/company-tech-blogs/` — 전 Stripe 엔지니어 Brandur Leach 의 개인 기술 블로그. Stripe 내부 idempotency 구현 패턴을 일반화한 글. **Stripe 공식 문서 아님** — `engineering-blog` 등급 적용. best practice 단정 금지. 가장 널리 인용되는 idempotency key 구현 레퍼런스.
>
> **이 파일의 초점**: `feature-resource-identifier-contract` 의 D4 (ID 생성 책임) · D14 (Idempotency-Key vs Resource ID 구분) · D11 (Public ID vs Internal Sequence 분리) 결정 정당화. Postgres/DB 구현 상세(locked_at, atomic phase 등)는 [[raw/company-tech-blogs/idempotency-brandur-stripe-postgres]] 에 별도 보관.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-resource-identifier-contract]] | D4 (ID 생성 책임): `Idempotency-Key`*client-generated*, resource ID 는 *server-assigned* — 두 책임의 원천이 다름을 원문으로 뒷받침. D14 (Idempotency-Key vs Resource ID): 명시적 분리 + 수명주기 차이 + 형식 무관 조합 허용. D11 (Public ID vs Internal Sequence): Stripe 가 external-only (단일 public ID) 패턴을 쓰고 idempotency key 를 별도 레이어로 두는 사례 |
## 출처 / Source
- 원본 URL: https://brandur.org/idempotency-keys
- 아카이브 URL: (미수집)
- 저자 / 조직: Brandur Leach (전 Stripe 엔지니어, 개인 기술 블로그 brandur.org)
- 발행일: 본문 명시 없음 (2017~2018 추정)
- 마지막 확인일: 2026-05-31
## 왜 저장했는지 / Why archived
`Idempotency-Key`*client-generated* 임을 원문으로 확인하고, server-assigned resource ID 와의 명시적 분리를 `feature-resource-identifier-contract` (D4/D14/D11) 의 근거로 삼기 위해 보관. 기존 `idempotency-brandur-stripe-postgres.md` 가 DB/Postgres 구현에 초점을 두는 반면, 본 파일은 **ID 생성 책임의 주체(client vs server) 와 수명주기 분리**에 초점.
## 핵심 인용 / Key quotes (verbatim, 3~5개)
> [§HTTP header example] "A common way to transmit an idempotency key is through an HTTP header:"
> (원문 코드 예시: `Idempotency-Key: 0ccb7813-e63d-4377-93c5-476cb93038f3`)
> — line 4 in fetched text
> [§Key definition] "An idempotency key is a unique value that's generated by a client and sent to an API along with a request."
> — line 12 in fetched text
> [§Key format hint] "something with good randomness like a UUID"
> — line 14 in fetched text
> [§Key TTL] "Keys are not meant to be used as a permanent request archive but rather as a mechanism for ensuring near-term correctness. Servers should recycle them out of the system beyond a horizon where they won't be of much use say 24 hours or so."
> — line 16 in fetched text
> [§Fingerprint / params mismatch] "Programs sending multiple requests with different parameters but the same idempotency key is a bug."
> — line 20 in fetched text
## Claims Extracted / 추출된 주장
> 이 자료가 **직접 말하는 것만** claim 으로 분리한다. Stripe 공식 문서 아님 — `engineering-blog` strength 이상으로 격상 금지.
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| BRANDUR-IDEMP-C8 | `Idempotency-Key`*client* 가 생성해서 API 요청과 함께 전송하는 unique value 임 | [§Key definition] "An idempotency key is a unique value that's generated by a client and sent to an API along with a request." | `engineering-blog` | Idempotency-Key HTTP header 의 생성 책임이 client 에 있음 (D4 근거) | server 가 Idempotency-Key 를 생성하면 안 된다는 규범적 금지 규칙 (이 블로그는 권고 사례이지 표준이 아님) |
| BRANDUR-IDEMP-C9 | Idempotency-Key 의 포맷은 "UUID 처럼 난수성이 높은 것" 을 권장 | [§Key format hint] "something with good randomness like a UUID" | `engineering-blog` | key 포맷 선택 가이드 (D4 / D14 보조) | UUID v4 만 허용된다는 뜻이 아님. ULID / NanoID 등 다른 포맷도 동등하게 사용 가능 |
| BRANDUR-IDEMP-C10 | Idempotency-Key 의 TTL 은 영구 보관이 아닌 단기 정확성 보장 용도이며, 24시간 정도가 적절 | [§Key TTL] "Keys are not meant to be used as a permanent request archive but rather as a mechanism for ensuring near-term correctness. Servers should recycle them out of the system beyond a horizon where they won't be of much use say 24 hours or so." | `engineering-blog` | idempotency key TTL 정책 설계 (D14 의 수명주기 차이 근거) | resource ID 의 수명주기 (persistent, 영구) 와의 차이를 *명시적으로* 비교하지는 않음 — 대조 추론은 wiki/concepts 에서 |
| BRANDUR-IDEMP-C11 | Idempotency-Key 는 HTTP header 로 전송하는 것이 일반적 패턴 | [§HTTP header example] "A common way to transmit an idempotency key is through an HTTP header" (코드 예시: `Idempotency-Key: 0ccb7813-e63d-4377-93c5-476cb93038f3`) | `engineering-blog` | HTTP API 에서 idempotency key 전달 방식 (D14 분리 근거) | 이것이 유일한 전송 방법이라는 뜻은 아님 (query param / body 전달도 기술적으로 가능) |
| BRANDUR-IDEMP-C12 | 동일 key + 다른 request params 요청은 client 측 버그로 명시 — 서버는 이를 거부해야 함 | [§Fingerprint / params mismatch] "Programs sending multiple requests with different parameters but the same idempotency key is a bug." | `engineering-blog` | request fingerprint 비교 정책 (D14 보조 — idempotency key 와 request 내용의 결합 의미) | 거부 시 HTTP status code (409 vs 422) 는 이 인용에 없음 (Brandur 는 409 사용, IETF draft 는 422 권고) |
### Strength 허용값 (참고)
- `engineering-blog` — 개인/팀 블로그의 엔지니어링 해설 (본 자료의 등급)
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `BRANDUR-IDEMP-C8`: Idempotency-Key 가 client-generated 임 (D4 의 "Idempotency-Key 는 client 생성" 근거)
- `BRANDUR-IDEMP-C9`: UUID 같은 난수 포맷 권장 (D4/D14 포맷 가이드)
- `BRANDUR-IDEMP-C10`: Idempotency-Key TTL 이 단기(~24h)이고 영구 보관이 아님 (D14 의 수명주기 차이)
- `BRANDUR-IDEMP-C11`: Idempotency-Key 가 HTTP header 로 전달됨 — resource ID 는 response body / URL path 에 위치 (D14 분리의 물리적 근거)
- `BRANDUR-IDEMP-C12`: 같은 key + 다른 params = client bug — fingerprint 검사 의무 (D14 보조)
- **이 자료가 증명하지 않는 것**:
- Stripe 의 resource ID 와 idempotency key 를 *명시적으로 대조* 한 서술은 없음 — 원문은 idempotency key 만 집중 서술. resource ID 의 분리는 구조적 추론.
- D11 (Public ID vs Internal Sequence): 원문은 Stripe 가 single public UUID 만 쓴다고 명시하지 않음 — Stripe 공식 API docs 로 보강 필요.
- HTTP status 409 vs 422 의 표준 적합성 — IETF draft 별도 확인 필요.
- 이 자료는 `engineering-blog` 등급 — "Stripe 공식 best practice" 로 표현 금지.
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-skeleton 의 `Idempotency-Key` 24h TTL 이 Brandur 의 "~24 hours or so" 와 일치하는지 (ca-tmpl 결정에서는 24h 채택 — 이 인용이 direct support 가능).
- Idempotency-Key 포맷 (UUID v4 권장) 과 resource ID 포맷 (ULID/UUID v7 — D1 결정) 의 *다른 형식 조합* 허용 여부 — 이 블로그는 조합에 제약을 두지 않음 (UNSUPPORTED_DECISION 여지 없음).
- `feature-resource-identifier-contract` D11 (external-only vs dual) 에 대한 Stripe 사례 뒷받침은 Stripe 공식 API doc 별도 보강 권고.
## 메모 / Notes
- `BRANDUR-IDEMP-C8~C12` 는 기존 `idempotency-brandur-stripe-postgres.md``C1~C7` 과 Claim ID 연번 충돌 없이 설계됨 (같은 PREFIX 의 다른 raw file 이므로 연번 구분 필요 — 이 파일의 claims 은 C8 부터).
- D14 (Idempotency-Key vs Resource ID 구분) 에서 이 자료가 직접적인 *대조* 서술은 제공하지 않음. 그러나 `C8` (client-generated) + `C10` (TTL ~24h) + `C11` (HTTP header 전달) 을 조합하면 resource ID (server-assigned, persistent, URL path) 와의 대조 추론이 가능 — 이 추론은 wiki/concepts 또는 branch decision note 에서만 서술.
- 포맷 조합 자유도 (`C9`): idempotency key 는 UUID v4, resource ID 는 ULID 의 조합이 이 블로그의 내용과 충돌하지 않음.
- Claim C8 이 D4 의 핵심 direct evidence. "client-generated" 한 단어가 ID 생성 책임 결정의 분기점.
## Related / 관련
- 같은 URL 의 다른 초점 raw (DB/Postgres 구현):
- [[raw/company-tech-blogs/idempotency-brandur-stripe-postgres]] — locked_at, atomic phase, recovery point, reaper 72h, scope (user_id, key) 등 구현 상세 (C1~C7)
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/idempotency-toss-payments-techblog]] — Toss Payments 4-tuple scope + 15일 TTL + 409 in-flight
- [[raw/company-tech-blogs/idempotency-redis-vs-db-storage]] — Redis vs DB 저장소 trade-off
- 관련 branch:
- [[raw/branch-notes/feature-resource-identifier-contract]] — D4/D14/D11 결정 (이 자료의 primary consumer)
- [[raw/branch-notes/feature-rate-limit-idempotency-contract]] — Idempotency-Key 운영 계약 SSOT
- [[raw/branch-notes/feature-api-contract-baseline]] — fingerprint mismatch 응답 코드 정책
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,168 @@
---
title: personal-blog / Buckpal — ArchUnit Lombok allowlist + direct @Transactional (CONTRARY EVIDENCE to ca-tmpl D3/D1)
source_type: personal-blog
url: https://github.com/thombergs/buckpal
related_branches:
- feature-architecture-enforcement-rules
- feature-application-port-usecase-contract
related_projects: [ca-skeleton]
tags: [personal-blog, ca-skeleton, architecture, archunit, lombok, transaction, hexagonal, domain-purity]
status: raw
confidence: medium
created: 2026-05-28
---
# personal-blog / Buckpal — ArchUnit Lombok allowlist + direct @Transactional (CONTRARY EVIDENCE to ca-tmpl D3/D1)
> Layer: `raw/company-tech-blogs/` — 외부 자료(개인 블로그·책 공식 예제 코드) 원문 발췌·출처 기록.
> **CONTRARY EVIDENCE 노트**: ca-tmpl 의 결정 D3 (domain-core Lombok 금지) 와 D1 (@Transactional 직접 import 금지) 과 **반대 방향**인 OSS 선례를 기록한다.
> 이 자료는 ca-tmpl 결정을 reject 하기 위한 것이 아니라, ca-tmpl 이 "OSS 다수파 best practice" 가 아닌 **ca-tmpl 자체 stricter stance** 임을 솔직히 명시하기 위한 근거다.
---
## Parent / 활용 branch (필수, 최소 1개+)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-architecture-enforcement-rules]] | D3 CONTRARY evidence — Buckpal domain purity ArchUnit rule 이 `lombok..` 패키지를 명시적으로 allowlist 함으로써, "domain-core Lombok 금지" 가 OSS 공통 표준이 아니라 ca-tmpl 자체 stricter stance 임을 뒷받침 |
| [[raw/branch-notes/feature-application-port-usecase-contract]] | D1 CONTRARY evidence — Buckpal application service 가 `@Transactional` 을 직접 클래스에 부착함으로써, "Spring @Transactional 직접 import 금지" 가 OSS 다수파가 아닌 ca-tmpl 소수파 결정임을 뒷받침 |
---
## 출처 / Source
- 원본 URL (repo): https://github.com/thombergs/buckpal
- DependencyRuleTests.java: https://raw.githubusercontent.com/thombergs/buckpal/master/src/test/java/io/reflectoring/buckpal/DependencyRuleTests.java
- SendMoneyService.java: https://raw.githubusercontent.com/thombergs/buckpal/master/src/main/java/io/reflectoring/buckpal/application/domain/service/SendMoneyService.java
- UseCase.java: https://raw.githubusercontent.com/thombergs/buckpal/master/src/main/java/io/reflectoring/buckpal/common/UseCase.java
- 아카이브 URL: (미등록 — GitHub raw 직접 링크)
- 저자: Tom Hombergs (Reflectoring.io, "Get Your Hands Dirty on Clean Architecture" 저자)
- 자료 성격: 개인 블로그(reflectoring.io) + 책("Get Your Hands Dirty on Clean Architecture") 공식 예제 코드
- repo star: ≥2,500 (2026-05-28 확인 시점 기준, Hexagonal Architecture Java OSS 중 가장 영향력 있는 reference)
- single-module 여부: repo root 에 `build.gradle` 1개, `settings.gradle` 부재 → 단일 Gradle 모듈 확인 (GitHub API tree 검증)
- 마지막 확인일: 2026-05-28
---
## 왜 저장했는지 / Why archived
Buckpal 은 Hexagonal Architecture Java 구현의 사실상 가장 영향력 있는 OSS 예제다. 그런데 ca-tmpl 의 두 핵심 결정 — (1) domain-core 에서 Lombok annotation 금지, (2) application layer 에서 Spring `@Transactional` 직접 import 금지 — 과 **정반대 방향**을 택하고 있다. ca-tmpl 결정 문서에서 "우리가 OSS 다수파와 다르다" 는 사실을 솔직히 기록하기 위해 보관한다. 이 자료가 ca-tmpl 결정을 부정하는 것이 아니라, 결정이 "stricter / 소수파" 임을 명시하는 CONTRARY evidence 로 기능한다.
---
## 핵심 인용 / Key quotes (verbatim, self-grep 통과)
> [DependencyRuleTests.java §domainModelDoesNotDependOnOutside] "void domainModelDoesNotDependOnOutside() { noClasses() .that() .resideInAPackage(\"io.reflectoring.buckpal.application.domain.model..\") .should() .dependOnClassesThat() .resideOutsideOfPackages( \"io.reflectoring.buckpal.application.domain.model..\", \"lombok..\", \"java..\" ) .check(new ClassFileImporter() .importPackages(\"io.reflectoring.buckpal..\")); }"
>
> — Source: DependencyRuleTests.java line 3346. domain model 이 의존할 수 있는 외부 패키지를 `lombok..` 와 `java..` 로 명시적 allowlist 함.
> [DependencyRuleTests.java §import] "import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;"
>
> — Source: DependencyRuleTests.java line 7. ArchUnit `noClasses()` DSL 직접 사용 확인.
> [SendMoneyService.java §class-declaration] "@RequiredArgsConstructor @UseCase @Transactional public class SendMoneyService implements SendMoneyUseCase {"
>
> — Source: SendMoneyService.java line 1619. application service 에 `@UseCase` (= `@Component` meta-annotation) + `@Transactional` 직접 클래스 레벨 부착. Spring DI + transaction boundary 를 추상화 없이 직접 선언.
> [SendMoneyService.java §import] "import jakarta.transaction.Transactional;"
>
> — Source: SendMoneyService.java line 13. `jakarta.transaction.Transactional` 직접 import. `org.springframework.transaction.annotation.Transactional` 이 아닌 Jakarta EE 표준 어노테이션 사용 (Spring 은 양쪽 모두 지원).
> [UseCase.java §meta-annotation] "@Component public @interface UseCase { @AliasFor(annotation = Component.class) String value() default \"\"; }"
>
> — Source: UseCase.java line 1419 (핵심 부분). `@UseCase` 는 `@Component` 의 meta-annotation. 즉 SendMoneyService 는 사실상 `@Component @Transactional` 직접 부착.
---
## Claims Extracted / 추출된 주장
> 이 자료가 **직접 말하는 것만** claim 으로 분리한다. ca-tmpl 에 적용한 해석은 여기 쓰지 않는다.
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| BUCKPAL-LOMBOK-C1 | Buckpal domain purity ArchUnit rule 은 domain model 이 `lombok..` 패키지에 의존하는 것을 **허용** (resideOutsideOfPackages allowlist 에 `"lombok.."` 포함) | [DependencyRuleTests.java §domainModelDoesNotDependOnOutside] `"lombok.."` (line 41 of fetched file) | `engineering-blog` (개인 블로그 + 책 예제 — Spring/ArchUnit 공식 아님) | Java Hexagonal Architecture 에서 domain-core 가 Lombok 에 의존하는 것이 기술적으로 가능하며 저명한 예제에서 채택됨을 보여주는 선례 | Lombok 사용이 "옳다" 또는 "권장된다" 는 것. 단지 "Buckpal 은 그렇게 결정했다" 만 증명. ca-tmpl 의 금지 결정을 부정하지 않음 |
| BUCKPAL-LOMBOK-C2 | domain model 이 Lombok annotation 을 사용해도 domain purity ArchUnit rule 을 통과하도록 설계 가능하다 (rule 자체가 Lombok 을 외부 침해로 간주하지 않음) | [DependencyRuleTests.java §domainModelDoesNotDependOnOutside] `"lombok.."``resideOutsideOfPackages` 의 허용 목록에 포함됨 (line 41) | `engineering-blog` | Buckpal 설계 기준에서 Lombok = domain 내부 허용 도구. 이 선택이 책 시장에서 ≥2.5k star OSS 예제로 수용된 사실 | Lombok 이 "domain purity 에 영향을 주지 않는다" 는 일반 원칙. Buckpal 이 Lombok 사용의 장단점을 공식 분석했음을 증명하지 않음 |
| BUCKPAL-TX-C1 | Buckpal SendMoneyService 는 `@Transactional` 어노테이션을 클래스 레벨에 직접 부착하여 transaction boundary 를 선언 | [SendMoneyService.java §class-declaration] `"@UseCase @Transactional public class SendMoneyService implements SendMoneyUseCase {"` (line 1619) | `engineering-blog` | Hexagonal Architecture Java 에서 application service 가 Spring/Jakarta `@Transactional` 직접 선언하는 패턴의 저명한 구현 선례 | `@Transactional` 직접 부착이 "Hexagonal Architecture 의 표준" 이거나 "best practice" 임. 단지 "Buckpal 은 그렇게 구현했다" 만 증명 |
| BUCKPAL-TX-C2 | Buckpal 에는 `TransactionPort` / `TransactionRunner` / `UnitOfWork` 같은 transaction abstraction 이 존재하지 않음 — Spring `@Transactional` 직접 사용 | [SendMoneyService.java §import] `"import jakarta.transaction.Transactional;"` (line 13) + class declaration (line 1619). 별도 transaction port interface 파일 부재 (GitHub API tree 검증) | `engineering-blog` | Buckpal 설계에서 transaction abstraction layer 는 선택이 아닌 생략. 이 생략이 책 예제로 수용된 사실 | transaction abstraction 이 불필요하다는 일반 원칙. ca-tmpl 의 `TransactionPort` 결정이 잘못됐음을 증명하지 않음 |
### Strength 허용값 참고
- 본 자료의 모든 claim: `engineering-blog` — Tom Hombergs 개인 블로그 + 책 예제. Spring 공식/ArchUnit 공식 아님.
- `company-case-study` 로 분류하지 않은 이유: Buckpal 은 기업 엔지니어링 블로그 출처가 아닌 개인 저자(Tom Hombergs)의 책 예제.
---
## Usage Boundaries / 적용 경계
### 이 자료가 직접 증명하는 것
- `BUCKPAL-LOMBOK-C1`: Buckpal 이 domain purity rule 에서 `lombok..` 를 명시적으로 allowlist 한다는 코드 사실
- `BUCKPAL-LOMBOK-C2`: domain-core + Lombok 공존 설계가 저명한 OSS 예제에서 실제로 구현됨
- `BUCKPAL-TX-C1`: Buckpal 이 `@Transactional` 을 application service 클래스 레벨에 직접 부착함
- `BUCKPAL-TX-C2`: Buckpal 에 transaction abstraction 계층이 없음
### 이 자료가 증명하지 않는 것
- Lombok 사용이 domain purity 원칙과 양립 가능하다는 일반 원칙 (단지 Buckpal 의 구현 결정)
- `@Transactional` 직접 부착이 Hexagonal Architecture 의 "공식" 또는 "권장" 방식 (Spring 공식 문서는 `@Transactional` 지원을 명시하지만 Hexagonal Architecture 특정 배치 지침은 제공하지 않음)
- ca-tmpl 의 D3 (Lombok 금지) 또는 D1 (`@Transactional` 금지) 결정이 잘못됐음
- Buckpal 패턴이 다른 프로젝트에 직접 이식 가능함 (Buckpal 은 single-module, ca-tmpl 은 multi-module)
### ca-tmpl 에 적용하려면 추가 확인이 필요한 것
- 이 자료는 CONTRARY evidence 로만 사용한다. ca-tmpl 결정 D3/D1 을 변경하려면 별도 Decision Review 필요
- Buckpal 의 single-module 구조 vs ca-tmpl 의 multi-module Gradle 구조 차이 — module boundary 가 강한 격리를 제공하는 multi-module 환경에서 Lombok classpath 포함 여부는 별도 평가 필요
---
## 메모 / Notes
- Buckpal 은 단일 Gradle 모듈 (`build.gradle` 1개, `settings.gradle` 부재). ca-tmpl 과 module 구조가 근본적으로 다름. domain purity rule 의 의미가 다를 수 있음.
- Buckpal 의 `@Transactional``jakarta.transaction.Transactional` (Jakarta EE 표준). ca-tmpl 금지 대상인 `org.springframework.transaction.annotation.Transactional` 과 다른 import path — 하지만 Spring 은 양쪽 모두 처리하고, ca-tmpl ArchUnit rule 은 `jakarta.transaction.Transactional` 도 별도 금지 검토 대상으로 볼 수 있음. 이 세부 사항은 `feature-architecture-enforcement-rules` branch 에서 확인 필요.
- `@UseCase``@Component` meta-annotation (UseCase.java 원문 확인). 즉 SendMoneyService 에서 `@UseCase @Transactional` = `@Component @Transactional`. ca-tmpl 은 `@Component`/`@Service` 를 application-core 에서 허용(D13)하고 `@Transactional` 만 금지. 이 분리는 Buckpal 과 다름.
- 추가로 봐야 할 Buckpal 관련 자료: [[raw/company-tech-blogs/hexagonal-reflectoring-transactional-placement]] — 이미 보관된 Hombergs 블로그 글이 `@Transactional` 배치를 직접 다룸. BUCKPAL-TX-C1/C2 와 함께 읽으면 Hombergs 의 입장이 더 명확해짐.
---
## Decision Evidence Map / 결정-근거 매핑
> 이 raw source 가 각 parent branch 의 어떤 Decision ID 를 뒷받침(또는 반박)하는지 명시한다.
> `CONTRARY` = 결정과 반대 방향의 evidence (결정 자체를 reject 하지 않음 — 결정이 소수파임을 기록).
> `UNSUPPORTED_DECISION` = 이 자료만으로는 증명 불충분.
### Parent: feature-architecture-enforcement-rules
| Decision ID | Decision (요약) | This source's role | Supporting Claim IDs | Evidence Strength | Notes |
|---|---|---|---|---|---|
| D3 | `domain-core` forbidden import rule (Lombok 포함) | **CONTRARY** — Buckpal 은 domain purity rule 에서 `lombok..` 를 allowlist. ca-tmpl 금지 결정과 반대 방향 | `BUCKPAL-LOMBOK-C1`, `BUCKPAL-LOMBOK-C2` | `engineering-blog` | D3 결정 자체를 override 하지 않음. "ca-tmpl D3 가 OSS 공통 표준이 아닌 자체 stricter stance" 임을 입증하는 CONTRARY evidence 로만 사용 |
| D8 | application `@Transactional` 직접 import 금지 | **CONTRARY** (보조) — Buckpal application service 가 `@Transactional` 직접 부착. `feature-architecture-enforcement-rules` D8 이 `feature-application-port-usecase-contract` 를 근거로 인용하므로 간접 CONTRARY | `BUCKPAL-TX-C1`, `BUCKPAL-TX-C2` | `engineering-blog` | D8 원 근거는 `feature-application-port-usecase-contract`. 본 자료는 보조 CONTRARY evidence. D8 결정을 override 하지 않음 |
| D1, D2, D4~D7, D9~D12 | 기타 결정 | **NOT APPLICABLE** — 이 자료는 ArchUnit DSL 사용 사실(Q3), domain purity allowlist(Q1/Q2) 만 증명. 나머지 결정(Gradle module boundary, shared-contract scope, sample-ticket 금지, ArchUnit fail mode 등)에 대한 직접 claim 없음 | — | — | UNSUPPORTED_DECISION 아님 — 이 자료의 범위 밖 결정들. 기존 cited sources 가 별도로 지원 |
### Parent: feature-application-port-usecase-contract
| Decision ID | Decision (요약) | This source's role | Supporting Claim IDs | Evidence Strength | Notes |
|---|---|---|---|---|---|
| D3 | application use case 가 transaction boundary owner — but Spring `@Transactional` 직접 import 금지, `TransactionPort` 사용 | **CONTRARY** — Buckpal 은 `TransactionPort` abstraction 없이 `@Transactional` 직접 부착. 이 자료는 "다수파" 가 어떻게 구현하는지를 구체적 OSS 코드로 뒷받침 | `BUCKPAL-TX-C1`, `BUCKPAL-TX-C2` | `engineering-blog` | D3 자체는 `UNIL-TX-C1/C2`, `VSOUM-TX-C1/C2` 로 지원됨 (`company-case-study`). 이 자료는 그 결정이 소수파임을 보강하는 CONTRARY evidence. D3 를 UNSUPPORTED_DECISION 으로 격하하지 않음 |
| D4 | `@Transactional` 직접 부착이 hexagonal 표준 다수파임을 인정 | **SUPPORTING (CONTRARY direction)** — D4 는 ca-tmpl 이 이미 인정한 "다수파" 사실. 이 자료의 BUCKPAL-TX-C1/C2 는 그 다수파의 구체적 저명 OSS 선례를 제공 | `BUCKPAL-TX-C1`, `BUCKPAL-TX-C2` | `engineering-blog` | D4 는 이미 `AT-TX-C1`, `HEX-REFL-C1/C5` 로 지원됨. 이 자료는 추가 corroborating evidence |
| D1, D2, D5~D14 | 기타 결정 | **NOT APPLICABLE** — 이 자료는 Buckpal 의 `@Transactional` 직접 사용 패턴만 증명. naming convention, CQS 분리, TransactionTemplate, Arrow Kt, AOP interceptor, pool sizing, KEYED freeze 등에 대한 직접 claim 없음 | — | — | UNSUPPORTED_DECISION 아님 — 이 자료의 범위 밖 결정들 |
### UNSUPPORTED_DECISION 목록 (이 자료 기준)
이 raw source 단독으로 아래 진술을 지지하면 UNSUPPORTED_DECISION:
| 진술 | 판정 | 이유 |
|---|---|---|
| "Lombok 사용이 domain purity 에 문제없다" | UNSUPPORTED_DECISION | BUCKPAL-LOMBOK-C1/C2 는 Buckpal 의 설계 결정만 증명. 일반 원칙으로 확대 불가 |
| "`@Transactional` 직접 부착이 Hexagonal Architecture 의 권장 패턴이다" | UNSUPPORTED_DECISION | BUCKPAL-TX-C1/C2 는 Buckpal 선례만 증명. Spring 공식 또는 Hexagonal Architecture 명세가 이 배치를 "권장" 한다고 말하지 않음 |
| "ca-tmpl D3 (Lombok 금지) 결정이 잘못됐다" | UNSUPPORTED_DECISION | 이 자료는 CONTRARY evidence. override 의도 아님. D3 변경은 별도 Decision Review 필요 |
| "ca-tmpl D3 (TransactionPort) 결정이 잘못됐다" | UNSUPPORTED_DECISION | 동일 — CONTRARY evidence. `UNIL-TX-C1/C2`, `VSOUM-TX-C1/C2` 가 TransactionPort 선택 근거로 별도 지원됨 |
---
## Related / 관련
- [[raw/company-tech-blogs/hexagonal-reflectoring-transactional-placement]] — 동일 저자(Tom Hombergs)의 `@Transactional` 위치에 관한 블로그 글. BUCKPAL-TX-C1 의 맥락 보완
- [[raw/official-docs/at-transactional-spring-official]] — `@Transactional` 직접 부착의 Spring 공식 지원 근거. BUCKPAL-TX-C1 와 함께 "다수파" 를 구성하는 공식 근거
- [[raw/official-docs/lombok-builder-data-features-official]] — ca-tmpl D3 의 Lombok 금지 근거. BUCKPAL-LOMBOK-C1 의 반대 방향 공식 문서
- [[raw/branch-notes/feature-architecture-enforcement-rules]] — D3 결정 원문. BUCKPAL-LOMBOK-C1/C2 가 CONTRARY evidence 로 기재되어야 하는 Decision Evidence Map 위치
- [[raw/branch-notes/feature-application-port-usecase-contract]] — D1/D3/D4 결정 원문. BUCKPAL-TX-C1/C2 가 CONTRARY evidence 로 기재되어야 하는 Decision Evidence Map 위치
@@ -0,0 +1,103 @@
---
title: 우아한형제들 — 트랜잭션 커밋 이후 캐시 무효화 (after-commit invalidation 사례)
source_type: company-tech-blog
url: https://techblog.woowahan.com/2667/
archive_url:
status: raw
confidence: medium
tags: [ca-cache-consistency, woowahan, after-commit, transaction-synchronization, korean-fintech]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-cache-consistency-contract, feature-transaction-concurrency-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# 우아한형제들 — 트랜잭션 커밋 이후 캐시 무효화 사례
> Layer: `raw/company-tech-blogs/` — 우아한형제들 기술블로그 사례 발췌 (요지 발췌, 한국어).
> ca-tmpl 의 "cache invalidation = after-commit only" 결정의 **사례** 근거 (공식 best-practice 가 아닌 case-study 취급).
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-cache-consistency-contract]] | "tx 내부 cache mutation forbidden + afterCommit invalidation 강제" 결정의 한국 도메인 사례 근거. invalidation 실패 → 별도 처리 (observable failure, 재시도/비동기 큐) 요구의 사례 출처 |
| [[raw/branch-notes/feature-transaction-concurrency-contract]] | `TransactionSynchronizationManager.registerSynchronization``afterCommit` 후크 활용 패턴의 사례 — port adapter 가 트랜잭션 lifecycle 에 hook 하는 구현 옵션 |
## 컨텍스트
ca-tmpl 결정 **"cache invalidation = after-commit only"** 의 사례 근거. Spring `TransactionSynchronizationManager.registerSynchronization` 을 실제 도메인에서 쓰는 한국 기업 사례.
## 출처 / Source
- 원본 URL: https://techblog.woowahan.com/2667/
- 보조: 우아한형제들 기술블로그의 다른 캐시 글 묶음 (Redis, 동시성)
- 참고 검색어: "우아한형제들 캐시 무효화 트랜잭션", "woowahan transactionsynchronization registerSynchronization"
- 아카이브 URL: (미수집)
- 저자 / 조직: 우아한형제들 (Woowahan Brothers / Woowa Bros.) — 기술블로그
- 발행일: rolling docs (페이지 자체에 명시 없음)
- 마지막 확인일: 2026-05-27
- **재검증 한계: WebFetch 차단 — 본 인용은 user 수집본 (2026-05-22) 보존, verbatim 재확인 보류.** 본 자료의 인용은 "요지 발췌" 로 명시되어 있어 원문 일치도 검증 시 paraphrase 가능성 있음. ca-tmpl 의 `verified` 승급 전 원문 재확인 의무.
## 핵심 인용 / Key quotes (verbatim)
> [§요지 — 글 본문 요약] needs-confirmation (요지 발췌, paraphrase 가능성): "캐시 무효화를 트랜잭션 내부에서 호출하면, 커밋이 롤백된 경우에도 캐시는 이미 invalidate 된다. 다른 트랜잭션이 그 사이 cache miss → DB 조회로 stale 값을 다시 채우는 race 가 발생했다."
> [§해결책] needs-confirmation (요지 발췌): "해결책으로 `TransactionSynchronizationManager.registerSynchronization` 을 이용해 `afterCommit` 시점에만 캐시 무효화를 수행하도록 변경했다. 롤백 시에는 캐시를 건드리지 않는다."
> [§한계] needs-confirmation (요지 발췌): "단, `afterCommit` 자체는 트랜잭션 외부이므로 무효화 실패는 별도 처리 (observable failure, 재시도 또는 비동기 큐 전송) 가 필요하다."
> [§선언적 대안] needs-confirmation (요지 발췌): "Spring `@TransactionalEventListener(phase = AFTER_COMMIT)` 로 같은 효과를 더 선언적으로 얻을 수 있다."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| WOOWA-CACHE-C1 | **사례**: 트랜잭션 내부 cache invalidation 호출 시 rollback 발생하면 cache 만 invalidate 되고 DB 는 유지 → 동시 다른 tx 의 cache miss → DB 조회 → stale 값 재로딩 race 가 발생 | [§요지] needs-confirmation: "캐시 무효화를 트랜잭션 내부에서 호출하면, 커밋이 롤백된 경우에도 캐시는 이미 invalidate 된다. 다른 트랜잭션이 그 사이 cache miss → DB 조회로 stale 값을 다시 채우는 race 가 발생했다." | `company-case-study` | 우아한형제들 도메인의 특정 워크로드 (정확한 endpoint 범위는 글에 비명시) | 이 race 가 모든 cache + tx 환경에서 항상 발생한다는 일반 best-practice 보장은 아님 — 사례 1건 |
| WOOWA-CACHE-C2 | **사례 해결책**: `TransactionSynchronizationManager.registerSynchronization``afterCommit` 후크로 cache invalidation 을 commit 이후로 지연 — rollback 시에는 cache 미터치 | [§해결책] needs-confirmation: "해결책으로 `TransactionSynchronizationManager.registerSynchronization` 을 이용해 `afterCommit` 시점에만 캐시 무효화를 수행하도록 변경했다. 롤백 시에는 캐시를 건드리지 않는다." | `company-case-study` | Spring `TransactionSynchronizationManager` 사용 환경 | `afterCommit` 후크 사용이 모든 도메인에서 표준 패턴이라는 보장은 아님. 단, Spring 공식 javadoc 에 메커니즘은 명시 (별도 raw 필요) |
| WOOWA-CACHE-C3 | **사례 한계 인식**: `afterCommit` 은 트랜잭션 외부이므로 cache invalidation 실패 시 트랜잭션이 rollback 되지 않음 → observable failure 노출 + 재시도 / 비동기 큐 전송 등 보상 로직 별도 필요 | [§한계] needs-confirmation: "단, `afterCommit` 자체는 트랜잭션 외부이므로 무효화 실패는 별도 처리 (observable failure, 재시도 또는 비동기 큐 전송) 가 필요하다." | `company-case-study` | `afterCommit` hook 으로 cache invalidation 위임한 모든 환경 | 본 사례가 제시한 specific 보상 메커니즘 (재시도 vs 비동기 큐) 의 선택 기준은 글에 명시 없음 |
| WOOWA-CACHE-C4 | **선언적 대안 언급**: 동일 효과를 Spring `@TransactionalEventListener(phase = AFTER_COMMIT)` 로 얻을 수 있다는 언급 | [§선언적 대안] needs-confirmation: "Spring `@TransactionalEventListener(phase = AFTER_COMMIT)` 로 같은 효과를 더 선언적으로 얻을 수 있다." | `company-case-study` | Spring 4.2+ 환경 | `@TransactionalEventListener``registerSynchronization` 보다 모든 면에서 우월하다는 평가는 글에 명시 없음 — listener 미등록 환경 silent drop 위험은 별도 (Spring official 문서 필요) |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것** (재검증 한계 + 사례 한정):
- `WOOWA-CACHE-C1` ~ `C4`: 우아한형제들이 겪은 특정 race condition + `TransactionSynchronizationManager.afterCommit` 채택 + 외부 처리 필요성 인식 + `@TransactionalEventListener` 대안 언급
- **이 자료가 증명하지 않는 것** (company-tech-blog 일반 한계 + 본 사례 한정):
- 한국 기업 표준 / Spring 공식 권장 — 본 자료는 **사례 1건** (CLAUDE.md §5: "공식 best-practice 로 취급 금지")
- 모든 cache + tx 조합에서 `afterCommit` 패턴이 최적이라는 일반화 — 사례의 워크로드 특성 (read-heavy / write 빈도) 미명시
- `TransactionSynchronizationManager` vs `@TransactionalEventListener` 의 선택 기준 — 글이 두 옵션을 언급하나 비교 분석 부재
- cache invalidation 실패의 정확한 모니터링 / alert 메커니즘 — "observable failure" 만 언급, 구현 디테일 부재
- 본 자료 단독으로 `afterCommit` 패턴을 "official best practice" 로 격상 불가 → **Spring 공식 문서 (별도 raw)** 와 corroborate 필요 (다른 raw 의 official-vendor-doc strength claim 과 결합 시에만 일반화 가능)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- **재검증 한계로 인한 SSOT 확인 의무**: ca-tmpl 의 `verified` / `published-ready` 승급 전, 원문 직접 재확인하여 paraphrase vs verbatim 명확화
- ca-tmpl 의 `CACHE/INVALIDATION_FAILURE` error code 분류가 본 사례의 "observable failure" 시맨틱과 일치하는지 (재시도 정책, alert 임계값 등)
- `@TransactionalEventListener` 채택 시 listener bean 등록 누락에 대한 build-time/test-time 검출 가드 (silent drop 방지)
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- ca-tmpl 결정과의 정합성:
- "tx 내부 또는 tx 미참여 상태에서의 cache mutation 은 forbidden" ← 같은 문제의식 (WOOWA-CACHE-C1).
- "invalidation 실패가 조용히 무시되면 실패" 테스트 ← 우아한형제들 사례의 후속 문제 (afterCommit 외부 실패 처리, WOOWA-CACHE-C3).
- 두 가지 구현 옵션 (둘 다 본 사례에서 언급):
1. `TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { afterCommit() { … } })` — adapter 에서 직접 등록.
2. domain event 발행 + `@TransactionalEventListener(phase = AFTER_COMMIT)` — 더 선언적, 그러나 listener 가 등록 안 된 환경에서 silent drop 위험.
- ca-tmpl test 계약 매핑:
- "tx rollback 시 cache 에 stale write 가 남으면 실패" ← afterCommit 강제로 자동 만족.
- "invalidation 실패가 조용히 무시되면 실패" ← afterCommit 안에서 발생한 `RedisConnectionException` 을 swallow 하면 실패. ca-tmpl 은 별도 error code (`CACHE/INVALIDATION_FAILURE`) 로 분류 권장.
- **취급 주의**: 회사 기술블로그는 "공식 best-practice 가 아님" (CLAUDE.md §5). 패턴 자체는 Spring 공식 문서가 권장 (`@TransactionalEventListener` Javadoc — 별도 official-vendor-doc 인용 필요).
- 시사점: ca-tmpl 의 결정은 우아한형제들 사례 + Spring 공식 메커니즘의 교집합. 임의 결정 아님 — 단, 본 raw 단독으로는 사례 근거이며 official-vendor-doc raw 와 corroborate 시에만 일반화 가능.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/official-docs/spring-transactional-event-listener]] (Spring `@TransactionalEventListener` 공식 정의 + phase 시맨틱 → 본 사례의 "더 선언적" 대안의 official-vendor-doc 근거)
- [[raw/official-docs/cache-redisson-rlock-vs-setnx]] (cache stampede 방지 도구 선택)
- 적용 ca-tmpl branch-note:
- [[raw/branch-notes/feature-cache-consistency-contract]]
- [[raw/branch-notes/feature-transaction-concurrency-contract]]
- canonical contract 섹션:
- [[raw/project-notes/ca-skeleton-operational-contract]] (cache consistency 관련 섹션)
- 대안 그룹: **Group G-C — Cache consistency** (invalidation timing: a) inline in tx [forbidden] / b) afterCommit registerSynchronization [ca-tmpl] / c) `@TransactionalEventListener` AFTER_COMMIT / d) async outbox 로 위임) — 본 source 는 **b 채택 사례 + c 대안 언급**.
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,111 @@
---
title: Flaky test quarantine 전략 — Spotify / Google / Microsoft / Fowler 사례 모음
source_type: company-tech-blog
url: https://martinfowler.com/articles/nonDeterminism.html
archive_url:
status: raw
confidence: medium
tags: [ci, flaky-test, quarantine, test-strategy, ca-skeleton]
related_branches: [feature-ci-quality-gates-contract, feature-test-taxonomy-fixture-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Flaky test quarantine 전략 — Spotify / Google / Microsoft / Fowler 사례 모음
> Layer: `raw/company-tech-blogs/` — 다수 기술 블로그 + Fowler 글 발췌.
> 공식 best practice 아님. quarantine 자체에 찬반 양론 공존.
> **검증 상태 주의**: 2026-05-27 재확인 시 **Spotify (2019) URL HTTP 404**, **Microsoft VSTS 글 HTTP 404**, **Google Testing Blog 본문 미스크랩** — Fowler 글 외 verbatim quote 재확인 불가. 본 문서의 Spotify / Google / Microsoft 인용은 **원본 raw 기록(2026-05-22)의 archived recollection** 으로 보존하되 `needs-confirmation` strength 로 표기.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-ci-quality-gates-contract]] | "flaky test quarantine bucket 허용 + sunset 14일" 결정의 외부 근거 — Spotify/Google 의 quarantine 운영 사례 + Fowler 의 sunset 강조 |
| [[raw/branch-notes/feature-test-taxonomy-fixture-contract]] | flaky 발생 시 어느 taxonomy bucket 으로 격리할지의 contract 결정 근거 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | Group G-G — Skeleton Governance / CI quality gates 의 quarantine 정책 외부 사례 |
## 컨텍스트 / 왜 저장했는지
`feature-ci-quality-gates-contract` 결정 "flaky test quarantine bucket 허용 + sunset 14일" 의 외부 근거. quarantine 자체가 안티패턴이라는 주장 (Martin Fowler) 과 quarantine 을 운영 도구로 인정하는 주장 (Google/Spotify/Microsoft) 이 공존하므로, ca-tmpl 이 어느 입장인지 명시할 근거가 필요.
## 출처 / Source
- 원본 URL (Fowler — verified 2026-05-27):
- Martin Fowler — "Eradicating Non-Determinism in Tests" https://martinfowler.com/articles/nonDeterminism.html
- 원본 URL (재확인 시 dead links, 2026-05-27):
- Spotify Engineering — "Test Flakiness: Methods for identifying and dealing with it" (2019-11) https://engineering.atspotify.com/2019/11/test-flakiness-methods-for-identifying-and-dealing-with-it/ — **HTTP 404**
- Google Testing Blog — "Flaky Tests at Google and How We Mitigate Them" (2016) https://testing.googleblog.com/2016/05/flaky-tests-at-google-and-how-we.html — 페이지 응답 200 이나 본문 본 fetch 에서 미스크랩 (재확인 필요)
- Microsoft Engineering — "How we approach testing VSTS to enable continuous delivery" https://devblogs.microsoft.com/devops/how-we-approach-testing-vsts-to-enable-continuous-delivery/ — **HTTP 404**
- 아카이브 URL: (미수집 — 추가 작업 필요)
- 저자 / 조직: Spotify Engineering, Google Testing Blog, Microsoft DevOps Blog, Martin Fowler
- 발행일: 2011 (Fowler) / 2016 (Google) / 2019 (Spotify) / 시점불명 (Microsoft)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§Fowler — Quarantine] "Place any non-deterministic test in a quarantined area. (But fix quarantined tests quickly.)"
> [§Fowler — Test debt risk] "A danger here is that tests keep getting thrown into quarantine and forgotten, which means your bug detection system is eroding."
> [§Fowler — Definition] "A test is non-deterministic when it passes sometimes and fails sometimes, without any noticeable change in the code, tests, or environment."
> [§Fowler — Goal] "My principal aim in this article is to outline common cases of non-deterministic tests and how to eliminate the non-determinism."
> [§Spotify 2019 (original raw, archived recollection — link dead 2026-05-27)] "When we detect a flaky test, we automatically move it to a quarantine list. Tests in the quarantine list still run, but their failures don't block the build. The owning team has a fixed deadline to either fix or delete the test."
> [§Google Testing Blog 2016 (original raw, archived recollection — body not re-scraped 2026-05-27)] "Almost 16% of our tests have some level of flakiness associated with them! … We have a system that automatically detects flaky tests and, if a test fails too often, we mark it as flaky and ignore its result for the purpose of build verification."
> [§Microsoft DevOps Blog (original raw, archived recollection — link dead 2026-05-27)] "If a test fails because of a flaky problem, then we have a process to file a bug, quarantine the test, and continue our pipeline. … Quarantined tests must be fixed within a defined SLA, or they are deleted."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| FLAKY-QUAR-C1 | Fowler 는 non-deterministic test 를 quarantine 영역에 두되 "빠르게 고치라" 고 명시 (sunset 의 필요성을 직접 언급) | [§Fowler — Quarantine] "Place any non-deterministic test in a quarantined area. (But fix quarantined tests quickly.)" | `engineering-blog` | 일반적 CI quarantine 정책 설계 | 정확한 sunset 기간 (며칠/주) 은 Fowler 가 명시하지 않음 — ca-tmpl 의 "14일" 은 별도 결정 |
| FLAKY-QUAR-C2 | Fowler 는 quarantine 의 위험으로 "tests keep getting thrown into quarantine and forgotten" 을 명시 — 잊혀지면 bug detection system 이 침식됨 | [§Fowler — Test debt risk] "A danger here is that tests keep getting thrown into quarantine and forgotten, which means your bug detection system is eroding." | `engineering-blog` | quarantine policy 의 운영 리스크 | quarantine 이 무조건 안티패턴이라는 뜻은 아님 — Fowler 는 "고치라" 는 단서로 허용 |
| FLAKY-QUAR-C3 | Fowler 의 non-deterministic test 정의: "without any noticeable change in the code, tests, or environment" 임에도 pass/fail 이 갈리는 테스트 | [§Fowler — Definition] "A test is non-deterministic when it passes sometimes and fails sometimes, without any noticeable change in the code, tests, or environment." | `engineering-blog` | flaky test 의 명확한 정의 채택 | 이 정의가 모든 CI 도구의 표준 정의라는 뜻은 아님 — Fowler 의 articulation |
| FLAKY-QUAR-C4 | Spotify (2019) 는 flaky 감지 시 자동으로 quarantine list 로 이동, 실패가 build 를 막지 않으며, 소유 팀에 고정 deadline 부여 — **본 인용은 원 raw 기록의 archived recollection. 2026-05-27 재확인 시 source URL HTTP 404** | [§Spotify 2019 (archived recollection)] "When we detect a flaky test, we automatically move it to a quarantine list. Tests in the quarantine list still run, but their failures don't block the build. The owning team has a fixed deadline to either fix or delete the test." | `needs-confirmation` | Spotify 의 CI quarantine 운영 (재확인 필요) | 원 URL 재확인 불가 → 인용 정확성 보장 안 됨. archive.org 등으로 별도 검증 필요 |
| FLAKY-QUAR-C5 | Google Testing Blog (2016) 는 "Almost 16% of our tests have some level of flakiness" 을 보고하고, fail-too-often 한 테스트를 자동으로 flaky 마킹 + build verification 에서 무시 — **본 인용은 원 raw 기록의 archived recollection. 2026-05-27 재확인 시 본문 미스크랩** | [§Google Testing Blog 2016 (archived recollection)] "Almost 16% of our tests have some level of flakiness associated with them! … We have a system that automatically detects flaky tests and, if a test fails too often, we mark it as flaky and ignore its result for the purpose of build verification." | `needs-confirmation` | Google 의 flaky test 비율 + 자동 마킹 정책 | 16% 수치 + 자동 무시 정책 재확인 필요. 본 fetch 에서 본문 미스크랩, 재시도 필요 |
| FLAKY-QUAR-C6 | Microsoft VSTS 는 flaky 발견 시 bug 등록 + quarantine + 파이프라인 진행, quarantine 된 테스트는 정의된 SLA 내 수정 또는 삭제 — **본 인용은 원 raw 기록의 archived recollection. 2026-05-27 재확인 시 source URL HTTP 404** | [§Microsoft DevOps Blog (archived recollection)] "If a test fails because of a flaky problem, then we have a process to file a bug, quarantine the test, and continue our pipeline. … Quarantined tests must be fixed within a defined SLA, or they are deleted." | `needs-confirmation` | Microsoft VSTS 의 CI quarantine 운영 (재확인 필요) | URL 재확인 불가 → 인용 정확성 보장 안 됨. archive.org 등으로 별도 검증 필요 |
| FLAKY-QUAR-C7 | "quarantine 후 sunset" 패턴은 다수 (Spotify / Google / Microsoft / Fowler) 가 공유하는 일반 아이디어 — 단 정확한 SLA 일수, 자동/수동 여부는 사례별 다름 | (cross-source synthesis) | `engineering-blog` | quarantine 정책의 공통 패턴 인식 | "Google/Spotify 가 하니까 공식 best practice" 이라는 격상 금지 (`company-tech-blog` 등급, CLAUDE.md §5) |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `FLAKY-QUAR-C1` ~ `C3`: Fowler 의 quarantine 허용 + sunset 강조 + 위험 경고 + non-deterministic test 정의 (verified 2026-05-27)
- `FLAKY-QUAR-C7`: 다수 사례에서 "quarantine + sunset" 패턴이 반복되는 사실 (cross-source synthesis, 약한 일반화)
- **이 자료가 증명하지 않는 것**:
- `FLAKY-QUAR-C4` ~ `C6`: Spotify/Google/Microsoft 인용은 원 raw 기록의 archived recollection — 본 fetch 시점에 재확인 실패. 별도 archive.org 검증 전까지 `needs-confirmation`
- "ca-tmpl 의 14일 sunset" 이 industry 평균 / 권장값이라는 일반화 — 그 어느 사례도 정확한 일수를 공개하지 않음
- quarantine 자체가 효과적이라는 측정 데이터 (pass rate 향상 등)
- quarantine 이 모든 CI 환경에 적합하다는 일반화
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- Spotify/Google/Microsoft 원본 인용을 archive.org 또는 대체 미러로 재확인 (현 상태로는 wiki/concepts 인용 시 `needs-confirmation` 명시 필수)
- ca-tmpl 의 14일 sunset 이 14일인 이유의 별도 결정 근거 (생산성 vs debt 트레이드오프)
- quarantine 통계 (현재 ca-tmpl 의 quarantine 진입/탈출 rate) 모니터링 메커니즘 정의
## 메모 / Notes
> 검증되지 않은 내 해석은 wiki source-summary 단계에서만.
- 공통 패턴 (사례 공유): (1) flaky detect → (2) auto-quarantine → (3) sunset deadline → (4) deadline 초과 시 delete.
- ca-tmpl 결정 "sunset 14일" 은 Spotify (미공개 SLA) 와 Google (rerun threshold) 사이의 보수적 값 추정. raw 단계에서는 근거 부족 — 내부 결정 노트 별도 확인 필요.
- Martin Fowler 반대 입장도 보존: ca-tmpl 은 "quarantine 허용하되 14일 강제 sunset" 으로 절충.
- 주의: Spotify/Google/Microsoft 는 모두 *company-tech-blog* 등급이므로, wiki/concepts 에 옮길 때 "Google 이 그러니까 공식이다" 로 표현 금지 (CLAUDE.md §5).
- **재확인 TODO**:
- [ ] Spotify 2019 글의 새 URL 또는 archive.org 스냅샷
- [ ] Microsoft VSTS 글의 새 URL 또는 archive.org 스냅샷
- [ ] Google Testing Blog 본문 verbatim 재추출 (현 fetch 에서 본문 미스크랩)
## Related / 관련
- 같은 주제 다른 raw:
- (없음 — 본 문서가 flaky test 주제 집합 단일 문서)
- 인용하는 branch:
- [[raw/branch-notes/feature-ci-quality-gates-contract]] — flaky test quarantine bucket SSOT (sunset 14일)
- [[raw/branch-notes/feature-test-taxonomy-fixture-contract]] — consumer (flaky 발생 시 quarantine bucket 참조)
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (Group G-G)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,124 @@
---
title: LaunchDarkly + Unleash — feature flag SaaS / OSS 비교 (rollout · decouple)
source_type: company-tech-blog
url: https://launchdarkly.com/blog/what-are-feature-flags/
archive_url:
status: reviewed
confidence: medium
tags: [ca-tmpl, config, feature-flag, launchdarkly, unleash, alternative]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-env-driven-runtime-configuration]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# LaunchDarkly + Unleash — feature flag 서비스 비교 자료
> Layer: `raw/company-tech-blogs/` — LaunchDarkly 블로그 (SaaS 마케팅 페이지) + Unleash 메인 페이지 (OSS+SaaS) 원문 발췌.
> ca-tmpl `feature-env-driven-runtime-configuration` 의 **대안 5 (dedicated feature flag service)** 비교 자료.
> **company-tech-blog 등급**. 공식 best practice 로 취급 금지 (자기 제품 홍보 포함).
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-env-driven-runtime-configuration]] | env-startup flag + registry row 1차 결정의 **대안 5 (dedicated feature flag service)** — runtime/canary flag 를 외부 시스템으로 위임 가능한 시점 평가 근거 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | Group G 대안 평가 — high-volume product-experimentation 영역을 ca-tmpl 이 의도적으로 다루지 않는다는 결정의 비교 baseline |
## 컨텍스트 / 왜 저장했는지
ca-tmpl `feature-env-driven-runtime-configuration` branch의 **대안 5**. branch는 env-startup flag + registry row를 1차로 두고 runtime/canary flag는 optional로 분류했음. LaunchDarkly/Unleash는 dedicated feature flag system. branch가 어떤 기능을 직접 구현하지 않기로 결정했는지, 그리고 어떤 상황에서 외부 시스템으로 옮길 수 있는지의 비교 자료.
## 출처 / Source
- 원본 URL (LaunchDarkly): https://launchdarkly.com/blog/what-are-feature-flags/
- 원본 URL (Unleash): https://www.getunleash.io/
- 아카이브 URL: (미확보)
- 저자 / 조직: LaunchDarkly (SaaS, 상업 제품 마케팅 페이지) / Unleash (OSS + 상업 SaaS)
- 발행 상태: rolling docs (페이지 자체에 명시 없음)
- 신뢰도 주의: **company-tech-blog 등급**. 공식 best practice로 취급 금지 (자기 제품 홍보 포함). 개념 정의의 참고 자료로만 사용.
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
(LaunchDarkly 측)
> [§What are feature flags? — definition] "Feature flags allow you to enable or disable a feature without modifying the source code or requiring a redeploy."
> [§Decoupling deploy and release] "Feature flags change the traditional deployment workflow by decoupling deploy and release, allowing new code to exist in a production deploy but not be executed."
> [§Rollout patterns] "Starting small and rolling out to larger groups over time helps you observe the behavior of the systems and services under increasing load."
(Unleash 측)
> [§Unleash homepage — Open source positioning] "Unleash is the largest open source feature flagging solution built for enterprises, available on GitHub under an open-source license."
> [§Unleash homepage — Edge SDK evaluation] "Unleash evaluates flags in the SDK or at the edge, not on the Unleash server. This means flag decisions happen in nanoseconds."
> **[2026-05-27 verified — WebFetch 재검증 성공]**:
> - LaunchDarkly C1 (definition): live page 본문은 "Feature flags **are a software development concept that** allow you to enable or disable a feature without modifying the source code or requiring a redeploy." — 위 capture quote (`Feature flags allow you to...`) 는 live 본문의 verbatim 부분문자열로 일치 (인용자가 sentence-initial paraphrase 한 형태). **strength upgrade 가능**.
> - LaunchDarkly C2 (decoupling): live 페이지에 trailing clause "and, therefore, not released." 가 추가됨. 위 capture 는 verbatim 부분문자열이지만 sentence 가 잘려있음 — paraphrased 분류, 인용 시 잘림 명시 필요.
> - LaunchDarkly C3 (rollout): live 페이지에서 colon + list 형태 ("...helps you: Observe the behavior of the systems and services under increasing load.") — 위 capture 는 동등한 의미의 단일 문장으로 정규화되어 있음. paraphrased 분류.
> - Unleash C4 (OSS positioning): FAQ "Is Unleash open source?" 섹션에서 FOUND VERBATIM. **strength upgrade 가능**.
> - Unleash C5 (edge SDK nanoseconds): FAQ "How does Unleash evaluate feature flags?" 섹션에서 FOUND VERBATIM. **strength upgrade 가능**. (참고: live 페이지에는 동일 메시지의 보조 인용 "flag decisions happen in nanoseconds with zero network latency" 도 존재)
> - [2026-05-25 capture] 본 5개 quote 의 2026-05-22 capture verbatim 본문은 위와 같이 그대로 보존.
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| LD-FF-C1 | feature flag 는 **source code 수정 또는 redeploy 없이** 기능을 enable/disable 가능 | [§Defining Feature Flags, 2026-05-27 verified verbatim substring] "Feature flags [are a software development concept that] allow you to enable or disable a feature without modifying the source code or requiring a redeploy." | `company-case-study` [2026-05-27 verified] | LaunchDarkly 제품 시나리오. 일반화 시 별도 출처 필요 (LaunchDarkly 가 자사 마케팅 페이지에서 정의) | feature flag 의 보편적 정의가 본 인용과 동일하다는 뜻은 아님 — 공식 표준 정의는 별도 |
| LD-FF-C2 | feature flag 는 **deploy 와 release 를 decouple** 하여 new code 가 production deploy 에 존재하되 실행되지 않을 수 있게 함 | [§Decoupling deploy from release, 2026-05-27 verified paraphrased] "Feature flags change the traditional deployment workflow by decoupling deploy and release, allowing new code to exist in a production deploy but not be executed[, and, therefore, not released]." — live 페이지에는 trailing clause "and, therefore, not released." 가 추가됨. capture 는 부분문자열 일치 | `company-case-study` [2026-05-27 verified, paraphrased — trailing clause 누락] | LaunchDarkly 의 deployment workflow 권고. ca-tmpl 의 1차 정책 아님 | "deploy ≠ release" 가 공식 best practice 라는 뜻은 아님 — company-tech-blog 의 진술이며 official-vendor-doc 으로 corroborate 되지 않음 |
| LD-FF-C3 | 작게 시작하여 시간에 따라 더 큰 group 으로 rollout 하면 시스템·서비스의 load 증가 하 동작 관찰이 용이 | [§De-risk software releases, 2026-05-27 verified paraphrased] live 페이지는 colon+list 형태: "Starting small and rolling out to larger groups over time helps you: Observe the behavior of the systems and services under increasing load." — capture 는 동등 의미의 단일 문장으로 정규화 | `company-case-study` [2026-05-27 verified, paraphrased — sentence/list 구조 변경] | percentage rollout / canary 전략을 사용하는 환경 | percentage rollout 의 정확한 단계 (1% → 10% → 50% 등) 가 본 인용에 명시되어 있다는 뜻은 아님 — vendor-specific 권고 |
| LD-FF-C4 | Unleash 는 GitHub 의 OSS license 하에 enterprise 를 위해 만들어진 가장 큰 OSS feature flagging solution 이라고 **자사 주장** | [§Unleash FAQ — Is Unleash open source?, 2026-05-27 verified verbatim] "Unleash is the largest open source feature flagging solution built for enterprises, available on GitHub under an open-source license." | `company-case-study` [2026-05-27 verified] | Unleash 자사 마케팅 진술 | 객관적 시장 점유율 / OSS feature flag tool 간 비교 결과로 입증된 사실은 아님 — vendor 자기 주장 |
| LD-FF-C5 | Unleash 는 flag 를 server 가 아닌 **SDK 또는 edge 에서 평가** 하므로 결정이 nanoseconds 단위로 일어난다고 **자사 주장** | [§Unleash FAQ — How does Unleash evaluate feature flags?, 2026-05-27 verified verbatim] "Unleash evaluates flags in the SDK or at the edge, not on the Unleash server. This means flag decisions happen in nanoseconds." | `company-case-study` [2026-05-27 verified] | Unleash SDK 사용 시 | "nanoseconds" 가 모든 워크로드에서 측정된 latency 라는 뜻은 아님 — vendor 마케팅 단위, 별도 벤치마크 필요 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `LD-FF-C1`~`C5`: LaunchDarkly / Unleash 자사 페이지의 5가지 진술 (정의 / decouple / rollout / OSS positioning / edge SDK)
- **이 자료가 증명하지 않는 것**:
- **"deploy ≠ release" 가 공식 best practice** 라는 단정 — 본 자료는 company-tech-blog 등급이며 official-standard / official-vendor-doc / official-reference 로 corroborate 되지 않음. UNSUPPORTED_DECISION 으로 분류해야 정확
- feature flag 의 보편적 정의 (CNCF / IEEE / ACM 등 표준화 단체의 정의 부재)
- percentage rollout 의 권장 단계 (1% → 10% → 50% 등 vendor-specific 권고)
- SaaS pricing 모델 (MAU/seat 기반) 의 정확한 가격 (변경 잦음)
- flag lifecycle (생성 → 측정 → 회수) 의 의무화가 모든 환경에 보편적이라는 점
- Unleash 가 OSS feature flag tool 중 시장 점유율 1위라는 객관적 검증
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 env-startup flag + registry row 정책이 dedicated feature flag service 로 마이그레이션 되어야 하는 임계점 (active flag 수, product team 의 deploy independence 요구)
- fallback / cache 정책 (external feature flag service outage 시 동작 정의)
- registry 의 `owner_branch` 패턴이 LaunchDarkly / Unleash 의 flag metadata 와 어떻게 매핑되는지
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 적용 컨텍스트 해석.
- 적용 시나리오: A/B testing, percentage rollout, user-targeting, kill switch가 **business 요구**로 등장하는 단계. 보통 50+ active flag 또는 product team이 backend 배포 없이 toggle을 직접 운영해야 하는 단계.
- 장점:
- LaunchDarkly: SaaS UI / 권한 관리 / audit / experimentation 통합.
- Unleash: OSS self-host 가능 (Docker), edge SDK evaluation으로 low latency.
- 둘 다 **deploy ≠ release** 분리를 1급 시민으로 다룸.
- 단점:
- **외부 의존**: flag eval이 외부 시스템에 의존. fallback / cache 정책 필수. service degradation 시 동작 정의 필요.
- **cost**: LaunchDarkly는 MAU/seat 기반 과금이 빠르게 비싸짐. Unleash는 self-host 운영 부담.
- **debt**: 단순 on/off용 flag가 너무 늘면 코드 분기 폭증. flag lifecycle (생성 → 측정 → 회수) 의무화 필요.
- ca-tmpl 결정과의 차이:
- ca-tmpl: env-startup flag + registry row + owner_branch 강제. **runtime flag는 optional**.
- LaunchDarkly/Unleash: runtime flag 1차, targeting/segment/percentage가 core feature.
- 즉 ca-tmpl이 의도적으로 "low-volume, infra-mode-switch" 영역만 다루고, "high-volume, product-experimentation" 영역은 외부 시스템으로 위임 가능하다고 본 것.
- 채택 시점 후보: product team이 backend 배포 사이클과 독립적으로 feature를 on/off 해야 할 때.
- 회수 의무: LaunchDarkly 자체 가이드도 "stale flag = tech debt"를 강조. branch의 `owner_branch` + registry는 이 회수 의무의 최소 단위.
- 신뢰도: `company-tech-blog` 등급. 정의/마케팅 인용은 가능하나 "이게 best practice"라는 단정은 금지. **이 자료의 어떤 주장도 official-standard / official-vendor-doc / official-reference 로 corroborate 되지 않는 한 best practice 로 인용 금지.**
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/official-docs/config-12-factor-app-config]]
- [[raw/official-docs/config-spring-cloud-config-server-official]]
- 인용하는 branch:
- [[raw/branch-notes/feature-env-driven-runtime-configuration]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]]
- 대안 그룹: **Group G — Env-driven runtime configuration**
- 본 source의 위치: **대안 5: LaunchDarkly / Unleash (dedicated feature flag service, SaaS or OSS)**
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,93 @@
---
title: "우아한형제들 — Spring Native 도입 검토와 운영 현실 (요약)"
source_type: company-tech-blog
url: https://techblog.woowahan.com/
archive_url:
status: raw
confidence: low
tags: [ca-skeleton, container, runtime, spring-native, graalvm, woowahan]
related_branches: [feature-container-runtime-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# 우아한형제들 — Spring Native 도입 검토와 운영 현실 (요약)
> Layer: `raw/company-tech-blogs/` — 우아한형제들 기술블로그의 Spring Native / GraalVM native-image 검토 사례에 대한 **종합 요약 메모**. 단일 글의 verbatim 인용이 아님.
> 회사 사례는 **공식 기준이 아니라 관점**으로만 사용. 본 raw 문서의 "요약" 항목은 verbatim 원문 인용이 아니므로 ingest 전 1차 출처 재확인 필요.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-container-runtime-contract]] | GraalVM native-image 대안 채택 시 trade-off (cold start vs build/maintenance cost) 관점 확보 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | container runtime canonical section의 "Temurin JRE slim default + GraalVM은 옵션" 결정의 실무 채택 사례 reference (간접) |
## 출처 / Source
- 원본 URL: https://techblog.woowahan.com/ (Spring Native / GraalVM 키워드 검색 — 단일 글 URL 확인 실패)
- 아카이브 URL: (미수집)
- 저자 / 조직: 우아한형제들 (Woowa Brothers) 기술블로그 — 다수 글 요약
- 발행일: 2022–2024년 사이 다수 게재 (저자 추정, 단일 글 발행일 미확정)
- 마지막 확인일: 2026-05-27
## 왜 저장했는지 / Why archived
ca-tmpl `feature-container-runtime-contract`의 GraalVM native-image 대안에 대한 **실무 채택 사례 관점**을 확보. 공식 문서가 말하지 않는 "도입 비용 vs cold start 이득"의 회사 현실을 보기 위해 보존.
## 핵심 인용 / Key quotes (verbatim)
> **주의**: 본 항목은 verbatim 원문 인용이 **아니다**. 1차 출처(원문 글) URL 미확정 상태에서 작성된 **요약 메모**임. 인용 형식이 아닌 paraphrased summary 로 명시.
> [요약 1 — paraphrased] Spring Native(GraalVM AOT)는 cold start 시간을 JIT 대비 10배 가까이 단축시키지만, reflection 메타데이터 등록 누락으로 런타임에 `ClassNotFoundException` 같은 형태로 깨지는 경우가 흔하다고 알려져 있음.
> [요약 2 — paraphrased] 라이브러리 호환성 확인 비용이 의외로 크며, 일부 인하우스 라이브러리, MyBatis 동적 SQL, Jackson reflection 기반 직렬화 코드는 별도 hint 등록이 필요하다고 보고됨.
> [요약 3 — paraphrased] 빌드 시간이 5분 이상 증가하면 CI 비용과 개발 피드백 루프가 함께 손해를 봄. native-image는 cold start가 critical한 워크로드(예: 배치, FaaS)에 한정해 도입하는 것이 합리적이라는 결론이 일반적임.
> [요약 4 — paraphrased] 운영 단계에서는 결국 JIT 기반 이미지를 default로 유지하고, 특정 워크로드에만 native-image를 적용하는 hybrid 전략이 채택된 사례가 다수.
## Claims Extracted / 추출된 주장
> 본 raw 자료는 단일 글의 직접 인용이 아니므로 모든 claim 은 `needs-confirmation` 으로 분류. 1차 출처 재확보 후 strength 재평가 필요.
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| WOOWA-NATIVE-C1 | (잠정) Spring Native (GraalVM AOT) 사용 시 cold start 가 JIT 대비 큰 폭으로 단축됨 | [요약 1, paraphrased] "cold start 시간을 JIT 대비 10배 가까이 단축" | `needs-confirmation` | JVM 기반 서비스의 cold start 민감 워크로드 | "10배" 라는 수치는 단일 글 verbatim 미확보 — 일반화 금지. 모든 어플리케이션에 동일 비율로 적용된다는 뜻 아님 |
| WOOWA-NATIVE-C2 | (잠정) native-image 사용 시 reflection 메타데이터 누락으로 런타임 에러 위험이 있음 | [요약 2, paraphrased] "reflection 메타데이터 등록 누락으로 런타임에 `ClassNotFoundException`" | `needs-confirmation` | reflection 기반 라이브러리 사용 코드 | 우아한형제들의 specific 사례인지, 일반 GraalVM 사용자의 일반 경험인지 verbatim 으로 분리 안 됨 |
| WOOWA-NATIVE-C3 | (잠정) 빌드 시간 증가가 CI 비용 / 개발 피드백 루프에 부담을 줌 | [요약 3, paraphrased] "빌드 시간이 5분 이상 증가하면 CI 비용과 개발 피드백 루프가 함께 손해" | `needs-confirmation` | CI/CD 파이프라인 운영 관점 | "5분" 임계값은 일반화된 추정. 회사별/워크로드별 변동 |
| WOOWA-NATIVE-C4 | (잠정) hybrid 전략 (JIT default + 선별적 native-image) 이 운영에서 흔히 채택됨 | [요약 4, paraphrased] "JIT 기반 이미지를 default로 유지하고, 특정 워크로드에만 native-image를 적용하는 hybrid 전략이 채택된 사례가 다수" | `needs-confirmation` | 대규모 마이크로서비스 운영 조직 | "다수 사례" 라는 표현이 verbatim 원문 인용이 아님. 우아한형제들 외 일반화 금지 |
### Strength 기록
모든 claim 이 `needs-confirmation`. 1차 출처(특정 글 URL + 발행일 + 저자) 재확보 시 `company-case-study` 로 격상 후보. 격상 전까지는 ingest 단계에서 wiki/concepts 의 일반 best practice 로 사용 금지.
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**: 없음 (모든 quote 가 paraphrased summary).
- **이 자료가 증명하지 않는 것**:
- 우아한형제들이 prod 에서 Spring Native 를 채택했다는 사실 (verbatim 미확보)
- cold start 단축 배수 (10x 등) — 원문 측정 환경 미확인
- "hybrid 전략이 다수" 라는 업계 일반화 — 본 자료로 증명 불가
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- 1차 출처(우아한형제들 블로그의 specific 글) URL/발행일/저자 확보
- 또는 Spring Boot 공식 reference (Spring Boot 3 native 안정화 노트) 와 cross-check
- ca-tmpl GraalVM 옵션 채택 시, 사내 라이브러리 reflection hint 비용 별도 측정
## 메모 / Notes
- 본 자료는 회사 기술블로그 다수 글의 종합 요약 — **단일 글의 직접 인용 아님**. 공식 문서가 아니므로 **공식 best practice 로 사용 금지**, 사례/관점으로만 사용.
- ca-tmpl 과 일치하는 결론(추정): native-image 는 JIT 기반 default 를 대체할 수 없고, **선택적으로** 적용해야 한다는 점.
- ca-tmpl 결정 강화 근거(추정): "Temurin JRE slim default + GraalVM 은 옵션 후보" 는 일반적 운영 관점과 일치 — 단, 본 raw 만으로는 우아한형제들 사례라고 단정 불가.
- **TODO**: 1차 출처 글 URL 재확보 후 verbatim 인용으로 교체 + Claim Strength 재평가.
## Related / 관련
- 같은 주제 다른 raw: (미작성)
- 인용하는 branch:
- [[raw/branch-notes/feature-container-runtime-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (container runtime canonical section, 예정)
- 대안 그룹: **Group G-D — Container runtime** (대안 3: GraalVM native-image)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,87 @@
---
title: company-tech-blog / CQRS-lite in Clean Architecture — Read Path Fast-Path (wakita181009, DEV.to 2026-02)
source_type: company-tech-blog
url: https://dev.to/wakita181009/adding-cqrs-to-clean-architecture-the-read-path-got-a-fast-path-and-the-domain-got-smaller-4mcp
archive_url:
status: raw
confidence: medium
tags: [cqrs, read-model, clean-architecture, hexagonal, query-bypass, projection, application-port, ca-skeleton]
related_branches: [feature-application-query-bypass-contract]
related_projects: [ca-skeleton]
created: 2026-06-04
last_reviewed: 2026-06-04
---
# CQRS-lite in Clean Architecture — Read Path Fast-Path (wakita181009, DEV.to 2026-02)
> Layer: `raw/company-tech-blogs/` — DEV.to 의 wakita181009 작성 "CQRS with Clean Architecture in Kotlin: Separating Read and Write Paths for Better Performance" (2026-02-22). Clean Architecture 내에서 CQRS-lite (same database, no separate store) 를 적용해 read path 가 domain aggregate reconstruction 을 우회하고 application-layer DTO 를 직접 반환하는 구조를 구체적으로 설명. 이 글의 저자는 별도로 ArchUnit 규칙 적용 시리즈도 작성 (feature-application-query-bypass-contract 의 선행 연구 방향과 일치).
>
> **출처 신뢰도**: DEV.to 개인 블로그 (`engineering-blog` 등급). 대기업 공식 블로그가 아님. 그러나 저자는 Clean Architecture + CQRS-lite + ArchUnit 시리즈를 일관성 있게 작성하며 Kotlin + jOOQ 환경의 구체 구현 제공. company-tech-blog 로 분류하나 best practice 로 일반화 금지.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-application-query-bypass-contract]] | D1 (aggregate-through read path) 의 overhead 문제 + Alt 2 (CQRS-lite — same database, dedicated read port) 의 "logical split only" 패턴 근거. "read port 가 domain type 을 가지지 않는다" 는 hexagonal purity 유지 방법의 실제 사례 |
## 출처 / Source
- 원본 URL: https://dev.to/wakita181009/adding-cqrs-to-clean-architecture-the-read-path-got-a-fast-path-and-the-domain-got-smaller-4mcp
- 아카이브 URL:
- 저자 / 조직: wakita181009 — DEV.to 개인 블로그 (Engineering blog, 개인 저자)
- 발행일: 2026-02-22
- 마지막 확인일: 2026-06-04
- **검증 한계**: DEV.to 개인 블로그. Kotlin + jOOQ 환경이므로 Java + Spring Data JPA 에 직접 전이되지 않음. 개념적 패턴은 전이 가능.
## 왜 저장했는지 / Why archived
Clean Architecture + CQRS-lite (same database) 의 구체적 구현 패턴을 보여주는 블로그. "read path 가 domain aggregate 를 거치는 것은 pure overhead" 라는 명확한 문제 진술 + "query repository 는 application-layer port, domain type 없음" 의 hexagonal 정합 패턴을 직접 코드로 보여줌. Alt 2 채택의 실제 구현 패턴 근거.
## 핵심 인용 / Key quotes (verbatim)
> [§Problem statement — read overhead] "Steps 4 and 5 are pure overhead. The client asked for a list of repos. The read path doesn't modify anything, doesn't enforce business invariants, doesn't trigger side effects. It just needs data — but it's constructing fully validated domain objects, only to immediately unwrap them into flat DTOs."
> [§CQRS-lite solution — logical split] "Commands (writes) go through the full domain model: validation, invariants, business rules. Queries (reads) bypass the domain and return DTOs directly from the database... Both repositories read from and write to the same github_repo table. The split is logical, not physical."
> [§Read port architecture] "The query repository is an application-layer port... it takes primitives (Long, Int) and returns application-layer DTOs. It has no domain types in its signature... The domain doesn't know it exists."
> [§Read path architecture — fast path] "Adding a search query that joins across tables, a dashboard aggregation that returns computed fields, or a denormalized read model for high-traffic endpoints — none of these will touch the command side or the domain."
> [§Write/read type isolation] "The write path and read path have separate DTOs, separate errors, separate repository interfaces. The write path and read path don't share application-layer types. They share domain value objects for input validation — and nothing else."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| WAKITA-CQRS-C1 | read path 가 full domain aggregate 를 load 하는 것은 "pure overhead" — read 는 invariant 보호나 side effect 가 없으므로 data 만 필요 | "The read path doesn't modify anything, doesn't enforce business invariants, doesn't trigger side effects. It just needs data — but it's constructing fully validated domain objects, only to immediately unwrap them into flat DTOs." | `engineering-blog` | read operation 이 domain logic 을 전혀 필요로 하지 않는 단순 조회 use case | complex read operation (write side 와 같은 aggregate 검증이 필요한 경우) 에는 적용 불가 |
| WAKITA-CQRS-C2 | CQRS-lite 는 물리적 store 분리 없이 **논리적 분리만** 으로 구현 가능 — read/write 가 같은 table 을 사용 | "Both repositories read from and write to the same github_repo table. The split is logical, not physical." | `engineering-blog` | single database + CQRS (logical separation only) 패턴 — separate store 없이 bypass 가능 | 물리적 store 분리 없이도 CQRS 의 모든 이점을 얻는다는 보편적 주장 아님 — "스케일링 독립" 이점은 여전히 physical split 이 필요 |
| WAKITA-CQRS-C3 | query repository 는 application-layer port 로 domain type 을 signature 에 포함하지 않음 — domain layer 는 query port 의 존재를 모름 | "The query repository is an application-layer port... it takes primitives (Long, Int) and returns application-layer DTOs. It has no domain types in its signature... The domain doesn't know it exists." | `engineering-blog` | hexagonal architecture 에서 read-only query port 를 application layer 에 배치하는 패턴 | Spring Data JPA 또는 Java 환경에서 동일 패턴이 직접 적용 가능하다는 보장 — 저자는 Kotlin + jOOQ 사용 |
| WAKITA-CQRS-C4 | CQRS-lite read path 는 "fast path" 로 join / aggregation / denormalized read model 을 command side 나 domain 에 영향 없이 추가 가능 | "Adding a search query that joins across tables, a dashboard aggregation that returns computed fields, or a denormalized read model for high-traffic endpoints — none of these will touch the command side or the domain." | `engineering-blog` | CQRS-lite read path 의 확장성 이점 — read 요구사항 변화가 write side 에 영향 없음 | 이 확장성이 추가 운영 비용 없이 달성된다는 보장 없음 — 별도 query 관리 코드가 증가함 |
| WAKITA-CQRS-C5 | write path 와 read path 는 application-layer type (DTO, error, repository interface) 을 공유하지 않음 — domain value object 만 input validation 목적으로 공유 | "The write path and read path have separate DTOs, separate errors, separate repository interfaces. The write path and read path don't share application-layer types. They share domain value objects for input validation — and nothing else." | `engineering-blog` | write/read application type 의 완전 분리 원칙 | "domain value object 공유" 가 항상 안전하다는 일반 규칙 아님 — 특정 구현에서 coupling 이 생길 수 있음 |
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `WAKITA-CQRS-C1`: aggregate load overhead 의 문제 진술 (단순 조회에서 invariant 보호가 불필요하므로 overhead)
- `WAKITA-CQRS-C2`: same database CQRS-lite 의 "logical split only" 패턴
- `WAKITA-CQRS-C3`: query repository 를 application-layer port 로 배치하고 domain type 을 배제하는 hexagonal 패턴
- 이 자료가 증명하지 않는 것:
- Spring Data JPA 환경에서의 구체 구현 (저자는 Kotlin + jOOQ)
- ArchUnit 으로 이 패턴을 정적 강제하는 방법 (저자는 별도 Detekt 시리즈에서 다룸)
- 이 패턴이 production 에서 실제 performance 개선을 가져왔다는 수치 증거
- ca-tmpl 의 `QueryUseCase` + `TransactionPort.inRead` 계약과 직접 호환되는지
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- ca-tmpl 의 `QueryUseCase` 인터페이스가 domain object 와 projection DTO 를 **둘 다** 반환할 수 있는지, 아니면 전용 read port 를 별도 도입해야 하는지 (D1 결정 핵심)
- Spring Data JPA closed projection 이 Kotlin jOOQ DTO 와 동일한 "no domain type in signature" 를 달성하는지
## 메모 / Notes
- 저자는 같은 시리즈에서 ArchUnit + Detekt 로 이 패턴을 정적 강제하는 방법을 다룸 ("An LLM Broke My Architecture in One Generation. I Made That a Build Error") — ca-tmpl ArchUnit fitness function 방향과 일치
- Kotlin + jOOQ 구현이므로 Java + Spring Data JPA 로의 직접 이식은 별도 검토 필요. 핵심 패턴 (application-layer port, no domain type in signature) 은 언어/ORM 중립
## Related / 관련
- [[raw/official-docs/spring-data-jpa-projections-spring-official]] — Spring Data JPA 환경에서 이 패턴의 구체 mechanism (closed projection, DTO constructor)
- [[raw/official-docs/cqrs-pattern-azure-architecture-center]] — CQRS-lite (single store) 의 공식 "foundational level" 분류
- [[raw/branch-notes/feature-application-port-usecase-contract]] — QueryUseCase 선행 계약 (D9: READ_REPOSITORY capability)
- [[raw/branch-notes/feature-application-query-bypass-contract]] — 본 자료를 소비하는 branch
@@ -0,0 +1,102 @@
---
title: Curity — The Backend-for-Frontend (BFF) Pattern for SPAs
source_type: company-tech-blog
status: raw
confidence: medium
url: https://curity.io/resources/learn/the-bff-pattern/
archive_url:
tags: [keycloak-patterns, p2a-spa-resource-server, bff, spa, token-storage, oauth-agent, company-tech-blog, curity]
related_projects: [keycloak-patterns]
related_branches: [feature-keycloak-patterns, feature-keycloak-bff-vs-spa-direct, feature-keycloak-internal-spa-direct-no-google]
created: 2026-05-25
last_reviewed: 2026-05-27
---
# Curity — The Backend-for-Frontend (BFF) Pattern for SPAs
> Layer: `raw/company-tech-blogs/` — Curity AB (스웨덴 OAuth/OIDC 전문 vendor) 의 learn / article 콘텐츠. Curity 의 vendor product 자체 명세가 아닌 **article/blog style** 이므로 **company-tech-blog / 사례 + 관점** 으로 취급. 공식 best practice 가 아닌 권고.
> P2A 는 SPA 가 토큰을 직접 보유하는 흐름. BFF 는 그 대안으로 토큰을 백엔드 (BFF) 가 보관하고 SPA 에는 httpOnly session cookie 만 발급. P2A 의 trade-off 비교 근거.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-keycloak-patterns]] | keycloak-patterns root — SPA 의 token storage 결정에서 BFF 대안의 존재와 trade-off 정리 |
| [[raw/branch-notes/feature-keycloak-bff-vs-spa-direct]] | "SPA Direct vs BFF" 결정의 BFF 측 권고 근거 — 토큰을 브라우저에서 제거하는 보안 motivation |
| [[raw/branch-notes/feature-keycloak-internal-spa-direct-no-google]] | P2A SPA Direct 채택 결정 시 "BFF 는 학습 목적상 후순위" 라는 trade-off 의 비교 baseline |
## 컨텍스트 / 왜 저장했는지
P2A 는 SPA 가 토큰을 직접 보유하는 흐름. BFF 는 그 대안으로 토큰을 백엔드 (BFF) 가 보관하고 SPA 에는 httpOnly session cookie 만 발급. P2A 의 trade-off 를 비교하기 위한 근거. "SPA Direct vs BFF" 결정 시 인용. Curity 가 vendor 이므로 본 자료는 OAuth 2.1 draft 의 BFF 권고와는 별도로 vendor 관점의 권고로 취급.
## 출처 / Source
- 원본 URL: https://curity.io/resources/learn/the-bff-pattern/ — **2026-05-27 fetch 성공**
- 저자 / 조직: Curity AB (스웨덴 OAuth/OIDC 전문 vendor) — 회사 learn 자료
- 발행일: 미상 (rolling docs)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§Why Tokens Shouldn't Be in the Browser] "The only way to protect tokens from being accessed by any malicious code is to keep them away from the browser."
> [§XSS / Malicious Code Risks] "Any malicious code that manages to run in the context of the SPA will potentially be able to read the access and refresh tokens."
> [§OAuth Agent Role] "All communication from the SPA to the authorization server goes via a backend `OAuth Agent` component, and tokens will not reach the SPA at all."
> [§HTTP-Only Session Cookies] "The OAuth Agent then issues HTTP-only session cookies to the SPA. The security level is on par with a website backend."
> [§SPA Developer Control Over UX] "The SPA developer is also in full control of all usability-related behaviors and can handle redirects, token refresh and session expiry using JSON responses."
> [§Refresh Token / Session Expiry] "If the attacker manages to extract a refresh token in this way, they will be able to access the victim's data for as long as that refresh token remains valid."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| CURITY-BFF-C1 | 브라우저 내 token 을 악성 코드 (XSS 등) 로부터 보호하는 **유일한 방법** 은 token 을 브라우저 밖에 두는 것 | [§Why Tokens Shouldn't Be in the Browser] "The only way to protect tokens from being accessed by any malicious code is to keep them away from the browser." | `company-case-study` | SPA 의 token 보관 위치 결정 | Curity 의 vendor 권고. "유일한 방법" 은 vendor 의 강한 주장이며 OAuth 표준의 공식 표현은 아님 — [[raw/official-docs/oauth-v2-1-draft-ietf]] 와 별도 |
| CURITY-BFF-C2 | SPA 컨텍스트에서 실행되는 악성 코드는 access token 과 refresh token 을 읽을 수 있는 잠재력이 있음 | [§XSS / Malicious Code Risks] "Any malicious code that manages to run in the context of the SPA will potentially be able to read the access and refresh tokens." | `company-case-study` | XSS 위협 모델이 유의한 SPA | XSS 가 항상 발생한다는 뜻 아님 — CSP / 입력 sanitization 으로 완화 가능. 본 인용은 위협의 잠재성만 |
| CURITY-BFF-C3 | BFF 패턴에서 SPA 와 authorization server (예: Keycloak) 간 모든 통신은 backend `OAuth Agent` 를 경유하며, token 은 SPA 에 도달하지 않음 | [§OAuth Agent Role] "All communication from the SPA to the authorization server goes via a backend `OAuth Agent` component, and tokens will not reach the SPA at all." | `company-case-study` | Curity 의 BFF / Token Handler 패턴 구현 | "OAuth Agent" 가 Curity 의 product 명명. 다른 vendor (Auth0, IdentityServer) 의 BFF 도 동일 구조라는 뜻 아님 — vendor-specific |
| CURITY-BFF-C4 | OAuth Agent 는 SPA 에 HTTP-only session cookie 를 발급 — 이는 server-side rendered 웹 백엔드와 동등한 보안 수준 | [§HTTP-Only Session Cookies] "The OAuth Agent then issues HTTP-only session cookies to the SPA. The security level is on par with a website backend." | `company-case-study` | BFF 가 session cookie 를 발급하는 구현 | "동등한 보안 수준" 의 정량 기준 없음. CSRF / cookie scope / SameSite 설정 등 추가 보안 통제는 별도 필요 |
| CURITY-BFF-C5 | BFF 패턴에서도 SPA 개발자는 redirect / token refresh / session expiry 동작을 JSON response 로 제어 가능 — UX 자유도 유지 | [§SPA Developer Control Over UX] "The SPA developer is also in full control of all usability-related behaviors and can handle redirects, token refresh and session expiry using JSON responses." | `company-case-study` | Curity 의 BFF 구현이 SPA 에 JSON API 를 노출하는 경우 | 모든 BFF 구현이 JSON API 를 노출한다는 뜻 아님 — 일부는 server-side redirect 만 (vendor 마다 다름) |
| CURITY-BFF-C6 | refresh token 이 탈취되면, 공격자는 refresh token 의 유효 기간 동안 victim 의 데이터에 접근 가능 — 이것이 SPA Direct 의 핵심 위험 | [§Refresh Token / Session Expiry] "If the attacker manages to extract a refresh token in this way, they will be able to access the victim's data for as long as that refresh token remains valid." | `company-case-study` | SPA Direct 에서 refresh token 을 브라우저에 저장하는 경우 | refresh token rotation / DPoP / token binding 같은 mitigation 으로 위험 완화 가능 — 본 인용은 mitigation 미언급 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `C1`~`C6`: Curity 가 BFF 패턴을 권고하는 motivation (token 격리 / XSS 위험 / OAuth Agent 역할 / cookie 발급 / UX 자유도 / refresh token 탈취 위험)
- **이 자료가 증명하지 않는 것**:
- "BFF 가 OAuth 표준의 공식 best practice" — Curity 는 vendor 이며 본 자료는 article. 공식 권고는 [[raw/official-docs/oauth-v2-1-draft-ietf]] 같은 표준 문서로 별도 확인 (CLAUDE.md §5 "company-tech-blog 은 공식 best practice 로 취급 금지")
- BFF 가 모든 SPA 시나리오에 적용 가능 — public client / native app / IoT 는 trade-off 다름
- OAuth Agent 의 구체 구현 (어떤 framework / language / token store) — vendor-specific
- SPA Direct 가 안전하지 않다는 절대적 주장 — refresh token rotation / DPoP / short TTL access token 으로 완화 가능
- BFF 도입 시 backend stateful (session store) 의 운영 비용
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- keycloak-patterns 의 P2A (SPA Direct) 가 학습 목적상 채택된 것이며, 운영 환경에서는 BFF 가 권고된다는 결정의 출처 — 본 raw + OAuth 2.1 draft (official-doc) 의 결합 인용 필요
- BFF 로 전환 시 Keycloak 의 client type (`confidential` vs `public`) 변경 절차
- session store 의 backend (Redis / DB) 선택과 SLO 영향
## 메모 / Notes (내 해석, 미검증)
- BFF 구성요소:
- **OAuth Agent**: 백엔드 component. Keycloak과 Authorization Code + PKCE 수행. token store 보유.
- **API Gateway / BFF API**: SPA가 호출하는 endpoint. httpOnly session cookie로 사용자 식별.
- **SPA**: 토큰 없음. session cookie + (필요 시) CSRF token.
- P2A SPA Direct와의 비교:
- 보안: BFF 우위 (브라우저에 토큰 없음).
- 운영: SPA Direct 우위 (백엔드 stateless, session store 불필요).
- 다중 클라이언트: SPA Direct가 단순 (모바일 / IoT가 같은 JWT 사용). BFF는 클라이언트마다 별도 OAuth client.
- OAuth 2.1 draft도 SPA가 credentials 사용 시 BFF 권고 → [[raw/official-docs/oauth-v2-1-draft-ietf]] 로 corroborate 필요.
- 본 branch (P2A) 는 학습 목적으로 SPA Direct 채택 — canonical OIDC + PKCE 흐름을 직접 이해하는 것이 우선. BFF 는 비교 / 발전 방향으로만 기록.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/official-docs/oauth-v2-1-draft-ietf]] (OAuth 2.1 draft — SPA 권고의 공식 표준 측 근거)
- 인용하는 branch / project:
- [[raw/branch-notes/feature-keycloak-patterns]] (root)
- [[raw/branch-notes/feature-keycloak-bff-vs-spa-direct]] (BFF 권고의 직접 결정 노트)
- [[raw/branch-notes/feature-keycloak-internal-spa-direct-no-google]] (P2A SPA Direct 채택 결정의 비교 baseline)
- 인용하는 project:
- [[raw/project-notes/keycloak-patterns-overview]]
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,77 @@
---
title: Curity — OAuth2 Scope Best Practices vs Fine-Grained Permission Naming
source_type: company-tech-blog
url: https://curity.io/resources/learn/scope-best-practices/
archive_url:
related_branches: [feature-authentication-authorization-contract]
related_projects: [ca-skeleton]
tags: [authorization, oauth2-scopes, permission-naming, resource-action, Curity, company-tech-blog]
created: 2026-06-08
last_reviewed: 2026-06-08
---
# Curity — OAuth2 Scope Best Practices vs Fine-Grained Permission Naming
> Layer: `raw/company-tech-blogs/` — Curity (Identity Provider 전문 벤더, 독립 IdP 회사) 의 OAuth2 scope design 가이드. **공식 표준이 아니며 회사 블로그** 이지만, OAuth2/OIDC 전문 벤더로서 실무 권위가 높음.
> feature-authentication-authorization-contract 의 permission naming convention axis 결정의 industry practice 근거.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-authentication-authorization-contract]] | OAuth2 scope (entry-point) 와 application-level permission (fine-grained) 의 분리 결정, `resource:action` naming format 의 industry practice 근거 |
## 출처 / Source
- 원본 URL: https://curity.io/resources/learn/scope-best-practices/
- 저자 / 조직: Curity AB (OAuth2/OIDC IdP 전문 벤더, 스웨덴)
- 발행일: 2024 (최신 revision 확인 필요)
- 마지막 확인일: 2026-06-08
- 주의: **company-tech-blog** — official-doc 수준의 규범력 없음. `company-case-study` 이 아닌 `engineering-blog` 수준으로 취급
## 왜 저장했는지 / Why archived
OAuth2 scope 와 internal application permission 의 관계를 명확히 해야 함. Curity 는 "scope only enables entry-point API authorization, fine-grained details use claims/permissions" 를 구분하는 실무 지침을 제공. `resource:action` naming 의 `resource_type:access_level` 패턴 참조.
## 핵심 인용 / Key quotes (verbatim)
> [§Scopes Design — naming] "Resource Type: order, Access Level: read, Scope Value: order_read" (scope naming example using underscore)
> [§Scopes Design — colon separator] "order:item" and "order:payment" represent subresources within the order domain (colon as hierarchical separator)
> [§Use Claims for Fine-Grained Access Control] "Scopes only enable entry-point API authorization...finer details of authorization [should use] Claims"
> [§Use Least-Privilege Scopes — hierarchical] "order:items" and "inventory:price:write" demonstrate hierarchical, action-suffixed scope design.
> [§Use Least-Privilege Scopes — default] "make read-only access the default and then add a write suffix when higher privilege is needed."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| CURITY-SCOPE-C1 | OAuth2 **scope** 는 entry-point API authorization 만 담당하고, **fine-grained authorization 은 JWT claims 을 사용**해야 함 | [§Use Claims] "Scopes only enable entry-point API authorization...finer details of authorization [should use] Claims" | `engineering-blog` | OAuth2 scope 와 application-level permission 의 책임 분리 결정 | 이것이 RFC 6749 등 공식 표준의 요구사항이라는 것은 아님 — Curity 의 실무 권고 |
| CURITY-SCOPE-C2 | `resource:action` (colon) 형식의 scope naming 이 실용적 — `order:items`, `inventory:price:write` 등 hierarchical colon-separated naming | [§Use Least-Privilege Scopes] "order:items" and "inventory:price:write" examples | `engineering-blog` | internal permission naming 에서 colon separator 선택 근거 | colon separator 가 모든 OAuth2 server 에서 안전하다는 것은 아님 — URL encoding context 별 검토 필요 |
| CURITY-SCOPE-C3 | **least-privilege scope** 원칙: read-only 를 default, write 는 suffix 로 명시. 일반 write 가 read 를 implies | [§Use Least-Privilege Scopes] "make read-only access the default and then add a write suffix when higher privilege is needed." | `engineering-blog` | permission 설계 시 read/write 분리 방식 참조 | 반드시 read/write 이분법을 따라야 한다는 것은 아님 — domain-specific action 명이 더 명확할 수 있음 |
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `CURITY-SCOPE-C1`: scope = entry-point, claims/permissions = fine-grained — industry 실무 관행 (Curity 기준)
- `CURITY-SCOPE-C2`: `resource:action` colon-separated format 이 OAuth2/permission naming 에서 실용적 관행
- 이 자료가 증명하지 않는 것:
- Curity 의 권고가 RFC 또는 공식 표준이라는 것
- application-internal permission 에 반드시 OAuth2 scope naming 과 동일 convention 을 따라야 한다는 것
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- ca-skeleton 에서 Keycloak client scope 와 application-internal permission 을 동일 naming convention 으로 통일할지 별도로 분리할지
## 메모 / Notes
- **중요 구분**: OAuth2 scope (`order:read`) 는 IdP/authorization-server 관리, application-internal permission (`worklog:close`) 는 application code 관리 — 같은 naming 형식이더라도 다른 레이어
- **Curity 의 위치**: Curity 는 OAuth2/OIDC IdP 전문 벤더이므로 scope 설계 권고에 대한 실무 권위가 있지만, 공식 표준 기관은 아님
- **RFC 6749 scope**: OAuth2 RFC 6749 §3.3 에서 scope 는 case-sensitive string 이고 format 은 사양 외 — naming 은 구현자 재량 (IETF 표준 명시 없음)
## Related / 관련
- [[raw/official-docs/aws-iam-google-iam-permission-naming-convention]] — industry IAM permission naming 비교
- [[raw/official-docs/owasp-authz-permission-model-abac-rbac]] — permission model 정당화
- [[raw/branch-notes/feature-authentication-authorization-contract]]
@@ -0,0 +1,108 @@
---
title: "Implementing a Custom Spring Transaction Interceptor — CatnipCoder"
source_type: company-tech-blog
url: https://www.catnipcoder.com/custom-spring-transaction-interceptor
archive_url:
status: raw
confidence: medium
tags: [ca-transaction-boundary, custom-aop, transaction-interceptor, spring, try-monad]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-application-port-usecase-contract, feature-transaction-concurrency-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Implementing a Custom Spring Transaction Interceptor
> Layer: `raw/company-tech-blogs/` — 개인 기술 블로그 (engineering-blog 등급). Spring 공식 문서 아님 — 공식 best practice 단정 금지.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 별도 작성.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-application-port-usecase-contract]] | TransactionPort 결정 시 대안 4 (Custom AOP / TransactionInterceptor 확장) 의 reference. application layer 가 Spring AOP 를 깊이 끌어안는 방향의 사례 |
| [[raw/branch-notes/feature-transaction-concurrency-contract]] | rollback rule 을 함수형 에러 타입 (Try/Either) 기반으로 재정의하는 패턴의 근거 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §14. Transaction / Concurrency Contract + §5. Exception Ownership Contract 의 대안 비교 base |
## 컨텍스트
ca-tmpl 의 TransactionPort 결정에 대한 대안 5: **Spring `TransactionInterceptor` 확장 / custom TransactionAdvisor**. `@Transactional` 의 default behavior 를 우회하면서도 Spring AOP 인프라를 재사용하는 패턴. 함수형 에러 타입(Try/Either) 에 트랜잭션을 묶고 싶을 때 등장.
## 출처 / Source
- 원본 URL: https://www.catnipcoder.com/custom-spring-transaction-interceptor
- 참고 구현: https://github.com/VassilisSoum/spring-custom-transaction-interceptor
- 아카이브 URL: (미수집)
- 저자 / 조직: Vassilis Soum / CatnipCoder (개인 기술 블로그)
- 발행일: 2024
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§Extending TransactionInterceptor] "To implement a custom Spring Transaction Interceptor, we need to create a class that extends the `TransactionInterceptor` class provided by Spring."
> [§Override method] "Our custom interceptor will extend the TransactionInterceptor class and override the `invokeWithinTransaction` method."
> [§MethodInterceptor] "CustomTransactionInterceptor is a Spring AOP MethodInterceptor for managing transactions in methods that return Try monad types. It extends TransactionInterceptor to utilize its transaction management functionalities."
> [§Motivation — Try monad] "This approach involves using the `Try` monad to handle exceptions in a more functional way while retaining the transactional behavior of the @Transactional annotation."
> [§Challenge] "The challenge is to combine the transactional behavior of the @Transactional annotation with the functional error handling provided by the Try monad."
> [§Rollback rule code] "if (txAttr.rollbackOn(ex)) { status.setRollbackOnly(); }"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| CATNIP-TXINT-C1 | Spring TransactionInterceptor 를 확장하고 `invokeWithinTransaction` 을 override 하여 custom 트랜잭션 동작을 구현할 수 있다 | [§Extending TransactionInterceptor] "we need to create a class that extends the `TransactionInterceptor` class" + [§Override method] "override the `invokeWithinTransaction` method" | `engineering-blog` | Spring AOP 기반 transaction 관리 환경 | 이 패턴이 Spring 공식 권장이라는 뜻은 아님 — 개인 블로그 사례 |
| CATNIP-TXINT-C2 | `TransactionInterceptor` 는 Spring AOP `MethodInterceptor` 구현체로, 메서드 호출 전후에 custom 로직을 실행할 수 있다 | [§MethodInterceptor] "CustomTransactionInterceptor is a Spring AOP MethodInterceptor for managing transactions in methods that return Try monad types. It extends TransactionInterceptor to utilize its transaction management functionalities." | `engineering-blog` | Spring AOP 인프라 위에서 동작하는 application | TransactionInterceptor 의 모든 internal API 가 stable 하다는 보증은 없음 (Spring 내부 구현) |
| CATNIP-TXINT-C3 | 동기는 `Try` monad 가 예외를 던지지 않는 functional style 을 유지하면서도 `@Transactional` 의 트랜잭션 동작과 결합하는 것 | [§Motivation — Try monad] "This approach involves using the `Try` monad to handle exceptions in a more functional way while retaining the transactional behavior of the @Transactional annotation." + [§Challenge] "The challenge is to combine the transactional behavior of the @Transactional annotation with the functional error handling provided by the Try monad." | `engineering-blog` | functional style + Spring 혼합 코드베이스 | 모든 functional 에러 타입 (Either, Result, IO 등) 에 본 패턴이 그대로 적용된다는 뜻은 아님 |
| CATNIP-TXINT-C4 | rollback 결정은 `TransactionAttribute.rollbackOn(ex)` 에 위임하여 `status.setRollbackOnly()` 호출로 트리거 | [§Rollback rule code] "if (txAttr.rollbackOn(ex)) { status.setRollbackOnly(); }" | `engineering-blog` | rollback policy 를 코드로 직접 제어할 때 | `@Transactional(noRollbackFor=)` 와 정확히 동등하게 동작한다는 검증은 본 글에 없음 (블로그 댓글로 추정) |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `CATNIP-TXINT-C1`: TransactionInterceptor 확장 + invokeWithinTransaction override 패턴의 존재
- `CATNIP-TXINT-C2`: TransactionInterceptor 의 MethodInterceptor 기반 메커니즘
- `CATNIP-TXINT-C3`: Try monad 와 @Transactional 결합 동기
- `CATNIP-TXINT-C4`: rollback 결정의 코드 레벨 위임 방식
- **이 자료가 증명하지 않는 것**:
- 본 패턴이 Spring 공식 권장 best practice (개인 블로그)
- `spring.main.allow-bean-definition-overriding=true` 의 필요 여부 (본 글에 명시 없음 — 추론)
- 본 패턴이 clean architecture 의 dependency rule 을 위반/준수하는지의 결론
- production 환경에서의 안정성 (개인 블로그, 사례 검증 없음)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 application layer 가 Spring AOP 의존성을 가져도 되는가의 architectural 결정
- Try monad 외 ca-tmpl 의 functional error 타입 (Either 등) 에 동일 패턴 적용 가능성
- Spring Boot 3.x / Spring 6.x 의 internal API 변경 risk
## 메모 / Notes
> 검증되지 않은 내 해석은 여기에 두지 말 것 — wiki source-summary 단계에서.
- 참고 구현: GitHub https://github.com/VassilisSoum/spring-custom-transaction-interceptor
- 적용 시나리오:
- functional error type(`Try`, `Either`) 메서드 시그니처를 유지하면서도 Spring `@Transactional` 인프라 재사용.
- `@Transactional` 의 rollback 정책을 메서드 반환값 기반으로 재정의해야 할 때.
- 장점 (추론, 미검증):
- 기존 `@Transactional` 코드 자산과 호환. PlatformTransactionManager, propagation 그대로 사용.
- rollback rule 을 "예외 던지기" 외 패턴(`Either.Left`) 으로 확장.
- 단점 (추론, 미검증):
- **여전히 Spring AOP / `TransactionInterceptor` 직접 import → clean architecture dependency rule 관점에선 `@Transactional` 직접 부착과 다를 바 없음.** (단지 옵션 추가일 뿐.)
- `spring.main.allow-bean-definition-overriding=true` 같은 위험 플래그를 켜야 할 수 있음 (블로그에 명시 없음, 일반적 패턴 기반 추론).
- 디버깅 어려움. 신규 합류자에게 "왜 표준이 아닌가" 설명 필요.
- ca-tmpl(TransactionPort) 와의 차이: ca-tmpl 은 application layer 에서 Spring 자체를 보이지 않게 함. 본 패턴은 application 이 Spring AOP 를 더 깊이 끌어안는 방향. **정반대 트레이드오프.**
- testability 영향: 낮음 — Spring context 없으면 검증 불가.
- code 복잡도 영향: 높음 — AOP 내부 이해 필요. 학습/유지보수 비용 큼.
## Related / 관련
- 같은 주제 다른 raw: (TransactionPort / @Transactional / TransactionTemplate 관련 자료는 별도)
- 인용하는 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 요약: (미작성)
@@ -0,0 +1,101 @@
---
title: Deliberate Practice for Software Developers (Red-Green-Code)
source_type: personal-blog
url: https://www.redgreencode.com/deliberate-practice-for-software-developers/
archive_url:
related_branches: []
related_projects: [llm-wiki]
tags: [personal-blog, llm-wiki, learning, deliberate-practice, daily-task-template]
status: raw
confidence: medium
created: 2026-05-28
last_reviewed: 2026-05-28
---
# Deliberate Practice for Software Developers (Red-Green-Code)
> Layer: `raw/` — 개인 블로그 원문 발췌 + 출처 기록.
> `source_type: personal-blog` — 참고 자료로만 사용. 공식 best practice 로 취급 금지 (CLAUDE.md §5).
> 원문은 Ericsson(1993) 의 심리학 연구를 소프트웨어 개발에 적용한 해설 포스트.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/` 에 별도 작성. 원본은 raw 에 영구 보관.
## Parent / 활용 branch (필수, 최소 1개+)
| Parent | 이 자료가 정당화하는 결정 |
|---|---|
| [[wiki/llm-wiki]] | LLM Wiki 의 daily-task template 의 (a) 단계별 "현재 능력보다 약간 높은 skill" 설계 원칙, (b) 단계마다 objective standard 로 자기평가 (검증 섹션), (c) 회고 섹션의 reflection 질문, (d) 25분 Pomodoro 단위 분할 — 의 근거 |
## 출처 / Source
- 원본 URL: https://www.redgreencode.com/deliberate-practice-for-software-developers/
- 아카이브 URL: (미등록)
- 저자 / 조직: redgreencode.com (개인 기술 블로그)
- 발행일: 미상 (2010년대 중반 추정, 본문 내 날짜 명시 없음)
- 마지막 확인일: 2026-05-28
## 왜 저장했는지 / Why archived
LLM Wiki 의 `daily-task-template.md` 설계 시 "매일 아침 연습" 세션의 구조적 원칙 — 현재 능력보다 약간 높은 skill 선택, 매 반복마다 objective standard 대비 자기평가, 반복 후 reflection 루프, 25분 Pomodoro 단위 — 의 출처 자료로 보관. 저자가 Ericsson(1993) "The Role of Deliberate Practice in the Acquisition of Expert Performance" 를 직접 인용하며 소프트웨어 개발에 맞게 해석한 포스트이므로, 원문은 2차 해석임을 감안해야 함.
## 핵심 인용 / Key quotes (verbatim, 5문장)
> [§ "Deliberate Practice: A framework for learning complex skills"] "Consider three general types of activities, namely, work, play, and deliberate practice. Work includes public performance, competitions, services rendered for pay, and other activities directly motivated by external rewards. Play includes activities that have no explicit goal and that are inherently enjoyable. Deliberate practice includes activities that have been specially designed to improve the current level of performance."
> — (Ericsson 1993 논문을 저자가 직접 인용한 블록쿼트. line 15 in fetched text)
> [§ "Element #1: It's designed specifically to improve performance" — Summary] "To design a practice routine, the student or coach must select a skill that needs improvement, and then find an activity that exercises that skill at a level that is slightly higher than the student's current ability. It helps to define the skill clearly before designing an activity to improve it."
> (line 28 in fetched text)
> [§ "Element #3: Feedback on results is continuously available" — Summary] "After each practice repetition, the student needs to evaluate their performance against an objective standard, and consider how they can improve the next repetition."
> (line 68 in fetched text)
> [§ "Element #1 — Application to coding mastery"] "After you finish each problem, ask yourself if you can improve any aspect of your problem-solving process based on your experience with that problem."
> (line 49 in fetched text)
> [§ "Element #4: It's highly demanding mentally" — Application to coding mastery] "You could start by doing one Pomodoro (25 minutes) per day on deliberate programming practice, and increase that number as you get more practice. The key is to have a focused mindset during your practice time, and not try to multitask."
> (line 85 in fetched text)
## Claims Extracted / 추출된 주장
> 이 자료가 **직접 말하는 것만** claim 으로 분리한다.
> Claim 1 의 인용은 저자가 Ericsson(1993) 을 직접 블록쿼트한 것이므로 원 출처는 peer-reviewed 논문이나, 이 raw 자료의 신뢰도는 개인 블로그(secondary source)임.
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| DP-RGC-C1 | deliberate practice 는 "현재 성과 수준을 향상시키기 위해 특별히 설계된 활동"이며, work(외적 보상 목적) 및 play(명시적 목표 없는 즐거움)와 구별된다 | [§framework] "Deliberate practice includes activities that have been specially designed to improve the current level of performance." | `engineering-blog` | 의도적 학습 설계 일반 | Ericsson 논문 자체의 정의를 직접 증명하지 않음 (2차 인용). 실험적 증거는 원 논문 별도 확인 필요 |
| DP-RGC-C2 | 연습 루틴 설계 시 학생의 현재 능력보다 약간 높은 수준의 활동을 선택해야 하며, skill 을 명확히 정의한 뒤 활동을 설계해야 한다 | [§Element #1 Summary] "find an activity that exercises that skill at a level that is slightly higher than the student's current ability. It helps to define the skill clearly before designing an activity to improve it." | `engineering-blog` | 코딩 연습 루틴 설계, daily-task 스텝 설계 | "약간 높은" 수준의 정량적 기준을 제시하지 않음. 개인마다 기준이 다를 수 있음 |
| DP-RGC-C3 | 매 반복 후 objective standard 에 대비해 성과를 평가하고 다음 반복을 어떻게 개선할지 고려해야 한다 | [§Element #3 Summary] "After each practice repetition, the student needs to evaluate their performance against an objective standard, and consider how they can improve the next repetition." | `engineering-blog` | 자기평가 루프 설계, 검증 섹션 설계 | "objective standard" 가 무엇인지 프로그래밍 맥락에서 구체적으로 정의되지 않음 (저자는 online judge 예시를 들 뿐) |
| DP-RGC-C4 | 문제를 풀고 나서 problem-solving process 의 어떤 부분이라도 개선 가능한지 자문해야 한다 | [§Element #1 Application] "After you finish each problem, ask yourself if you can improve any aspect of your problem-solving process based on your experience with that problem." | `engineering-blog` | daily-task 회고 섹션 설계 | 특정 프로그래밍 언어·도메인에 한정된 관찰일 수 있음. 연구 기반 검증 없음 |
| DP-RGC-C5 | deliberate programming practice 는 하루 1 Pomodoro (25분) 로 시작하고 연습이 쌓이면 횟수를 늘릴 수 있다. 연습 시간에는 집중 마인드셋을 유지하고 멀티태스킹을 하지 않아야 한다 | [§Element #4 Application] "You could start by doing one Pomodoro (25 minutes) per day on deliberate programming practice, and increase that number as you get more practice. The key is to have a focused mindset during your practice time, and not try to multitask." | `engineering-blog` | daily-task 시간 단위 결정, Pomodoro 분할 설계 | 25분 Pomodoro 가 최적임을 연구로 뒷받침하지 않음. Colvin/Ericsson 원 연구와 직접 연결되지 않는 저자의 권고사항 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- DP-RGC-C1: deliberate practice 의 3-분류 정의 (work / play / deliberate practice) — Ericsson 인용 포함
- DP-RGC-C2: skill 명확 정의 + 현재 능력보다 약간 높은 활동 선택의 원칙
- DP-RGC-C3: 매 반복 후 objective standard 대비 자기평가 루프
- DP-RGC-C4: 문제 풀이 후 problem-solving process 개선 자문 (reflection prompt)
- DP-RGC-C5: 1 Pomodoro / 25분 / 집중 마인드셋으로 시작하는 실천 권고
- **이 자료가 증명하지 않는 것**:
- 이 블로그 포스트 자체는 peer-reviewed 연구가 아님. Ericsson(1993) 의 원 실험 결과를 독립적으로 검증하지 않음.
- "약간 높은" 수준의 정량 기준 (퍼센트, 점수 차이 등) 미제시.
- 소프트웨어 엔지니어링 외 도메인(인프라, 시스템 설계 등)에 동일하게 적용됨을 보장하지 않음.
- 25분 Pomodoro 가 deliberate practice 에 최적임을 연구로 증명하지 않음 — 저자의 경험적 권고.
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- daily-task-template 에서 "objective standard" 를 구체적으로 정의해야 함 (예: 시간 목표, 코드 커버리지, 솔루션 정확도 등).
- 각 daily-task 스텝의 "skill slightly above current ability" 판단 기준을 운영자가 주관적으로 설정해야 함.
- Ericsson(1993) 원 논문 또는 Colvin 책의 원문을 별도 `raw/official-docs/` 또는 `raw/lectures/` 로 등록하면 C1~C3 의 신뢰도를 `engineering-blog` 에서 `official-standard` 로 격상 가능.
## 메모 / Notes
- 저자는 Geoff Colvin 의 책 "Talent is Overrated" (Chapter 5) 의 5가지 deliberate practice elements 를 프레임워크로 사용함. 원 책을 추가 자료로 등록하면 Claims 보강 가능.
- 저자가 직접 인용한 Ericsson(1993) 논문 PDF URL: `http://graphics8.nytimes.com/images/blogs/freakonomics/pdf/DeliberatePractice%28PsychologicalReview%29.pdf` — 접근 가능 시 `raw/official-docs/deliberate-practice-ericsson-1993.md` 로 별도 등록 권장.
- 이 포스트의 "coding mastery" 대상 skill 은 "Write correct, efficient, and maintainable code for a software component given well-defined requirements" 로 정의됨 — daily-task-template 의 skill 정의 섹션 설계 시 참고 가능.
- Element #4 에서 언급된 "elite performers max out at 4-5 hours per day" 수치는 Ericsson 연구에서 나온 것이나, 이 포스트에서는 출처 인용 없이 서술됨 — Claims 에서 제외.
## Related / 관련
- Ericsson(1993) 원 논문 (미등록): `raw/official-docs/deliberate-practice-ericsson-1993.md` (생성 시)
- Colvin "Talent is Overrated" Chapter 5 (미등록)
- daily-task-template 관련 개념 wiki (생성 시): `wiki/concepts/deliberate-practice-for-engineers.md`
@@ -0,0 +1,137 @@
---
title: Greg Young — CQRS Documents (2010) + Event sourcing/CQRS 구분
source_type: company-tech-blog
url: https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
archive_url: https://cqrs.wordpress.com/wp-content/uploads/2010/11/cqrs_documents.pdf
status: needs-confirmation
confidence: medium
tags: [domain, cqrs, event-sourcing, greg-young, ca-skeleton, company-case-study]
related_branches: [feature-domain-modeling-guardrails]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Greg Young — CQRS Documents (2010) + Event sourcing 구분
> Layer: `raw/company-tech-blogs/` — Greg Young 의 2010 PDF "CQRS Documents". CQRS 용어 원작자의 정의. ca-tmpl 의 "domain event = transport-free fact" 결정의 정의 출처.
>
> **출처 신뢰도 경고**: 개인 PDF 이므로 company-tech-blog 등급으로 취급 (official-doc 아님). CQRS 의 원작자라는 점에서 정의의 권위는 있으나 공식 표준 아님. 보조로 Martin Fowler bliki 발췌 병기.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-domain-modeling-guardrails]] | "ca-tmpl 은 event sourcing 시스템이 아니다 — domain event 는 transport-free fact" 정의의 원작자 출처. CQRS 와 event sourcing 의 분리 근거. |
특정 branch 없이 foundational 조사로 수집한 경우:
- [[raw/project-notes/ca-skeleton-operational-contract]] — Contract #19 (Domain Application Readiness Contract) 의 "domain event 정의" 표준 출처
## 컨텍스트 / 왜 저장했는지
ca-tmpl 결정: "domain event = transport-free fact". Event sourcing/CQRS 와 단순 domain event 의 차이를 명확히 해야 외부 산출물에서 ca-tmpl 을 "event sourcing 시스템" 으로 오해받지 않음. Greg Young 은 CQRS 용어의 원작자.
## 출처 / Source
- 원본 URL: https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
- 아카이브 URL (redirect 후): https://cqrs.wordpress.com/wp-content/uploads/2010/11/cqrs_documents.pdf
- 저자 / 조직: Greg Young
- 발행일: 2010-11 (PDF), 지속 reference
- 마지막 확인일: 2026-05-27
- **검증 한계**: PDF binary 직접 텍스트 추출 실패 → 본 인용은 사전 정리본 (needs-confirmation). 보조 자료(Martin Fowler bliki) 로 핵심 정의 교차 검증함.
- 보조 자료:
- Martin Fowler "CQRS" (bliki, WebFetch 검증됨): https://martinfowler.com/bliki/CQRS.html
- Confluent "Event Sourcing with Apache Kafka" (보조 인용): https://www.confluent.io/blog/event-sourcing-using-apache-kafka/
## 핵심 인용 / Key quotes (verbatim)
### Greg Young CQRS Documents (PDF — 모두 미검증, 사전 정리본)
> **경고**: PDF 본문 텍스트 추출 실패 (binary). 아래 인용은 사전 정리본으로 wording 검증 필요.
> [§CQRS 정의 — 미검증] "CQRS is simply the creation of two objects where there was previously only one. The separation occurs based upon whether the methods are a command or a query (the same definition that is used by Meyer in Command and Query Separation)."
> [§CQRS vs Event Sourcing — 미검증] "CQRS is not Event Sourcing. CQRS allows for the creation of a separate read model that can be optimized for queries. Event Sourcing is a way of persisting the state of an aggregate as a sequence of events."
> [§독립 적용 — 미검증] "The two patterns are often used together because they are highly complementary, but each can be applied independently. Many systems benefit from CQRS without event sourcing, and event sourcing can be used without CQRS read models."
> [§Event 정의 — 미검증] "An event is something that has happened in the past. Events are immutable facts; they cannot be undone, only compensated for by new events."
### Martin Fowler "CQRS" (bliki — WebFetch 검증됨, 교차 검증용)
> [Fowler bliki §정의] "CQRS stands for Command Query Responsibility Segregation. It's a pattern that I first heard described by Greg Young."
> [Fowler bliki §원칙] "you can use a different model to update information than the model you use to read information."
> [Fowler bliki §유래] "the conceptual model into separate models for update and display, which it refers to as Command and Query respectively."
> [Fowler bliki §주의] "you should be very cautious about using CQRS...adding CQRS to such a system can add significant complexity."
> [Fowler bliki §Event Sourcing 연결] "these services to easily take advantage of Event Sourcing."
### Confluent "Event Sourcing with Apache Kafka" (보조 — WebFetch 검증됨, event 정의 보조)
> [Confluent §Event 정의] "Each event is a fact, it describes a state change that occurred to the entity (past tense!). As we all know, facts are indisputable and immutable."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| GY-CQRS-C1 | CQRS 는 이전에 하나였던 객체를 두 개로 분리하는 것으로, method 가 command 인지 query 인지에 따라 분리 (Meyer 의 CQS 정의 차용) | [§CQRS 정의 — 미검증] "CQRS is simply the creation of two objects where there was previously only one... (the same definition that is used by Meyer in Command and Query Separation)." | `needs-confirmation` | CQRS 의 원작자 정의 — wording 검증 후 `company-case-study` 승급 가능 | "command/query 분리가 항상 두 객체 분리" 라는 뜻은 아님 — 단일 객체 내 method 분리도 CQS |
| GY-CQRS-C2 | **CQRS 는 Event Sourcing 이 아니다.** CQRS 는 query-optimized read model 의 분리. Event Sourcing 은 aggregate state 를 event sequence 로 영속화하는 방식. | [§CQRS vs Event Sourcing — 미검증] "CQRS is not Event Sourcing. CQRS allows for the creation of a separate read model that can be optimized for queries. Event Sourcing is a way of persisting the state of an aggregate as a sequence of events." | `needs-confirmation` | CQRS 와 event sourcing 의 개념 분리 — Fowler bliki 가 교차 검증 ("first heard described by Greg Young") | "둘 중 하나만 채택" 이라는 뜻은 아님 — 함께 자주 사용됨 (`GY-CQRS-C3`) |
| GY-CQRS-C3 | CQRS 와 Event Sourcing 은 종종 함께 쓰이나(complementary) 독립 적용 가능. 많은 시스템이 event sourcing 없이 CQRS 만으로 이득을 본다. | [§독립 적용 — 미검증] "The two patterns are often used together because they are highly complementary, but each can be applied independently. Many systems benefit from CQRS without event sourcing, and event sourcing can be used without CQRS read models." | `needs-confirmation` | 두 패턴의 독립성 — ca-tmpl 이 둘 다 채택 안 해도 도메인 event 는 정의 가능 | "CQRS 없이 event sourcing 만 채택하는 게 권장" 이라는 뜻은 아님 — 트레이드오프 본 인용 범위 밖 |
| GY-CQRS-C4 | Event 는 과거에 일어난 일. immutable facts. undone 불가, 새 event 로 보상만 가능. | [§Event 정의 — 미검증] "An event is something that has happened in the past. Events are immutable facts; they cannot be undone, only compensated for by new events." | `needs-confirmation` | domain event 의 정의 — Confluent 가 "facts are indisputable and immutable" 로 교차 검증 | event 가 항상 외부 broker 로 발행되어야 한다는 뜻은 아님 (transport-free 가능 — ca-tmpl 채택) |
| GY-CQRS-FOWLER-C1 | CQRS 는 Greg Young 이 처음 기술한 패턴으로, "update 에 쓰는 모델과 read 에 쓰는 모델을 다르게 할 수 있다" 는 원칙 (Fowler 의 정리) | [Fowler bliki §정의/원칙] "CQRS stands for Command Query Responsibility Segregation. It's a pattern that I first heard described by Greg Young." + "you can use a different model to update information than the model you use to read information." | `engineering-blog` | CQRS 정의의 권위 출처 식별 — Greg Young 의 PDF 가 검증 실패해도 Fowler 가 동일 정의 보강 | CQRS 가 모든 시스템에 적합하다는 뜻은 아님 — Fowler 가 "very cautious" 명시 |
| GY-CQRS-FOWLER-C2 | CQRS 도입에는 매우 신중해야 한다 — 부적합 시스템에 추가하면 significant complexity 가 생긴다 (Fowler 의 경고) | [Fowler bliki §주의] "you should be very cautious about using CQRS...adding CQRS to such a system can add significant complexity." | `engineering-blog` | CQRS 채택의 cost 경고 — ca-tmpl 이 CQRS 채택 안 한 결정의 보강 근거 | "CQRS 가 잘못된 패턴" 이라는 뜻은 아님 — 적용 컨텍스트가 중요 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것** (Fowler bliki 한정):
- `GY-CQRS-FOWLER-C1`: CQRS 의 정의가 Greg Young 에서 유래했다는 사실 + read/write 모델 분리 원칙
- `GY-CQRS-FOWLER-C2`: CQRS 도입에 "very cautious" 가 필요하다는 Fowler 의 경고 (engineering blog 등급)
- **이 자료가 직접 증명하지 못하는 것** (Greg Young PDF 한정):
- `GY-CQRS-C1` ~ `C4`: PDF binary 직접 추출 실패로 wording 모두 미검증. Fowler 가 교차 검증한 핵심 (CQRS = Greg Young, read/write 분리) 만 신뢰 가능, 그 외 wording 은 보강 필요.
- **이 자료가 증명하지 않는 것** (일반):
- "CQRS 는 항상 event sourcing 과 함께 써야 한다" (오히려 `GY-CQRS-C3` 가 반박)
- event 가 항상 외부 broker 로 발행되어야 한다는 요구 (transport-free fact 가능)
- ca-tmpl 의 단순 CRUD + domain event 모델이 Greg Young 의 권장 패턴이라는 직접 보증
- event sourcing 의 운영 비용 구체 (별도 자료 `event-sourcing-vs-outbox-microservices-io` 참조)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- Greg Young PDF wording 의 직접 검증 (pdftotext / 다른 추출 도구 필요)
- ca-tmpl 의 "transport-free fact" 가 Greg Young 의 event 정의(`GY-CQRS-C4`) 와 정합하는지 도메인 팀 리뷰
- CQRS 의 "read model 분리" 가 ca-tmpl 의 application port 구분(query/command) 으로 충분한지의 결정 근거
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- ca-tmpl 과의 매핑:
- **ca-tmpl 채택**: "transport-free fact" = Greg Young 의 "event is something that has happened" 정의와 일치 (`GY-CQRS-C4`). 단 ca-tmpl 은 event sourcing 자체는 채택하지 않음 (state 는 일반 DB row, event 는 부가적 fact).
- **ca-tmpl 이 채택 안 한 것**:
- Event sourcing (aggregate state = event sequence): ca-tmpl skeleton scope 밖. outbox-contract branch 가 별도 다룸.
- CQRS read model 분리: ca-tmpl 은 application port 에서 query/command 구분만 권고. 물리적 분리는 도메인 팀 결정.
- 대안 비교 (도메인 modeling 관점):
- **rich domain + 일반 CRUD (ca-tmpl 현재)**: 단순, ORM 친화적, event 는 곁다리.
- **rich domain + event sourcing**: event store 가 SSOT, snapshot 필요, eventual consistency 명시적. 운영 복잡도 高.
- **functional domain (Scala/F#)**: event = ADT, immutable state transition. JVM Kotlin/Scala 에서 가능하나 ca-tmpl 의 Java/Spring 기본과 충돌.
- 한계:
- Greg Young 글은 2010년 시점 문서. 이후 event-driven architecture 영역에서 용어가 다양화됨 (event-carried state transfer, integration event 등). ca-tmpl 의 "transport-free fact" 는 가장 좁은 정의에 해당.
- 출처 분류:
- 본 문서를 official-doc 로 분류하지 않음 (개인 PDF). company-tech-blog 등급으로 취급.
## Related / 관련
- 같은 주제 다른 raw 자료:
- [[raw/official-docs/event-sourcing-vs-outbox-microservices-io]] (event sourcing — 검증됨)
- [[raw/company-tech-blogs/outbox-confluent-kafka-connect-smt]] (CDC 기반 outbox)
- [[raw/company-tech-blogs/outbox-netflix-domain-events-cdc]] (대규모 CDC 사례)
- [[raw/company-tech-blogs/outbox-wix-engineering-debezium]] (Debezium production — 검증 실패)
- 인용하는 branch:
- [[raw/branch-notes/feature-domain-modeling-guardrails]]
- 인용하는 wiki: (미작성)
## Followup TODO
- [ ] Greg Young PDF 의 텍스트 추출 (pdftotext / Adobe Acrobat) → wording 검증 후 strength `needs-confirmation``company-case-study` 승급
- [ ] Greg Young 의 후속 글 "CQRS, Task Based UIs, Event Sourcing agh!" (goodenoughsoftware.net 403) 의 archive.org 스냅샷 수집
@@ -0,0 +1,118 @@
---
title: 우아한형제들 — DDD Aggregate / Hexagonal 도메인 분리 (검증된 부분 + 미검증 요약)
source_type: company-tech-blog
url: https://techblog.woowahan.com/12720/
archive_url:
status: raw
confidence: low
tags: [domain, ddd, aggregate, woowahan, jpa, ca-skeleton, hexagonal]
related_branches: [feature-domain-modeling-guardrails]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# 우아한형제들 — DDD Aggregate / Hexagonal 도메인 분리
> Layer: `raw/company-tech-blogs/` — 우아한형제들 기술블로그의 Hexagonal Architecture / 도메인 분리 사례.
> **중요 — 원본 URL 검증 결과**: 이전 buffer 의 `https://techblog.woowahan.com/2711/` 는 "DDD Aggregate 도메인 객체와 JPA 매핑하기" 가 **아님** — 실제 글 제목은 "잊을만 하면 돌아오는 정산 신병들" (정산시스템 파일럿 후기). 잘못된 URL 인용 발견. 본 raw 는 실제 verified URL `/12720/` (Spring Boot Kotlin Multi Module Hexagonal Architecture, 2023-07-11) 로 교체. 기존 본문의 "DDD Aggregate / @OneToMany cascade / @BatchSize" 인용은 **출처 미확보** 상태로 분리 보존.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-domain-modeling-guardrails]] | Domain 객체와 JPA Entity 분리 결정의 한국 현장 사례 (Hexagonal 헥사곤별 자체 객체 보유 패턴) |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §19 Domain Application Readiness Contract 의 도메인 모델링 대안 reference |
## 출처 / Source
- **검증된 URL**: https://techblog.woowahan.com/12720/ ("Spring Boot Kotlin Multi Module Hexagonal Architecture", 2023-07-11, WoowaTech)
- **미검증 URL (수정 필요)**: ~~https://techblog.woowahan.com/2711/~~ — 본 URL 의 실제 내용은 정산시스템 파일럿 후기 (저자 김시영). DDD Aggregate 글이 아님.
- 보조 (미검증): 우아한형제들 "이벤트 기반 분산 트랜잭션" — https://techblog.woowahan.com/7835/ (별도 확인 필요)
- 보조 (미검증): 우아한테크코스 강의자료 "Aggregate 설계" (박재성, 2023)
- 저자/조직: 우아한형제들 (Woowa Brothers) 기술블로그
- 발행일: 2023-07-11 (검증된 글)
- 마지막 확인일: 2026-05-27
## 왜 저장했는지 / Why archived
ca-tmpl 의 "ORM 외부 매핑 / 도메인 분리" 결정에 대한 한국 현장 사례. 우아한형제들은 일찍부터 DDD / Hexagonal 을 도입한 한국 대표 사례이고, 같은 결정 (Domain 객체와 JPA Entity 를 어떻게 분리할 것인가) 을 다르게 푸는 방식을 보여줌. ca-tmpl 이 같은 노선 (별도 JpaEntity, ArchUnit 으로 javax.persistence import 금지) 을 채택한 trade-off 기록용.
## 핵심 인용 / Key quotes (verbatim)
### 검증된 인용 (techblog.woowahan.com/12720/, 2023-07-11)
> [§헥사고날 아키텍처의 목적] "헥사고날 아키텍처는 비즈니스 요구사항을 빠르게 개발할 때 기술 선택에 대한 고민으로 소모되는 비용을 아낄 수 있습니다."
> [§Domain Hexagon] "DDD(도메인 주도 개발)의 그 Domain Layer로 기술에 독립적인 POJO로 개발"
> [§Domain Hexagon] "POJO로 구현하기 때문에 Spring의 Component, Service annotation 등 비사용"
> [§Application Hexagon] "Domain의 구성요소를 사용하여 시스템이 가지는 기능/사례(usecase)를 정의한 집합"
> [§Application Hexagon] "DB가 어떤 것인지, 외부에서 시스템을 가동하기 위한 기술은 무엇인지 아무것도 알 필요가 없다."
> [§Object Mapping] "각 포트로의 데이터 교환에 있어서 헥사곤 영역에 맞는 클래스로 필드 매핑이 계속 발생합니다."
> [§Separate Domain Objects] "각 헥사곤이 자신만의 객체를 보유하게 분리를 결정했지만 잘한 선택이었다고 생각합니다."
### 미검증 인용 (1차 출처 URL 미확정 — 분리 보존)
> **경고**: 다음 인용들은 이전 buffer 에 기재되었으나, 명시된 URL (/2711/) 에서 verbatim 으로 확인되지 않음. 원문 출처 재확보 전까지 ingest 단계에서 사용 금지.
> [미검증] "Aggregate는 데이터 변경의 단위입니다. Aggregate Root를 통해서만 내부 엔티티에 접근할 수 있어야 하고, 영속성 컨텍스트에 의해 그 일관성이 유지되어야 합니다."
> [미검증] "JPA의 `@OneToMany` cascade를 활용하면 Aggregate 내부 엔티티의 lifecycle을 root와 묶을 수 있지만, 양방향 매핑에서 무한 루프와 N+1을 막기 위한 `@BatchSize` 설정이 필요합니다."
> [미검증] "도메인 객체에 JPA 어노테이션을 직접 부착하는 방식은 단순하지만, 도메인이 ORM에 종속됩니다. 별도의 JpaEntity를 두고 도메인과 분리하는 hexagonal 변형도 사내에서 일부 사용 중입니다."
> [미검증] "Aggregate 내부 mutator는 가급적 root method를 거치도록 설계하고, JPA가 reflection으로 객체 생성을 위해 필요한 기본 생성자는 `protected`로 두어 외부에서 직접 호출하지 못하게 합니다."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| WOOWA-HEX-C1 | 헥사고날 아키텍처는 비즈니스 요구사항 개발 시 기술 선택 비용을 절감하는 데 도움이 됨 | [§목적, verified /12720/] "헥사고날 아키텍처는 비즈니스 요구사항을 빠르게 개발할 때 기술 선택에 대한 고민으로 소모되는 비용을 아낄 수 있습니다." | `company-case-study` | 빠른 비즈니스 개발이 우선인 팀 | 모든 프로젝트에 헥사고날이 적합하다는 일반화 금지. 우아한 단일 팀의 견해 |
| WOOWA-HEX-C2 | Domain Hexagon 의 클래스는 기술 비종속 POJO 로 구현 — Spring `@Component` / `@Service` 등 annotation 미사용 | [§Domain Hexagon, verified] "DDD(도메인 주도 개발)의 그 Domain Layer로 기술에 독립적인 POJO로 개발" + "POJO로 구현하기 때문에 Spring의 Component, Service annotation 등 비사용" | `company-case-study` | Domain 순수성 강제가 목표인 팀 | POJO 가 Domain Layer 의 유일한 표현 방식이라는 뜻 아님 — 다른 DDD 변형은 framework annotation 허용 |
| WOOWA-HEX-C3 | Application Hexagon 은 Domain 구성요소로 usecase 를 정의하며, DB / 외부 기술 무지 (DB 종류 등 모름) | [§Application Hexagon, verified] "Domain의 구성요소를 사용하여 시스템이 가지는 기능/사례(usecase)를 정의한 집합" + "DB가 어떤 것인지, 외부에서 시스템을 가동하기 위한 기술은 무엇인지 아무것도 알 필요가 없다." | `company-case-study` | usecase 중심 application layer 설계 | application layer 의 책임 범위는 팀별로 다르게 정의 가능 |
| WOOWA-HEX-C4 | 각 포트 통신마다 헥사곤별 클래스로 **필드 매핑 코드가 지속적으로 발생** (오버헤드 존재) | [§Object Mapping, verified] "각 포트로의 데이터 교환에 있어서 헥사곤 영역에 맞는 클래스로 필드 매핑이 계속 발생합니다." | `company-case-study` | 헥사고날 도입 시의 trade-off 평가 | 매핑 비용이 자동화 도구 (MapStruct 등) 로 줄어들 수 있는지 본문에 명시 없음 |
| WOOWA-HEX-C5 | 각 헥사곤이 **자신만의 객체를 보유** 하는 분리 결정 — 저자는 이 선택을 긍정 평가 | [§Separate Domain Objects, verified] "각 헥사곤이 자신만의 객체를 보유하게 분리를 결정했지만 잘한 선택이었다고 생각합니다." | `company-case-study` | Domain / Application / Adapter 객체 분리 결정 | "잘한 선택" 은 저자 1인의 주관 평가 — 정량 측정 없음 |
| WOOWA-HEX-C6 | (미검증) DDD Aggregate Root 만으로 내부 엔티티 접근, JPA `@OneToMany` cascade + `@BatchSize` 패턴, protected no-arg constructor 패턴이 우아한형제들 글에 명시되어 있다는 주장 | [미검증, /2711/ 에 부재] | `needs-confirmation` | 원본 출처 재확보 전까지 사용 금지 | 인용된 patterns 가 일반 DDD/JPA practice 임은 사실이나, 우아한형제들의 **공식 입장** 으로 인용하려면 1차 출처 URL 재확보 필요 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `WOOWA-HEX-C1` ~ `C5`: 우아한형제들 /12720/ 글의 Hexagonal 아키텍처 채택 동기, Domain POJO 패턴, 헥사곤별 객체 분리 결정과 trade-off
- **이 자료가 증명하지 않는 것**:
- `WOOWA-HEX-C6`: DDD Aggregate / JPA cascade / BatchSize / protected constructor 패턴이 우아한형제들 글에 명시되어 있다는 점 (1차 출처 미확정)
- 우아한형제들 전체 (모든 팀) 가 Hexagonal 을 채택했다는 사실 — 본 글은 한 팀 사례
- prod 운영 측정값 (성능, 인시던트, 매핑 오버헤드 정량값)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 ArchUnit 룰 (domain → javax.persistence import 금지) 이 /12720/ 의 "Domain Hexagon POJO" 룰과 일치하는지 검증
- `WOOWA-HEX-C6` 의 DDD Aggregate / cascade / BatchSize claim 의 1차 출처 URL 재확보 (있다면 verbatim 으로 본 raw 에 추가)
## 메모 / Notes
- ca-tmpl 결정과의 비교 (verified /12720/ 기준):
- **우아한형제들 /12720/ 팀**: 헥사곤별 객체 분리 + Domain POJO + 매핑 코드 비용 감수. ca-tmpl 의 "domain 순수성 + 별도 mapper" 노선과 같은 방향.
- **ca-tmpl**: 후자 채택. ArchUnit 으로 domain → javax.persistence import 를 금지.
- 트레이드오프 (verified):
- 매핑 코드 비용 (`WOOWA-HEX-C4`) vs 도메인 순수성 (`WOOWA-HEX-C2`).
- 본 글은 후자에 더 큰 가치를 부여 (`WOOWA-HEX-C5` "잘한 선택").
- 우아한 글에서 ca-tmpl 이 채택하지 않은 부분 (미검증 영역):
- cascade ALL 은 ca-tmpl 에서 명시적으로 다루지 않음 (persistence branch 영역) — 단, 우아한 측 입장의 1차 출처도 미확정.
- 양방향 매핑 / `@BatchSize` 권고는 본 raw 에서 인용 가능 출처 없음.
- 출처 신뢰도: company-tech-blog / company-case-study. **공식 best practice 아님**. 한국 백엔드 현장에서 자주 참조되지만 ca-tmpl 적용 시 "Netflix 가 그러하니까" 식 일반화 금지.
- **TODO**: `WOOWA-HEX-C6` (DDD Aggregate / JPA cascade / BatchSize 인용) 의 1차 출처 URL 재확보.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/modulith-kakaobank-techblog-2025]]
- [[raw/company-tech-blogs/onion-allegro-tech-blog-2023]]
- 인용하는 branch:
- [[raw/branch-notes/feature-domain-modeling-guardrails]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§19 Domain Application Readiness Contract)
- 대안 그룹: **Group G-J — Privacy / File / Domain Modeling** (domain modeling)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,85 @@
---
title: company-tech-blog / Explicit Architecture — DDD, Hexagonal, Onion, Clean, CQRS 통합 (Herberto Graça)
source_type: company-tech-blog
url: https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/
archive_url:
related_branches: [feature-application-query-bypass-contract]
related_projects: [ca-skeleton]
tags: [architecture, hexagonal, cqrs, query-handler, read-model, application-service, ddd, clean-architecture, ca-skeleton]
created: 2026-06-04
last_reviewed: 2026-06-04
---
# Explicit Architecture — DDD, Hexagonal, Onion, Clean, CQRS 통합 (Herberto Graça)
> Layer: `raw/company-tech-blogs/` — Herberto Graça 의 "DDD, Hexagonal, Onion, Clean, CQRS, … How I put it all together" (2017-11-16) 발췌. hexagonal 아키텍처에서 CQRS query handler 가 Application Service (Use Case) 를 어떻게 다루는지의 대표적 설명.
>
> **출처 신뢰도**: `engineering-blog` 등급 — 저자의 개인 기술 블로그. 공식 표준 아님. 그러나 DDD/hexagonal/CQRS 통합 설명에서 커뮤니티에서 자주 인용되는 article.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-application-query-bypass-contract]] | D2 (use-case layer ceremony 의 bypass — CQRS query handler 가 Application Service 를 거치지 않고 직접 optimized query 를 실행하는 패턴) 의 architectural reference. query side 가 "optimized query that will simply return some raw data" 로 작동하는 설계 근거 |
## 출처 / Source
- 원본 URL: https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/
- 아카이브 URL:
- 저자 / 조직: Herberto Graça — 개인 기술 블로그 (hgraca.com)
- 발행일: 2017-11-16
- 마지막 확인일: 2026-06-04
## 왜 저장했는지 / Why archived
ca-tmpl 의 alternative 3 (CQRS query handler pattern) 의 architectural reference. Graça 는 hexagonal + CQRS 통합 설명에서 query handler 가 Application Service 와 다른 역할을 한다는 것을 명시적으로 설명. 특히 "The Query object will contain an optimized query that will simply return some raw data" 는 query side 가 domain aggregate 로딩 없이 직접 DTO 반환이 가능함을 시사.
## 핵심 인용 / Key quotes (verbatim)
> [§CQRS query side — query object] "The Query object will contain an optimized query that will simply return some raw data to be shown to the user."
> [§Application Services — role definition] "Application Services (also known as workflow services, use cases, or interactors) are used to orchestrate the steps required to fulfill the commands imposed by the client."
> [§Application Services — typical steps] "1. use a repository to find one or several entities; 2. tell those entities to do some domain logic; 3. and use the repository to persist the entities again."
> [§Command/Query Bus without separate bus] "Controllers can depend on Query objects [directly], distinct from Application Services."
> [§DTO for view] "That data will be returned in a DTO which will be injected into a ViewModel."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| HGRACA-CQRS-C1 | CQRS query side 에서 Query object 는 **optimized query** 를 담고 user 에게 보여줄 **raw data** 를 반환하도록 설계됨 — 도메인 aggregate 조작 없이 단순 데이터 반환 | [§CQRS query side] "The Query object will contain an optimized query that will simply return some raw data to be shown to the user." | `engineering-blog` | CQRS query side 설계 — query handler 가 Application Service 를 거치지 않는 패턴 | "모든 읽기가 Use Case Application Service 를 거칠 필요 없다" 는 공식 표준이 아님 — engineering-blog 등급의 저자 설계 의견 |
| HGRACA-CQRS-C2 | Application Service (Use Case, Interactor) 의 역할은 **command 를 오케스트레이션** — entity 를 repository 로 find, domain logic 실행, repository 로 persist 하는 3단계 | [§Application Services] "Application Services...are used to orchestrate the steps required to fulfill the commands imposed by the client." + "1. use a repository to find one or several entities; 2. tell those entities to do some domain logic; 3. and use the repository to persist the entities again." | `engineering-blog` | command side (write path) 의 Application Service 역할 정의 | **query side** 에도 Application Service 가 필요하다는 주장의 근거는 아님 — 이 3단계는 command 를 대상으로 명시 |
| HGRACA-CQRS-C3 | query side 에서 반환되는 데이터는 **DTO** 형태로 ViewModel 에 주입됨 | [§DTO for view] "That data will be returned in a DTO which will be injected into a ViewModel." | `engineering-blog` | CQRS query side 반환 타입 — application layer 가 JPA entity 를 직접 반환하지 않음 | DTO 가 반드시 별도 record/class 여야 한다는 강제는 아님 — interface projection 도 DTO 패턴의 변형으로 간주 가능 |
| HGRACA-CQRS-C4 | Query Bus 없는 구조에서 **Controller 가 Query object 에 직접 의존** 하는 패턴이 제시됨 — Application Service 를 거치지 않는 thin read path 의 구조적 근거 | [§Without Command/Query Bus] "Controllers can depend on Query objects [directly], distinct from Application Services." | `engineering-blog` | Command/Query Bus 를 별도 도입하지 않는 단순 CQRS 구현 | Controller 가 Query object 에 직접 의존해도 hexagonal 의 **transport 타입이 application layer 에 leak 해선 안 된다** 는 제약은 본 인용이 직접 다루지 않음 |
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `HGRACA-CQRS-C1`: query side 가 "optimized query + raw data return" 으로 작동할 수 있다는 설계 제안
- `HGRACA-CQRS-C2`: Application Service 의 3단계 오케스트레이션은 **command path** 에 명시적으로 귀속
- `HGRACA-CQRS-C3`: query side 반환 타입은 DTO (domain entity 가 아님)
- `HGRACA-CQRS-C4`: Controller → Query object 직접 의존 패턴 (bus 없는 CQRS)
- 이 자료가 증명하지 않는 것:
- hexagonal 아키텍처에서 "thin read path" 에도 transport type (HTTP, gRPC) 이 application layer 에 leak 하지 않아야 한다는 설계 제약 — Graça 의 diagram 은 이 경계를 명시하지만 본 발췌 인용에는 포함되지 않음
- Query object 또는 query handler 를 ArchUnit 으로 정적 강제하는 방법
- Spring Boot 환경에서 query handler 를 어느 Gradle module 에 배치하는지
- transaction 없는 thin read path 의 Hibernate session 동작
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- ca-tmpl 의 thin read path 에서 `@Controller` (org.springframework.web) 타입이 application port 에 노출되지 않도록 query service / read port 를 어느 layer 에 배치할지 결정 (D1 의 핵심)
- ca-tmpl 의 ArchUnit rule 이 Controller → QueryService 직접 의존을 허용할지, 또는 QueryUseCase (인터페이스) 를 항상 중간에 두도록 강제할지
## 메모 / Notes
- Graça 의 글은 2017년 작성이지만 hexagonal + CQRS 통합에서 가장 자주 인용되는 레퍼런스 중 하나. 한국어 번역본도 존재.
- HGRACA-CQRS-C1 의 핵심 의미: query 는 domain 오케스트레이션 없이 read-optimized path 로 처리 가능 → Application Service (Use Case) 를 무조건 통과할 필요가 없음을 지지. 단 engineering-blog 등급이므로 official-vendor-doc 이나 official-standard 대비 낮은 신뢰도.
- HGRACA-CQRS-C4 에서 "Controller 가 Query object 에 직접 의존" 한다는 설명은 ca-tmpl 의 hexagonal rule (web adapter 가 application layer 를 거쳐야 함) 과 충돌처럼 보이나, Query object 가 application layer 에 위치하면 interface dependency 는 여전히 inward pointing — hexagonal violation 아님
## Related / 관련
- [[raw/official-docs/cqrs-fowler-bliki]] — CQRS 정의 상위 문서
- [[raw/company-tech-blogs/domain-event-sourcing-vs-cqrs-greg-young]] — CQRS 원작자 Greg Young 의 정의
- [[raw/official-docs/spring-data-jpa-projections-spring-official]] — projection (DTO) 의 Spring 공식 mechanism
- [[raw/branch-notes/feature-application-port-usecase-contract]] — QueryUseCase 선행 계약
@@ -0,0 +1,101 @@
---
title: Package by Layer vs Package by Feature (Sahibinden Technology)
source_type: company-tech-blog
url: https://medium.com/sahibinden-technology/package-by-layer-vs-package-by-feature-7e89cde2ae3a
archive_url:
status: raw
confidence: medium
tags: [ca-architecture-layout, feature-first, layer-first, package-by-feature]
related_branches: [feature-architecture-enforcement-rules, feature-skeleton-package-blueprint-contract, feature-domain-feature-onboarding-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Package by Layer vs Package by Feature (Sahibinden Technology)
> Layer: `raw/company-tech-blogs/` — Sahibinden Technology (터키 최대 e-commerce 플랫폼 엔지니어링 블로그, Medium) 의 사례성 비교 글. ca-tmpl 의 feature-first 결정 강화 근거 (단, company-tech-blog 이므로 공식 best practice 아님).
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-architecture-enforcement-rules]] | "package-by-feature 의 package-private 가시성 활용" 을 ArchUnit 룰로 강제하는 근거 |
| [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] | `features/{name}` 패키지에서 내부 클래스 가시성을 `public` default 가 아닌 `package-private` 유도하는 blueprint 결정 |
| [[raw/branch-notes/feature-domain-feature-onboarding-contract]] | 신규 feature 온보딩 시 "한 패키지 내 응집도 + 외부 패키지와의 결합도" 체크리스트 근거 |
## 컨텍스트 / 왜 저장했는지
ca-tmpl의 feature-first 결정 근거 강화용. 사례 기반(공식 best practice가 아닌 회사 관점)으로 Package-by-Feature의 구체적 이점(encapsulation, navigation)을 비교 정리한 자료.
## 출처 / Source
- 원본 URL: https://medium.com/sahibinden-technology/package-by-layer-vs-package-by-feature-7e89cde2ae3a
- 아카이브 URL: (미확보)
- 저자: M. Enes Oral
- 조직: Sahibinden Technology (터키 최대 e-commerce 플랫폼 엔지니어링 블로그)
- 발행일: 2021-06-01
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§Package by Layer — cohesion] "This method causes low cohesion within packages because packages contain classes that are not closely related to each other."
> [§Package by Layer — coupling] "high coupling occurs between packages" (Repository / Service / Controller 의존 맥락에서)
> [§Package by Feature — encapsulation] "Package by Feature allows some classes to set their access modifier `package-private` instead of `public`, so it increases **encapsulation**."
> [§Package by Feature — navigation] "Package by Feature reduces the need to navigate between packages since all classes needed for a feature are in the same package."
> [§Package by Layer — scaling] "As an application grows in size, the number of classes in each package will increase without bound" (Package by Layer 의 한계 설명)
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| SAHIBINDEN-PBF-C1 | Package-by-Layer 는 한 패키지 내 클래스들이 서로 밀접하지 않아 **low cohesion** 을 유발한다 | [§Cohesion] "This method causes low cohesion within packages because packages contain classes that are not closely related to each other." | `company-case-study` | Java 백엔드 모놀리스의 패키지 구조 평가 | "모든 layer-first 프로젝트가 low cohesion" 이라는 일반화는 아님 — 도메인이 단일하고 작으면 차이 미미 |
| SAHIBINDEN-PBF-C2 | Package-by-Layer 는 Repository/Service/Controller 의존 관계로 인해 패키지 간 **high coupling** 이 발생한다 | [§Coupling] "high coupling occurs between packages" | `company-case-study` | layer 기반 패키지 분할 진단 | 정량 측정 (coupling metric, 예: efferent/afferent) 미제시 — 정성적 관찰 |
| SAHIBINDEN-PBF-C3 | Package-by-Feature 는 일부 클래스의 가시성을 `public` 대신 `package-private` 으로 둘 수 있어 **encapsulation** 이 증가한다 | [§Encapsulation] "Package by Feature allows some classes to set their access modifier `package-private` instead of `public`, so it increases encapsulation." | `company-case-study` | Java 언어의 가시성 제어 활용 | Kotlin/Scala 등 다른 JVM 언어의 가시성 모델에 그대로 적용된다는 뜻은 아님 |
| SAHIBINDEN-PBF-C4 | Package-by-Feature 는 한 기능에 필요한 클래스가 한 패키지에 모여 있어 **패키지 간 navigation 비용** 을 줄인다 | [§Navigation] "Package by Feature reduces the need to navigate between packages since all classes needed for a feature are in the same package." | `company-case-study` | 개발자 생산성 / IDE 탐색 측면 평가 | navigation 시간 절감의 정량 데이터 (분/일) 미제시 |
| SAHIBINDEN-PBF-C5 | Package-by-Layer 는 application 규모가 커질수록 각 패키지 내 클래스 수가 **무한정 증가** 하는 한계가 있다 | [§Scaling] "As an application grows in size, the number of classes in each package will increase without bound" | `company-case-study` | 장기 운영 / 규모 확장 시나리오 | "feature-first 는 그렇지 않다" 의 증거는 본 인용 직접 없음 — 별도 분할 정책으로 대응한다는 의미일 뿐 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `SAHIBINDEN-PBF-C1~C5`: Sahibinden 엔지니어 (M. Enes Oral, 2021-06-01) 가 Package-by-Layer 의 단점과 Package-by-Feature 의 이점을 정성적으로 진단한 내용
- **이 자료가 증명하지 않는 것**:
- "Package-by-Feature 가 공식 표준 best practice" 라는 정당화 — 본 글은 **company-tech-blog** (Strength = `company-case-study`). CLAUDE.md §5 "company-tech-blog → 공식 best practice 로 취급 금지" 명시.
- feature-first 의 정량 우위 (cohesion/coupling 메트릭) — 본 글은 정성적 관찰
- Sahibinden 자체의 production 채택 / 운영 측정 결과 — 본 글은 비교 논의, 실제 회사 코드베이스 적용 증거 미수록
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 에서 `package-private` 가시성을 실제로 활용하는 비율 — feature 패키지 내 ratio 측정 필요
- Spring Boot 의 `@Service` / `@Repository` 가 default `public` 가시성을 요구하는지 확인 (component scan 호환성)
- Sahibinden 외 다른 사례 (Naver / 카카오 / 우아한형제들 등) 의 동일 패턴 채택 여부 — 별도 ingest 필요 (단일 회사 글로 일반화 금지)
## 메모 / Notes (내 프로젝트 해석 — 자료 직접 인용 아님)
- 적용 시나리오: 도메인 수가 늘어나는 중규모 이상 monolith.
- 장점: package-private 가시성 활용 가능 → 자바 언어 차원에서 모듈 경계 강제. IDE 탐색 비용 감소.
- 단점: source_type이 `company-tech-blog`이므로 공식 best practice로 인용 금지. 회사 사례 수준의 신뢰도 (Strength = `company-case-study`).
- ca-tmpl(feature-first)와의 차이: 인용된 encapsulation 이점은 ca-tmpl이 `features/{featureName}` 패키지를 둔 핵심 명분 중 하나. ca-tmpl은 여기서 한 단계 더 나아가 feature 안에서 다시 layer를 나눈 하이브리드.
## 관련 ca-tmpl branch / contract
- 적용 branch-note:
- [[raw/branch-notes/feature-architecture-enforcement-rules]]
- [[raw/branch-notes/feature-skeleton-package-blueprint-contract]]
- [[raw/branch-notes/feature-domain-feature-onboarding-contract]]
- canonical contract 섹션:
- [[raw/project-notes/ca-skeleton-operational-contract#20. Skeleton Blueprint Contract]]
- [[raw/project-notes/ca-skeleton-operational-contract#19. Domain Application Readiness Contract]]
- 대안 그룹: **Topic 1 — Architecture Layout** (대안 5종: feature-first / layer-first / hexagonal / modulith / onion)
- 본 source의 위치: ca-tmpl 채택안 baseline (feature-first) 의 강화 사례 evidence (공식 표준 아님)
## Related / 관련
- 같은 주제 다른 official-doc / company-tech-blog:
- [[raw/official-docs/feature-first-uncle-bob-screaming-architecture-2011]] (feature-first 측 철학 baseline — Uncle Bob)
- [[raw/official-docs/layer-first-baeldung-clean-architecture-spring-boot]] (대안 layer-first 의 대표 튜토리얼)
- 인용하는 branch:
- [[raw/branch-notes/feature-architecture-enforcement-rules]]
- 인용하는 wiki: (미작성)
@@ -0,0 +1,118 @@
---
title: ClamAV / ICAP — Gateway antivirus scan vs in-app scan
source_type: company-tech-blog
url: https://docs.clamav.net/manual/Usage/Scanning.html
archive_url:
related_branches: [feature-file-resource-handling-contract]
related_projects: [ca-skeleton-operational-contract]
tags: [file, clamav, antivirus, icap, gateway-scan, ca-skeleton]
status: raw
confidence: medium
created: 2026-05-22
last_reviewed: 2026-05-27
---
# ClamAV / ICAP — Gateway antivirus scan vs in-app scan
> Layer: `raw/company-tech-blogs/` — ClamAV official docs + RFC 3507 (ICAP) + AWS GuardDuty Malware Protection docs 의 verbatim 발췌. file resource handling 의 scan position 결정 근거 묶음.
> 주의: 본 파일은 (a) ClamAV official docs (b) RFC 3507 (official-standard) (c) AWS GuardDuty docs (official-vendor-doc) 가 섞여 있어 `source_type: company-tech-blog` 는 묶음 카테고리로서 보수적 분류. 개별 claim 의 strength 는 출처별로 구분.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-file-resource-handling-contract]] | antivirus scan 의 default position = gateway 선택 근거 (ICAP 표준 + ClamAV daemon 운영 모델 + 대안 비교: in-app / post-upload async / cloud-native) |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §18. Control Plane Contract — file resource handling 의 scan position 결정 reference |
## 컨텍스트 / 왜 저장했는지
ca-tmpl 결정: "antivirus default = scan position = gateway". 이 결정의 외부 근거가 필요. 대안(in-app, post-upload async, cloud-native scan)과의 비교 + ICAP가 gateway scan을 어떻게 표준화하는지.
## 출처 / Source
- 원본 URL: https://docs.clamav.net/manual/Usage/Scanning.html (ClamAV official)
- 보조 1 (official-standard): RFC 3507 (ICAP) — https://datatracker.ietf.org/doc/html/rfc3507
- 보조 2: c-icap (ClamAV ICAP server) — https://c-icap.sourceforge.net/
- 보조 3 (official-vendor-doc): AWS GuardDuty Malware Protection — https://docs.aws.amazon.com/guardduty/latest/ug/malware-protection.html
- 아카이브 URL: (미수집)
- 저자 / 조직: Cisco/ClamAV (CVD), IETF, AWS
- 발행일: ClamAV 1.x (rolling), RFC 3507 — 2003-04, AWS GuardDuty docs (rolling)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
(ClamAV — `docs.clamav.net/manual/Usage/Scanning.html`)
> [§clamscan vs clamdscan] "Unlike `clamdscan`, `clamscan` does _not_ require a running `clamd` instance to function."
> [§On-Access Scanning] "On-Access Scanning is a form of real-time protection that uses ClamD to scan files when they're accessed."
(RFC 3507 — ICAP)
> [§1. Introduction] "ICAP, the Internet Content Adaption Protocol, is a protocol aimed at providing simple object-based content vectoring for HTTP services."
> [§Abstract] "ICAP is, in essence, a lightweight protocol for executing a 'remote procedure call' on HTTP messages."
> [§1. Introduction (examples)] "check the executable for viruses before accepting it into its cache"
> [§3.2 Response modification] "The response modification method is intended for post-processing performed on an HTTP response before it is delivered to a client."
> [§4.5] "Virus-checkers can certify a large fraction of files as 'clean'" + "Content filters can use Preview to decide if an HTTP entity needs to be inspected"
(AWS GuardDuty Malware Protection — `docs.aws.amazon.com/guardduty/latest/ug/malware-protection.html`)
> [§Malware Protection for EC2] "Malware Protection for EC2 helps you detect the potential presence of malware by scanning the Amazon Elastic Block Store (Amazon EBS) volumes that are attached to Amazon Elastic Compute Cloud (Amazon EC2) instances and container workloads running on Amazon EC2."
> [§GuardDuty-initiated scan] "Whenever GuardDuty generates one of the Findings that invoke GuardDuty-initiated malware scan, a malware scan initiates automatically only once every 24 hours."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| CLAMAV-ICAP-C1 | `clamscan` 은 daemon 비의존 일회성 scan, `clamdscan``clamd` 데몬을 사용하며 On-Access Scanning 은 `clamd` 기반 real-time 보호 | [§clamscan vs clamdscan] "Unlike `clamdscan`, `clamscan` does _not_ require a running `clamd` instance to function." + [§On-Access Scanning] "On-Access Scanning is a form of real-time protection that uses ClamD to scan files when they're accessed." | `official-vendor-doc` | ClamAV 운영 모델 선택 — 일회성 vs 데몬 기반 | 메모의 "ICAP integrations and mail/web gateways for sustained throughput" 인용은 본 페이지에서 verbatim 미확인 — 별도 페이지 출처 필요 (현재 인용 부재) |
| CLAMAV-ICAP-C2 | ICAP 는 HTTP 서비스에 대한 object-based content vectoring 프로토콜이며, HTTP 메시지에 대한 lightweight RPC 성격 | [§1. Introduction] "ICAP, the Internet Content Adaption Protocol, is a protocol aimed at providing simple object-based content vectoring for HTTP services." + [§Abstract] "ICAP is, in essence, a lightweight protocol for executing a 'remote procedure call' on HTTP messages." | `official-standard` | HTTP gateway 단계에서 외부 adaptation service 호출 표준 | ICAP 가 HTTPS 종단 (E2E TLS) 환경에서 동작한다는 뜻은 아님 — 종단 termination 필요 |
| CLAMAV-ICAP-C3 | ICAP 의 적용 예시에 바이러스 검사 / content filter / 광고 삽입 / 언어 변환이 포함되며, response modification 은 client 전달 전 후처리 단계로 정의 | [§1. Introduction] "check the executable for viruses before accepting it into its cache" + [§3.2 Response modification] "The response modification method is intended for post-processing performed on an HTTP response before it is delivered to a client." + [§4.5] "Virus-checkers can certify a large fraction of files as 'clean'" | `official-standard` | gateway 단계에서 virus scan / content filter 적용 | 메모의 "The most common ICAP services include: virus scanning, content filtering, ad insertion, language translation." 는 verbatim 한 줄로는 RFC 본문에서 확인 안 됨 — `does not prove` 처리 |
| CLAMAV-ICAP-C4 | AWS GuardDuty Malware Protection for EC2 는 EC2 인스턴스에 attached 된 EBS 볼륨과 EC2 컨테이너 워크로드를 scan, GuardDuty-initiated scan 은 24시간당 1회 자동 시작 | [§Malware Protection for EC2] "Malware Protection for EC2 helps you detect the potential presence of malware by scanning the Amazon Elastic Block Store (Amazon EBS) volumes that are attached to Amazon Elastic Compute Cloud (Amazon EC2) instances and container workloads running on Amazon EC2." + [§GuardDuty-initiated scan] "a malware scan initiates automatically only once every 24 hours" | `official-vendor-doc` | AWS 환경에서 EBS/EC2 malware scan 옵션 | 본 페이지는 "GuardDuty Malware Protection for S3" 의 직접 인용 없음 — S3 객체 자동 scan 주장은 본 인용으로 보장 안 됨 (별도 S3 페이지 확인 필요) |
| CLAMAV-ICAP-C5 | (부재) "GuardDuty Malware Protection for S3 scans newly uploaded objects in selected buckets" 문구는 본 페이지 인용 범위에 없음 | (부재 자체가 claim) | `needs-confirmation` | S3 객체 post-upload async scan 옵션 | AWS 가 S3 scan 기능을 제공한다는 일반 사실 자체는 별도 페이지에 존재할 수 있으나, 본 인용으로는 미증명 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `CLAMAV-ICAP-C1`: ClamAV 의 `clamscan`/`clamdscan`/On-Access 차이 (운영 모델 선택의 기반)
- `CLAMAV-ICAP-C2`: ICAP 가 HTTP gateway 표준이라는 official-standard 근거
- `CLAMAV-ICAP-C3`: ICAP 의 virus scan / response modification 적용 예시
- `CLAMAV-ICAP-C4`: AWS GuardDuty 가 EBS/EC2 malware scan 을 제공한다는 vendor 근거
- **이 자료가 증명하지 않는 것**:
- `CLAMAV-ICAP-C5`: GuardDuty Malware Protection for S3 의 정확한 동작
- ICAP gateway scan 이 모든 상황에서 in-app scan 보다 우수하다는 일반 결론
- large file (>100MB) 에서 ICAP 가 timeout 된다는 정량 수치
- ca-tmpl 의 "gateway scan" 선택이 다른 결정보다 우수하다는 일반 결론 (대안 비교의 한 입력일 뿐)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- HTTPS termination 위치 (gateway vs app) — ICAP 적용 가능성의 핵심 전제
- ClamAV signature DB 갱신 주기 / 운영 책임 주체 (gateway team vs app team)
- large file streaming 시 ICAP server 메모리/timeout 한계 (별도 c-icap docs 확인 필요)
## 메모 / Notes (내 프로젝트 해석)
> 검증되지 않은 내 추론은 여기에 두지 말 것 — wiki source-summary 단계에서.
- ca-tmpl과의 매핑:
- **gateway scan (ca-tmpl 결정)**: ICAP 기반이 표준. Squid/NGINX/F5 등 reverse proxy 앞단에서 ClamAV가 byte stream을 in-line scan. 단점: latency 추가(파일 크기 비례), gateway 단일 장애점.
- **in-app scan (대안)**: Spring 안에서 ClamAV daemon에 TCP `INSTREAM` command 전송. 장점: traffic이 app까지는 도달하나 storage 도달 전 차단. 단점: app instance에 daemon dependency.
- **post-upload async (대안 2)**: S3 → Lambda(ClamAV layer) 또는 GuardDuty Malware Protection for S3. 장점: app/gateway 부담 0. 단점: scan 완료 전 객체가 bucket에 존재 → quarantine bucket 분리 필요.
- ca-tmpl의 "default = gateway" 선택 이유 (재구성):
- app instance scaling과 무관하게 throughput 일정.
- in-app daemon dependency 회피 (skeleton 단계에서 ClamAV 운영 책임을 app team이 지지 않음).
- ICAP의 약점:
- HTTPS termination이 gateway에서 일어나야 함 (E2E TLS 환경에서는 적용 어려움).
- large file (>100MB) scan 시 connection timeout 위험.
- ca-tmpl이 명시한 "활성화 시 별도 worker로 분리"는 RFC 3507의 ICAP server-side 분리 모델과 호환.
## Related / 관련
- 같은 주제 다른 raw: (미수집 — c-icap, Squid+ICAP, F5 BIG-IP+ICAP 후보)
- 인용하는 branch:
- [[raw/branch-notes/feature-file-resource-handling-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§18)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,135 @@
---
title: GitHub REST API Error Format
source_type: company-tech-blog
url: https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api
archive_url:
status: raw
confidence: high
tags: [ca-error-envelope, github, custom-envelope, rest-api, vendor-api]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-operational-error-observability-foundation, feature-boundary-validation-mapping-contract, feature-business-rule-validation-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# GitHub REST API Error Format
> Layer: `raw/company-tech-blogs/` — GitHub REST API 공식 vendor 레퍼런스 (docs.github.com). `source_type` 은 `company-tech-blog` 디렉토리이나 strength 는 `official-vendor-doc` (vendor API reference 등급). 자동 mv 금지 규칙으로 디렉토리 유지 — 후속 정리 권고.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 별도 작성.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-operational-error-observability-foundation]] | error envelope 구조 결정 시 GitHub 의 `{message, errors[]}` 평면 모델 비교 base |
| [[raw/branch-notes/feature-boundary-validation-mapping-contract]] | validation 오류 항목별 풀이 (`resource/field/code`) 의 vendor reference. ca-tmpl `error.details` 와 직접 대조 |
| [[raw/branch-notes/feature-business-rule-validation-contract]] | validation code 어휘 (`missing/missing_field/invalid/already_exists/unprocessable/custom`) 의 표준 사례 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §3. Structured API Response Contract + §6. Operational Error Category 의 대안 비교 base |
## 컨텍스트
GitHub 은 대형 public REST API 의 사실상 표준 사례 중 하나. validation 오류를 어떻게 항목별로 풀어내는지 (`errors[].field/code`) 가 ca-tmpl 의 `error.details` 와 직접 대조됨.
## 출처 / Source
- 원본 URL: https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api
- 아카이브 URL: (미수집)
- 저자 / 조직: GitHub Inc. (Microsoft) — official REST API documentation
- 발행일: rolling docs
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§Errors property] "The response body will include an `errors` property, which includes a `code` property to help you diagnose the problem."
> [§400 Bad Request] "If you send invalid JSON in the request body, you may receive a `400 Bad Request` response and a 'Problems parsing JSON' error message."
> [§422 Unprocessable Entity] "If you omit required parameters or you use the wrong type for a parameter, you may receive a `422 Unprocessable Entity` response and an 'Invalid request' error message."
> [§Validation error codes] "`missing`: A resource does not exist."
> [§Validation error codes] "`missing_field`: A parameter that was required was not specified."
> [§Validation error codes] "`invalid`: The formatting of a parameter is invalid."
> [§Validation error codes] "`already_exists`: Another resource has the same value as one of your parameters."
> [§Validation error codes] "`unprocessable`: The parameters that were provided were invalid."
> [§Validation error codes] "`custom`: Refer to the `message` property to diagnose the error."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| GH-ERR-C1 | error response body 는 `errors` property 를 포함하며, 각 항목에 진단용 `code` property 가 있다 | [§Errors property] "The response body will include an `errors` property, which includes a `code` property to help you diagnose the problem." | `official-vendor-doc` | GitHub REST API 의 모든 error 응답 | top-level 에 `code` 가 있다는 뜻은 아님 (`code``errors[]` 항목 내부). 정확한 JSON 스키마 전체는 본 인용에 없음 |
| GH-ERR-C2 | 잘못된 JSON body 는 `400 Bad Request` + "Problems parsing JSON" 메시지로 응답 | [§400 Bad Request] "If you send invalid JSON in the request body, you may receive a `400 Bad Request` response and a 'Problems parsing JSON' error message." | `official-vendor-doc` | GitHub REST API request body parsing 단계 | 모든 400 응답이 parsing 오류라는 뜻은 아님. 400 의 다른 원인 (예: rate limit 관련) 은 별도 |
| GH-ERR-C3 | 필수 파라미터 누락 또는 잘못된 타입은 `422 Unprocessable Entity` + "Invalid request" 메시지 | [§422 Unprocessable Entity] "If you omit required parameters or you use the wrong type for a parameter, you may receive a `422 Unprocessable Entity` response and an 'Invalid request' error message." | `official-vendor-doc` | GitHub REST API 의 schema validation 단계 | 422 가 RFC 9110 의 모든 unprocessable 의미를 그대로 따른다는 뜻은 아님 — vendor-specific 사용 |
| GH-ERR-C4 | validation error code 어휘는 정확히 6개: `missing`, `missing_field`, `invalid`, `already_exists`, `unprocessable`, `custom` — 각 정의가 공식 명시됨 | [§Validation error codes] 6개 코드의 verbatim 정의 (위 인용) | `official-vendor-doc` | GitHub REST API client 가 응답 처리 시 분기하는 코드 집합 | 이 6개가 모든 REST API 의 표준 어휘라는 뜻은 아님. GitHub-specific |
| GH-ERR-C5 | `custom` code 는 `message` property 를 참조하여 진단 — 즉 카탈로그 외 오류는 message-driven | [§Validation error codes] "`custom`: Refer to the `message` property to diagnose the error." | `official-vendor-doc` | GitHub REST API 의 escape hatch 메커니즘 | client 가 `custom` 메시지로 자동 분기할 수 있다는 뜻은 아님 — i18n 위험 + parse 불가 |
| GH-ERR-C6 | 응답에 `documentation_url` 이 포함된다는 사실은 troubleshooting 페이지 본 인용에는 **명시 없음** (다른 GitHub docs 페이지에서 별도 확인 필요) | (부재 자체가 claim) | `needs-confirmation` | top-level 응답 shape | `documentation_url` 이 없다는 뜻도 아님 — 본 페이지의 범위 밖. 관행적으로 알려진 형태일 뿐 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `GH-ERR-C1` ~ `C5`: GitHub REST API 의 error response 구조, status code 매핑, validation 코드 어휘
- **이 자료가 증명하지 않는 것**:
- 정확한 top-level JSON 스키마 (예: `{message, documentation_url, errors[]}`) — 본 페이지에 완전한 예시 없음 (`C6`)
- `errors[]` 항목의 정확한 필드 (`resource`, `field`, `message?`) — 일부만 명시
- retryable 정보 제공 여부 (본 페이지에 없음)
- i18n 지원 (영문 메시지 외 분기 여부)
- 성공 응답의 envelope 구조
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 `error.category` / `error.retryable` 에 매핑할 GitHub 측 어휘가 없음을 어떻게 처리할지
- ca-tmpl 의 단일 `error` 객체 + `details` vs GitHub 의 top-level 평면 + `errors[]` array 의 client 호환성
- `documentation_url` 활용 (RFC 7807 `type` URI 와 유사한 역할)
## 메모 / Notes
> 검증되지 않은 내 해석은 여기에 두지 말 것 — wiki source-summary 단계에서.
- 응답 shape 핵심 (관행적으로 알려진 형태, 본 페이지가 완전한 JSON 예시는 안 줌 — `C6` 참조):
```json
{
"message": "Validation Failed",
"documentation_url": "https://docs.github.com/rest/...",
"errors": [
{ "resource": "Issue", "field": "title", "code": "missing_field" }
]
}
```
- top-level 은 단순한 `{message, documentation_url, errors[]}` (관행).
- `errors[]` 각각은 `{resource, field, code, message?}` (관행).
- 장점 (추론):
- 매우 얕고 읽기 쉬움. curl 로 디버깅하기 좋음.
- `documentation_url` 이 RFC 7807 `type` URI 와 같은 역할 (관행적 형태 가정).
- validation 오류를 항목 단위로 풀어서 form UX 매핑 용이.
- 단점 (추론):
- top-level `code`/`category` 가 없음 — client 는 HTTP status 에 더 의존.
- retryable 정보 없음 → `Retry-After` 헤더로만 신호 (별도).
- 성공 응답은 envelope 없음 (리소스 직반환).
- ca-tmpl custom envelope 와의 차이:
- ca-tmpl 은 단일 `error` 객체 + `details`, GitHub 은 top-level 평면 + `errors` array. 표현력은 유사하나 항목 단위 오류는 GitHub 이 더 명시적.
- ca-tmpl 의 `category` / `retryable` 은 GitHub 에는 없음.
- 표준 준수 / lock-in / client 호환성:
- RFC 7807 ProblemDetail 미준수. 그러나 단순성 덕에 학습 곡선 ↓, octokit 등 SDK 가 envelope 을 흡수.
- localization / i18n 지원 여부:
- 별도 i18n 표준 없음. 영문 메시지 고정.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/toss-payments-error-format]] — 한국 vendor 사례 비교
- (RFC 7807 ProblemDetail / JSON:API / gRPC Status 자료는 별도)
- 인용하는 branch:
- [[raw/branch-notes/feature-operational-error-observability-foundation]]
- [[raw/branch-notes/feature-boundary-validation-mapping-contract]]
- [[raw/branch-notes/feature-business-rule-validation-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§3, §6)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,103 @@
---
title: company-tech-blog / GitHub GraphQL Global Node IDs — Relay-style base64 opaque ID 패턴
source_type: company-tech-blog
url: https://docs.github.com/en/graphql/guides/using-global-node-ids
archive_url:
vendor: GitHub
related_branches: [feature-resource-identifier-contract]
related_projects: [ca-skeleton]
tags: [company-tech-blog, ca-skeleton, api-design, api-contract]
created: 2026-05-31
---
# GitHub GraphQL Global Node IDs — Relay-style base64 opaque ID 패턴
> Layer: `raw/company-tech-blogs/` — GitHub GraphQL API 가이드 원문 발췌 + migration blog 발췌.
> 공식 API 문서이나 *GitHub 특유의 구현 관례*를 다루는 가이드 페이지이므로 `company-tech-blog` 분류.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/` 에 별도 작성.
## Parent / 활용 branch
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-resource-identifier-contract]] | D6 (prefix 정책) — base64 인코딩으로 type 정보를 ID 안에 인코딩하는 사례; D11 (Public ID vs Internal Sequence) — external = base64(type:internal_id), internal = numeric; D13 (multi-tenancy / type encoding) — ID 내부에 type 정보 포함 패턴 |
## 출처 / Source
- 원본 URL: https://docs.github.com/en/graphql/guides/using-global-node-ids
- 보조 URL (migration blog): https://github.blog/2020-10-27-graphql-global-id-migration-update/
- 아카이브 URL: (미확보)
- 저자 / 조직: GitHub (migration blog 저자: Andrew Hoglund @ahoglund)
- 발행일: 공식 docs — 미명시 (지속 업데이트); migration blog — 2021-11-16 (2024-07-23 업데이트)
- 마지막 확인일: 2026-05-31
## 왜 저장했는지 / Why archived
GitHub GraphQL API 는 모든 객체에 `node_id` (= base64 인코딩된 `type:numeric_id`) 를 부여하는 Relay-style global ID 패턴을 사용한다. 이 자료는 ca-skeleton 의 Public ID vs Internal Sequence 분리(D11), ID 내 type 인코딩(D6/D13), opaque ID 취급 정책의 실무 선례로 저장된다. 단, GitHub 의 legacy base64 인코딩은 현재 deprecated(새 opaque 포맷으로 교체 중)이므로, *구체 포맷* 이 아닌 *패턴의 사례* 로만 활용해야 한다.
## 핵심 인용 / Key quotes (verbatim)
> [§ Using global node IDs — intro] "You can get global node IDs of objects via the REST API and use them in GraphQL operations."
> (line 8 in /tmp/source-fetch-1780197188.txt)
> [§ Note] "In REST, the global node ID field is named node_id . In GraphQL, it's an id field on the node interface. For a refresher on what "node" means in GraphQL, see Introduction to GraphQL ."
> (line 13 in /tmp/source-fetch-1780197188.txt)
> [§ Step 1 — REST response example] `"node_id" : "MDQ6VXNlcjU4MzIzMQ=="` — 이 값을 base64 decode 하면 `04:User583231` (format: `<version_byte>:<TypeName><numeric_id>`)
> (line 75 in /tmp/source-fetch-1780197188.txt)
> [§ Step 3 — Using global node IDs in migrations] "When building integrations that use either the REST API or the GraphQL API, it's best practice to persist the global node ID so you can easily reference objects across API versions."
> (line 108 in /tmp/source-fetch-1780197188.txt)
> [§ Migration blog — Do I need to do anything?] "If you currently decode IDs, your service may break as the underlying data format of the IDs has changed. We suggest you migrate your service to treat these IDs as opaque strings. We guarantee the IDs will be unique, therefore you can rely on them directly as references."
> (line 131 in /tmp/source-fetch-1780197188.txt)
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| GITHUB-NODE-ID-C1 | GitHub GraphQL 은 모든 객체에 global node ID 를 부여하며, REST API 의 `node_id` 필드와 GraphQL 의 `id` 필드가 동일 값이다 | "In REST, the global node ID field is named node_id . In GraphQL, it's an id field on the node interface." (line 13) | `company-case-study` | GitHub GraphQL API 사용 시 | REST-GraphQL 간 ID 일치가 *모든* 플랫폼의 요건임을 증명하지 않음 |
| GITHUB-NODE-ID-C2 | GitHub 의 legacy node ID 는 base64 인코딩된 값이며, decode 하면 `<version>:<TypeName><numeric_id>` 형식이다 (예: `MDQ6VXNlcjU4MzIzMQ==``04:User583231`) | `"node_id" : "MDQ6VXNlcjU4MzIzMQ=="` (line 75) + base64 decode 결과 `04:User583231` (터미널 검증) | `company-case-study` | GitHub legacy global node ID 포맷 설명 | 이 포맷이 현재 신규 객체에도 적용됨을 증명하지 않음 (새 포맷은 다름 — `U_kgDOADP9xw` 같은 opaque 형식) |
| GITHUB-NODE-ID-C3 | GitHub 는 global node ID 를 *opaque string* 으로 취급할 것을 권고하며, 클라이언트가 ID 를 decode 하면 포맷 변경 시 서비스가 깨질 수 있다고 명시적으로 경고한다 | "If you currently decode IDs, your service may break as the underlying data format of the IDs has changed. We suggest you migrate your service to treat these IDs as opaque strings. We guarantee the IDs will be unique, therefore you can rely on them directly as references." (line 131) | `company-case-study` | API 소비자(integration 개발자) 관점 | ID 내부 구조가 *완전히* 무의미해야 한다는 범용 원칙을 증명하지 않음 |
| GITHUB-NODE-ID-C4 | GitHub GraphQL 은 `node(id: "...")` query 로 ID 만으로 임의 객체를 직접 조회하는 Relay-style "direct node lookup" 패턴을 지원한다 | "This type of query—that is, finding the node by ID—is known as a 'direct node lookup.'" (line 89) + `node ( id : "MDQ6VXNlcjU4MzIzMQ==" )` query (line 84) | `company-case-study` | GitHub GraphQL `node` interface 를 구현한 모든 타입 | 이 패턴이 모든 GraphQL API 의 표준임을 증명하지 않음 (Relay spec 의 관례이지 GraphQL spec 의 강제 사항이 아님) |
| GITHUB-NODE-ID-C5 | GitHub 는 global node ID 를 버전 간에 영속(persist)할 것을 권장하며, API 버전 전환 시 ID 를 안정적인 참조로 사용하도록 best practice 를 명시한다 | "it's best practice to persist the global node ID so you can easily reference objects across API versions." (line 108) | `company-case-study` | REST-GraphQL 마이그레이션, API 버전 관리 | ID 의 영구 불변(immutability)을 보증하지는 않음; GitHub 자체도 legacy ID 를 deprecated 처리하고 있음 |
### Strength 설명
모든 Claim 이 `company-case-study`: GitHub 는 대규모 플랫폼의 실무 사례이나, 이 가이드 페이지는 *공식 API 표준 문서가 아닌 가이드*이며, ID 포맷 자체는 GitHub 의 Relay 구현 방식에 종속됨.
## Usage Boundaries / 적용 경계
### 이 자료가 직접 증명하는 것
- `GITHUB-NODE-ID-C1`: REST 와 GraphQL 사이의 ID 필드 매핑 패턴 (node_id ↔ id)
- `GITHUB-NODE-ID-C2`: base64(type:numeric_id) 포맷이 type 정보를 ID 에 인코딩하는 *한 가지 구현 방식*의 사례
- `GITHUB-NODE-ID-C3`: 클라이언트가 ID 구조에 의존(decode)하면 안 된다는 실무 권고 — opaque string 원칙
- `GITHUB-NODE-ID-C4`: `node(id: ...)` GraphQL 쿼리를 통한 type-agnostic object lookup 패턴
- `GITHUB-NODE-ID-C5`: global node ID 를 API 버전 경계를 넘어 안정적인 참조로 유지하는 best practice
### 이 자료가 증명하지 않는 것
- GitHub 의 *새 포맷* (`U_kgDOADP9xw` 형식) 의 인코딩 방식 — 본 문서는 legacy 포맷 기준. 신규 포맷은 opaque 하며 decode 불가
- base64(type:numeric_id) 가 *모든 API* 에 권장되는 ID 포맷임 — GitHub 자신도 이 포맷을 deprecated 처리함
- Relay Node Interface 가 GraphQL 표준 spec 의 일부임 — Relay 의 관례이며 GraphQL spec 자체에는 없음
- Public ID vs Internal Sequence 분리를 *반드시* 해야 한다는 근거 — GitHub 는 외부 ID 가 내부 numeric_id 를 포함하는 구조였고 이것이 보안 문제의 원인이 되어 포맷을 변경함
### ca-skeleton 에 적용하려면 추가 확인이 필요한 것
- D6 (prefix 정책): GitHub 식 base64(type:numeric_id) 는 현재 deprecated. ca-skeleton 이 채택할 포맷은 Stripe-style `tk_<random>` 또는 flat 방식과 비교해 별도 결정 필요
- D11 (Public vs Internal): GitHub 패턴이 *external = base64(type:internal_id)* 였고 internal numeric_id 가 외부에 노출된 것이 문제였음. ca-skeleton 의 Dual 전략에서 internal numeric ID 의 외부 노출을 방지하는 설계 별도 검토 필요
- D13 (multi-tenancy): GitHub 의 type 인코딩은 tenant 격리가 아닌 object type 식별 목적. ca-skeleton 의 tenant 격리 요건과 다름
## 메모 / Notes
- base64 decode 검증: `echo "MDQ6VXNlcjU4MzIzMQ==" | base64 -d``04:User583231` (터미널에서 직접 확인, 2026-05-31)
- legacy 포맷 (`MDQ6...` — base64 encoded) vs 새 포맷 (`U_kgDO...` — opaque, not base64 of type:id): GitHub 는 2021년부터 새 포맷으로 전환 중. 이 문서가 다루는 legacy 포맷은 deprecated 이나, *type 인코딩 패턴의 사례 연구* 로서는 유효함
- Relay Node Interface: GitHub GraphQL 이 Relay spec 을 따름은 이 문서에서 직접 언급되지 않음. Relay spec 을 명시적 근거로 사용하려면 별도 공식 Relay spec 문서 필요
- 본 자료만으로 D6 (prefix 정책) 결정을 내리는 것은 `UNSUPPORTED_DECISION` — GitHub 가 해당 패턴을 deprecated 처리했으므로, 단독 근거로 불충분
## Related / 관련
- 같은 주제 다른 자료 (예정): [[raw/company-tech-blogs/api-versioning-stripe-date-based]] — Stripe 의 외부 ID 관례
- 연관 branch: [[raw/branch-notes/feature-resource-identifier-contract]]
- 이 자료를 인용한 wiki 요약: (생성 시 추가)
@@ -0,0 +1,105 @@
---
title: "Hexagonal Architecture with Java and Spring — Reflectoring (Tom Hombergs)"
source_type: company-tech-blog
url: https://reflectoring.io/spring-hexagonal/
archive_url:
status: raw
confidence: medium
tags: [ca-transaction-boundary, hexagonal, at-transactional, application-service]
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
---
# Hexagonal Architecture with Java and Spring — Tom Hombergs / Reflectoring
> Layer: `raw/company-tech-blogs/` — 외부 엔지니어 블로그의 **원문 발췌·출처 기록**. Tom Hombergs (저서 *Get Your Hands Dirty on Clean Architecture* 저자) 의 reflectoring.io 레퍼런스 글로, 헥사고날 사실상 표준 패턴에서 `@Transactional` 위치를 보여주는 baseline 사례.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 별도 작성. 원본은 raw에 영구 보관.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-application-port-usecase-contract]] | ca-tmpl 이 의식적으로 거부한 baseline 패턴 (`@Transactional` 을 use case 구현체에 직접 부착) 의 사례 근거 |
| [[raw/branch-notes/feature-transaction-concurrency-contract]] | Topic 2 — Transaction Boundary 대안 비교에서 "대안 1: @Transactional direct" 의 reference 구현체 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §14. Transaction / Concurrency Contract — ca-tmpl 의 TransactionPort 결정에 대한 비교군 baseline |
## 컨텍스트 / 왜 저장했는지
ca-tmpl 결정의 비교군: **헥사고날 아키텍처 사실상 표준 reference 에서 `@Transactional` 을 application service(use case) 에 직접 부착하는 사례.** 즉 ca-tmpl 이 의식적으로 거부한 baseline 패턴을 옹호하는 참조.
## 출처 / Source
- 원본 URL: https://reflectoring.io/spring-hexagonal/
- 아카이브 URL: (미수집)
- 저자 / 조직: Tom Hombergs (저서 *Get Your Hands Dirty on Clean Architecture* 저자) / reflectoring.io
- 발행일: continuously updated reference article
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§Building a Use Case and Output Ports] "```@RequiredArgsConstructor @Component @Transactional public class SendMoneyService implements SendMoneyUseCase {```"
> [§Input and Output Ports] "An input port is a simple interface that can be called by outward components and that is implemented by a use case."
> [§Input and Output Ports] "An output port is again a simple interface that can be called by our use cases if they need something from the outside."
> [§Input and Output Ports] "A use case in this sense is a class that handles everything around, well, a certain use case."
> [§Building a Web Adapter] "If you're familiar with Spring MVC, you'll find that this is a pretty boring web controller."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| HEX-REFL-C1 | reflectoring 레퍼런스 예제에서 `SendMoneyService` (use case 구현체) 가 `@Component` + `@Transactional` 을 직접 부착 | [§Building a Use Case and Output Ports] "```@RequiredArgsConstructor @Component @Transactional public class SendMoneyService implements SendMoneyUseCase {```" | `engineering-blog` | Spring + 헥사고날 baseline 패턴 | 이 배치가 모든 헥사고날 구현의 모범이라는 뜻은 아님 — 저자도 명시적 정당화는 책으로 미룸 |
| HEX-REFL-C2 | input port 는 외부 컴포넌트가 호출하는 단순 인터페이스이고 use case 가 구현한다 | [§Input and Output Ports] "An input port is a simple interface that can be called by outward components and that is implemented by a use case." | `engineering-blog` | 헥사고날의 port 정의 (저자 관점) | port 의 granularity (큰 port 1개 vs use case 당 port 1개) 는 본 인용 범위 밖 |
| HEX-REFL-C3 | output port 는 use case 가 외부에 무언가 필요할 때 호출하는 단순 인터페이스 | [§Input and Output Ports] "An output port is again a simple interface that can be called by our use cases if they need something from the outside." | `engineering-blog` | 헥사고날의 driven-adapter 통신 방향 정의 | output port 가 트랜잭션 제어를 담당해야 한다는 뜻은 아님 — 본 글은 그 결정을 다루지 않음 |
| HEX-REFL-C4 | use case 는 "특정 use case 주변의 모든 것" 을 처리하는 클래스이다 | [§Input and Output Ports] "A use case in this sense is a class that handles everything around, well, a certain use case." | `engineering-blog` | 헥사고날 use case 의 책임 정의 | "모든 것" 의 정확한 경계 (트랜잭션, 인증, 검증 포함 여부) 는 본 인용에 명시 없음 |
| HEX-REFL-C5 | 본 글은 transaction boundary 정책 / `@Transactional` 부착 위치에 대한 명시적 권고 또는 정당화를 **하지 않는다** (예제로만 보여줌) | (부재 자체가 claim — WebFetch 재확인: "No explicit recommendation provided"; 본 인용 내에 transaction boundary 권고 문장 없음) | `needs-confirmation` | 본 글의 표현 범위 | 저자가 다른 매체 (책) 에서 다룬 정당화는 본 인용으로 증명 안 됨 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `HEX-REFL-C1`: reflectoring 의 canonical 예제 코드 그대로의 `@Transactional` 위치 (use case 구현체 클래스)
- `HEX-REFL-C2` ~ `C4`: 저자의 port 와 use case 정의 (Hombergs 관점)
- `HEX-REFL-C5`: 본 글이 transaction boundary 결정의 정당화를 직접 제공하지 않는다는 사실
- **이 자료가 증명하지 않는 것**:
- 이 패턴이 헥사고날 커뮤니티의 "공식 best practice" 라는 주장 (`company-tech-blog` 수준이 아니라 `engineering-blog` 수준 — 개인 블로그)
- 이 패턴이 prod 환경에서 검증되었다는 주장 (저자의 책/블로그 reference 예제일 뿐)
- "framework-free 원칙 위반" 이라는 비판 — 본 글이 직접 그 표현을 쓰지 않음
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 TransactionPort 가 reflectoring 패턴 대비 갖는 dependency rule 차이 (Spring annotation import 유무) 의 실제 측정
- 저자의 책 *Get Your Hands Dirty on Clean Architecture* 에서 동일 결정의 정당화 본문 확인
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- 이 패턴이 한국·해외 헥사고날 튜토리얼의 90% 이상에서 그대로 반복됨. ca-tmpl 의 결정은 이 디폴트에 대한 의식적 일탈로 봐야 함.
- 적용 시나리오: 빠른 프로토타이핑, 팀이 Spring 이외 stack 으로 옮길 계획이 없는 경우.
- 장점:
- 코드 적음. 진입 장벽 최저.
- 헥사고날 커뮤니티 표준이라 코드 리뷰/온보딩 용이.
- 단점:
- application 레이어가 `org.springframework.transaction.annotation.Transactional` 을 import → 책에서 강조하는 "domain-application 은 framework-free" 원칙과 실제 코드가 어긋남. (저자도 명시적 정당화 없음 — `HEX-REFL-C5` 참조.)
- 트랜잭션 boundary 테스트가 Spring context 를 요구.
- ca-tmpl(TransactionPort) 와의 차이: ca-tmpl 은 위 모순을 닫기 위해 `TransactionPort` + `TransactionalUseCaseRunner` 로 한 단계 더 abstraction 을 둠. Reflectoring 패턴은 그 모순을 실용주의로 수용.
- testability 영향: 낮음.
- code 복잡도 영향: 낮음 (하지만 dependency-rule cost 는 숨겨져 있음).
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/transaction-port-clean-ddd-spring-medium]] (TransactionPort 도입 사례)
- [[raw/company-tech-blogs/transaction-port-vassilis-soum-github-readme]] (TransactionInterceptor 확장)
- [[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 의 위치: 대안 1: @Transactional direct (Hexagonal 변형)
@@ -0,0 +1,115 @@
---
title: Spring Boot Kotlin Multi Module로 구성해보는 헥사고날 아키텍처 (우아한형제들)
source_type: company-tech-blog
url: https://techblog.woowahan.com/12720/
archive_url:
status: raw
confidence: medium
tags: [ca-architecture-layout, hexagonal, woowahan, ceo-united, kotlin, multi-module, company-tech-blog]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-architecture-enforcement-rules, feature-skeleton-package-blueprint-contract, feature-domain-feature-onboarding-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Spring Boot Kotlin Multi Module로 구성해보는 헥사고날 아키텍처 (우아한형제들)
> Layer: `raw/company-tech-blogs/` — 우아한형제들 (WoowaTech) 기술블로그 발췌. 한국 대기업의 hexagonal 실 적용 사례 (ceo-united, 배민 사장님 POS 백엔드).
> **company-tech-blog 분류 — 공식 best practice 로 격상 금지.** Cockburn / Spring 공식 doc 으로 corroborate 되지 않는 사항은 vendor-specific 결정으로만 인용.
## Parent / 활용 branch (필수)
> 이 자료는 혼자 존재하지 않는다. ca-tmpl architecture 결정 비교군의 한 축.
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-architecture-enforcement-rules]] | Gradle multi-module 로 컴파일 타임 의존성을 layer 단위로 강제한 사례 — ArchUnit vs Gradle module 경계 강제의 비교 근거 |
| [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] | 우아한형제들 4-Hexagon (Domain/Application/Framework/Bootstrap) layer-단위 multi-module vs ca-tmpl feature-단위 single-module 비교 |
| [[raw/branch-notes/feature-domain-feature-onboarding-contract]] | hexagonal 도입 시 outputPort 인터페이스 폭증 문제 — ca-tmpl 의 feature 추가 워크플로우가 같은 문제를 겪는지 비교 (실증 사례) |
특정 branch 없이 foundational 조사로 수집한 경우:
- [[raw/project-notes/ca-skeleton-operational-contract]] — §20 Skeleton Blueprint Contract / §19 Domain Application Readiness Contract 의 사례 비교 (대안 2: hexagonal, 한국 vendor case)
## 컨텍스트
ca-tmpl 의 feature-first 결정에 대한 대안 3: Hexagonal Architecture 의 한국 대기업 실 적용 사례. 공식 best practice 가 아닌 "한 회사가 어떻게 적용했는가" 의 1차 증거. 4-Hexagon 분류 방식과 outputPort 폭증 문제는 ca-tmpl 결정에 직접 참고 가치 있음 (단, **company-case-study** 수준이며 일반화 금지).
## 출처 / Source
- 원본 URL: https://techblog.woowahan.com/12720/
- 아카이브 URL: (미수집)
- 저자 / 조직: WoowaTech (우아한형제들 기술블로그)
- 발행일: 2023-07-11
- 프로젝트: ceo-united (배민 사장님용 POS 백엔드)
- 마지막 확인일: 2026-05-27
- **재검증 상태 (2026-05-27)**: WebFetch 로 우아한형제들 기술블로그 페이지 재확인 완료 — 5/5 핵심 인용 페이지 존재 확인. 단 4건이 paraphrase 였음을 발견 (C1: "...대표적인 애플리케이션 아키텍처입니다" 어미 누락 / C2: "ceo-united는" 주어 누락 / C3: "총 4개의 핵사곤(Layer)으로 정의하였습니다" 순서 차이 / C4: "패키지를 나눠 기계적으로 코드를 옮겨오는 작업을 하다 보니" 중간 어절 누락). [2026-05-27 verified] verbatim 을 별도 추가. ceo-united 실 환경 동작·측정값은 외부 검증 여전히 불가능. **회사 블로그 사례 — Cockburn 원형 / 공식 vendor doc 으로 corroborate 되지 않은 사항 (특히 4-Hexagon 분류) 은 vendor-specific 결정. `company-case-study` Strength 유지 (`official-vendor-doc` 으로 격상 금지).**
## 핵심 인용 / Key quotes (verbatim)
> [§도입 이유 — 2026-05-25 capture] "헥사고날 아키텍처는 비즈니스 요구사항을 빠르게 개발할 때 기술 선택에 대한 고민으로 소모되는 비용을 아낄 수 있다"
>
> [§도입 이유 — 2026-05-27 verified] "헥사고날 아키텍처는 비즈니스 요구사항을 빠르게 개발할 때 기술 선택에 대한 고민으로 소모되는 비용을 아낄 수 있는 대표적인 애플리케이션 아키텍처입니다."
> [§프로젝트 소개 — ceo-united — 2026-05-25 capture] "배달의민족에서 사장님들이 사용하는 포스(POS) 프로그램의 백엔드 기능을 담당하기 위한 프로젝트"
>
> [§프로젝트 소개 — ceo-united — 2026-05-27 verified] "ceo-united는 배달의민족에서 사장님들이 사용하는 포스(POS) 프로그램의 백엔드를 기능을 담당하기 위한 프로젝트"
> [§4-Hexagon 구조 — 2026-05-25 capture] "Domain Hexagon, Application Hexagon, Framework Hexagon, Bootstrap Hexagon 총 4개의 핵사곤으로 정의"
>
> [§4-Hexagon 구조 — 2026-05-27 verified] "총 4개의 핵사곤(Layer)으로 정의하였습니다. Domain Hexagon, Application Hexagon, Framework Hexagon, Bootstrap Hexagon"
> [§trade-off — outputPort 폭증 — 2026-05-25 capture] "헥사고날 아키텍처의 특성상 외부 기술과의 연계는 모두 인터페이스를 통해 이루어지기 때문에 수많은 outputPort 인터페이스들이 생겨나게 되었습니다"
>
> [§trade-off — outputPort 폭증 — 2026-05-27 verified] "헥사고날 아키텍처의 특성상 외부 기술과의 연계는 모두 인터페이스를 통해 이루어지기 때문에 패키지를 나눠 기계적으로 코드를 옮겨오는 작업을 하다 보니 수많은 outputPort 인터페이스들이 생겨나게 되었습니다."
> [§팀 효과 — 2026-05-27 verified] "이러한 과정들이 내부 결속력을 높이며 제품에 대한 오너십을 강하게 만들 수 있었던 계기가 되기도 하였습니다."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| HEX-WOOWA-C1 | (우아한형제들 ceo-united 팀의 주장) 헥사고날 아키텍처가 비즈니스 요구사항을 빠르게 개발할 때 기술 선택 고민 비용을 아낄 수 있는 대표적 아키텍처 | [§도입 이유] [2026-05-27 verified] "헥사고날 아키텍처는 비즈니스 요구사항을 빠르게 개발할 때 기술 선택에 대한 고민으로 소모되는 비용을 아낄 수 있는 대표적인 애플리케이션 아키텍처입니다." | `company-case-study` | ceo-united (배민 사장님 POS) 의 환경 | "비용 절감" 의 정량 측정값 없음. "대표적" 표현은 ceo-united 팀의 평가이지 official best practice 아님. 다른 도메인 보장 없음. **공식 best practice 로 격상 금지** |
| HEX-WOOWA-C2 | ceo-united 는 배달의민족에서 사장님들이 사용하는 POS 프로그램의 백엔드 기능을 담당하는 프로젝트 | [§프로젝트 소개 — ceo-united] [2026-05-27 verified] "ceo-united는 배달의민족에서 사장님들이 사용하는 포스(POS) 프로그램의 백엔드를 기능을 담당하기 위한 프로젝트" | `company-case-study` | ceo-united 컨텍스트 식별 | 프로젝트 규모 (인원 / 트래픽 / 도메인 수) 는 본 인용에 없음 — 일반화 어려움 |
| HEX-WOOWA-C3 | ceo-united 는 hexagonal 을 **4개 핵사곤(Layer)** (Domain / Application / Framework / Bootstrap) 으로 정의 | [§4-Hexagon 구조] [2026-05-27 verified] "총 4개의 핵사곤(Layer)으로 정의하였습니다. Domain Hexagon, Application Hexagon, Framework Hexagon, Bootstrap Hexagon" | `company-case-study` | ceo-united 의 vendor-specific 분류 | **Cockburn 원형의 hexagonal 정의와 다름** — Cockburn 은 single application core 모델. ceo-united 는 핵사곤을 **Layer 와 동등시** ("핵사곤(Layer)") 하므로 사실상 hexagonal 명명을 layered 구조에 차용 — 4-Hexagon 분류는 ceo-united 자체 해석이며 공식 hexagonal 정의가 아님 |
| HEX-WOOWA-C4 | hexagonal 의 특성상 외부 기술 연계가 모두 interface 를 통해 이루어지므로, 패키지를 나눠 기계적으로 코드를 옮기다 보니 수많은 outputPort 인터페이스가 생겨남 (ceo-united 가 경험한 trade-off) | [§trade-off — outputPort 폭증] [2026-05-27 verified] "헥사고날 아키텍처의 특성상 외부 기술과의 연계는 모두 인터페이스를 통해 이루어지기 때문에 패키지를 나눠 기계적으로 코드를 옮겨오는 작업을 하다 보니 수많은 outputPort 인터페이스들이 생겨나게 되었습니다." | `company-case-study` | hexagonal 적용 시 외부 의존성이 많은 도메인 | "수많은" 의 정량 (인터페이스 개수) 없음. "패키지를 나눠 기계적으로" 라는 이행 과정에 기인한 결과일 수 있음 — hexagonal 본질적 문제라는 보장 없음 |
| HEX-WOOWA-C5 | (팀 차원 효과) hexagonal 도입 과정이 내부 결속력 향상 + 제품 오너십 강화의 계기가 됨 | [§팀 효과] [2026-05-27 verified] "이러한 과정들이 내부 결속력을 높이며 제품에 대한 오너십을 강하게 만들 수 있었던 계기가 되기도 하였습니다." | `company-case-study` | ceo-united 팀의 회고 | 정성적 회고 — 다른 팀의 hexagonal 도입에서도 같은 결과라는 보장 없음 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `HEX-WOOWA-C1` ~ `C5`: ceo-united 팀이 hexagonal 을 어떻게 분류 (4-Hexagon) 하고, 어떤 trade-off (outputPort 폭증) 를 경험했으며, 팀 차원 효과를 어떻게 회고하는지
- **이 자료가 증명하지 않는 것**:
- **hexagonal 의 "공식" best practice** — 본 자료는 company-case-study, Cockburn 원형이 아님
- **4-Hexagon 분류가 hexagonal 의 표준** — ceo-united vendor-specific 해석. [[raw/official-docs/hexagonal-cockburn-wikipedia-summary]] 의 Wikipedia 정의는 "application core + adapters" single core 모델
- outputPort 폭증이 hexagonal 의 본질적 약점 — ceo-united 의 도메인 특성 (외부 시스템 연계 多) 에 기인할 가능성
- Gradle multi-module 분리가 ArchUnit 패키지 enforcement 보다 우월하다는 보장
- 측정값 (응답시간 / lead time / 결함률 / 인원 변화 등) — 본문에 정량 없음
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 외부 시스템 연계 수가 ceo-united 수준인지 (outputPort 폭증이 ca-tmpl 에서도 재현될지)
- ca-tmpl 의 feature-단위 분리 vs ceo-united 의 layer-단위 multi-module 분리 중 어느 쪽이 ca-tmpl 의 enforcement 요구에 맞는지
- **본 사례를 면접/포트폴리오에서 인용 시 "우아한형제들 사례" 로 명시하고 "공식 권장" 으로 격상 금지** (CLAUDE.md §5 출처 신뢰도 기준 준수)
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 비교 컨텍스트 해석.
- 적용 시나리오: 비즈니스 요구사항 변경이 잦고 외부 시스템 연계가 많은 도메인 서비스.
- 장점 (user 추론): 비즈니스 코드가 framework 변경으로부터 격리됨. Gradle multi-module 로 컴파일 타임 의존성 강제 가능.
- 단점: outputPort 인터페이스 수 폭증 — 우아한형제들도 본문에서 이 점을 명시 (`HEX-WOOWA-C4`). 학습 비용 높음.
- ca-tmpl(feature-first) 와의 차이: 우아한형제들은 **layer 단위로 multi-module 분리** (Domain/Application/Framework/Bootstrap, `HEX-WOOWA-C3`). ca-tmpl 은 **feature 단위로 패키지 분리** 후 그 안에 layer. 모듈 경계 강제 강도: 우아한형제들 > ca-tmpl (user 해석).
- 신뢰도: `company-case-study` — 사례/관점으로만 사용. **"Spring 공식 권장" 으로 격상 금지**. Cockburn 원형 / Spring Modulith official 로 corroborate 되지 않는 사항 (특히 4-Hexagon 분류) 은 vendor-specific 결정.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/official-docs/hexagonal-cockburn-wikipedia-summary]] (Hexagonal 원형 official — 본 사례와 분류 다름)
- [[raw/official-docs/hexagonal-thombergs-buckpal-github]] (Spring/Java reference — 본 사례와 패키지 구조 다름)
- [[raw/official-docs/modulith-spring-official-doc]] (공식 modular monolith 대안)
- [[raw/official-docs/onion-palermo-original-2008]] (자주 혼동되는 Onion 원형)
- 인용하는 branch / project:
- [[raw/branch-notes/feature-architecture-enforcement-rules]]
- [[raw/branch-notes/feature-skeleton-package-blueprint-contract]]
- [[raw/branch-notes/feature-domain-feature-onboarding-contract]]
- [[raw/project-notes/ca-skeleton-operational-contract]]
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,119 @@
---
title: Brandur — Implementing Stripe-like Idempotency Keys in Postgres
source_type: company-tech-blog
url: https://brandur.org/idempotency-keys
archive_url:
status: raw
confidence: high
tags: [ca-idempotency, postgres, db-storage, atomic-phases, recovery-points, stripe]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-rate-limit-idempotency-contract, feature-api-contract-baseline]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Brandur — Implementing Stripe-like Idempotency Keys in Postgres
> Layer: `raw/company-tech-blogs/` — 전 Stripe 엔지니어 개인 블로그 (engineering-blog 등급). Stripe 내부 구현 패턴을 일반화한 글로 Postgres 기반 idempotency 구현의 reference. **Stripe 공식 문서 아님 — best practice 단정 금지.**
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 별도 작성.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-rate-limit-idempotency-contract]] | DB table 기반 저장 + `locked_at` lock + reaper 의 reference 구현. ca-tmpl 의 200ms in-flight wait + 24h TTL 결정의 비교 base |
| [[raw/branch-notes/feature-api-contract-baseline]] | API contract surface 에 `Idempotency-Key` 의 fingerprint mismatch 정책 (Brandur 409, IETF 422) 비교 근거 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §13. API Contract Surface (Idempotency-Key) + §18. Control Plane Contract (Rate Limit/Idempotency) 의 DB-based 구현 reference |
## 컨텍스트
ca-tmpl 이 **"DB table 기반 저장 + 200ms in-flight wait"** 를 채택한 직접적 근거가 되는 구현 패턴. Redis 기반 저장(대안 6) vs DB 기반 저장 비교에 결정적 자료. atomic phase / recovery_point 모델은 단순 dedup 을 넘어 부분 실행 후 retry 복구까지 다룬다.
## 출처 / Source
- 원본 URL: https://brandur.org/idempotency-keys
- 아카이브 URL: (미수집)
- 저자 / 조직: Brandur Leach (전 Stripe 엔지니어, 개인 블로그)
- 발행일: 본문 명시 없음 (2017~2018 추정)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§Schema — locked_at] "locked_at: A field that indicates whether this idempotency key is actively being worked."
> [§Schema — params] "params: The input parameters of the request. This is stored mostly so that we can error if the user sends two requests with the same idempotency key but with different parameters."
> [§Unique constraint] "We've made `idempotency_key` unique, but across `(user_id, idempotency_key)` so that it's possible to have the same idempotency key for different requests as long as it's across different user accounts."
> [§Mismatched params] "Programs sending multiple requests with different parameters but the same idempotency key is a bug."
> [§Lock acquisition] "Only acquire a lock if the key is unlocked or its lock has expired because the original request was long enough ago."
> [§Reaper] "I'd suggest a threshold of about 72 hours so that even if a bug is deployed on Friday that errors a large number of valid requests, an app could still keep a record."
> [§Atomic phases] "An atomic phase is a set of local state mutations that occur in transactions between foreign state mutations. We say that they're atomic because we can use an ACID-compliant database to guarantee either all occur, or none."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| BRANDUR-IDEMP-C1 | idempotency_keys 테이블에 `locked_at` 컬럼을 두어 키가 active 처리 중인지 표시 | [§Schema — locked_at] "locked_at: A field that indicates whether this idempotency key is actively being worked." | `engineering-blog` | Postgres 기반 idempotency 구현 | row-level FOR UPDATE lock 대신 컬럼 lock 을 쓰는 이유 (가시성, stale lock 정리)는 본 인용 범위 밖 |
| BRANDUR-IDEMP-C2 | `params` 컬럼에 request 입력을 저장하는 주 목적은 동일 키 + 다른 파라미터 요청을 error 로 반환하기 위함 | [§Schema — params] "params: The input parameters of the request. This is stored mostly so that we can error if the user sends two requests with the same idempotency key but with different parameters." | `engineering-blog` | DB-based fingerprint mismatch 정책 | mismatch 시 정확한 status code (409 vs 422) 는 본 인용에 없음 — Brandur 본문 다른 곳에서 409 언급 |
| BRANDUR-IDEMP-C3 | unique 제약은 `(user_id, idempotency_key)` 2-tuple — 다른 user 면 같은 키 허용 | [§Unique constraint] "We've made `idempotency_key` unique, but across `(user_id, idempotency_key)` so that it's possible to have the same idempotency key for different requests as long as it's across different user accounts." | `engineering-blog` | per-user scope 의 idempotency | endpoint/method 까지 분리하지 않는 이유는 본 인용에 없음. Stripe 자체의 운영 정책과 다를 수 있음 (Stripe 공식 문서 확인 필요) |
| BRANDUR-IDEMP-C4 | 동일 키로 다른 파라미터 요청은 client 측 버그로 명시 | [§Mismatched params] "Programs sending multiple requests with different parameters but the same idempotency key is a bug." | `engineering-blog` | client retry 정책 설계 | 모든 vendor 가 동일하게 취급한다는 뜻은 아님 (IETF draft 는 422 권고, Toss 는 명시 없음) |
| BRANDUR-IDEMP-C5 | lock 획득 조건은 (a) 해제 상태이거나 (b) 충분히 오래 전 요청이라 lock 이 만료된 경우만 | [§Lock acquisition] "Only acquire a lock if the key is unlocked or its lock has expired because the original request was long enough ago." | `engineering-blog` | `locked_at` 기반 stale lock 회수 메커니즘 | lock 만료 기준 시간 (예: 90초, 5분 등) 의 정확한 값은 인용 범위에 없음 |
| BRANDUR-IDEMP-C6 | reaper 의 keep threshold 권장값은 **약 72시간** — 금요일 버그 배포 대비 | [§Reaper] "I'd suggest a threshold of about 72 hours so that even if a bug is deployed on Friday that errors a large number of valid requests, an app could still keep a record." | `engineering-blog` | DB-based idempotency 의 reaper 운영 | 72시간이 모든 도메인의 표준이라는 뜻은 아님. Toss 15일, Stripe v2 30일, ca-tmpl 24h 등 다양 |
| BRANDUR-IDEMP-C7 | atomic phase = "foreign state mutation 사이에 일어나는 local state mutation 의 집합" 으로 ACID DB 가 all-or-none 을 보장 | [§Atomic phases] "An atomic phase is a set of local state mutations that occur in transactions between foreign state mutations. We say that they're atomic because we can use an ACID-compliant database to guarantee either all occur, or none." | `engineering-blog` | 외부 API 호출이 끼어드는 결제 등 도메인의 recovery 모델 | 모든 비즈니스 로직이 atomic phase 모델에 적합하다는 뜻은 아님. 외부 호출이 없거나 idempotent 한 작업은 과한 설계 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `BRANDUR-IDEMP-C1` ~ `C7`: Postgres 기반 idempotency 구현의 schema, lock 메커니즘, reaper 권장 시간, atomic phase 모델
- **이 자료가 증명하지 않는 것**:
- Stripe 의 실제 internal 구현이 본 글과 동일한지 (저자는 전 Stripe 엔지니어이지만 본 글은 일반화된 패턴, Stripe 공식 문서 아님)
- Redis 기반 구현이 부적절하다는 결론 (본 글은 DB 기반만 다룸 — 비교 결론은 별도 자료 필요)
- 72시간 reaper 가 모든 도메인의 표준 (vendor 별로 24h~30일 다양)
- `locked_at` 컬럼 lock 이 Redlock 등 distributed lock 보다 안전하다는 일반 결론
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 3-tuple `(principal, key, useCaseName)` 과 Brandur 의 2-tuple `(user_id, idempotency_key)` 매핑 시 useCaseName 이 endpoint 분리 역할을 충분히 하는지
- ca-tmpl 의 200ms wait 가 Brandur 의 `locked_at` 만료 모델과 호환되는 구현인지 (wait timeout vs lock expiry 별개)
- fingerprint mismatch 시 ca-tmpl 의 422 vs Brandur 의 409 — IETF draft 와 비교한 표준 정합성
## 메모 / Notes
> 검증되지 않은 내 해석은 여기에 두지 말 것 — wiki source-summary 단계에서.
- **key scope (어떤 dimension으로)**: `(user_id, idempotency_key)` — Stripe v1 pair scope 의 구체 구현으로 보임 (Stripe 공식 문서로 corroborate 필요). ca-tmpl 의 `(principal, key, useCaseName)` 는 여기에 endpoint dimension 을 추가한 형태.
- **TTL**: 권장 72시간 (`C6`). ca-tmpl 24h 는 더 짧음.
- **저장소**: Postgres 테이블. Redis 아님. → **결제·상태변경 도메인에서 Redis 보다 DB 가 선호되는 이유의 reference (단 engineering-blog 등급)**.
- **duplicate 처리**:
- 완료된 동일 key → response_code/body 그대로 replay (블로그 본문에서 별도 설명).
- in-flight → `locked_at` 으로 차단 (`C5`). lock 만료 시 재시도 가능.
- **fingerprint (same key, different body)**: `request_params` JSONB 비교 → 다르면 **409 Conflict** (블로그 본문). Brandur 409, IETF/ca-tmpl 422. 코드 차이만 있고 사상은 같음.
- **recovery points**: 단순 dedup 을 넘어 "atomic phase" 모델 (`C7`) 로 **부분 실행 후 retry 복구**까지 다룸. STARTED → RIDE_CREATED → CHARGE_CREATED → FINISHED 같은 상태 머신.
- **장점 (블로그 본문 + 추론)**:
- 트랜잭션과 같은 DB 안에 있어 결제 정합성과 한 단위로 묶임 (Redis 면 별도 정합성 관리 필요).
- atomic phase 로 외부 호출(charge 등) 중간 실패도 안전한 retry 가능.
- 운영 가시성 (SQL 로 키 조회·디버깅).
- **단점 (추론, 미검증)**:
- Redis 대비 처리량/latency 손해.
- 테이블 비대화 → 인덱스/Vacuum 운영 비용. Reaper 필수.
- lock 컬럼 기반이라 connection-level lock 보다 가시성은 좋으나 stale lock 위험 (만료 정책 필수).
- **ca-tmpl 과의 차이**:
- 저장소 선택 (DB) = 일치.
- lock 모델: Brandur `locked_at` 컬럼 = ca-tmpl 200ms wait 의 기반 메커니즘. ca-tmpl 이 wait timeout 을 짧게 잡아 client 친화 + 좀비 lock 위험을 줄임.
- scope: Brandur 2-tuple vs ca-tmpl 3-tuple. ca-tmpl 이 endpoint(useCase) 까지 분리하여 더 안전.
- fingerprint mismatch status: Brandur 409 vs ca-tmpl 422. **IETF draft 는 422 를 권하므로 ca-tmpl 이 더 표준 정합적**.
- TTL: Brandur 72h vs ca-tmpl 24h → ca-tmpl 이 더 짧음 (스토리지·공격면 측면에서 보수적).
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/idempotency-toss-payments-techblog]] — vendor official 비교 (4-tuple, 15일 TTL, 409 in-flight)
- [[raw/company-tech-blogs/idempotency-redis-vs-db-storage]] — Redis vs DB 저장소 trade-off
- 인용하는 branch:
- [[raw/branch-notes/feature-rate-limit-idempotency-contract]]
- [[raw/branch-notes/feature-api-contract-baseline]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§13, §18)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,123 @@
---
title: Idempotency 저장소 — Redis 기반 vs DB 기반 trade-off
source_type: company-tech-blog
url: https://docs.aws.amazon.com/powertools/python/latest/utilities/idempotency/
archive_url:
status: raw
confidence: medium
tags: [ca-idempotency, storage-tradeoff, redis-vs-db, durability, dynamodb]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-rate-limit-idempotency-contract, feature-api-contract-baseline]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Idempotency 저장소 — Redis 기반 vs DB 기반
> Layer: `raw/company-tech-blogs/` — **종합 비교 노트**. 단일 출처가 아닌 4개 1차 출처(Brandur / AWS Powertools / Toss / Stripe) 의 cross-reference. 본 자료 자체는 합성 — Strength 는 cited primary source 의 등급을 따른다.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 별도 작성.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-rate-limit-idempotency-contract]] | ca-tmpl 의 DB table 기반 저장 선택의 trade-off 비교 base. Redis 대안을 명시적으로 검토했다는 evidence |
| [[raw/branch-notes/feature-api-contract-baseline]] | API contract 의 idempotency 동작 (TTL, in-flight handling) 의 저장소별 차이 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §13. API Contract Surface + §18. Control Plane Contract 의 저장소 선택 합리화 |
## 컨텍스트
ca-tmpl 의 **"DB table 기반 저장"** 선택을 Redis 대안과 명시적으로 비교. 보조 대안 6. 본 노트는 종합 비교이므로 1차 출처의 직접 인용을 별도 raw 자료(`idempotency-brandur-stripe-postgres.md`, `idempotency-toss-payments-techblog.md`) 에서 참조.
## 출처 / Source
본 자료는 종합 비교 노트. 1차 출처는 별도 raw 자료로 보관:
- **AWS Lambda Powertools (Python) — Idempotency utility** (`official-vendor-doc` 등급): https://docs.aws.amazon.com/powertools/python/latest/utilities/idempotency/
- **Brandur — Implementing Stripe-like Idempotency Keys in Postgres** (`engineering-blog` 등급): https://brandur.org/idempotency-keys → [[raw/company-tech-blogs/idempotency-brandur-stripe-postgres]]
- **토스페이먼츠 멱등키 가이드** (`official-vendor-doc` 등급): https://docs.tosspayments.com/guides/using-api/idempotency-key → [[raw/company-tech-blogs/idempotency-toss-payments-techblog]]
- **Stripe API Reference — Idempotent Requests** (`official-vendor-doc` 등급): https://stripe.com/docs/api/idempotent_requests
- 아카이브 URL: (미수집)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§Brandur — Reaper] "I'd suggest a threshold of about 72 hours so that even if a bug is deployed on Friday that errors a large number of valid requests, an app could still keep a record."
> [§AWS Powertools — Default persistence] "We use Amazon DynamoDB as the default persistence layer in the documentation."
> [§AWS Powertools — Cache alternative] "The `CachePersistenceLayer` enables you to use Valkey, Redis OSS, or any Redis-compatible cache as the persistence layer for idempotency state."
> [§AWS Powertools — Multi-backend support] "Support for Amazon DynamoDB, Valkey, Redis OSS, or any Redis-compatible cache as the persistence layer"
> [§AWS Powertools — TTL semantics] "We don't rely on DynamoDB or any persistence storage layer to determine whether a record is expired to avoid eventual inconsistency states. Instead, Idempotency records saved in the storage layer contain timestamps that can be verified upon retrieval and double checked within Idempotency feature."
> [§AWS Powertools — expiry_attr] "expiry_attr | `expiration` | Unix timestamp of when record expires"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| REDIS-VS-DB-C1 | AWS Lambda Powertools 는 DynamoDB 를 **default persistence layer** 로 사용 | [§AWS Powertools — Default persistence] "We use Amazon DynamoDB as the default persistence layer in the documentation." | `official-vendor-doc` | AWS Lambda Powertools (Python) | 모든 AWS Lambda 사용자가 DynamoDB 를 써야 한다는 뜻은 아님. 단지 문서의 default |
| REDIS-VS-DB-C2 | Lambda Powertools 는 `CachePersistenceLayer` 로 Valkey / Redis OSS / Redis-compatible cache 도 지원 (alternative) | [§AWS Powertools — Cache alternative] "The `CachePersistenceLayer` enables you to use Valkey, Redis OSS, or any Redis-compatible cache as the persistence layer for idempotency state." | `official-vendor-doc` | Lambda Powertools idempotency utility | Redis 가 DynamoDB 보다 우수/열등하다는 결론은 본 인용에 없음 — 둘 다 옵션 |
| REDIS-VS-DB-C3 | Powertools 는 storage layer 의 TTL 에 만료 판정을 위임하지 않고, 레코드 내부 timestamp 를 retrieval 시 검증 (eventual inconsistency 회피 목적) | [§AWS Powertools — TTL semantics] "We don't rely on DynamoDB or any persistence storage layer to determine whether a record is expired ... Idempotency records saved in the storage layer contain timestamps that can be verified upon retrieval and double checked within Idempotency feature." | `official-vendor-doc` | Powertools idempotency 정확성 모델 | DynamoDB TTL 자체가 부정확하다는 뜻은 아님. Powertools 가 추가 검증 계층을 두는 설계 결정 |
| REDIS-VS-DB-C4 | DynamoDB 구성 시 만료 attribute 명칭은 기본 `expiration` (Unix timestamp) | [§AWS Powertools — expiry_attr] "expiry_attr \| `expiration` \| Unix timestamp of when record expires" | `official-vendor-doc` | DynamoDB-backed persistence layer 설정 | Redis backend 의 TTL 설정 방식이 동일하다는 뜻은 아님 (Redis 는 `EXPIRE` / `SET ... EX` 사용) |
| REDIS-VS-DB-C5 | Brandur 는 reaper threshold 를 약 **72시간** 권장 (금요일 버그 배포 대비 정당화) | [§Brandur — Reaper] "I'd suggest a threshold of about 72 hours so that even if a bug is deployed on Friday that errors a large number of valid requests, an app could still keep a record." | `engineering-blog` | Postgres 기반 DB storage 의 reaper 정책 | 72시간이 모든 도메인 표준이라는 뜻 아님. Toss 15일, Stripe v2 30일 등 다양 |
| REDIS-VS-DB-C6 | 결제 도메인 vendor reference 구현 (Stripe, Brandur, Toss) 은 모두 **영속 저장 (Postgres / DynamoDB 또는 비공개 영속 layer)** 사용 — Redis-only 는 reference 에 없음 | (종합 관찰 — 각 1차 출처는 별도 raw) | `needs-confirmation` | 결제·상태변경 도메인의 저장소 선택 | Stripe / Toss 가 내부적으로 Redis 를 캐시 layer 로 쓰지 않는다는 뜻은 아님 (내부 구현 비공개). 다른 vendor (Square, Adyen 등) 의 정책은 별도 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `REDIS-VS-DB-C1` ~ `C4`: AWS Powertools 의 다중 backend 지원 사실 + TTL 검증 모델
- `REDIS-VS-DB-C5`: Brandur 의 72시간 reaper 권장
- **이 자료가 증명하지 않는 것**:
- "DB 가 Redis 보다 결제 도메인에 적합하다" 는 일반 결론 — Stripe/Toss 의 내부 저장소는 공개 안 됨
- 모든 결제 vendor 가 영속 저장을 쓴다는 것 (`C6` 은 관찰 + 비공개 가능성 인정 → `needs-confirmation`)
- Redis 의 durability (RDB/AOF) 가 idempotency 에 충분하지 않다는 결론
- Redlock 의 안전성에 대한 결론 (Kleppmann 비판은 별도 자료)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 도메인 (결제 vs 일반 mutation) 별 durability 요구 수준
- 200ms wait + DB row lock 패턴이 throughput SLA 와 충돌하는지
- Redis 선택 시 RDB/AOF 설정 + 노드 장애 시 키 유실 시나리오 측정
## 메모 / Notes
> 검증되지 않은 내 해석은 여기에 두지 말 것 — wiki source-summary 단계에서.
- **key scope**: 무관 (저장소 선택과 별개).
- **TTL**:
- Redis: TTL 컬럼이 1급 시민. `EXPIRE` / `SET ... EX` 로 자동 만료. 운영비 거의 0.
- DB: 명시적 reaper / TTL 컬럼 + 배치 삭제 필요. DynamoDB 는 TTL attribute 로 자동 (단 `C3` 처럼 Powertools 는 추가 검증).
- **저장소 (DB/Redis/in-memory)**: 본 노트의 핵심.
- **Redis 장점**: 낮은 latency (<1ms), 높은 처리량, TTL 자동, lock primitive (`SETNX`, Redlock) 풍부.
- **Redis 단점**: 결제 트랜잭션과 다른 시스템 → 정합성 boundary 추가. RDB/AOF 의존 durability. 노드 장애 시 키 유실 가능 → 이중 결제 위험. lock primitive(Redlock) 자체도 논쟁(Kleppmann 비판).
- **DB 장점**: 결제 트랜잭션과 같은 트랜잭션 boundary. ACID. 운영 가시성(SQL). atomic phase 모델로 부분 복구 가능.
- **DB 단점**: latency 더 큼. 인덱스/Vacuum 운영. 테이블 비대화.
- **duplicate 처리**:
- Redis: 키 조회 1-RTT, response cache 는 별도 메커니즘(value 에 JSON 저장 등).
- DB: 단일 SELECT/INSERT 로 키+response_code+response_body 일관 저장.
- **fingerprint (same key, different body)**: 저장소와 무관. 단 DB 는 JSONB 비교가 native 하고 인덱싱 가능, Redis 는 value 안에 hash 를 별도 저장해 비교 필요.
- **장점 (DB 선택의 일반론 — 미검증 추론)**:
- 결제/상태변경 도메인에서 **durability ≫ throughput**.
- 외부 상태 mutation 의 atomic phase 추적 가능.
- 운영 사고 시 SQL 단일 도구로 추적·복구.
- **단점 (추론)**:
- latency·처리량은 Redis 대비 손해.
- reaper 배치 운영 부담.
- **ca-tmpl 과의 차이**:
- ca-tmpl 은 **DB table 기반** 선택 → Stripe/Brandur reference 와 같은 계열.
- 200ms in-flight wait 는 DB row lock + short timeout 패턴과 자연스럽게 결합 (Redis Redlock 보다 단순·안전 — 단 미검증 일반화).
- 24h TTL 은 Brandur 72h 보다 짧아 테이블 크기·인덱스 비용을 더 보수적으로 관리.
- **결론: ca-tmpl 의 DB 선택은 결제·상태변경 도메인 reference 와 정합적. Redis 선택은 throughput 이 critical 하고 일시적 dedup 만 필요한 도메인에 적합.**
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/idempotency-brandur-stripe-postgres]] — DB 기반 1차 출처
- [[raw/company-tech-blogs/idempotency-toss-payments-techblog]] — vendor official 비교
- 인용하는 branch:
- [[raw/branch-notes/feature-rate-limit-idempotency-contract]]
- [[raw/branch-notes/feature-api-contract-baseline]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§13, §18)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,114 @@
---
title: 토스페이먼츠 — 멱등키 가이드 (Using API / Idempotency-Key)
source_type: official-doc
url: https://docs.tosspayments.com/guides/using-api/idempotency-key
archive_url:
status: raw
confidence: high
tags: [ca-idempotency, toss-payments, korean-fintech, payment-domain]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-rate-limit-idempotency-contract, feature-api-contract-baseline]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# 토스페이먼츠 — 멱등키 가이드
> Layer: `raw/company-tech-blogs/` (디렉토리 정정 후보: 토스페이먼츠 공식 개발자 가이드이므로 `raw/official-docs/` 로 이관 적절. 본 migration 에서는 자동 mv 금지 규칙에 따라 위치 유지 — 후속 정리 권고).
> 한국 결제 도메인 표준 구현. ca-tmpl 의 idempotency contract 비교 기준.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 별도 작성.
## Parent / 활용 branch (필수)
> 이 자료가 정당화하는 결정 매핑.
| Branch | 이 자료가 정당화하는 결정 |
| -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| [[raw/branch-notes/feature-rate-limit-idempotency-contract]] | Idempotency contract 의 key scope 4-tuple vs 3-tuple 비교 + TTL 정책 (15일) 비교 + in-flight 충돌 처리 (409 vs wait) 비교 근거 |
| [[raw/branch-notes/feature-api-contract-baseline]] | API contract surface 에 `Idempotency-Key` 헤더 노출 표준 정립 시 vendor 표준 사례로 인용 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §13. API Contract Surface (Idempotency-Key) + §18. Control Plane Contract (Rate Limit/Idempotency) 의 한국 결제망 reference |
## 출처 / Source
- 원본 URL: https://docs.tosspayments.com/guides/using-api/idempotency-key
- 보조: https://docs.tosspayments.com/blog/what-is-idempotency (개념 설명 블로그)
- 참고: astor-dev "결제 도메인에서의 멱등성 보장" (개인 블로그, 사례 분석)
- 아카이브 URL: (미수집)
- 저자 / 조직: 토스페이먼츠 (TossPayments) Developer Documentation
- 발행일: rolling docs (페이지 자체에 명시 없음)
- 마지막 확인일: 2026-05-27
## 왜 저장했는지 / Why archived
한국 결제 도메인의 vendor 표준 구현. ca-tmpl 이 한국 환경에서 운영된다면 토스의 정책 (4-tuple scope, 15일 TTL, 409 in-flight) 이 직접 비교 대상.
## 핵심 인용 / Key quotes (verbatim)
> [§Idempotency-Key 사용] "요청 헤더에 `Idempotency-Key`를 추가하면 멱등한 요청을 보낼 수 있습니다"
> [§Idempotency-Key 사용] "멱등키는 UUID(/resources/glossary/uuid)와 같이 충분히 무작위적인 고유 값으로 생성해주세요"
> [§멱등성 보장 메커니즘] "토스페이먼츠 서버는 상점에서 API 요청 헤더로 보낸 멱등키와 API 키, API 주소, HTTP 메서드 조합이 같은 요청이 있는지 확인해서 멱등성을 보장합니다"
> [§TTL] "멱등키는 처음 요청에 사용한 날부터 15일간 유효합니다"
> [§에러] "HTTP `400 - INVALID_IDEMPOTENCY_KEY`"
> [§에러] "HTTP `409 - IDEMPOTENT_REQUEST_PROCESSING`"
> [§주의] "멱등한 요청에서 에러가 반환되었을 때 멱등키를 변경해서 동일한 요청을 재시도하는 것은 위험이 있습니다"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| TOSS-IDEMP-C1 | 모든 POST API 에 `Idempotency-Key` 헤더를 추가하여 멱등 요청 가능, 값은 UUID 등 충분히 무작위 고유 값 권장 | [§Idempotency-Key 사용] "요청 헤더에 `Idempotency-Key`를 추가하면 멱등한 요청을 보낼 수 있습니다" + "멱등키는 UUID(/resources/glossary/uuid)와 같이 충분히 무작위적인 고유 값으로 생성해주세요" | `official-vendor-doc` | TossPayments API 의 POST endpoint | UUID 외 다른 형식 (예: 비즈니스 키, hash) 사용 시 충돌 위험은 별도 — 본 인용은 권장만 |
| TOSS-IDEMP-C2 | 멱등성 보장 범위는 **(멱등키, API 키, API 주소, HTTP 메서드) 4-tuple** 조합 | [§멱등성 보장 메커니즘] "토스페이먼츠 서버는 상점에서 API 요청 헤더로 보낸 멱등키와 API 키, API 주소, HTTP 메서드 조합이 같은 요청이 있는지 확인해서 멱등성을 보장합니다" | `official-vendor-doc` | TossPayments 가맹점 × endpoint × method 단위 | request body 가 다를 때의 처리 정책은 인용 범위에 없음 — body fingerprint 정책 부재 |
| TOSS-IDEMP-C3 | 멱등키 유효 기간은 첫 요청일로부터 **15일** | [§TTL] "멱등키는 처음 요청에 사용한 날부터 15일간 유효합니다" | `official-vendor-doc` | TossPayments idempotency store | 15일 정책이 모든 결제 도메인의 표준이라는 뜻은 아님. Stripe v1 24h / v2 30일과 다른 vendor-specific 결정 |
| TOSS-IDEMP-C4 | 잘못된 멱등키 형식 (예: 300자 초과 등) 은 `400 - INVALID_IDEMPOTENCY_KEY`, in-flight 동일 요청은 `409 - IDEMPOTENT_REQUEST_PROCESSING` | [§에러] "HTTP `400 - INVALID_IDEMPOTENCY_KEY`" + "HTTP `409 - IDEMPOTENT_REQUEST_PROCESSING`" | `official-vendor-doc` | TossPayments 의 표준 에러 매핑 | 409 가 즉시 반환되므로 클라이언트가 backoff 책임. wait/poll 동작 안 함 |
| TOSS-IDEMP-C5 | 멱등 요청 에러 시 키 변경 후 재시도는 위험이 있다 (공식적으로 권장 안 됨) | [§주의] "멱등한 요청에서 에러가 반환되었을 때 멱등키를 변경해서 동일한 요청을 재시도하는 것은 위험이 있습니다" | `official-vendor-doc` | retry 로직 설계 | 동일 키로 재시도해야 하는 정확한 조건 / 결과 코드별 분기는 본 인용에 없음 — 별도 가이드 확인 필요 |
| TOSS-IDEMP-C6 | 동일 키 + 동일 4-tuple + 다른 body 의 처리 정책은 본 인용 범위 내에 **명시 없음** | (부재 자체가 claim) | `needs-confirmation` | body fingerprint mismatch 처리 | 토스가 body diff 를 무시한다는 뜻도, 거부한다는 뜻도 아님. 문서가 직접 다루지 않음 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `TOSS-IDEMP-C1` ~ `C5`: TossPayments idempotency API 의 헤더 사용법, key scope 4-tuple, TTL 15일, 에러 매핑, retry 위험 안내
- **이 자료가 증명하지 않는 것**:
- `TOSS-IDEMP-C6`: same-key + different-body 시 동작 (body fingerprint 정책)
- idempotency store 의 backend (DB vs Redis vs 그 외) — 외부 관찰 불가
- 다른 한국 결제사 (KG이니시스, 카카오페이 등) 도 동일 정책을 사용하는지
- in-flight 409 가 race condition 의 짧은 window 도 흡수하는지 (즉시 거부이므로 클라이언트 backoff 필수로 추정)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 3-tuple `(principal, key, useCaseName)` 과 토스의 4-tuple `(account, key, URL, method)` 매핑 시 `useCaseName` 이 URL+method 역할을 충분히 대체하는지 (비즈니스 식별자 일관성)
- ca-tmpl 의 15일이 아닌 24h TTL 결정의 위험 (긴 retry window 손실 vs 저장소 부하)
## 메모 / Notes
> 검증되지 않은 내 해석은 여기에 두지 말 것 — wiki source-summary 단계에서.
- **key scope**: 4-tuple = `(API 키 = 가맹점, idempotency-key, API 주소, HTTP 메서드)`. ca-tmpl 의 3-tuple `(principal, key, useCaseName)` 와 유사하나 토스는 method 까지 명시.
- **TTL**: 15일 (Stripe v1 24h 보다 길고, v2 30일보다 짧음).
- **저장소**: 명시 안됨 — 외부에서 알 수 없음. 결제 도메인 특성상 영속 저장 추정 (검증 불가).
- **duplicate 처리**:
- 완료 후 동일 키 재요청 → first 응답 그대로 replay (`C2` 의 일반적 동작).
- in-flight 동일 키 재요청 → `409 IDEMPOTENT_REQUEST_PROCESSING` (즉시 거부, ca-tmpl 처럼 wait 안 함).
- **fingerprint (same key, different body)**: 공식 문서에 명시 없음 (`C6` 참조).
- **장점 (추론)**: 가맹점 × endpoint × method 까지 분리되어 사고 범위가 좁음. 15일 긴 TTL.
- **단점 (추론)**: body fingerprint 정책 부재. in-flight 409 → 클라이언트 backoff 책임.
- **ca-tmpl 과의 차이 (대안 비교 후보, wiki/projects 추출 시 활용)**:
- 토스 4-tuple ↔ ca-tmpl 3-tuple. `useCaseName` 이 URL+method 역할 통합. 동일 사상.
- TTL: 토스 15일 ≫ ca-tmpl 24h. ca-tmpl 이 더 짧고 보수적.
- in-flight: 토스 즉시 409 vs ca-tmpl 200ms wait → ca-tmpl 이 클라이언트 친화적.
- fingerprint: 토스 미명시 vs ca-tmpl 명시적 422 → ca-tmpl 이 더 엄격.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/idempotency-brandur-stripe-postgres]]
- [[raw/company-tech-blogs/idempotency-redis-vs-db-storage]]
- 인용하는 branch:
- [[raw/branch-notes/feature-rate-limit-idempotency-contract]]
- [[raw/branch-notes/feature-api-contract-baseline]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§13, §18)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,87 @@
---
title: WorkOS — The Developer's Guide to JWKS (unknown-kid on-demand refresh, rate-limit pattern, overlap window formula)
source_type: company-tech-blog
url: https://workos.com/blog/developers-guide-jwks
archive_url:
related_branches: [feature-security-operational-baseline]
related_projects: [ca-skeleton]
tags: [jwks, jwt, key-rotation, unknown-kid, rate-limit, overlap-window, resource-server, company-tech-blog]
status: raw
confidence: medium
created: 2026-06-08
last_reviewed: 2026-06-08
---
# WorkOS — The Developer's Guide to JWKS (unknown-kid on-demand refresh, rate-limit pattern, overlap window formula)
> Layer: `raw/company-tech-blogs/` — WorkOS 엔지니어링 블로그의 JWKS 운영 가이드.
> company-tech-blog = case study / engineering practice, **공식 best practice 로 승격 금지**.
> D10 의 unknown kid rate-limit (5~10분 권고) + overlap window 공식 의 engineering practice 근거.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-security-operational-baseline]] | D10: unknown kid on-demand refresh rate-limit (5~10분 권고) + rotation overlap window 공식 (token TTL + cache TTL + buffer) 의 engineering practice 근거 |
## 출처 / Source
- 원본 URL: https://workos.com/blog/developers-guide-jwks
- 아카이브 URL: (미수집)
- 저자 / 조직: WorkOS (IdP/Authentication-as-a-Service vendor)
- 마지막 확인일: 2026-06-08
## 왜 저장했는지 / Why archived
WorkOS 는 IdP vendor 로서 resource server 측에서 JWKS 를 어떻게 캐시하고, unknown kid 를 어떻게 처리하며, rotation overlap window 를 어떻게 설계해야 하는지에 대한 실무 패턴을 설명한다. 특히 thundering herd 방지를 위한 rate limit 의 권고 구간(5~10분)과 overlap window 공식이 이 자료에만 명시적으로 나온다.
## 핵심 인용 / Key quotes (verbatim)
> [WorkOS JWKS guide §Unknown KID Handling] "If a JWT arrives with a kid not present in your cached JWKS, refetch the JWKS before rejecting the token"
> [WorkOS JWKS guide §Rate Limiting] "implement a minimum refresh interval (typically 510 minutes)"
> [WorkOS JWKS guide §Rate Limiting context] "To prevent abuse (e.g., an attacker flooding your service with tokens signed by unknown keys), implement a minimum refresh interval (typically 510 minutes)." [paraphrase reconstructed from verbatim fragment — see note below]
> [WorkOS JWKS guide §Caching] "Cache the JWKS according to the Cache-Control headers returned by the endpoint."
> [WorkOS JWKS guide §Caching example] Cache-Control: max-age=86400 (24시간 캐시 예시로 제시)
> [WorkOS JWKS guide §Overlap Window] "overlap window = token TTL + JWKS cache TTL + 10 minutes"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| WORKOS-JWKS-C1 | unknown kid 를 가진 JWT 가 도착하면 즉시 거부하기 전에 JWKS 를 재조회해야 한다 | "If a JWT arrives with a kid not present in your cached JWKS, refetch the JWKS before rejecting the token" | `engineering-blog` | JWKS 기반 JWT 검증을 하는 resource server 일반 | 이 패턴이 RFC 나 공식 표준에서 normative 하게 요구되는 것은 아님 (공식 표준에는 unknown kid 처리 방식 미명세) |
| WORKOS-JWKS-C2 | unknown kid on-demand refresh 의 rate limit 권고값은 "5~10분" 이다 | "implement a minimum refresh interval (typically 510 minutes)" | `engineering-blog` | JWKS 기반 JWT 검증 resource server — thundering herd 방지 목적 | 이 수치가 normative 하게 정해진 것이 아님; ca-tmpl 의 1/min (60초) 는 이 권고보다 작은 구간이므로 trade-off 명시 필요 |
| WORKOS-JWKS-C3 | JWKS 는 endpoint 가 반환하는 Cache-Control 헤더에 따라 캐시해야 한다 | "Cache the JWKS according to the Cache-Control headers returned by the endpoint." | `engineering-blog` | JWKS endpoint 를 HTTP 로 조회하는 모든 resource server | IdP 가 Cache-Control 헤더를 반환하지 않는 경우의 fallback TTL 은 미명세 |
| WORKOS-JWKS-C4 | rotation overlap window 의 최소 안전값 공식: token TTL + JWKS cache TTL + 10분 | "overlap window = token TTL + JWKS cache TTL + 10 minutes" | `engineering-blog` | JWT access token 기반 OAuth2 resource server 의 rotation overlap 설계 | 이 공식이 RFC 나 vendor 공식 문서에서 normative 하게 채택된 것은 아님; engineering practice 수준 |
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `WORKOS-JWKS-C1`: unknown kid → JWKS refetch before reject 패턴 (engineering practice)
- `WORKOS-JWKS-C2`: thundering herd 방지를 위한 rate limit 구간 5~10분 (engineering practice)
- `WORKOS-JWKS-C3`: Cache-Control 헤더 기반 JWKS 캐시 (engineering practice)
- `WORKOS-JWKS-C4`: overlap window = token TTL + cache TTL + 10분 공식 (engineering practice)
- 이 자료가 증명하지 않는 것:
- ca-tmpl 의 rate limit "1회/1분" 이 올바른 값임을 증명하지 않음 — WorkOS 권고(5~10분)보다 짧으므로 thundering herd 위험 증가 (WORKOS-JWKS-C2 와 충돌, trade-off 명시 필요)
- rotation overlap window "24h" 가 이 공식에서 도출됨을 증명하지 않음 — ca-tmpl 의 token TTL 이 불명확한 상태에서 24h 는 별도 trade-off
- 이 가이드가 RFC 나 공식 표준을 인용하는지 확인되지 않음 (공식 표준으로 승격 금지)
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- ca-tmpl 의 JWT access token TTL 확인 → WORKOS-JWKS-C4 공식으로 minimum overlap window 계산 후 24h 정당화 또는 재검토
- ca-tmpl 의 JWKS cache TTL (10분) 확인 → overlap window = access_token_TTL + 10min + 10min 이 24h 보다 작은지 검증
- rate limit 1/min 이 5~10분 권고보다 짧은 것의 trade-off: rotation key가 매우 빠르게 전파되는 환경에서는 이점이 있으나, 공격자가 무작위 kid 로 DoS 시도 시 1/min 은 protection 이 약함
## 메모 / Notes
- WorkOS 는 IdP/AuthN-as-a-Service vendor 이므로 이 가이드는 IdP 를 운영하는 쪽과 resource server 를 운영하는 쪽 모두의 관점에서 쓰여 있다. resource server 관점의 권고임을 확인.
- "5~10분" 은 Nimbus JOSE+JWT 의 기본 rate limit (30초, `NIMBUS-JWKS-C1`)보다 훨씬 길다. ca-tmpl 의 1/min (60초) 는 Nimbus 기본값(30초)보다는 길고 WorkOS 권고(5~10분)보다는 짧음 — 이 위치를 trade-off 로 branch-note 에 명시.
- 이 자료의 claim 은 `company-case-study``engineering-blog` 강도이므로 별도 official-doc (RFC 7517, Spring Security ref) 과 교차 검증 필요. D10 을 `UNSUPPORTED_DECISION` → 부분 지지 상태로 격상시키기 위해서는 mechanism (NIMBUS-JWKS-C6) 의 공식 근거 + 이 engineering practice 를 함께 사용.
## Related / 관련
- [[raw/official-docs/jwks-nimbus-jose-jwksourcebuilder-spring-integration]] — mechanism 공식 근거 (official-vendor-doc)
- [[raw/official-docs/jwks-keycloak-key-rotation-active-passive]] — rotation overlap window 의 IdP-side 근거
- [[raw/branch-notes/feature-security-operational-baseline]] — D10 결정 컨텍스트
@@ -0,0 +1,97 @@
---
title: Keycloak with Google Login — Codemancers 기술블로그
source_type: company-tech-blog
url: https://www.codemancers.com/blog/keycloak-with-google-login
archive_url:
status: raw
confidence: medium
tags: [keycloak-patterns, p1b-edge-google-federation, idp-brokering, keycloak, google-oidc, company-tech-blog]
related_branches: [feature-keycloak-patterns, feature-keycloak-edge-forwardauth-google-federation]
related_projects: [keycloak-patterns]
created: 2026-05-25
last_reviewed: 2026-05-27
---
# Keycloak with Google Login — Codemancers
> Layer: `raw/company-tech-blogs/` — Codemancers (system analyst Mohammad Hussain, 2025-06-12). Keycloak Admin Console 에서 Google IdP 등록하는 step-by-step 튜토리얼 사례. **공식 best practice 아님 — Keycloak 공식 docs 와 교차 확인 필수.**
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-keycloak-patterns]] | keycloak-patterns root — Google IdP federation 설정 실무 화면 흐름의 사례 자료 |
| [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]] | P1B Edge + Google federation 구현 시 Google Cloud Console / Keycloak Admin Console 등록 trap 예방 사례 |
## 컨텍스트 / 왜 저장했는지
공식 문서는 추상적 절차만 제공. 실무 환경에서 Google Cloud Console / Keycloak Admin Console 을 오가며 등록할 때 발생하는 구체적 화면 흐름, redirect URI 매칭 실수 등의 **사례적 근거** 확보. P1B 구현 시 trap 예방용 메모.
## 출처 / Source
- 원본 URL: https://www.codemancers.com/blog/keycloak-with-google-login
- 아카이브 URL: (미수집)
- 저자 / 조직: Mohammad Hussain (System Analyst, Codemancers)
- 발행일: 2025-06-12
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§Keycloak Admin Console] "Go to the **Identity Providers** section from the left-hand menu."
> [§Add Provider] "Click **Add Provider** and select **Google** from the list of available providers."
> [§Google Cloud Console] "Head over to the [Google Cloud Console](https://console.cloud.google.com/)."
> [§Google credentials] "Navigate to **API & Services > Credentials**."
> [§Create credentials] "Click **Create Credentials** and choose **OAuth Client ID**."
> [§Application type] "Select **Web Application** as the application type and click **Create**."
> [§Client ID / Secret 확보] "You'll be presented with a **Client ID** and **Client Secret**. Copy both."
> [§Redirect URI 매칭] "copy the **Redirect URI** displayed here and add it to the **Authorized redirect URIs** in your Google Cloud configuration."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| CM-KC-GG-C1 | Keycloak Admin Console 의 Identity Providers 메뉴 → Add Provider → Google 선택으로 Google IdP 추가 가능 | [§Keycloak Admin Console / Add Provider] "Go to the Identity Providers section from the left-hand menu." + "Click Add Provider and select Google from the list of available providers." | `company-case-study` | Keycloak Admin UI 의 Identity Provider 등록 흐름 | Keycloak 버전 별 메뉴 위치/이름이 동일한지 본 인용 범위 밖. 공식 docs 별도 확인 |
| CM-KC-GG-C2 | Google credentials 발급은 Google Cloud Console > API & Services > Credentials > Create Credentials > OAuth Client ID 경로 | [§Google credentials / Create credentials] "Navigate to API & Services > Credentials." + "Click Create Credentials and choose OAuth Client ID." | `company-case-study` | Google Cloud Console UI 흐름 (2025-06 시점) | Google Cloud Console UI 가 변경되지 않는다는 보장 아님 — 본 인용은 2025-06 스냅샷 |
| CM-KC-GG-C3 | OAuth Client 타입 으로 **Web Application** 선택 필요 | [§Application type] "Select Web Application as the application type and click Create." | `company-case-study` | Keycloak ↔ Google OIDC 통합 시 OAuth client type 선택 | "Web Application" 외 다른 타입 (예: Desktop / iOS) 으로는 통합 불가하다는 직접 증명 아님 — 단지 본 사례의 선택 |
| CM-KC-GG-C4 | 생성된 Client ID / Client Secret 을 Keycloak Google IdP 설정에 입력하고, Keycloak 이 표시한 Redirect URI 를 Google 의 Authorized redirect URIs 에 추가해야 함 (양방향 등록) | [§Client ID / Secret 확보] "You'll be presented with a Client ID and Client Secret. Copy both." + [§Redirect URI 매칭] "copy the Redirect URI displayed here and add it to the Authorized redirect URIs in your Google Cloud configuration." | `company-case-study` | Keycloak ↔ Google OIDC handshake 의 redirect URI 정합성 | Redirect URI 경로 형식 (`/realms/<realm>/broker/google/endpoint`) 의 정확한 spec 은 본 인용에 없음 — Keycloak 공식 docs 확인 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `CM-KC-GG-C1`~`C4`: Keycloak Admin Console 과 Google Cloud Console 의 화면 흐름 / 등록 순서 (2025-06 시점 Codemancers 튜토리얼)
- **이 자료가 증명하지 않는 것**:
- "Web Application" 외 OAuth client type 선택 시 redirect URI 입력 칸이 사라진다는 trap (raw 메모에 적혀 있으나 본 fetch 인용에 직접 없음)
- Keycloak realm 이름 변경 시 redirect URI 가 함께 변경되어 Google 콘솔 재등록 필요 (raw 메모에 적혀 있으나 본 fetch 인용에 직접 없음)
- prod 환경에서의 Google API rate limit / Google account suspended 시 Keycloak 측 처리 (원래 raw 메모에서 `needs-confirmation` 으로 표기됨, 본 글 범위 밖)
- `sub` claim 기반 매칭 vs email 기반 매칭의 선택 (별도 raw: keycloak-first-login-flow)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- Keycloak 버전 (예: 22 / 23 / 24) 별 Admin Console UI 메뉴 위치 일치 여부
- Redirect URI 경로 `/realms/<realm>/broker/google/endpoint` 의 spec — Keycloak 공식 docs (Identity Brokering chapter)
- Google `email_verified` claim 의 신뢰 정책 — `feature-keycloak-account-linking-sub-vs-email` 결정과 결합
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. P1B 결정 컨텍스트 해석.
- **공식 vs 블로그 구분**: 절차 자체는 [[raw/official-docs/keycloak-google-idp-setup]] 와 일치 (추정). 본 블로그는 화면 캡처·트러블슈팅 측면에서 보조 자료. **공식 best practice 로 인용 금지.**
- **사례에서 자주 나오는 trap (본 raw 직접 증명 아님, 일반 운영 경험):**
- Google Cloud Console 에서 OAuth client type 을 "Web Application" 이 아닌 다른 것으로 선택 → redirect URI 입력 칸 자체가 안 뜸.
- Keycloak realm 이름 변경 시 redirect URI 경로 (`/realms/<realm>/broker/google/endpoint`) 도 같이 변경 → Google 콘솔 재등록 필요.
- **확인 안 됨 (P1B 학습 범위 밖, 원래 raw 메모 보존)**: prod 환경에서의 Google API rate limit, Google account suspended 시 Keycloak 측 처리. → `needs-confirmation`.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/official-docs/keycloak-google-idp-setup]] (공식 절차)
- [[raw/official-docs/keycloak-first-login-flow]] (외부 IdP 최초 로그인 정책)
- 인용하는 branch:
- [[raw/branch-notes/feature-keycloak-patterns]] (root)
- [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]] (P1B sub-branch)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,91 @@
---
title: "Extract roles from access token issued by Keycloak using Spring Security (Between Data / Christian Huff)"
source_type: personal-blog
url: https://betweendata.io/posts/secure-spring-rest-api-using-keycloak/
archive_url:
related_branches: [feature-keycloak-spring-rs-role-mapping]
related_projects: [keycloak-patterns]
tags: [personal-blog, keycloak-patterns, auth, spring-security, keycloak]
status: raw
confidence: medium
created: 2026-07-18
last_reviewed: 2026-07-18
---
# Extract roles from access token issued by Keycloak using Spring Security (Between Data / Christian Huff)
> Layer: `raw/company-tech-blogs/` (분류: 실제 `source_type` 은 `personal-blog` — 저자 Christian Huff 개인 블로그. 저장소 기존 관행([[raw/company-tech-blogs/test-pyramid-vs-trophy-kent-dodds]], `senior-engineer-competency-mubin-shaikh.md`, `deliberate-practice-software-developers-redgreencode.md`)에 따라 `company-tech-blogs/` 디렉토리에 위치하되 frontmatter `source_type: personal-blog` 유지).
> 회사 기술 블로그가 아니므로 **공식 best practice 로 격상 금지** (CLAUDE.md §5, §11). 아래 모든 Claim 은 `engineering-blog` 강도 — 개인 저자의 구현 사례일 뿐, Spring/Keycloak 공식 권고가 아니다.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-keycloak-spring-rs-role-mapping]] | Keycloak realm role 을 Spring `GrantedAuthority` 로 매핑하는 구현 방식 — 손으로 작성한 `Converter<Jwt, Collection<GrantedAuthority>>` 가 nested `realm_access` claim 을 읽어 `ROLE_` prefix 붙은 authority 로 변환하고, 필요 시 `DelegatingJwtGrantedAuthoritiesConverter` 로 default scope converter 와 결합하는 접근의 **사례(case study) 근거**. `feature-keycloak-spring-rs-role-mapping` D4 (`realm role 만 매핑`) 의 구현 detail 참고 자료.
## 출처 / Source
- 원본 URL: https://betweendata.io/posts/secure-spring-rest-api-using-keycloak/
- 아카이브 URL: (미수집)
- 저자 / 조직: Christian Huff (개인 블로그 "Between Data")
- 발행일: 2023-02-23
- 마지막 확인일: 2026-07-18
## 왜 저장했는지 / Why archived
Spring Boot 3 + `spring-boot-starter-oauth2-resource-server` 환경에서 Keycloak 이 발급한 JWT 의 `realm_access`/`resource_access` claim 은 Spring 의 기본 `JwtGrantedAuthoritiesConverter` 가 자동으로 추출하지 못한다. 이 자료는 그 문제를 **커스텀 `Converter<Jwt, Collection<GrantedAuthority>>`** 로 해결한 구체 코드 사례를 담고 있어, `feature-keycloak-spring-rs-role-mapping` 의 role mapping 구현 detail 을 정당화하는 참고 사례로 보관.
## 핵심 인용 / Key quotes (verbatim)
> [§Extract Roles from Access Token — class declaration] "public class KeycloakJwtRolesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {"
> [§Extract Roles from Access Token — realm_access claim 이름 정의 + 실제 읽기] "private static final String CLAIM_REALM_ACCESS = "realm_access";" [...] "Map<String, Collection<String>> realmAccess = jwt.getClaim(CLAIM_REALM_ACCESS);"
> [§Extract Roles from Access Token — ROLE_ prefix 상수] "public static final String PREFIX_REALM_ROLE = "ROLE_realm_";" [...] "public static final String PREFIX_RESOURCE_ROLE = "ROLE_";"
> [§Extract Roles from Access Token — ROLE_ prefix 설명 (본문)] "In the returned authorities the realm roles are prefixed with ROLE_realm_ while the resource roles are prefixed with ROLE_[NAME_OF_THE_RESOURCE]_."
> [§Define Access Rules — DelegatingJwtGrantedAuthoritiesConverter 조합] "new DelegatingJwtGrantedAuthoritiesConverter(" [...] "new JwtGrantedAuthoritiesConverter()," [...] "new KeycloakJwtRolesConverter());"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| KC-ROLE-BD-C1 | 저자는 `Converter<Jwt, Collection<GrantedAuthority>>` 를 구현하는 `KeycloakJwtRolesConverter` 클래스를 작성해, `realm_access` claim 이름을 상수로 정의하고 `jwt.getClaim(CLAIM_REALM_ACCESS)` 로 직접 읽는다 | "public class KeycloakJwtRolesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {" / "private static final String CLAIM_REALM_ACCESS = \"realm_access\";" / "Map<String, Collection<String>> realmAccess = jwt.getClaim(CLAIM_REALM_ACCESS);" | `engineering-blog` | Spring Boot 3 + Spring Security OAuth2 Resource Server 환경에서 Keycloak `realm_access` (nested claim map) 을 `GrantedAuthority` 로 변환하는 구현 패턴 | 이 방식이 Spring 또는 Keycloak 공식 권고 패턴이라는 것은 아님 (원문에 공식 문서 인용 없음). Keycloak 모든 버전에서 `realm_access` claim 구조가 동일하다는 보증도 아님 — 원문 예시 토큰은 특정 시점(2023-02) Keycloak 버전 기준 |
| KC-ROLE-BD-C2 | realm-level role 은 `ROLE_realm_` prefix, resource(client)-level role 은 `ROLE_[리소스명]_` prefix 를 붙여 `SimpleGrantedAuthority` 로 변환한다고 명시 | "public static final String PREFIX_REALM_ROLE = \"ROLE_realm_\";" / "public static final String PREFIX_RESOURCE_ROLE = \"ROLE_\";" / "In the returned authorities the realm roles are prefixed with ROLE_realm_ while the resource roles are prefixed with ROLE_[NAME_OF_THE_RESOURCE]_." | `engineering-blog` | Spring Security `hasAuthority(...)` 매칭을 위한 authority 명명 규칙의 한 예시(개인 저자 관례) | `ROLE_` prefix 가 Spring Security 의 필수 요구사항이라는 것은 아님 — `hasAuthority` 는 임의 문자열 매칭이 가능하고, `ROLE_` prefix 규칙은 `hasRole(...)` 사용 시에만 Spring 이 자동으로 붙이는 것과는 다른 맥락(원문은 이 구분을 설명하지 않음) |
| KC-ROLE-BD-C3 | `WebSecurityConfiguration.filterChain(...)` 에서 `DelegatingJwtGrantedAuthoritiesConverter` 를 사용해 default `JwtGrantedAuthoritiesConverter` 와 커스텀 `KeycloakJwtRolesConverter` 를 함께 등록한다 | "new DelegatingJwtGrantedAuthoritiesConverter(" [...] "new JwtGrantedAuthoritiesConverter()," [...] "new KeycloakJwtRolesConverter());" | `engineering-blog` | scope 기반 default authority 와 realm/resource role 기반 custom authority를 하나의 authorities 집합으로 합치는 조합 패턴의 사례 | 이 조합이 모든 프로젝트에 필요하다는 것은 아님 — scope 기반 인가를 병행하지 않는 프로젝트라면 default converter 생략 가능. 원문도 코드 주석 수준("Using the delegating converter multiple converters can be combined")의 설명만 제공하며 `DelegatingJwtGrantedAuthoritiesConverter` API 계약 자체의 공식 문서화는 아님 |
### 참고: 이 raw 는 `engineering-blog` 강도만 제공 — official 보강 필요
`KC-ROLE-BD-C1`~`C3` 는 모두 `engineering-blog` (개인 저자 사례). branch-note 에서 이를 "공식 best practice" 로 인용하면 안 된다 (CLAUDE.md §5, §11). `realm_access` 가 default `JwtGrantedAuthoritiesConverter` 로 자동 매핑되지 않는다는 사실 자체의 공식 근거가 필요하면 [[raw/official-docs/spring-security-resource-server-jwt]] (예: 기존 branch-note 인용 `SSRS-JWT-C4` — default converter 는 `scope`/`scp``SCOPE_` prefix 로 자동 변환) 를 함께 인용해야 `official-vendor-doc` 급 근거가 된다. 이 raw 단독으로는 D4(realm role 만 매핑) 의 "왜 커스텀 컨버터가 필요한가"에 대한 **사례**일 뿐, "Spring 이 이렇게 하라고 권고한다"는 근거는 아니다.
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `KC-ROLE-BD-C1`: 손으로 작성한 `Converter<Jwt, Collection<GrantedAuthority>>` 구현이 `realm_access` claim 을 nested map 으로 읽어올 수 있다는 동작 사례 (저자 GitHub 리포지토리에 테스트 100% 커버리지 존재한다고 원문이 주장 — 코드 자체는 미검증)
- `KC-ROLE-BD-C2`: `ROLE_realm_` / `ROLE_[resource]_` prefix 부여 방식 예시
- `KC-ROLE-BD-C3`: `DelegatingJwtGrantedAuthoritiesConverter` 로 default + custom converter 를 합치는 코드 구조 예시
- **이 자료가 증명하지 않는 것**:
- 이 구현이 Spring Security 또는 Keycloak 의 공식 권장 패턴이라는 명제 — 원문은 개인 저자의 "minimally invasive" 선택 설명일 뿐, RFC/공식 문서 인용 없음
- `realm_access.roles` 매핑이 모든 Keycloak 버전·모든 client 설정에서 동일하게 동작한다는 명제 — 예시 토큰은 특정 realm/client 설정(`backend` realm, `rest-api` client) 기준
- `ROLE_` prefix 없이 `hasAuthority`/`hasRole` 을 섞어 쓸 때의 Spring Security 내부 동작 차이에 대한 설명 — 원문 미포함
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- `feature-keycloak-spring-rs-role-mapping` 이 실제로 `resource_access` (client-level role) 까지 매핑할지, 아니면 D4 결정대로 `realm_access` 만 매핑할지 — 이 raw 의 `KeycloakJwtRolesConverter` 는 두 claim 을 모두 처리하므로 branch 결정과 범위가 다름(branch 는 realm role만, 이 raw 는 realm+resource 모두)에 주의
- 로컬 Keycloak 인스턴스에서 발급한 access token 의 `realm_access.roles` 실제 JSON 구조가 이 raw 의 예시 토큰과 일치하는지 확인
- `DelegatingJwtGrantedAuthoritiesConverter` 조합이 `feature-keycloak-spring-rs-role-mapping` 의 범위(§구현 가이드)에 실제로 필요한지 — branch 는 `@PreAuthorize` 대신 SecurityFilterChain matcher 를 우선하기로 결정했으므로 (D5), 이 raw 의 `.requestMatchers(...).hasAuthority(...)` 패턴과의 정합 재검토 필요
## 메모 / Notes
> 검증되지 않은 내 해석은 wiki source-summary 단계에서만.
- 원문은 realm-level role 과 resource(client)-level role 을 **모두** 매핑하는 구현(`KeycloakJwtRolesConverter`)을 제시하지만, `feature-keycloak-spring-rs-role-mapping` 의 D4 는 "realm role만 매핑 (resource_access 무시)"로 범위를 좁혔다 — 이 raw 를 인용할 때 **resource_access 부분은 branch 범위 밖**임을 명시해야 함 (OUT_OF_BRANCH_SCOPE 유사 주의).
- 원문 저자는 Keycloak 기본 설정(mapper 미변경)을 유지하는 쪽을 "minimally invasive" 라고 표현 — 이는 branch 의 "Keycloak mapper 커스터마이징 대신 Spring 쪽 컨버터로 흡수" 방향과 같은 트레이드오프 축으로 보인다(해석, 미검증).
- 저자는 GitHub 코드 링크(`ChristianHuff-DEV/secure-spring-rest-api-using-keycloak`)와 100% 테스트 커버리지를 주장하나, 이 raw 는 블로그 본문만 발췌·검증했고 GitHub 코드 자체는 self-grep 대상에 포함하지 않음 — 실제 사용 시 코드 diff 재확인 필요.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/official-docs/spring-security-resource-server-jwt]] — default `JwtGrantedAuthoritiesConverter``scope`/`scp` 만 자동 매핑한다는 공식 근거 (`SSRS-JWT-C4`) — 이 raw 의 C1과 짝을 이뤄야 `official-vendor-doc` 급 근거 완성
- 인용하는 branch:
- [[raw/branch-notes/feature-keycloak-spring-rs-role-mapping]]
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,107 @@
---
title: kamilmazurek/layered-architecture-template (GitHub) — Java/Spring Boot layer-first 구현 사례
source_type: company-tech-blog
url: https://github.com/kamilmazurek/layered-architecture-template
archive_url:
related_branches: [feature-architecture-enforcement-rules, feature-skeleton-package-blueprint-contract, feature-domain-feature-onboarding-contract]
related_projects: [ca-skeleton-operational-contract]
tags: [ca-architecture-layout, layer-first, github-template, spring-boot]
status: raw
confidence: low
created: 2026-05-22
last_reviewed: 2026-05-27
---
# kamilmazurek/layered-architecture-template
> Layer: `raw/company-tech-blogs/` — 개인 GitHub template README verbatim. Spring Boot 환경의 layer-first 4-layer (API/Service/Repository/Database) 구조 예시.
> 주의: 본 자료는 **개인 GitHub repository (star 수 낮음)** 이므로 strength = `engineering-blog`. 공식 best practice 로 인용 금지.
> 분류 메모: 본 카테고리 `company-tech-blog` 는 묶음. 본 자료의 정확한 분류는 `personal-blog` 에 가깝지만 현재 raw 디렉토리 구조가 `raw/personal-blogs/` 를 갖지 않아 가장 가까운 카테고리에 보존. 후속 정리 시 재분류 검토.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-architecture-enforcement-rules]] | feature-first vs layer-first 비교 시 layer-first 의 구체 구현 예시 (대안 비교 매트릭스 입력) |
| [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] | skeleton package blueprint 결정 시 4-layer 이름은 동일하나 최상위 분할이 반대인 layer-first 의 사례 |
| [[raw/branch-notes/feature-domain-feature-onboarding-contract]] | 새 도메인 추가 시 layer-first 가 디렉토리 비대화로 이어지는 한계 비교 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §19, §20 — Skeleton Blueprint Contract / Domain Application Readiness Contract 의 layer-first 대안 reference |
## 컨텍스트 / 왜 저장했는지
ca-tmpl의 feature-first 결정에 대한 대안 2: Layer-first 구조를 그대로 구현한 GitHub template. 별 1000+ 후보(`bezkoder/spring-boot-three-layer` 류)는 직접 본문 확인 어려움 — 동일 구조를 가진 template로 대체. Java 21 + Spring Boot 최신 스택에서의 전형적 layered 패키지 구조를 보존.
## 출처 / Source
- 원본 URL: https://github.com/kamilmazurek/layered-architecture-template
- 아카이브 URL: (미수집)
- 저자 / 조직: Kamil Mazurek (개인)
- 발행일: rolling (지속 유지보수)
- 마지막 확인일: 2026-05-27
- Star 수: 소규모 reference (개인 template)
## 핵심 인용 / Key quotes (verbatim)
> [§README intro] "This repository contains a Spring Boot microservice template that follows a modern REST-based Layered Architecture approach."
> [§README intro] "a Spring Boot microservice template that follows a clean layered architecture. It offers modular REST API with a clear separation of concerns"
> [§Layers — API Layer] "**API Layer**: Exposes REST endpoints and handles HTTP requests/responses (equivalent to Presentation)."
> [§Layers — Service Layer] "**Service Layer**: Implements business logic and orchestrates operations (equivalent to Business Logic)."
> [§Layers — Repository Layer] "**Repository Layer**: Interfaces with the database, handling CRUD operations (equivalent to Persistence)."
> [§Layers — Database Layer] "**Database Layer**: Stores the application data."
> [§Benefits — Simplicity] "**Simplicity and Familiarity**: Widely adopted, this pattern is easy to understand and implement"
> [§Benefits — Separation] "**Separation of Responsibilities**: The architecture organizes code into layers like controller, service, and repository, each handling its role clearly."
> [§Benefits — Maintainability] "**Maintainability**: Encapsulation of responsibilities within layers makes the application easier to debug, extend, and refactor"
> [§Benefits — Testability] "**Testability**: With clearly defined boundaries between layers, unit and integration testing become more straightforward"
> [§Benefits — Scalability] "**Scalability for Simple Use Cases**: Good fit for CRUD or moderate business logic apps, as layers support growth"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| LAYER-FIRST-TMPL-C1 | 본 template 는 Spring Boot 마이크로서비스 + REST 기반 layered architecture 접근법을 따름 | [§README intro] "This repository contains a Spring Boot microservice template that follows a modern REST-based Layered Architecture approach." | `engineering-blog` | Spring Boot REST API 마이크로서비스 reference | 본 template 의 구조가 모든 Spring Boot 프로젝트의 best practice 라는 뜻은 아님 — 개인 template, star 수 낮음 |
| LAYER-FIRST-TMPL-C2 | layered 구조의 4 layer 는 API / Service / Repository / Database 로 분할되며 각각 REST endpoint / 비즈니스 로직 / DB CRUD / 데이터 저장 책임 | [§Layers — API/Service/Repository/Database Layer] (4개 verbatim 인용 위 참조) | `engineering-blog` | 단일 도메인 CRUD API 의 layer-first 구조 | 4-layer 외 다른 분할 (예: hexagonal 의 port/adapter, modulith 의 module) 이 invalid 라는 뜻은 아님 |
| LAYER-FIRST-TMPL-C3 | layer-first 의 장점은 (1) Simplicity & Familiarity (2) Separation of Responsibilities (3) Maintainability (4) Testability (5) Scalability for Simple Use Cases | [§Benefits — 5개 항목] (5개 verbatim 인용 위 참조) | `engineering-blog` | 학습용 / 단일 도메인 microservice / MVP 시 layer-first 채택 시 | 본 인용은 self-attestation (template 저자 자체 평가). 대형 도메인에서의 단점 (cross-cutting concern, 패키지 비대화) 은 본 인용에 없음 |
| LAYER-FIRST-TMPL-C4 | (부재) layer-first 가 도메인 증가 시 디렉토리 비대화 / cross-cutting concern 분산 / feature 단위 응집도 저하 같은 단점을 갖는다는 진술은 본 인용 범위 내에 **명시 없음** | (부재 자체가 claim — self-marketing 한계) | `needs-confirmation` | layer-first 의 한계 비교 | 메모 섹션의 단점 진술은 본 자료 외 다른 근거 필요 (예: Vaughn Vernon "Implementing DDD", Sam Newman "Building Microservices") |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `LAYER-FIRST-TMPL-C1`, `C2`, `C3`: Spring Boot layer-first 구조의 한 구체 구현 예시 + 저자가 명시한 장점 5개
- **이 자료가 증명하지 않는 것**:
- `LAYER-FIRST-TMPL-C4`: layer-first 의 단점 (도메인 증가 시 패키지 비대화 등)
- layer-first vs feature-first 의 일반적 우위 비교
- 본 template 가 production 에서 검증되었다는 사실 (star 수 낮음, 개인 reference)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 `features/{name}/{presentation,application,domain,infrastructure}` 구조와의 정량 비교 (NRR, ArchUnit rule 수 등)
- 도메인 5+ 추가 시 layer-first 의 cross-cutting concern (transaction, security) 분산 사례
- 본 template 외 star 수 높은 layer-first reference (bezkoder/spring-boot-three-layer 등) 의 추가 수집
## 메모 / Notes (내 프로젝트 해석)
> 검증되지 않은 내 추론은 여기에 두지 말 것 — wiki source-summary 단계에서.
- 적용 시나리오: 단일 도메인 microservice, MVP, 학습용.
- 장점: 새 팀원이 5초 만에 구조 파악. controller → service → repository 흐름이 디렉터리 트리에 그대로 드러남.
- 단점: 별 수에서 보이듯 reference로서의 권위는 약함. 도메인이 늘면 패키지가 비대해짐.
- ca-tmpl(feature-first)와의 차이: 동일한 4-layer 이름을 쓰되 최상위 분할이 반대. 이 template은 `api/`, `service/`, `repository/`가 최상위. ca-tmpl은 `features/{name}/{presentation,application,domain,infrastructure}`.
## Related / 관련
- 같은 주제 다른 raw: (미수집 — bezkoder/spring-boot-three-layer 후보)
- 인용하는 branch:
- [[raw/branch-notes/feature-architecture-enforcement-rules]]
- [[raw/branch-notes/feature-skeleton-package-blueprint-contract]]
- [[raw/branch-notes/feature-domain-feature-onboarding-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§19, §20)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,91 @@
---
title: "Leveraging Postgres Advisory Locks for Distributed Consensus — Subskribe Engineering Blog"
source_type: company-tech-blog
url: https://www.subskribe.com/blog/leveraging-postgres-advisory-locks-for-distributed-consensus
archive_url:
related_branches: [feature-distributed-lock-contract]
related_projects: [ca-skeleton-operational-contract]
tags: [company-tech-blog, ca-skeleton-operational-contract, persistence, postgresql, advisory-lock, distributed-lock, company-case]
created: 2026-06-12
---
# Leveraging Postgres Advisory Locks for Distributed Consensus — Subskribe Engineering Blog
> Layer: `raw/company-tech-blogs/` — 외부 기술 블로그 원문 발췌·출처 기록.
> **주의**: 이 자료는 `company-tech-blog` 입니다. 특정 회사의 사례·관점이며, 공식 PostgreSQL 문서나 공식 best practice로 취급하지 않습니다.
## Parent / 활용 branch
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-distributed-lock-contract]] | PostgreSQL advisory lock 의 production 사용 사례 — 추가 인프라 없이 DB 만으로 distributed mutual exclusion 을 달성한 사례 + "optimistic variant (try-lock) 만 사용, pessimistic blocking 은 비권장" 운영 교훈이 `distributedLockProvider` 메커니즘 비교의 사례 근거 (공식 best practice 아님 — 사례/관점으로만 취급) |
## 출처 / Source
- 원본 URL: https://www.subskribe.com/blog/leveraging-postgres-advisory-locks-for-distributed-consensus
- 아카이브 URL: (미등록)
- 저자 / 조직: Subbu Nagarajan / Subskribe Engineering
- 발행일: 2022-09-20
- 마지막 확인일: 2026-06-12
## 왜 저장했는지 / Why archived
`feature-distributed-lock-contract` 브랜치에서 `distributedLockProvider` 의 구현 메커니즘으로 PostgreSQL advisory lock 을 검토 중이며, Subskribe 가 동일 메커니즘을 production 에서 invoice 중복 생성 방지에 사용한 사례가 "추가 인프라 없이 advisory lock 만으로 distributed mutual exclusion 달성 가능 여부"를 뒷받침하는 사례 근거가 된다. 특히 "try-lock 만 사용하고 pessimistic blocking 은 쓰지 않았다"는 운영 결정이 ca-tmpl 의 `tryLock` 전용 contract 비교에 직접 활용된다.
## 핵심 인용 / Key quotes (verbatim, 5개)
> [§Problem Statement] "at any given time you should generate only one invoice for a given subscription."
> [§Advisory Locks API/Contract] "At Subskribe, we only use the optimistic variant (try to acquire lock and fail) of the advisory locks. Pessimistic locking (try to acquire lock but wait until you can or timeout) is, in general, not a good pattern, and we haven't seen much use for it in our engineering needs."
> [§Why Advisory Locks] "PostgreSQL provides a means for creating locks that have application-defined meanings. This allows you to create locks on items that are not stored in the DB and mean something only to the application (e.g., locking on an arbitrary key that is stored only in application memory)."
> [§WARNING] "If you acquire a session level lock from the application, it is the responsibility of the application to explicitly release that lock (otherwise the lock would be held). If you acquire a transaction level advisory lock, Postgres automatically releases the lock when the transaction ends ."
> [§How Did It Solve the Problem] "We managed to achieve distributed mutual exclusion using Postgres advisory locks using only an arbitrary key (which is not even stored in the database)."
## Claims Extracted / 추출된 주장
> 이 자료는 `company-tech-blog` 입니다. 아래 Claim 은 **Subskribe 의 단일 사례**이며, 공식 PostgreSQL 표준이나 업계 공통 best practice 를 증명하지 않습니다. advisory lock 의 동작 명세(session-level/transaction-level 해제 시맨틱 등)는 공식 PostgreSQL 문서(`raw/official-docs/lock-postgres-advisory-locks`)에서 별도 검증 필요.
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| SUBSKRIBE-LOCK-C1 | Subskribe 는 distributed mutual exclusion(invoice 중복 생성 방지)을 PostgreSQL advisory lock 만으로 달성했으며, 추가 인프라(Zookeeper, ETCD)를 사용하지 않았다 | [§How Did It Solve the Problem] "We managed to achieve distributed mutual exclusion using Postgres advisory locks using only an arbitrary key (which is not even stored in the database)." | `company-case-study` | PostgreSQL DB 를 이미 사용하는 서비스에서 중복 실행 방지가 필요한 경우 | PostgreSQL advisory lock 이 모든 분산 상호 배제 문제에 충분하다는 것, 대규모 트래픽에서의 성능·충돌률 데이터 |
| SUBSKRIBE-LOCK-C2 | Subskribe 는 advisory lock 중 optimistic variant(try-and-fail) 만 사용하며, pessimistic(blocking) locking 은 "not a good pattern" 으로 판단해 사용하지 않았다 | [§Advisory Locks API/Contract] "At Subskribe, we only use the optimistic variant (try to acquire lock and fail) of the advisory locks. Pessimistic locking (try to acquire lock but wait until you can or timeout) is, in general, not a good pattern, and we haven't seen much use for it in our engineering needs." | `company-case-study` | advisory lock 기반 분산 락 구현 시 try-lock vs blocking 선택 결정 | pessimistic locking 이 모든 시나리오에서 잘못됐다는 것; 이 주장은 Subskribe 엔지니어링 팀의 운영 경험 관점 |
| SUBSKRIBE-LOCK-C3 | advisory lock 은 DB 에 저장되지 않는 application-defined arbitrary key 에 대해 잠금을 획득할 수 있어, SELECT FOR UPDATE 와 달리 DB row 없이도 사용 가능하다 | [§Why Advisory Locks] "PostgreSQL provides a means for creating locks that have application-defined meanings. This allows you to create locks on items that are not stored in the DB and mean something only to the application (e.g., locking on an arbitrary key that is stored only in application memory)." | `company-case-study` | lock key 가 DB row 가 아닌 application 레벨 개념(예: 구독 ID + 작업 context 문자열)인 경우 | SELECT FOR UPDATE 와의 성능 비교 수치; PostgreSQL 내부 구현 명세(공식 문서 별도 확인 필요) |
| SUBSKRIBE-LOCK-C4 | session-level advisory lock 은 애플리케이션이 명시적으로 해제해야 하며, transaction-level advisory lock 은 트랜잭션 종료 시 PostgreSQL 이 자동 해제한다 | [§WARNING] "If you acquire a session level lock from the application, it is the responsibility of the application to explicitly release that lock (otherwise the lock would be held). If you acquire a transaction level advisory lock, Postgres automatically releases the lock when the transaction ends ." | `company-case-study` | advisory lock 의 session-level vs transaction-level 해제 시맨틱 설명 | 이 해제 시맨틱은 공식 PostgreSQL 문서에서 별도 검증 필요 — 이 문서는 사례 설명이지 공식 명세가 아님 |
| SUBSKRIBE-LOCK-C5 | Subskribe 는 문자열 key 를 advisory lock 의 bigint 인자로 변환하기 위해 Google Guava 의 SipHash(64-bit non-cryptographic hash)를 사용했다 | [§Locking String Vs. Number] "We settled on the Sip Hash . This is a lesser known but very useful hash function of the 'add-rotate-xor' family , which is reasonably fast, has very good distribution properties, and a Guava implementation known to work well." | `company-case-study` | 문자열 lock key 를 bigint 로 해시해야 하는 구현 시 hash 함수 선택 사례 | SipHash 가 이 용도의 유일한 정답이거나 collision-free 라는 것; hash collision 시 동작 보장 없음 |
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `SUBSKRIBE-LOCK-C1`: PostgreSQL advisory lock 을 이미 사용 중인 단일 서비스(Subskribe)에서 invoice 중복 생성 방지에 적용한 사례
- `SUBSKRIBE-LOCK-C2`: Subskribe 엔지니어링팀의 optimistic-only 운영 정책 ("try-and-fail 만, blocking 은 안 씀")
- `SUBSKRIBE-LOCK-C3`: advisory lock 의 arbitrary key 특성 — DB row 필요 없음 (이는 공식 문서에서도 확인 가능한 사실이지만 이 자료는 사례 설명)
- `SUBSKRIBE-LOCK-C4`: session-level vs transaction-level 해제 시맨틱 (공식 문서 별도 검증 필요)
- `SUBSKRIBE-LOCK-C5`: SipHash를 사용한 string→bigint 변환 구현 사례
- 이 자료가 증명하지 않는 것:
- advisory lock 이 모든 규모·환경에서 distributed lock 의 공식 정답이라는 것
- pessimistic locking 이 항상 나쁘다는 것 (이는 Subskribe 의 운영 판단)
- `pg_try_advisory_xact_lock` 의 성능 수치·SLA 보장
- hash collision 발생 시 동작 (SipHash 충돌 시 두 개의 다른 키가 같은 bigint 로 매핑될 수 있음)
- ca-tmpl `distributedLockProvider` 구현 시 PostgreSQL advisory lock 이 Redis/ShedLock 대비 최선이라는 것
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- PostgreSQL advisory lock 의 session-level/transaction-level 해제 시맨틱은 공식 문서(`raw/official-docs/lock-postgres-advisory-locks`) 에서 재확인
- ca-tmpl 의 JPA/HikariCP connection pool 환경에서 transaction-level advisory lock 이 Spring `@Transactional` 경계와 정합하는지 검증 필요
- SipHash collision 허용 여부 — ca-tmpl lock key space 에서 collision 확률·영향도 검토
## 메모 / Notes
- Subskribe 는 `pg_try_advisory_xact_lock` (transaction-level) 만 사용 — session-level(`pg_try_advisory_lock`) 은 명시적 해제 필요로 인해 connection pool 환경에서 "lock 해제 누락" 위험이 있음
- 코드 전체가 공개되어 있으며(`PostgresAdvisoryLock.java` 전체 listing), Spring/jOOQ 기반 구현 사례로 ca-tmpl JPA 기반 구현과 직접 비교 가능
- lock key 설계 패턴: `<context>/<entity-id>` (예: `"invoice_gen/SUB-1234"`) — context prefix 를 붙여 동일 entity 에 대한 서로 다른 잠금 범위를 분리하는 패턴
- 이 블로그 포스트의 자료 강도는 `company-case-study` — 공식 PostgreSQL 문서(`raw/official-docs/lock-postgres-advisory-locks`)와 함께 사용해야 결정 근거로서 완전함
## Related / 관련
- 공식 문서 (advisory lock 동작 명세): [[raw/official-docs/lock-postgres-advisory-locks]]
- 같은 branch 의 다른 source (Spring Integration Lock Registry): [[raw/official-docs/lock-spring-integration-lock-registry]]
- 이 자료를 인용한 wiki 요약: (생성 시 추가)
@@ -0,0 +1,114 @@
---
title: 토스 — 결제/Gateway 모니터링과 알람 운영 (raw 인용 검증 실패)
source_type: company-tech-blog
url: https://toss.tech/article/slash23-server
archive_url:
status: needs-confirmation
confidence: low
tags: [ca-metrics-alerting, toss, alerting, korean-fintech, severity]
related_branches: [feature-metrics-alerting-contract, feature-operational-runbook-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# 토스 — 결제/Gateway 모니터링과 알람 운영 (raw 인용 검증 실패)
> Layer: `raw/company-tech-blogs/` — 토스 SLASH 23 발표 글 발췌 시도.
> **2026-05-27 검증 결과**: 원 raw 기록 (2026-05-22) 에 적힌 5개 한국어 인용 ("P1은 사용자가 결제를 못 하는 상황...", "에러율 1%가 critical 일 수도...", "alert 에는 항상 1차 확인할 dashboard 링크...", "metric naming 은 일관성이 핵심..." 등) 은 인용 출처로 명시된 `toss.tech/article/slash23-server` 페이지의 본문에서 **재확인되지 않음**.
> 실제 해당 페이지 (제목: "토스는 Gateway 이렇게 씁니다", 저자: 최준우, 2023-10-12) 는 **Gateway 아키텍처** 주제이며, 모니터링 섹션은 Logging (Elasticsearch) / Metrics (Prometheus + Grafana) / Tracing 의 도구 언급만 있고 severity 정의·임계값·payload 구조에 대한 인용은 없음.
> 따라서 본 문서는 raw 보존 + `needs-confirmation` 라벨로 마이그레이션하되, **claim 들을 사실로 격상 금지**.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-metrics-alerting-contract]] | P1/P2/P3 severity 정의 + alert payload 5종 필드의 외부 fintech 사례 후보 — **단 본 문서 인용 미검증, 결정의 1차 근거로 사용 금지** |
| [[raw/branch-notes/feature-operational-runbook-contract]] | alert 와 runbook 링크 연결 정책의 외부 사례 후보 — 동일하게 인용 미검증 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §18. Control Plane Contract (Metrics / Alerting) 의 외부 사례 — 인용 검증 후 별도 wiki 인용 가능 여부 재판단 |
## 컨텍스트 / 왜 저장했는지
원 raw 의 의도: ca-tmpl 이 결정한 "**P1/P2/P3 정량 기준**" 및 "**alert payload 에 operation/dependency/error.code/error.category/runbook_link 필수**" 의 국내 fintech 사례 근거로 보관.
**검증 후 실제 상태**: 인용 출처 URL 이 다른 주제 (Gateway 아키텍처) 의 글이므로, 본 자료는 ca-tmpl 결정의 근거로 **사용 불가**. 별도 토스/카카오페이/네이버페이의 실제 alerting 사례 글을 찾아 raw 재수집 필요.
## 출처 / Source
- 원본 URL (검증 시점에 본문 확인): https://toss.tech/article/slash23-server
- 실제 글 제목: "토스는 Gateway 이렇게 씁니다"
- 실제 저자: 최준우 (Toss Server Developer)
- 발행일: 2023-10-12
- 아카이브 URL: (미수집)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
**verified (2026-05-27, 실제 글 본문에서 확인된 인용)**:
> [§모니터링 — 로깅] "Gateway를 지나는 모든 요청, 응답의 Route id와 method, URI, 상태 코드 등을 Elasticsearch에 남기고 있습니다"
> [§모니터링 — 메트릭/트레이싱 요약 (verbatim 일부만 회수, 본 fetch 한계)] 시스템·애플리케이션 메트릭은 Prometheus 수집 + Grafana 시각화 + Slack 알림. 트레이싱은 분산 트레이싱 구현 언급.
**unverified (원 raw 2026-05-22 기록, 출처 URL 본문에 부재 — `needs-confirmation`)**:
> [§unverified] "결제는 사용자 경험과 매출에 직결되기 때문에, 단순 error rate threshold 보다 영향 범위와 비즈니스 임팩트를 기준으로 알람을 나눕니다."
> [§unverified] "P1은 사용자가 결제를 못 하는 상황, P2는 일부 가맹점·일부 카드사 영향, P3는 내부 운영 지표 이상으로 구분합니다."
> [§unverified] "에러율 1%가 critical 일 수도 minor 일 수도 있어서, baseline 대비 spike (예: 평소 0.1% → 1%로 10배) 기준도 같이 봅니다."
> [§unverified] "alert 에는 항상 1차 확인할 dashboard 링크, 관련 로그 query, on-call runbook 링크가 함께 들어가야 한다."
> [§unverified] "metric naming 은 일관성이 핵심. `결제_성공률` 같은 한글 metric 은 절대 금지하고, 영문 dot-case 로 통일했습니다."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| TOSS-ALERT-C1 | 토스 Gateway 는 모든 요청/응답의 Route id, method, URI, 상태 코드를 Elasticsearch 에 로깅 | [§모니터링 — 로깅] "Gateway를 지나는 모든 요청, 응답의 Route id와 method, URI, 상태 코드 등을 Elasticsearch에 남기고 있습니다" | `company-case-study` | 토스 Gateway 의 로깅 시스템 | 결제 도메인 전체의 로깅 표준이라는 뜻은 아님 — Gateway 한 영역 |
| TOSS-ALERT-C2 | 토스 는 메트릭 수집에 Prometheus, 시각화에 Grafana, 알림에 Slack 을 사용 (도구 스택) | (본 fetch 본문 요약 — verbatim 일부 회수) | `company-case-study` | 토스 의 모니터링 도구 선택 | Slack 알림의 payload 구조 / severity 정의 / threshold 는 본 인용 범위 밖 |
| TOSS-ALERT-C3 | **(unverified)** P1=결제 차단, P2=일부 가맹점/카드사 영향, P3=내부 지표 이상 — 비즈니스 임팩트 기반 severity 분류 | [§unverified] "P1은 사용자가 결제를 못 하는 상황, P2는 일부 가맹점·일부 카드사 영향, P3는 내부 운영 지표 이상으로 구분합니다." | `needs-confirmation` | 토스 결제 도메인의 severity 정책 (재확인 필요) | 본 인용은 cited URL 본문에서 확인 안 됨. 토스 의 실제 정책일 수 있으나 출처 재발굴 전까지 사실로 격상 금지 |
| TOSS-ALERT-C4 | **(unverified)** 에러율 절대값이 아닌 baseline 대비 spike (예: 평소 0.1% → 1% = 10배) 도 같이 기준으로 사용 | [§unverified] "에러율 1%가 critical 일 수도 minor 일 수도 있어서, baseline 대비 spike ... 기준도 같이 봅니다." | `needs-confirmation` | spike-based alert 정책 (재확인 필요) | 출처 검증 실패. 일반 모니터링 기법이지만 토스 의 명시적 정책이라는 증명 없음 |
| TOSS-ALERT-C5 | **(unverified)** alert payload 에 dashboard 링크 + 로그 query + runbook 링크 동시 포함 의무 | [§unverified] "alert 에는 항상 1차 확인할 dashboard 링크, 관련 로그 query, on-call runbook 링크가 함께 들어가야 한다." | `needs-confirmation` | alert payload 표준 (재확인 필요) | 출처 검증 실패. 일반적 권고이나 토스 의 명시적 contract 증거 없음 |
| TOSS-ALERT-C6 | **(unverified)** metric naming 은 영문 dot-case 통일, 한글 metric 금지 | [§unverified] "metric naming 은 일관성이 핵심. `결제_성공률` 같은 한글 metric 은 절대 금지하고, 영문 dot-case 로 통일했습니다." | `needs-confirmation` | metric naming convention (재확인 필요) | 출처 검증 실패. Micrometer dot-case 는 별도 OpenTelemetry/Prometheus 표준 — 토스 의 명시적 정책 증거 부재 |
| TOSS-ALERT-C7 | **(unverified)** 비즈니스 임팩트 기반 severity 분류 가 단순 error rate threshold 보다 우선 | [§unverified] "결제는 사용자 경험과 매출에 직결되기 때문에, 단순 error rate threshold 보다 영향 범위와 비즈니스 임팩트를 기준으로 알람을 나눕니다." | `needs-confirmation` | 결제 도메인의 alerting 철학 (재확인 필요) | 출처 검증 실패 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `TOSS-ALERT-C1` ~ `C2`: 토스 Gateway 의 로깅 / 메트릭 도구 스택 (verified 2026-05-27, Gateway 한정)
- **이 자료가 증명하지 않는 것**:
- `TOSS-ALERT-C3` ~ `C7`: severity 정의, spike threshold, alert payload 구조, metric naming, 비즈니스 임팩트 기반 분류 — **모두 출처 URL 본문에서 미확인**. `needs-confirmation` 상태로 보존
- "토스가 이러니까 한국 fintech 표준" 격상 (CLAUDE.md §5: `company-tech-blog` 등급은 사례/관점)
- ca-tmpl 의 P1/P2/P3 정량 threshold 의 fintech 산업 검증
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- 토스 의 실제 alerting/severity 정책이 공개된 다른 글 (예: 토스페이먼츠 기술 블로그, SLASH 컨퍼런스 다른 발표, infcon 발표) 의 raw 재수집
- 카카오페이 / 네이버페이 / KG이니시스 등 다른 한국 fintech 의 비교 가능한 공개 자료
- ca-tmpl 의 정량 threshold (>5% 5분 / >1% 10분 / >0.1% 1시간) 의 별도 근거 (SRE workbook 등)
## 메모 / Notes
> 검증되지 않은 내 해석은 wiki source-summary 단계에서만.
- **검증 실패의 의미**: 원 raw 에 적힌 인용 5종은 의도는 합리적이나 (P1/P2/P3 비즈니스 임팩트 기반, spike threshold, payload 표준, dot-case naming) **cited URL 본문에 존재하지 않음**. 이는 원작성자의 해석/요약을 인용 형태로 기록했거나, 출처 URL 이 부정확할 가능성.
- **재발굴 후보 키워드**:
- "토스 결제 알람 severity"
- "토스페이먼츠 on-call runbook"
- "SLASH 22/23/24 결제 모니터링"
- "infcon 토스 alerting"
- **ca-tmpl 결정과의 관계 (재검토 권고)**:
- 본 raw 가 cited 출처와 불일치하므로 `feature-metrics-alerting-contract` 의 결정 근거 표에서 본 raw 인용 제거 또는 `needs-confirmation` 명시 필요.
- 새 raw (토스/카카오페이 실제 alerting 글) 발굴 시까지 정책 결정은 SRE Workbook / Google SRE Book / OpenTelemetry semconv 등 official-doc 으로 보강 권고.
- **공정 기록**: 본 migration 은 raw 정확성 회복이 목표 — fabricated quote 를 사실로 ingest 하면 wiki/concepts → wiki/blog 까지 오염되므로 단호한 라벨링 필요 (CLAUDE.md §11 "출처 없는 단정적 진술" 금지 조항).
## Related / 관련
- 같은 주제 다른 raw:
- (재발굴 필요 — 토스 실제 alerting 글, 카카오페이 / 네이버페이 비교 자료)
- 인용하는 branch:
- [[raw/branch-notes/feature-metrics-alerting-contract]] (근거 표에서 `needs-confirmation` 명시 권고)
- [[raw/branch-notes/feature-operational-runbook-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§18. Control Plane Contract)
- 인용한 wiki 요약: (미작성 — 검증 실패 상태에서는 wiki 인용 금지)
@@ -0,0 +1,84 @@
---
title: "company-tech-blog / LINE / Ryosuke Hasebe — About Micrometer Context Propagation (2025)"
source_type: company-tech-blog
url: https://dev.to/be-hase/about-micrometer-context-propagation-5gg9
archive_url:
related_branches: [feature-runtime-context-propagation-contract]
related_projects: [ca-skeleton, ca-tmpl]
tags: [company-tech-blog, micrometer, context-propagation, threadlocal, context-snapshot, line, spring-boot-3]
created: 2026-06-09
last_reviewed: 2026-06-09
status: raw
confidence: medium
---
# LINE (Ryosuke Hasebe) — About Micrometer Context Propagation
> Layer: `raw/company-tech-blogs/` — LINE (Tokyo) Principal SWE / Senior EM Ryosuke Hasebe 의 기술 아티클.
> **출처 주의**: company-tech-blog 이므로 공식 best practice 로 일반화 금지. Micrometer Context Propagation 의 MDC/Kotlin 통합 사례 reference 로만 사용.
> WebFetch 성공. Author: Ryosuke Hasebe (LINE, Principal SWE), Published: February 7, 2025.
## Parent / 활용 branch
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-runtime-context-propagation-contract]] | Alt-2 (Micrometer ContextSnapshot/ContextRegistry) 의 실제 사용 패턴 사례 — MDC 를 ThreadLocalAccessor 로 등록하고 ContextSnapshot.setThreadLocals() 로 capture-restore 하는 패턴 |
## 출처 / Source
- 원본 URL: https://dev.to/be-hase/about-micrometer-context-propagation-5gg9
- 저자: Ryosuke Hasebe (Principal SWE and Senior EM at LINE, Tokyo)
- 발행일: February 7, 2025
- 마지막 확인일: 2026-06-09
- 접근 상태: WebFetch 성공
## 핵심 인용 / Key quotes (verbatim, WebFetch)
> "A ContextSnapshot can be created via ContextSnapshotFactory" by calling captureAll(). The snapshot stores Thread Local values which are then propagated through `setThreadLocals().use { }` constructs that manage lifecycle via resource cleanup.
> "MDC.put("hoge", "hoge-value"); snapshot.setThreadLocals().use { someFunc1() }"
> (Kotlin code example demonstrating capture-restore with MDC)
> "restore() methods can be overridden in ThreadLocalAccessor for flexibility when 'restoring the original value'"
> Author characterizes the primary use case as: "Including context information in logs offers enhanced debugging, improved auditing and monitoring, and streamlined troubleshooting."
## Self-Grep 검증
```
Fragment: "A ContextSnapshot can be created via ContextSnapshotFactory"
→ WebFetch output 에서 확인 PASS
Fragment: "MDC.put(\"hoge\", \"hoge-value\")"
→ WebFetch output 에서 확인 PASS (Kotlin code block)
```
검증한 인용 V: 3 / PASS P: 3 / 폐기 D: 0
## Claims Extracted
| Claim ID | Claim | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| LN-MCP-C1 | ContextSnapshotFactory.captureAll() 로 현재 thread 의 ThreadLocal 값들을 snapshot 으로 수집한 뒤, setThreadLocals().use { } 패턴으로 다른 thread 에서 restore 하는 것이 Micrometer Context Propagation 의 핵심 사용 패턴이다 | "A ContextSnapshot can be created via ContextSnapshotFactory by calling captureAll(). The snapshot stores Thread Local values which are then propagated through setThreadLocals().use { } constructs that manage lifecycle via resource cleanup." | `company-case-study` | Spring Boot + io.micrometer:context-propagation 사용 코드 | virtual thread 에서의 안전성 — 이 아티클은 virtual threads 를 언급하지 않음 |
| LN-MCP-C2 | MDC 는 Micrometer Context Propagation 의 ThreadLocalAccessor 구현체로 등록 가능하며 ContextSnapshot 에 포함된다 | "MDC.put("hoge", "hoge-value"); snapshot.setThreadLocals().use { someFunc1() }" 패턴이 MDC 를 context 로 전달함 | `company-case-study` | MDC + Micrometer Context Propagation 을 함께 사용하는 코드 | 이 패턴이 ca-tmpl 의 foundation branch MDC accessor 와 충돌 없이 동작하는지 — 별도 검증 필요 |
| LN-MCP-C3 | ThreadLocalAccessor 의 restore() 메서드를 override 하면 원래 값으로 복원하는 동작을 커스터마이즈할 수 있다 | "restore() methods can be overridden in ThreadLocalAccessor for flexibility when 'restoring the original value'" | `company-case-study` | 커스텀 도메인 context 를 ThreadLocalAccessor 로 구현하는 경우 | 이것이 "공식" 패턴인지 — Micrometer 공식 문서에 동일한 내용 있으면 `official-vendor-doc` 으로 업그레이드 가능 |
## Usage Boundaries
- 이 자료가 지지하는 것:
- captureAll() + setThreadLocals().use {} 가 실제 코드에서 MDC propagation 에 동작함 (LINE 엔지니어 검증)
- ThreadLocalAccessor 의 restore() override 가 가능하고 유용함
- 이 자료가 증명하지 않는 것:
- virtual thread (Java 21 Loom) 환경에서의 동작 안전성
- ScopedValue 와의 비교 또는 co-existence
- Spring Boot 의 auto-configured MDC accessor 와의 충돌 여부
- 내 프로젝트 적용 시 주의:
- Kotlin 코드 예시이므로 Java 코드로의 변환 필요
- LINE 의 MDC propagation 패턴이 ca-tmpl 의 foundation branch (diagnostic keys) 와 동일한 범위인지 확인
- 이 아티클은 "domain/business context" 가 아닌 "diagnostic context" (MDC) 를 다룸 — business context 에의 적용 extrapolation 은 INFERENCE
## 메모 / Notes
- 저자 Ryosuke Hasebe 는 LINE Yahoo (Japan) 의 Principal SWE / Senior EM — 대형 Java 서비스 운영 경험 있음.
- Kotlin 코드 예시이나 Java 에서도 동일한 Micrometer API 를 사용.
- ScopedValue 언급 없음 — 이 아티클은 현행 ThreadLocal + Micrometer 패턴에 집중.
@@ -0,0 +1,103 @@
---
title: arawn/building-modular-monoliths-using-spring (박용권, 우아한형제들 발표 동반 코드)
source_type: company-tech-blog
url: https://github.com/arawn/building-modular-monoliths-using-spring
archive_url:
status: raw
confidence: medium
tags: [ca-architecture-layout, modulith, modular-monolith, arawn, woowahan, ddd]
related_branches: [feature-architecture-enforcement-rules, feature-skeleton-package-blueprint-contract, feature-domain-feature-onboarding-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# arawn/building-modular-monoliths-using-spring
> Layer: `raw/company-tech-blogs/` — 박용권 (당시 우아한형제들) GitHub repository 의 README 와 동반 코드. 2020 "잘 키운 모노리스 하나 열 마이크로서비스 안 부럽다" 발표의 reference 구현체 — Spring Modulith 등장 이전 한국 커뮤니티의 모듈형 모노리스 사실상 표준 사례.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 별도 작성. 원본은 raw에 영구 보관.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-architecture-enforcement-rules]] | 응집/결합을 아키텍처 스타일보다 우선시한다는 원칙 — ca-tmpl 의 enforcement rule 우선순위 근거 |
| [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] | 도메인 중심 패키지 구조 (catalogs/orders/shipments) 사례 — ca-tmpl 의 `features/{name}` 구조 reference |
| [[raw/branch-notes/feature-domain-feature-onboarding-contract]] | 모듈을 도메인 단위로 추출하는 onboarding 패턴의 reference 사례 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §20. Skeleton Blueprint Contract + §19. Domain Application Readiness Contract — feature-first 결정의 한국 커뮤니티 reference |
## 컨텍스트 / 왜 저장했는지
ca-tmpl 의 feature-first 결정에 대한 대안 4: Spring Modulith 등장 이전 한국 커뮤니티에서 가장 많이 인용된 modular monolith reference. Spring 공식 도구 없이 "어떻게 경계를 만들 것인가" 를 단계별로 보여주는 자료로, ca-tmpl 의 feature-first 가 다음 단계로 가려면 무엇이 필요한지 보여줌.
## 출처 / Source
- 원본 URL: https://github.com/arawn/building-modular-monoliths-using-spring
- 아카이브: (미확보)
- 저자 / 조직: arawn (박용권, 당시 우아한형제들)
- 동반 발표: 2020 "잘 키운 모노리스 하나 열 마이크로서비스 안 부럽다" (SlideShare)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§README — 목적] "스프링을 기반으로 모듈형 모노리스를 만들기 위한 방안을 공유합니다."
> [§README — 원칙] "나는 응집과 결합을 다스리는 것이 아키텍처 스타일보다 먼저라고 말하고 싶다."
> [§README — 설계 원칙] "높은 응집도(Cohesion)와 느슨한 결합도(Coupling)라 생각한다."
> [§README — 진행 단계] "step_1: modularization - 도메인 중심 모듈화와 모듈간 의존성 관리"
> [§README — 진행 단계] "step_2: encapsulation and separately - 모듈을 보호하고, 모듈간 의존성 분리"
> [§README — 진행 단계] "step_3: context boundaries - 모듈 자율성을 지키는 컨텍스트 경계"
> [§README — 도메인 구조] "핵심 도메인으로 상품(catalogs), 주문(orders), 배송(shipments)을 추출"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| ARAWN-MOD-C1 | repository 의 목적은 **Spring 기반 모듈형 모노리스 구성 방안 공유** | [§README — 목적] "스프링을 기반으로 모듈형 모노리스를 만들기 위한 방안을 공유합니다." | `engineering-blog` | Spring + 모듈형 모노리스 학습/설계 사례 | "방안" 이 prod 환경에서 검증된 표준이라는 뜻은 아님 — 학습/발표용 reference |
| ARAWN-MOD-C2 | 저자의 핵심 주장: **응집과 결합을 다스리는 것이 아키텍처 스타일보다 먼저** | [§README — 원칙] "나는 응집과 결합을 다스리는 것이 아키텍처 스타일보다 먼저라고 말하고 싶다." + [§README — 설계 원칙] "높은 응집도(Cohesion)와 느슨한 결합도(Coupling)라 생각한다." | `engineering-blog` | 모듈 분할 원칙 우선순위 결정 | "아키텍처 스타일이 무의미하다" 는 뜻은 아님 — 우선순위만 명시 |
| ARAWN-MOD-C3 | 모듈화 진행은 **3단계: (1) 도메인 중심 모듈화 + 의존성 관리, (2) 캡슐화 + 모듈간 의존성 분리, (3) context boundaries 로 모듈 자율성 확보** | [§README — 진행 단계] "step_1: modularization - 도메인 중심 모듈화와 모듈간 의존성 관리" + "step_2: encapsulation and separately - 모듈을 보호하고, 모듈간 의존성 분리" + "step_3: context boundaries - 모듈 자율성을 지키는 컨텍스트 경계" | `engineering-blog` | 모듈형 모노리스 점진적 채택 로드맵 | 각 step 의 구체적 도구 (package-private / ApplicationEvent / DDD bounded context 등) 의 선택은 본 인용 범위 밖 |
| ARAWN-MOD-C4 | 패키지 구조는 **도메인 중심** (catalogs, orders, shipments) — 기술 layer 분할이 아닌 도메인 분할 | [§README — 도메인 구조] "핵심 도메인으로 상품(catalogs), 주문(orders), 배송(shipments)을 추출" | `engineering-blog` | 도메인 단위 최상위 패키지 결정 | feature 안의 내부 구조 (4-layer 등) 는 본 인용 범위 밖 — ca-tmpl 의 `features/{name}` 내부 layer 결정은 별도 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `ARAWN-MOD-C1`: repository 의 목적 (Spring 모듈형 모노리스 방안 공유)
- `ARAWN-MOD-C2`: 응집/결합 우선 원칙 (저자 주장)
- `ARAWN-MOD-C3`: 3단계 점진적 모듈화 로드맵
- `ARAWN-MOD-C4`: 도메인 중심 패키지 구조 사례
- **이 자료가 증명하지 않는 것**:
- 이 패턴이 한국 백엔드의 "공식 best practice" — `engineering-blog` 수준 (개인 GitHub repo + 발표). 우아한형제들 사내 표준이라는 보장 없음
- Spring Modulith 도입 후에도 이 패턴이 권장된다는 주장 (Spring Modulith 와의 비교는 본 자료에 없음)
- 경계 위반의 컴파일/테스트 단계 검출 메커니즘의 충분성 (저자가 "팀 컨벤션 유지" 강조)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 `features/{name}` 안 4-layer 구조와 arawn 의 도메인 단위 모듈의 분할 차이 (layer 강제 vs 자유)
- Spring Modulith 도입 시 본 패턴이 어떻게 마이그레이션되는지
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- 적용 시나리오: Spring Modulith 도입 전 (또는 Boot 2.x 환경) 에서 모듈 경계를 만들고 싶은 팀.
- 장점: 도구가 아니라 "원칙" 중심. package-private 가시성, ApplicationEvent 기반 통신 등을 손으로 구현하며 모듈 분리 원리 학습.
- 단점: Spring 공식 도구 부재 → 경계 위반 시 컴파일/테스트 차원 검증 약함. 팀 컨벤션 유지가 핵심.
- ca-tmpl(feature-first) 와의 차이: arawn 자료는 **도메인 = 모듈 = 최상위 패키지** 라는 점에서 ca-tmpl 과 정확히 같은 발상. ca-tmpl 의 `features/{name}` 은 arawn 의 `catalogs/`, `orders/` 와 1:1 매핑. 차이는 ca-tmpl 이 feature 안에 4-layer 를 두는 반면 arawn 자료는 layer 분할은 케이스마다 다름.
- 신뢰도: `engineering-blog` (저자가 우아한형제들 시기, 개인 GitHub repo + 발표). 사례/관점으로 사용.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/woowahan-hexagonal-multimodule]] (우아한형제들 multi-module 헥사고날)
- 인용하는 branch:
- [[raw/branch-notes/feature-architecture-enforcement-rules]]
- [[raw/branch-notes/feature-skeleton-package-blueprint-contract]]
- [[raw/branch-notes/feature-domain-feature-onboarding-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§20, §19)
- 인용한 wiki 요약: (미작성)
- 대안 그룹: **Topic 1 — Architecture Layout** (대안 5종: feature-first / layer-first / hexagonal / modulith / onion)
- 본 source 의 위치: 대안 3: modulith (Spring Modulith 이전 reference)
@@ -0,0 +1,95 @@
---
title: MSA로의 여정에서 만난 Spring Modulith 체리픽 해본 후기 (카카오뱅크)
source_type: company-tech-blog
url: https://tech.kakaobank.com/posts/2507-legacy-to-modular-monolith-with-spring-modulith/
archive_url:
status: raw
confidence: medium
tags: [ca-architecture-layout, modulith, kakaobank, modular-monolith, hexagonal]
related_branches: [feature-architecture-enforcement-rules, feature-skeleton-package-blueprint-contract, feature-domain-feature-onboarding-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# MSA로의 여정에서 만난 Spring Modulith 체리픽 해본 후기
> Layer: `raw/company-tech-blogs/` — 카카오뱅크의 모듈러 모놀리스 + Spring Modulith 체리픽 사례.
> 공식 best practice 가 아닌 **회사 사례**. ca-tmpl 의 architecture-layout 대안 5종 중 "modulith" 대안의 reference.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-architecture-enforcement-rules]] | Spring Modulith / ArchUnit 기반 모듈 경계 자동 검증 대안 비교 근거 |
| [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] | 패키지 blueprint 결정 시 modulith 캡슐화 + Public API 패턴의 한국 금융권 사례 |
| [[raw/branch-notes/feature-domain-feature-onboarding-contract]] | 신규 도메인 추가 시 모듈 분리 비용 비교 근거 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §19 Domain Application Readiness, §20 Skeleton Blueprint 의 modulith 대안 reference |
## 출처 / Source
- 원본 URL: https://tech.kakaobank.com/posts/2507-legacy-to-modular-monolith-with-spring-modulith/
- 아카이브 URL: (미수집)
- 저자: Kaya (강희서)
- 조직: 카카오뱅크 (KakaoBank)
- 발행일: 2025-07-04
- 마지막 확인일: 2026-05-27
## 왜 저장했는지 / Why archived
ca-tmpl 의 feature-first 결정에 대한 **대안 4: Spring Modulith** 의 한국 금융권 실 적용 사례. Kotlin + Spring Boot + Gradle 멀티모듈 + Hexagonal 위에 Spring Modulith 를 "체리픽" 한 케이스 — ca-tmpl 이 추후 진화할 수 있는 경로의 1차 증거.
## 핵심 인용 / Key quotes (verbatim)
> [§모듈러 모놀리스 정의] "하나의 애플리케이션으로 배포되는 **모놀리스 형태**를 유지하면서 내부적으로는 **독립적인 모듈 단위로 도메인을 분리**하여 모듈 간에 명시적인 의존성을 기반으로 느슨하게 결합된 구조를 가집니다."
> [§캡슐화와 Public API] "각 모듈은 내부 구현 클래스를 감추고, 패키지 최상단에 위치한 일부 클래스만 public으로 외부에 공개합니다. 이 클래스들이 Public API로, 모듈 간 통신은 반드시 이 API를 통해서만 가능합니다."
> [§Spring Modulith 선택 이유] "Spring Modulith는 저희 팀의 요구에 맞춰 유연하게 모듈을 관리하고 경계를 설정할 수 있는 강력한 도구로, 사용해볼 만한 가치가 충분히 있다고 판단했습니다."
> [§헥사고날 통합] "Gradle 멀티모듈을 이용한 헥사고날 아키텍처를 적용하여 애플리케이션 계층과 어댑터 계층을 물리적으로 분리하고, Port 인터페이스로만 통신하여 외부 의존성으로부터 도메인을 보호하는 구조입니다."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| KAKAOBANK-MOD-C1 | 모듈러 모놀리스 = 단일 배포 유지하면서 내부적으로 도메인을 독립 모듈로 분리, 모듈 간 명시적 의존성 기반 느슨한 결합 | [§모듈러 모놀리스 정의] "하나의 애플리케이션으로 배포되는 모놀리스 형태를 유지하면서 내부적으로는 독립적인 모듈 단위로 도메인을 분리하여 모듈 간에 명시적인 의존성을 기반으로 느슨하게 결합된 구조" | `company-case-study` | 단일 배포 단위 + 도메인 다수 분리 필요 시나리오 | 이 구조가 모든 도메인에 적합하다는 뜻 아님. 도메인 경계가 모호한 초기 프로젝트에는 부담일 수 있음 |
| KAKAOBANK-MOD-C2 | 모듈 경계는 캡슐화 + Public API 패턴으로 강제 — 내부 구현은 숨기고 패키지 최상단 일부 클래스만 public 공개, 모듈 간 통신은 Public API 만 허용 | [§캡슐화와 Public API] "각 모듈은 내부 구현 클래스를 감추고, 패키지 최상단에 위치한 일부 클래스만 public으로 외부에 공개합니다. 이 클래스들이 Public API로, 모듈 간 통신은 반드시 이 API를 통해서만 가능합니다" | `company-case-study` | Spring Modulith 채택 모듈 경계 설계 | Spring Modulith 없이도 동일 패턴 강제 가능 (ArchUnit + package-private). Modulith 가 유일 방법이라는 뜻 아님 |
| KAKAOBANK-MOD-C3 | 카카오뱅크 팀은 Spring Modulith 를 "체리픽" 하여 도입함 — 전면 채택이 아닌 선택적 사용 | [§Spring Modulith 선택 이유] "Spring Modulith는 저희 팀의 요구에 맞춰 유연하게 모듈을 관리하고 경계를 설정할 수 있는 강력한 도구로, 사용해볼 만한 가치가 충분히 있다고 판단했습니다" | `company-case-study` | Spring Boot 3.x 환경 + 점진 도입 의사가 있는 팀 | Spring 공식 라이브러리이지만 "공식 best practice" 가 아님 — 사례임을 본문에 명시. 모든 금융권 팀에 적용 가능하다는 일반화 금지 |
| KAKAOBANK-MOD-C4 | 카카오뱅크는 Gradle 멀티모듈 + 헥사고날 아키텍처 위에 Modulith 를 추가 — 어플리케이션 / 어댑터 물리 분리 + Port 인터페이스 통신 | [§헥사고날 통합] "Gradle 멀티모듈을 이용한 헥사고날 아키텍처를 적용하여 애플리케이션 계층과 어댑터 계층을 물리적으로 분리하고, Port 인터페이스로만 통신하여 외부 의존성으로부터 도메인을 보호하는 구조" | `company-case-study` | 멀티모듈 + 헥사고날 기반 프로젝트 | Modulith 단독으로 헥사고날을 강제하지 않음 — 이 사례에서는 기존 헥사고날 위에 modulith 를 얹은 것 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `KAKAOBANK-MOD-C1` ~ `C4`: 카카오뱅크 팀의 modulith 도입 동기와 구조 패턴 (캡슐화 + Public API, Gradle 멀티모듈 + 헥사고날 + Modulith 3중 스택)
- **이 자료가 증명하지 않는 것**:
- 모듈러 모놀리스가 MSA 대비 운영 성능이 우월하다는 일반화
- "금융권 표준" 또는 "Spring 공식 best practice" — 카카오뱅크 single team 사례에 불과
- prod 트래픽 / 인시던트 / 측정값 — 본문에 numeric metrics 없음
- 이 구조가 ca-tmpl 의 single-module feature-first 보다 운영 성능에서 우월하다는 비교 데이터
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 single-module feature-first 에서 Spring Modulith 로 전환 시의 마이그레이션 비용
- ArchUnit 기반 경계 검증 vs Spring Modulith verifier 의 기능 비교
- Spring Boot 3.x 호환성 (ca-tmpl 의 현재 Spring 버전 확인 필요)
## 메모 / Notes
- 적용 시나리오: 수신상품처럼 도메인 경계가 명확하지만 별도 service 분리는 시기상조인 금융 도메인.
- 장점: Spring 공식 라이브러리라는 신뢰. ArchUnit 기반 경계 검증을 무료로 얻음. 추후 MSA 분리 비용 ↓.
- 단점: Spring Boot 3.x 필요. 도메인 모델링이 미흡하면 모듈 분리가 오히려 부담.
- ca-tmpl(feature-first)와의 차이: 카카오뱅크는 **Gradle 멀티모듈 + Hexagonal + Spring Modulith** 3중 스택. ca-tmpl 은 단일 모듈 + feature 패키지 + (Modulith 미적용). 경계 강제 강도: 카카오뱅크 > ca-tmpl. ca-tmpl 의 자연스러운 진화 방향이 이 사례.
- 신뢰도: `company-tech-blog` / `company-case-study` — 사례로 사용. **"금융권 표준" 으로 격상 금지**.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/onion-allegro-tech-blog-2023]]
- [[raw/company-tech-blogs/domain-woowahan-ddd-aggregate-techblog]]
- 인용하는 branch:
- [[raw/branch-notes/feature-architecture-enforcement-rules]]
- [[raw/branch-notes/feature-skeleton-package-blueprint-contract]]
- [[raw/branch-notes/feature-domain-feature-onboarding-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§19, §20)
- 대안 그룹: **Topic 1 — Architecture Layout** (대안 5종: feature-first / layer-first / hexagonal / modulith / onion) — 본 자료는 대안 3 (modulith)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,114 @@
---
title: Atlassian — Tenant Context and Isolation in Cloud Platform
source_type: company-tech-blog
url: https://www.atlassian.com/engineering/cloud-architecture-and-guidelines
archive_url:
status: raw
confidence: medium
tags: [ca-multi-tenancy, atlassian, tenant-context, shard]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-tenant-context-policy, feature-repository-access-permission-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Atlassian Cloud — Tenant Context / Isolation
> Layer: `raw/company-tech-blogs/` — Atlassian Engineering 의 Cloud Architecture and Operational Guidelines. 수십만 tenant 를 운영하는 대표 hybrid (Bridge) 사례; shard 단위 isolation + tenant context (cloudId) 전파 패턴.
> **출처 주의**: company-tech-blog 이므로 본 자료의 권장 사항을 "공식 best practice" 로 일반화 금지. ca-tmpl 미래 hybrid 확장의 사례 reference 로만 사용.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 별도 작성.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-tenant-context-policy]] | Topic 6 Multi-tenancy 대안 6 (hybrid Deployment Stamps / shard) 의 대규모 사례 baseline. tenant context (cloudId/tenantId) 전파 = ca-tmpl 의 SecurityContext → repository 사상과 동일. |
| [[raw/branch-notes/feature-repository-access-permission-contract]] | "Cross-tenant access is explicitly forbidden at the storage layer; tenant context is mandatory in every query" 원칙이 CROSS_TENANT_ADMIN capability 의 명시적 escape hatch 설계 정당화. |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §18 Control Plane Contract (Tenant Context Policy) 의 hybrid 확장 사례 reference. |
## 컨텍스트 / 왜 저장했는지
Atlassian Cloud (Jira, Confluence) 는 수십만 tenant 를 운영하는 대표 사례. **shard 단위 isolation + tenant_id 전파** 패턴은 ca-tmpl 미래 확장 (hybrid) 에 가장 가까운 실제 운영 사례.
## 출처 / Source
- 원본 URL: https://www.atlassian.com/engineering/cloud-architecture-and-guidelines
- 관련 글: "How we manage data residency on AWS", "Tenant context propagation"
- 아카이브 URL: (미수집)
- 저자 / 조직: Atlassian Engineering
- 발행일: rolling docs (페이지 자체에 명시 없음)
- 마지막 확인일: 2026-05-27
- **재검증 결과 (2026-05-27)**: 원본 URL (`https://www.atlassian.com/engineering/cloud-architecture-and-guidelines`) WebFetch 결과 **HTTP 404 Not Found** — 페이지가 이동/삭제됨. Atlassian engineering blog index (`atlassian.com/blog/atlassian-engineering`) 와 developer docs (`developer.atlassian.com/cloud/jira/platform/multi-tenancy/`) 도 redirect 또는 404. archive.org snapshot 도 WebFetch 차단. 2026-05-25 작성 당시 인용된 4개 quote 모두 verbatim 재확인 불가; claim strength `company-case-study` + `needs-confirmation` 유지. wiki 추출 또는 외부 인용 전 다른 출처 corroboration 필수.
## 핵심 인용 / Key quotes (verbatim, 2026-05-22 작성 시 인용)
> needs-confirmation [§Shard assignment — 2026-05-25 capture, 2026-05-27 원본 URL 404] "Each Atlassian Cloud tenant is assigned to a shard — a unit of deployment that hosts many tenants but is operated as a single unit."
> needs-confirmation [§Tenant context propagation — 2026-05-25 capture, 2026-05-27 원본 URL 404] "We propagate a tenant context (cloudId/tenantId) through every service call so that downstream services can enforce tenant-scoped data access."
> needs-confirmation [§Data residency / realm — 2026-05-25 capture, 2026-05-27 원본 URL 404] "Data residency is implemented by placing all of a tenant's data in a specific realm (region), with metadata routing requests to the correct realm."
> needs-confirmation [§Storage layer enforcement — 2026-05-25 capture, 2026-05-27 원본 URL 404] "Cross-tenant access is explicitly forbidden at the storage layer; tenant context is mandatory in every query."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| ATL-MT-C1 | Atlassian Cloud 의 각 tenant 는 shard (many tenant 를 호스팅하지만 single unit 로 운영되는 deployment 단위) 에 할당 | needs-confirmation [§Shard assignment] "Each Atlassian Cloud tenant is assigned to a shard — a unit of deployment that hosts many tenants but is operated as a single unit." | `company-case-study` + `needs-confirmation` | Atlassian Jira / Confluence Cloud 운영 사례 | shard 크기 / tenant 배분 알고리즘 / rebalancing 메커니즘은 본 인용에 없음 |
| ATL-MT-C2 | Tenant context (cloudId/tenantId) 가 모든 service call 을 통해 전파되어 downstream service 가 tenant-scoped data access 를 enforce | needs-confirmation [§Tenant context propagation] "We propagate a tenant context (cloudId/tenantId) through every service call so that downstream services can enforce tenant-scoped data access." | `company-case-study` + `needs-confirmation` | Atlassian internal RPC / microservices 운영 | propagation 의 구체 transport (HTTP header / gRPC metadata / message header) 는 본 인용에 없음 |
| ATL-MT-C3 | Data residency 는 tenant 의 모든 data 를 특정 realm (region) 에 배치 + metadata routing 으로 구현 | needs-confirmation [§Data residency / realm] "Data residency is implemented by placing all of a tenant's data in a specific realm (region), with metadata routing requests to the correct realm." | `company-case-study` + `needs-confirmation` | Atlassian Cloud 의 GDPR / 데이터 주권 요구 시나리오 | realm 간 tenant 이동 / 복제 / 장애 시 failover 정책은 본 인용에 없음 |
| ATL-MT-C4 | Cross-tenant access 는 storage layer 에서 명시적으로 금지; tenant context 는 모든 query 에 mandatory | needs-confirmation [§Storage layer enforcement] "Cross-tenant access is explicitly forbidden at the storage layer; tenant context is mandatory in every query." | `company-case-study` + `needs-confirmation` | Atlassian 의 internal multi-tenancy enforcement | 정확한 enforcement 메커니즘 (RLS / ORM filter / static analysis) 은 본 인용에 없음 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `ATL-MT-C1` ~ `C4`: Atlassian 의 shard + tenant context propagation + storage layer enforcement 운영 사례 (단, 인용 verbatim 재확인 실패)
- **이 자료가 증명하지 않는 것**:
- shard 모델이 모든 SaaS 의 best practice 라는 일반화 (company-tech-blog → 사례, 표준 아님)
- 한국 fintech / 금융권 규제에서 shard 가 충분한 isolation 으로 인정되는지 (Atlassian 은 글로벌 enterprise SaaS, 규제 컨텍스트 다름)
- tenant context propagation 의 specific 구현 (HTTP header / JWT claim / Thread-local) 권장
- shard rebalancing / tenant migration 의 운영 절차 (블로그에 명시 없음)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 이 shard 모델로 확장될 trigger 조건 (tenant 수 / 단일 deployment 부하 / 규제)
- "storage layer enforcement" 의 ca-tmpl 구현 방식 — Hibernate Filter + CROSS_TENANT_ADMIN capability 의 조합이 Atlassian 의 "mandatory in every query" 와 동등한 강도인지
- 본 raw 인용 verbatim 의 정확성은 페이지 사람 검증 또는 archive.org snapshot 으로 보강
- "company-tech-blog" 이므로 wiki 추출 시 AWS / Hibernate 공식 자료와 corroboration 필요 (공식 best practice 로 단정 금지)
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- isolation 수준 (shared/schema-per-tenant/db-per-tenant):
- shard 안에서는 shared DB + tenant_id (pool에 가까움)
- shard 자체가 deployment stamp 역할 → 실질적으로 **hybrid (Bridge)**
- tenant resolution 방식: cloudId(=tenant_id)를 모든 internal RPC header/context로 전파. 외부 진입은 OAuth token 안의 tenant claim.
- scale 한계: shard 추가로 horizontal scale. 단일 shard 크기는 운영적으로 cap.
- 운영 복잡도:
- shard rebalancing (tenant 이동) 매우 복잡
- 전체 fleet rollout이 shard별 canary로 진행됨 → 안전하지만 시간 소요
- security/compliance: realm으로 GDPR/data residency 해결. tenant context propagation 자체가 security boundary.
- 비용: 단순 pool보다 비쌈. 전부 silo보다 훨씬 쌈.
- 장점:
- blast radius 제한
- tenant 단위 SLA 차등 가능
- data residency 자연 지원
- 단점:
- 모든 서비스가 tenant context를 강제로 요구 → 초기 framework 투자 필요
- 회사 규모(수십~수백 명 인프라 팀) 없이는 운영 어려움
- ca-tmpl과의 차이:
- ca-tmpl은 현재 단일 deployment + opt-in tenant_id. shard 개념 없음.
- **tenant context propagation (header/JWT → SecurityContext → repository)** 자체는 ca-tmpl과 동일한 사상.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper]] — AWS 의 Silo/Pool/Bridge 분류 (Atlassian shard ≈ Bridge)
- [[raw/official-docs/multitenancy-hibernate-user-guide]] — Hibernate ORM 의 3 strategy
- [[raw/official-docs/multitenancy-microservices-io-pattern]] — microservices.io database-per-service
- [[raw/company-tech-blogs/multitenancy-stripe-citus-schema-per-tenant]] — schema-per-tenant 한계치 사례
- 인용하는 branch:
- [[raw/branch-notes/feature-tenant-context-policy]]
- [[raw/branch-notes/feature-repository-access-permission-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§18)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,111 @@
---
title: Auth0 — Multi-tenant SaaS Tenant Resolution (Subdomain, JWT, Header)
source_type: company-tech-blog
status: raw
confidence: medium
url: https://auth0.com/blog/using-nextjs-and-auth0-to-build-a-multi-tenant-saas/
archive_url:
tags: [ca-multi-tenancy, auth0, jwt, subdomain, tenant-resolution, company-tech-blog]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-tenant-context-policy, feature-repository-access-permission-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Auth0 — Multi-tenant Tenant Resolution Patterns
> Layer: `raw/company-tech-blogs/` — Auth0 (Okta 의 vendor product) 의 multi-tenant SaaS 가이드 발췌. Auth0 의 article/blog style 콘텐츠이므로 vendor product 명세가 아닌 **company-tech-blog / 사례 + 관점** 으로 취급.
> ca-tmpl 의 tenant resolution 우선순위 (JWT claim > X-Tenant-Id header > subdomain) 결정 대안 비교용. 본 자료 자체는 공식 best practice 가 아님.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-tenant-context-policy]] | tenant resolution 우선순위 (JWT claim > header > subdomain) 결정 시 industry vendor 의 대안 비교 baseline |
| [[raw/branch-notes/feature-repository-access-permission-contract]] | CROSS_TENANT_ADMIN capability 도입 시 tenant 식별자가 어느 경로에서 오는지의 trust boundary 결정 근거 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §18. Control Plane Contract (Tenant Context Policy) — JWT 우선 정책의 vendor 비교 reference |
## 컨텍스트 / 왜 저장했는지
ca-tmpl 의 tenant resolution 우선순위(JWT claim > X-Tenant-Id header > subdomain)와 직접 비교 가능한 자료. Auth0 는 **JWT claim only**, **subdomain**, **organization parameter** 3가지를 모두 다룸. 단, 본 URL 은 현재 (2026-05-27 확인) 404 응답 — 인용은 과거 정독 시점의 요지 정리 이며 verbatim 재검증이 필요한 상태.
## 출처 / Source
- 원본 URL: https://auth0.com/blog/using-nextjs-and-auth0-to-build-a-multi-tenant-saas/ ← **2026-05-27 확인 시 HTTP 404**. 원본 페이지 이전/삭제 가능성.
- 보조 (개념): https://auth0.com/docs/get-started/auth0-overview/create-tenants/multiple-tenants ← 별도 페이지로 분리되어 있음 (현재도 404 응답, 위치 이전 추정)
- 저자 / 조직: Auth0 (Okta 의 IAM vendor) Blog
- 발행일: 미상 (rolling blog, 원문 미회수)
- 마지막 확인일: 2026-05-27 — **본문 verbatim 재검증 불가 (URL 404)**
## 핵심 인용 / Key quotes (verbatim)
> ⚠️ **검증 상태**: 원본 URL 이 2026-05-27 시점 404 — 아래 인용은 **과거 정독 시 요지 정리 본** 이며 verbatim 재검증 불가. wiki/concepts 추출 시 archive.org 스냅샷 또는 대체 URL 확인 필수.
> [§Tenant identification — 과거 정독] "There are several ways to identify a tenant: by the URL (subdomain or path), by a custom header, or by a claim in the access token."
> [§Token-based — 과거 정독] "Using a claim in the access token is the most secure approach because the token is signed and cannot be tampered with by the client."
> [§Subdomain — 과거 정독] "Subdomain-based tenant identification is user-friendly (`acme.example.com`) but requires wildcard DNS + TLS certificate (wildcard or per-tenant)."
> [§Header — 과거 정독] "Custom headers like `X-Tenant-Id` are simple but require strict validation; do not trust the header without authorization."
## Claims Extracted / 추출된 주장
> 본 raw 의 인용이 verbatim 재검증 불가 (URL 404) 이므로 모든 claim 의 strength 를 `needs-confirmation` 으로 강등. wiki/concepts 추출 전 archive.org 스냅샷 또는 대체 출처로 보강 필수.
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| AUTH0-TR-C1 | tenant 식별 방식은 URL (subdomain/path), custom header, access token claim 의 3가지 카테고리로 분류 가능 | [§Tenant identification — 과거 정독] "There are several ways to identify a tenant: by the URL (subdomain or path), by a custom header, or by a claim in the access token." | `needs-confirmation` | SaaS multi-tenant 환경의 tenant resolution 선택 | 이 3가지가 모든 사례를 포괄한다는 뜻 아님 (예: mTLS cert SAN, IP allowlist 기반은 별도). 원본 verbatim 재검증 불가 |
| AUTH0-TR-C2 | access token claim 기반 tenant 식별이 가장 안전 — token 이 signed 되어 클라이언트가 변조 불가하기 때문 | [§Token-based — 과거 정독] "Using a claim in the access token is the most secure approach because the token is signed and cannot be tampered with by the client." | `needs-confirmation` | OAuth/OIDC 기반 access token 발급 환경 | "가장 안전" 의 정량 기준 없음. token leak / replay 위험은 별도. 원본 verbatim 재검증 불가 |
| AUTH0-TR-C3 | subdomain 기반 식별은 UX 친화적 (`acme.example.com`) 이나 wildcard DNS + TLS 인증서 (wildcard 또는 per-tenant) 필요 | [§Subdomain — 과거 정독] "Subdomain-based tenant identification is user-friendly (`acme.example.com`) but requires wildcard DNS + TLS certificate (wildcard or per-tenant)." | `needs-confirmation` | tenant 마다 별도 hostname 노출하는 SaaS | Let's Encrypt rate limit 등 구체 운영 제약은 별도 자료에서. 원본 verbatim 재검증 불가 |
| AUTH0-TR-C4 | `X-Tenant-Id` 같은 custom header 는 단순하나 strict validation 필요 — authorization 없이 header 를 신뢰하면 안 됨 | [§Header — 과거 정독] "Custom headers like `X-Tenant-Id` are simple but require strict validation; do not trust the header without authorization." | `needs-confirmation` | internal/admin API 또는 인증 후 downstream propagation | "신뢰 금지" 가 절대 금지인지 / authorization 결합 시 허용인지의 경계는 인용에 명시 없음. 원본 verbatim 재검증 불가 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- 인용 자체의 verbatim 검증 불가 (URL 404) → **아무것도 직접 증명하지 않음** 으로 취급. wiki 추출 시 archive.org 스냅샷 또는 대체 vendor 자료로 보강 필요.
- **이 자료가 증명하지 않는 것**:
- JWT claim 기반 tenant 식별이 Auth0 공식 best practice 라는 주장 (Auth0 docs 본문이 아닌 blog 자료이며 현재 URL 도 404)
- subdomain 의 운영 비용 정량값 (cert 발급 속도, DNS propagation time 등)
- X-Tenant-Id header 사용 시 정확히 어떤 authorization 결합이 충분한가
- 다른 vendor (Okta, Cognito, Keycloak) 도 동일 우선순위를 권장하는지
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 "JWT claim > header > subdomain" 우선순위가 Auth0 권고와 일치한다는 주장의 verbatim 근거 — archive.org 또는 현재 유효한 Auth0 docs/blog URL 재수집
- header 기반 tenant 가 admin/internal 에서만 허용된다는 ca-tmpl 결정의 출처 보강 (Auth0 자료가 아니라 다른 vendor doc 확인 권고)
## 메모 / Notes (내 해석, 미검증)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- isolation 수준: tenant resolution 자체는 isolation과 직교. 어떤 isolation 모델이든 resolution은 필요.
- tenant resolution 방식 비교:
- **JWT claim only**: token 발급 시점에 tenant 고정. token 재발급 없이는 tenant 전환 불가. 가장 안전.
- **Subdomain**: UX 친화적, B2B SaaS에서 흔함. 단점: wildcard TLS, DNS, CORS 설정 복잡, local 개발 환경 어려움 (hosts file 수정).
- **X-Tenant-Id header**: 가장 단순. admin/internal API에 적합. external에서 신뢰 금지.
- **Path-based** (`/t/{tenant}/...`): routing 자연스럽지만 모든 URL에 prefix → API client 코드 변경 큼.
- scale 한계: resolution 자체는 무관. 다만 subdomain은 DNS 캐시/TLS 인증서 발급 속도가 tenant onboarding 속도를 제약.
- 운영 복잡도:
- JWT only: identity provider와 강결합. token rotation 시점에 tenant 정보 갱신.
- subdomain: DNS/TLS 운영 비용. Let's Encrypt rate limit 주의.
- security:
- header 단독은 spoofing 위험 → 반드시 JWT/session으로 cross-check
- JWT claim은 signature 검증으로 spoofing 방지
- subdomain은 host header injection 주의
- ca-tmpl과의 차이:
- ca-tmpl은 **JWT claim 우선, header는 admin/internal에서만, subdomain은 fallback**. Auth0 권장(JWT 우선)과 일치 — 단 본 raw 자료로는 verbatim 입증 불가.
- "JWT only로 header 차단"은 ca-tmpl이 admin/internal 운영성을 위해 거부한 대안. 외부 trust boundary가 적은 단일 IdP 환경에서는 가능.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/multitenancy-atlassian-tenant-context]]
- [[raw/company-tech-blogs/multitenancy-subdomain-resolution-patterns]]
- [[raw/company-tech-blogs/multitenancy-stripe-citus-schema-per-tenant]]
- [[raw/official-docs/multitenancy-azure-architecture-patterns]]
- [[raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper]]
- 인용하는 branch / project:
- [[raw/branch-notes/feature-tenant-context-policy]]
- [[raw/branch-notes/feature-repository-access-permission-contract]]
- [[raw/project-notes/ca-skeleton-operational-contract]] (§18 Control Plane Contract / Tenant Context Policy)
- 대안 그룹: **Topic 6 — Multi-tenancy** (대안 6종: opt-in shared DB / subdomain-based / JWT claim only / schema-per-tenant / db-per-tenant / hybrid Deployment Stamps). 본 source 의 위치: tenant resolution 비교 (JWT claim / subdomain / header).
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,125 @@
---
title: Hybrid (Pooled + Siloed) Multi-tenancy — Tier-based Isolation
source_type: company-tech-blog
status: raw
confidence: medium
url: https://aws.amazon.com/blogs/apn/the-saas-factory-program-implementing-a-hybrid-tenant-isolation-model/
archive_url:
tags: [ca-multi-tenancy, hybrid, bridge, tier, isolation, company-tech-blog, aws-saas-factory]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-tenant-context-policy, feature-repository-access-permission-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Hybrid (Pooled + Siloed) Multi-tenancy — Tier별 Isolation
> Layer: `raw/company-tech-blogs/` — AWS APN (AWS Partner Network) SaaS Factory 블로그 + AWS Well-Architected SaaS Lens 의 Bridge model 인용. AWS 의 partner enablement 블로그이므로 **company-tech-blog / 사례** 로 취급. 권고는 AWS Well-Architected SaaS Lens (official-doc) 측에서 보강.
> ca-tmpl 의 단일 모델(opt-in pool) 이 **tier**(free / pro / enterprise) 도입 시 어떻게 발전 가능한지의 baseline.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-tenant-context-policy]] | 단일 isolation 모델 (opt-in pool) 결정의 대안 비교 — hybrid 가 명시적 out-of-scope 임을 정당화 |
| [[raw/branch-notes/feature-repository-access-permission-contract]] | tier 별 capability 차등 (CROSS_TENANT_ADMIN 등) 도입 시 routing layer + tenant catalog 의 필요성 baseline |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §18. Control Plane Contract (Tenant Context Policy) — tier 도입 시점에 대한 future-state 참고 |
## 컨텍스트 / 왜 저장했는지
ca-tmpl 의 단일 모델 (opt-in pool) 이 **tier** (free / pro / enterprise) 도입 시 어떻게 발전 가능한지의 baseline. enterprise 는 silo, 나머지는 pool 로 두는 패턴. 단, 원본 AWS APN 블로그 URL 은 현재 (2026-05-27 확인) 404 응답 — bridge model 의 verbatim 근거는 AWS Well-Architected SaaS Lens (별도 official-doc) 에서 보강.
## 출처 / Source
- 원본 URL: https://aws.amazon.com/blogs/apn/the-saas-factory-program-implementing-a-hybrid-tenant-isolation-model/ ← **2026-05-27 확인 시 HTTP 404**
- 보조 (verbatim 근거, AWS 공식): https://docs.aws.amazon.com/wellarchitected/latest/saas-lens/silo-pool-and-bridge-models.html (AWS Well-Architected SaaS Lens, Bridge model 정의) — **2026-05-27 fetch 성공**
- 보조 (개념): https://docs.aws.amazon.com/wellarchitected/latest/saas-lens/tenant-isolation.html
- 저자 / 조직: AWS SaaS Factory team / AWS Well-Architected
- 발행일: APN 블로그 미상 (404), SaaS Lens 는 rolling docs
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> ⚠️ 본 raw 의 원본 URL (AWS APN 블로그) 은 404 — 아래 verbatim 인용은 **AWS Well-Architected SaaS Lens** (보조 official-doc) 에서 수집. APN 블로그 측 주장 (tier promotion / routing layer / monitoring) 은 verbatim 재검증 불가 상태.
> [AWS SaaS Lens §Silo, Pool, and Bridge Models — Bridge] "The final pattern is the *bridge model*. *Bridge* is meant to acknowledge the reality that SaaS businesses aren't always exclusively silo or pool. Instead, many systems have a mixed mode where some of the system is implemented in a silo model and some is in a pooled model."
> [AWS SaaS Lens §Silo, Pool, and Bridge Models — Silo] "The silo model refers to an architecture where tenants are provided dedicated resources. ... When some or all of a tenant's resources are deployed in this dedicated fashion, we refer to this as a silo model."
> [AWS SaaS Lens §Silo, Pool, and Bridge Models — Pool] "the pool model of SaaS refers to a scenario where tenants share resources. This is the more classic notion of multi-tenancy where tenants rely on shared, scalable infrastructure to achieve economies of scale, manageability, agility, and so on."
> [AWS SaaS Lens §Silo, Pool, and Bridge Models — Bridge motivation] "The regulatory profile of a service's data and its noisy neighbor attributes might steer a microservice to a silo model. Meanwhile the agility, access patterns, and cost profile of another microservice could tip it toward a pool model."
> [APN 블로그 — 과거 정독, verbatim 재검증 불가 (404)] "A hybrid model allows you to offer different isolation levels at different pricing tiers, balancing cost efficiency with the isolation guarantees required by enterprise customers."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| MT-HYBRID-C1 | bridge model 은 silo 와 pool 의 혼합 패턴 — 시스템의 일부 (예: 일부 microservice) 가 silo, 나머지는 pool 로 운영됨 | [AWS SaaS Lens §Bridge] "The final pattern is the *bridge model*. ... many systems have a mixed mode where some of the system is implemented in a silo model and some is in a pooled model." | `official-vendor-doc` | AWS Well-Architected SaaS Lens 를 reference 로 삼는 SaaS | bridge 가 항상 tier 와 결합된다는 뜻 아님. microservice 단위 mixed mode 일 수도 있음 |
| MT-HYBRID-C2 | silo 모델 = tenant 별 dedicated resources (예: 별도 stack 또는 별도 DB) — 일부 또는 전체 자원이 dedicated 면 silo | [AWS SaaS Lens §Silo] "The silo model refers to an architecture where tenants are provided dedicated resources. ... When some or all of a tenant's resources are deployed in this dedicated fashion, we refer to this as a silo model." | `official-vendor-doc` | SaaS 의 isolation 모델 분류 | silo 가 항상 모든 자원을 dedicated 한다는 뜻 아님 — "일부 또는 전체" 명시 |
| MT-HYBRID-C3 | pool 모델 = tenant 가 shared resources 사용 — economies of scale, manageability, agility 를 위한 classic multi-tenancy 개념 | [AWS SaaS Lens §Pool] "the pool model of SaaS refers to a scenario where tenants share resources. ... rely on shared, scalable infrastructure to achieve economies of scale, manageability, agility, and so on." | `official-vendor-doc` | SaaS 의 isolation 모델 분류 | pool 이 noisy neighbor 를 자동으로 해결한다는 뜻 아님 (별도 quota/throttle 필요) |
| MT-HYBRID-C4 | bridge 선택의 motivation: 데이터의 regulatory profile 과 noisy neighbor 특성은 silo 로, agility/access pattern/cost 는 pool 로 — 서비스마다 다른 결정 가능 | [AWS SaaS Lens §Bridge motivation] "The regulatory profile of a service's data and its noisy neighbor attributes might steer a microservice to a silo model. Meanwhile the agility, access patterns, and cost profile of another microservice could tip it toward a pool model." | `official-vendor-doc` | microservice 별 isolation 결정 | tier-based hybrid 가 유일한 motivation 이라는 뜻 아님 — service-level decision 이 우선 |
| MT-HYBRID-C5 | hybrid model 은 pricing tier 별 isolation 수준 차등을 가능케 함 (예: enterprise tier 는 silo, 그 외는 pool) — cost efficiency 와 enterprise 의 isolation 요구를 절충 | [APN 블로그 — 과거 정독] "A hybrid model allows you to offer different isolation levels at different pricing tiers, balancing cost efficiency with the isolation guarantees required by enterprise customers." | `needs-confirmation` | tier-based SaaS pricing 모델 | "enterprise = silo, 나머지 = pool" 이 표준 매핑이라는 뜻 아님 — 비즈니스 결정. 원본 URL 404 로 verbatim 재검증 불가 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `C1`~`C4`: AWS SaaS Lens 의 silo/pool/bridge 정의와 bridge motivation
- `C5`: tier-based hybrid 의 의도 (단, **verbatim 재검증 불가**`needs-confirmation`)
- **이 자료가 증명하지 않는 것**:
- tier promotion (pool → silo) 의 정확한 마이그레이션 도구 / 절차 (APN 블로그 본문 회수 불가)
- routing layer 가 반드시 API Gateway / load balancer 여야 한다는 주장
- 운영 인력 비용이 단일 모델 대비 1.5~2배라는 정량 추정
- hybrid 를 도입한 실제 사례의 incident / outage 통계
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 이 hybrid 로 전환할 때의 trigger 조건 (tenant 수 / 매출 / 규제 요구) — 본 자료는 일반론
- tenant catalog 의 데이터 모델 (어느 stamp / 어느 tier) 구현 detail — 별도 자료 필요
- 한국 SaaS 시장에서 hybrid 채택 사례 (본 자료는 미국 SaaS 중심)
## 메모 / Notes (내 해석, 미검증)
- isolation 수준 (shared/schema-per-tenant/db-per-tenant):
- 한 시스템 안에 두 가지 이상 공존
- 일반: shared DB + tenant_id (pool)
- 엔터프라이즈: 전용 DB instance 또는 전용 stamp (silo)
- tenant resolution 방식:
- JWT claim 또는 tenant catalog lookup이 일반적
- 모든 요청이 catalog로 "이 tenant는 어느 tier/stamp?"를 결정
- scale 한계:
- 각 tier가 독립적으로 scale
- tier 간 routing layer가 single point가 되지 않게 분산 필요
- 운영 복잡도:
- **가장 높음**: 두 개 이상의 isolation 모델을 동시에 운영
- 마이그레이션 도구도 두 가지 (pool 마이그레이션 + silo 마이그레이션)
- tier 승급 (pool → silo) 데이터 이동 절차 필요
- security/compliance:
- enterprise tier가 silo로 가면 규제 요구 충족 가능
- tier별 SLA 차등
- 비용:
- tier 가격에 isolation 비용을 반영 가능 → 비즈니스 모델 친화적
- 운영 인력 비용은 단일 모델 대비 1.5~2배 (추정, 미검증)
- 장점:
- 비즈니스 가치(엔터프라이즈 매출)와 직접 연결
- blast radius 차등 (enterprise tenant는 다른 tenant 영향 받지 않음)
- 단점:
- 운영 복잡도가 가장 높음
- 초기 도입 비용 큼
- 작은 팀에서는 권장하지 않음
- ca-tmpl과의 차이:
- ca-tmpl은 현재 단일 모델 (opt-in pool). hybrid는 명시적 out-of-scope.
- **hybrid 도입 시점**: enterprise tier 등장 + 규제 요구 + 매출 정당화 가능 시점. 일반적으로 product-market fit 이후 단계.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper]] (verbatim Bridge model 정의의 1차 출처)
- [[raw/official-docs/multitenancy-azure-architecture-patterns]] (Deployment Stamps 패턴 — Azure 측 hybrid)
- [[raw/company-tech-blogs/multitenancy-atlassian-tenant-context]]
- [[raw/company-tech-blogs/multitenancy-stripe-citus-schema-per-tenant]]
- 인용하는 branch / project:
- [[raw/branch-notes/feature-tenant-context-policy]]
- [[raw/branch-notes/feature-repository-access-permission-contract]]
- [[raw/project-notes/ca-skeleton-operational-contract]] (§18 Control Plane Contract)
- 대안 그룹: **Topic 6 — Multi-tenancy** (대안 6종). 본 source 의 위치: 대안 5 — tier-based hybrid.
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,118 @@
---
title: Citus (Microsoft) — Schema vs Row-based Multi-tenancy on Postgres
source_type: company-tech-blog
url: https://www.citusdata.com/blog/2016/10/03/designing-your-saas-database-for-high-scalability/
archive_url:
status: raw
confidence: low
tags: [ca-multi-tenancy, postgres, citus, schema-per-tenant, shared-schema]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-tenant-context-policy, feature-repository-access-permission-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Citus — Designing SaaS DB for High Scalability (Schema vs Row)
> Layer: `raw/company-tech-blogs/` — Citus Data (현 Microsoft) 2016 블로그. Postgres 환경에서 **schema-per-tenant** vs **shared schema + tenant_id** 의 실제 한계치를 가장 구체적 숫자로 다룬 사례.
> **출처 주의**: company-tech-blog 이므로 본 자료의 권장 사항을 "공식 best practice" 로 일반화 금지. ca-tmpl 의 shared schema 결정의 임계점 사례 reference 로만 사용.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 별도 작성.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-tenant-context-policy]] | Topic 6 Multi-tenancy 대안 3 (schema-per-tenant) 의 사례 baseline. ca-tmpl 이 shared schema (Pool) 를 채택한 임계점 (~수백 tenant) 의 사례 근거. |
| [[raw/branch-notes/feature-repository-access-permission-contract]] | shared schema 채택 결정의 trade-off — application bug 한 줄 cross-tenant leak 위험을 CROSS_TENANT_ADMIN capability 의 명시적 enforcement 로 완화하는 정당화. |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §18 Control Plane Contract (Tenant Context Policy) 의 Postgres 사례 reference. |
## 컨텍스트 / 왜 저장했는지
Postgres 환경에서 **schema-per-tenant** vs **shared schema + tenant_id** 의 실제 한계치를 가장 구체적인 숫자로 다룬 자료. ca-tmpl 이 shared schema 를 택한 결정의 임계점을 가늠하는 근거.
## 출처 / Source
- 원본 URL: https://www.citusdata.com/blog/2016/10/03/designing-your-saas-database-for-high-scalability/
- 관련: "At what scale does Postgres multi-tenancy need to shard?"
- 아카이브 URL: (미수집)
- 저자 / 조직: Citus Data (현 Microsoft Azure Database for PostgreSQL — Hyperscale)
- 발행일: 2016-10-03
- 마지막 확인일: 2026-05-27
- **재검증 결과 (2026-05-27) — CRITICAL FINDING**: 원본 URL WebFetch 성공 — 페이지는 접근 가능 (Ozgun Erdogan 작성, "Designing your SaaS Database for High Scalability"). 그러나 2026-05-25 capture 의 4개 quote (수백~수천 tenant cut-off, pg_class/pg_attribute overhead, Flyway 마이그레이션, search_path/plan cache invalidation) 는 **현재 페이지에서 NOT FOUND** — 페이지는 3 옵션 (one DB per tenant / one schema per tenant / shared tables) 과 shared-tables + tenant_id sharding 권장 (Google F1 기반), Alter Table 처리, JSONB/hstore semi-structured types 만 다루며 인용된 구체적 수치/도구/Postgres internals 는 본 URL 본문에 없음. 2026-05-25 capture 의 4개 quote 는 본 자료 출처가 **아닐 가능성** (다른 Citus 블로그 또는 paraphrase 가능성). claim strength `company-case-study` + `needs-confirmation` 유지하되, 본 raw 자료를 근거로 한 downstream claim 은 **출처 재추적 필수**.
## 핵심 인용 / Key quotes (verbatim, 2026-05-22 작성 시 인용)
> needs-confirmation [§Schema-per-tenant scaling — 2026-05-25 capture, 2026-05-27 페이지 NOT FOUND (본 quote 가 원본 URL 에 부재)] "Schema-per-tenant works well up to a few hundred to a few thousand tenants. Beyond that, Postgres metadata overhead (pg_class, pg_attribute) grows substantially."
> needs-confirmation [§Shared schema + tenant_id — 2026-05-25 capture, 2026-05-27 페이지 NOT FOUND (본 quote 가 원본 URL 에 부재)] "Shared schema with a tenant_id column scales to many more tenants but requires careful indexing — every index should include tenant_id as the leading column where queries filter by tenant."
> needs-confirmation [§Migrations — 2026-05-25 capture, 2026-05-27 페이지 NOT FOUND (본 quote 가 원본 URL 에 부재)] "Migrations on schema-per-tenant must be applied N times; tools like Flyway support this but rollout time grows linearly with tenant count."
> needs-confirmation [§Connection pooling — 2026-05-25 capture, 2026-05-27 페이지 NOT FOUND (본 quote 가 원본 URL 에 부재)] "Connection pooling is a primary pain point for schema-per-tenant: switching `search_path` per request invalidates plan cache and causes connection thrash."
> **[2026-05-27 verified] 원본 URL 에서 verbatim 확인된 별도 내용 (위 4개 quote 와 별개)**:
> - 페이지가 다루는 3 옵션: "Create one database per tenant," "Create one schema per tenant," "Have all tenants share the same table(s)."
> - 권장: shared tables + tenant_id sharding (Google F1 기반 hierarchical model).
> - 스케일: 별도 DB per tenant 는 5-50 tenant 까지만 적합, 수천 단위는 shared tables.
> - Schema 변경: "the database will either ensure that an Alter Table goes through across all shards, or it will roll it back."
> - Variable tenant data: JSONB/hstore/JSON semi-structured types 사용 권장.
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| CITUS-MT-C1 | Schema-per-tenant 는 수백~수천 tenant 까지 잘 동작, 그 이상에서는 Postgres metadata (pg_class, pg_attribute) overhead 가 substantial 하게 증가 | needs-confirmation [§Schema-per-tenant scaling] "Schema-per-tenant works well up to a few hundred to a few thousand tenants. Beyond that, Postgres metadata overhead (pg_class, pg_attribute) grows substantially." | `company-case-study` + `needs-confirmation` | Citus / Postgres 컨텍스트 (2016 시점) | 정확한 "수백" "수천" 의 cut-off 수치는 Postgres 버전 / 하드웨어 / 테이블 수에 따라 다름 — 본 인용은 order of magnitude 만 |
| CITUS-MT-C2 | Shared schema + tenant_id 는 더 많은 tenant 로 확장 가능하나 indexing 주의 필요 — query 가 tenant 로 filter 하는 모든 index 는 tenant_id 가 leading column 이어야 함 | needs-confirmation [§Shared schema + tenant_id] "Shared schema with a tenant_id column scales to many more tenants but requires careful indexing — every index should include tenant_id as the leading column where queries filter by tenant." | `company-case-study` + `needs-confirmation` | Postgres + shared schema multi-tenancy | tenant_id 가 leading column 이 아니면 무조건 성능 저하라는 일반화는 아님 — query plan 에 따라 다름 |
| CITUS-MT-C3 | Schema-per-tenant migration 은 N 번 적용되어야 함; Flyway 같은 도구가 지원하나 rollout 시간이 tenant 수에 비례 | needs-confirmation [§Migrations] "Migrations on schema-per-tenant must be applied N times; tools like Flyway support this but rollout time grows linearly with tenant count." | `company-case-study` + `needs-confirmation` | schema-per-tenant 운영 | rollout 의 parallelism / dry-run 권장은 본 인용에 없음 |
| CITUS-MT-C4 | Schema-per-tenant 의 1차 pain point 는 connection pooling — request 마다 `search_path` 변경이 plan cache invalidation + connection thrash 유발 | needs-confirmation [§Connection pooling] "Connection pooling is a primary pain point for schema-per-tenant: switching `search_path` per request invalidates plan cache and causes connection thrash." | `company-case-study` + `needs-confirmation` | schema-per-tenant + Postgres + connection pooler 사용 | PgBouncer 의 transaction-level pooling 으로 완화 가능한지는 본 인용에 없음 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- 2026-05-27 verbatim 재확인 완료: 3 옵션 분류 (one DB / one schema / shared tables), shared tables + tenant_id sharding 권장, 별도 DB 는 5-50 tenant 까지만, Alter Table all-or-rollback 보장, JSONB/hstore 권장
- `CITUS-MT-C1` ~ `C4`: **본 quote 들이 원본 URL 에 부재** — 출처 재추적 필요 (다른 Citus 블로그 또는 paraphrase 가능성)
- **이 자료가 증명하지 않는 것**:
- 본 자료가 공식 Postgres 가이드라는 보증 (Citus 는 Postgres extension vendor 였고 2019년 Microsoft 인수, 본 블로그는 vendor case study)
- 2026 시점의 Postgres 14+ 또는 PgBouncer 신버전에서 동일 한계가 그대로 유지되는지 (페이지 outdated 가능성)
- 모든 SaaS 가 수천 tenant 에서 schema-per-tenant 를 포기해야 한다는 일반화 (use case 별 trade-off)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 예상 tenant 수가 수십 / 수백 / 수천 중 어디인지 (임계 판단의 입력값)
- shared schema 채택 시 모든 index 에 tenant_id 를 leading column 으로 포함하는 규약을 ca-tmpl 의 schema migration policy 에 명문화했는지
- 본 raw 인용 verbatim 의 정확성은 페이지 사람 검증 또는 archive.org snapshot 으로 보강
- "company-tech-blog" 이므로 wiki 추출 시 AWS / Hibernate 공식 자료와 corroboration 필요 (공식 best practice 로 단정 금지)
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- isolation 수준 (shared/schema-per-tenant/db-per-tenant):
- **Schema-per-tenant**: 같은 DB, 다른 schema. Postgres `search_path` 또는 fully-qualified table name.
- **Shared schema + tenant_id**: ca-tmpl 모델.
- tenant resolution 방식: 둘 다 application layer가 결정. schema-per-tenant는 connection 단위로 `SET search_path`.
- scale 한계 (구체 수치):
- Schema-per-tenant: ~수천 tenant까지. catalog bloat, autovacuum 부하, plan cache miss.
- Shared schema: tenant 수는 제약 없음. 다만 단일 테이블 row 수가 수억 → partition 또는 Citus 같은 sharding 필요.
- 운영 복잡도:
- schema-per-tenant: tenant 추가/삭제 자동화 스크립트 필수. 백업/복원이 tenant별 가능 (장점).
- shared schema: 단일 마이그레이션. 단점은 tenant별 백업이 사실상 불가 (logical export로 우회).
- security/compliance:
- schema-per-tenant는 Postgres role/grant로 OS 레벨 분리 가능 → application bug 방어막
- shared schema는 application bug 한 줄로 cross-tenant leak
- 비용: 둘 다 단일 DB instance → 인프라 비용 동일. 운영 비용은 schema-per-tenant가 더 큼.
- ca-tmpl과의 차이:
- ca-tmpl은 shared schema 선택. tenant 수가 ~수십 단위면 schema-per-tenant도 충분히 운영 가능했지만, 마이그레이션/connection pool 복잡도를 회피하기 위해 shared 채택.
- **임계 지점**: tenant 수가 수백 단위 + 규제(GDPR/금융권) 요구 시 schema-per-tenant 또는 stamp(=db-per-tenant) 검토.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper]] — AWS 의 Silo/Pool/Bridge 분류
- [[raw/official-docs/multitenancy-hibernate-user-guide]] — Hibernate ORM 의 3 strategy
- [[raw/official-docs/multitenancy-microservices-io-pattern]] — microservices.io database-per-service
- [[raw/company-tech-blogs/multitenancy-atlassian-tenant-context]] — shard + tenant context 운영 사례
- 인용하는 branch:
- [[raw/branch-notes/feature-tenant-context-policy]]
- [[raw/branch-notes/feature-repository-access-permission-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§18)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,126 @@
---
title: Subdomain-based Tenant Resolution — Practical Notes (Vercel / Supabase 사례)
source_type: company-tech-blog
status: raw
confidence: medium
url: https://vercel.com/docs/multi-tenant
archive_url:
tags: [ca-multi-tenancy, subdomain, dns, tls, tenant-resolution, vercel, company-tech-blog]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-tenant-context-policy, feature-repository-access-permission-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Subdomain-based Tenant Resolution — 실무 메모
> Layer: `raw/company-tech-blogs/` — Vercel 의 multi-tenant 가이드 발췌. Vercel 은 platform vendor 이지만 본 자료는 product overview / blog style 이므로 **company-tech-blog / 사례** 로 취급. 공식 best practice 가 아닌 vendor 의 권장 패턴.
> ca-tmpl 이 subdomain 방식을 **resolution 3순위 (fallback)** 로 둔 결정의 대안 평가.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-tenant-context-policy]] | subdomain 을 tenant resolution 1순위 가 아닌 3순위 (fallback) 로 둔 결정 — Vercel 의 운영 비용 (wildcard cert, custom domain 자동화) 을 회피한다는 trade-off 근거 |
| [[raw/branch-notes/feature-repository-access-permission-contract]] | tenant 식별이 hostname 에서 오는 경우 host header injection 방어 필요 — capability 검증 layer 의 trust boundary 결정 근거 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §18. Control Plane Contract (Tenant Context Policy) — subdomain 채택 시점에 대한 future-state 참고 (end-user facing web 추가 시) |
## 컨텍스트 / 왜 저장했는지
ca-tmpl 이 subdomain 방식을 **resolution 3순위 (fallback)** 로 둔 결정의 대안 평가. 만약 subdomain 을 1순위로 선택한다면 어떤 운영 부담이 있는지 정리.
## 출처 / Source
- 원본 URL: https://vercel.com/docs/multi-tenant — **2026-05-27 fetch 성공**. 단 high-level overview 이며 세부 구현 (wildcard DNS / TLS rate limit / local dev) 은 다루지 않음
- 원래 가이드 (404): https://vercel.com/guides/nextjs-multi-tenant-application — **2026-05-27 확인 시 페이지 이전 / 통합**
- 보조 (개념): Supabase, Cloudflare for SaaS (custom hostname) — 별도 자료
- 저자 / 조직: Vercel
- 발행일: page metadata `last_updated: 2025-12-18`
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§Vercel for Platforms — opening] "A **multi-tenant application** serves multiple customers (tenants) from a single codebase."
> [§Vercel for Platforms — opening] "Each tenant gets its own domain or subdomain, but you only have one Next.js (or similar) deployment running on Vercel. This approach simplifies your infrastructure, scales well, and keeps your branding consistent across all tenant sites."
> [§Why build multi-tenant apps — example] "A root domain for your platform: `acme.com` / Subdomains for tenants: `tenant1.acme.com`, `tenant2.acme.com` / Fully custom domains for certain customers: `tenantcustomdomain.com`"
> [§Why build multi-tenant apps] "Vercel's platform automatically issues [SSL certificates](https://vercel.com/docs/domains/working-with-ssl), handles DNS routing via its Anycast network, and ensures each of your tenants gets low-latency responses from the closest CDN region."
> [§Getting started — starter kit features] "Custom subdomain routing with Next.js middleware / Tenant-specific content and pages / Redis for tenant data storage / Admin interface for managing tenants / Compatible with Vercel preview deployments"
> [§Multi-tenant features on Vercel] "Unlimited custom domains / Unlimited `*.yourdomain.com` subdomains / Automatic SSL certificate issuance and renewal / Domain management through REST API or SDK / Low-latency responses globally with the Vercel CDN / Preview environment support to test changes / Support for 35+ frontend and backend frameworks"
> [§Let's Encrypt rate limit — 과거 정독, **본문 미수록 / 재검증 불가**] "Let's Encrypt has a rate limit of 50 certificates per registered domain per week, which can throttle onboarding if not using wildcard or a CDN-managed cert provider."
> [§Local dev — 과거 정독, **본문 미수록 / 재검증 불가**] "Local development requires `hosts` file modification or a wildcard DNS provider like `nip.io` / `lvh.me`."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| MT-SUBDOM-C1 | multi-tenant 앱은 단일 codebase 로 여러 고객 (tenant) 에게 서비스 — 각 tenant 는 자신의 domain 또는 subdomain 을 가짐 | [§Vercel for Platforms — opening] "A **multi-tenant application** serves multiple customers (tenants) from a single codebase." + "Each tenant gets its own domain or subdomain, but you only have one Next.js (or similar) deployment running on Vercel." | `company-case-study` | Vercel 의 platform 모델을 따르는 Next.js / 유사 framework 배포 | "단일 codebase" 가 모든 multi-tenant 패턴의 요건이라는 뜻은 아님 (Deployment Stamps 같은 multi-deployment 패턴 별도) |
| MT-SUBDOM-C2 | tenant 식별의 hostname 패턴: root domain (`acme.com`) + per-tenant subdomain (`tenant1.acme.com`) + 일부 enterprise 의 fully custom domain (`tenantcustomdomain.com`) | [§Why build multi-tenant apps — example] "A root domain for your platform: `acme.com` / Subdomains for tenants: `tenant1.acme.com`, `tenant2.acme.com` / Fully custom domains for certain customers: `tenantcustomdomain.com`" | `company-case-study` | subdomain + custom domain 혼합 운영하는 SaaS | custom domain 이 항상 enterprise tier 전용이어야 한다는 뜻 아님 — Vercel 의 운영 패턴 사례 |
| MT-SUBDOM-C3 | Vercel platform 은 SSL 인증서 자동 발급, Anycast DNS routing, CDN 최적화를 platform 차원에서 제공 | [§Why build multi-tenant apps] "Vercel's platform automatically issues [SSL certificates](https://vercel.com/docs/domains/working-with-ssl), handles DNS routing via its Anycast network, and ensures each of your tenants gets low-latency responses from the closest CDN region." | `company-case-study` | Vercel platform 사용 시 | self-host 시에도 동일한 자동화가 보장된다는 뜻 아님 — Vercel 종속적 capability |
| MT-SUBDOM-C4 | Vercel 의 multi-tenant feature: 무제한 custom domain, 무제한 `*.yourdomain.com` subdomain, SSL 자동 갱신, REST API/SDK 기반 domain 관리, preview environment 지원 | [§Multi-tenant features on Vercel] "Unlimited custom domains / Unlimited `*.yourdomain.com` subdomains / Automatic SSL certificate issuance and renewal / Domain management through REST API or SDK / Low-latency responses globally with the Vercel CDN / Preview environment support to test changes" | `company-case-study` | Vercel for Platforms 가입 | "무제한" 의 정확한 fair-use / pricing 임계는 본 인용 범위 밖 — `/docs/multi-tenant/limits` 별도 확인 |
| MT-SUBDOM-C5 | Next.js middleware 가 custom subdomain routing 의 표준 구현 패턴 (Vercel starter kit 의 feature 로 명시) | [§Getting started — starter kit features] "Custom subdomain routing with Next.js middleware" | `company-case-study` | Next.js + Vercel 조합 | middleware 가 hostname 을 어떻게 파싱/검증하는지의 구체 구현은 본 인용 범위 밖 — starter kit 코드 별도 확인 |
| MT-SUBDOM-C6 | Let's Encrypt 의 인증서 발급 rate limit (도메인당 주 50개) 이 tenant onboarding 속도의 제약 — wildcard 또는 CDN-managed cert provider 사용 시 회피 가능 | [§Let's Encrypt rate limit — 과거 정독] "Let's Encrypt has a rate limit of 50 certificates per registered domain per week, which can throttle onboarding if not using wildcard or a CDN-managed cert provider." | `needs-confirmation` | Let's Encrypt 사용 SaaS | 본 Vercel docs 본문에는 미수록. Let's Encrypt 공식 rate limit 문서로 직접 verbatim 검증 필요 |
| MT-SUBDOM-C7 | local dev 환경에서 subdomain 테스트는 `hosts` 파일 수정 또는 `nip.io` / `lvh.me` 같은 wildcard DNS provider 가 필요 | [§Local dev — 과거 정독] "Local development requires `hosts` file modification or a wildcard DNS provider like `nip.io` / `lvh.me`." | `needs-confirmation` | local 개발 환경에서 subdomain routing 테스트 | 본 Vercel docs 본문에는 미수록. 별도 dev 가이드 확인 필요 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `C1`~`C5`: Vercel 의 multi-tenant feature 와 hostname 패턴 (root / subdomain / custom domain)
- Next.js middleware 가 Vercel 의 표준 subdomain routing 구현임 (starter kit 의 feature)
- **이 자료가 증명하지 않는 것**:
- `C6`, `C7`: Let's Encrypt rate limit 과 local dev workaround 는 본 docs 본문에 없음 (`needs-confirmation`)
- subdomain takeover 의 위험 / 방어 패턴 (본 docs 미언급)
- host header injection 방어 (본 docs 미언급)
- cross-subdomain cookie / SSO 설정 (본 docs 미언급)
- mobile app 의 UX 차이 (본 docs 는 web 중심)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 이 subdomain 으로 전환 시 self-host (non-Vercel) 환경에서 cert-manager + Let's Encrypt 자동화 cost
- subdomain 1순위 채택의 trigger 조건 (end-user facing UI 추가 / brand 가치 / API 외 web 확장)
- JWT claim 과 subdomain 이 mismatch 일 때의 처리 정책 (예: JWT 의 tenant ≠ hostname tenant)
## 메모 / Notes (내 해석, 미검증)
- isolation 수준: resolution 방식이라 isolation과 직교. shared/schema/db 어느 모델과도 결합 가능.
- tenant resolution 방식: subdomain 단독. 보통 reverse proxy/gateway가 Host header에서 tenant 추출 후 downstream에 X-Tenant-Id 또는 context로 전파.
- scale 한계:
- DNS propagation 시간 (수 분~수십 분)
- TLS 인증서 발급 rate limit (Let's Encrypt 주 50개/도메인) — `C6` 참조
- wildcard 인증서를 쓰면 위 제약 없으나 custom domain 지원 시 별도 자동화 필요
- 운영 복잡도:
- DNS 관리 자동화 (Route53/Cloudflare API)
- TLS 자동화 (cert-manager, ACM)
- local dev 환경 (`lvh.me` 등) — `C7` 참조
- CORS 설정이 wildcard origin으로 복잡
- security:
- Host header injection 방어 필수 (allowlist)
- subdomain takeover 위험 (tenant 삭제 후 DNS record 미정리)
- 장점:
- UX (북마크, 공유)
- tenant 별 brand
- CDN 캐싱 정책을 hostname 단위로 분리 가능
- 단점:
- 위 운영 부담 전반
- mobile app에서는 UX 이점이 적음 (사용자가 URL을 보지 않음)
- JWT/session 쿠키 domain 설정 까다로움 (cross-subdomain SSO 필요 시 parent domain cookie)
- ca-tmpl과의 차이:
- ca-tmpl은 B2B API 중심 가정 → subdomain의 UX 이점이 약함 → JWT claim 우선.
- **subdomain 1순위 채택 시점**: end-user facing web app + tenant brand가 product value의 일부일 때 (e.g. Notion, Slack, Linear).
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/multitenancy-auth0-tenant-resolution]] (JWT claim 우선 vs subdomain 비교)
- [[raw/company-tech-blogs/multitenancy-atlassian-tenant-context]]
- [[raw/official-docs/multitenancy-azure-architecture-patterns]]
- 인용하는 branch / project:
- [[raw/branch-notes/feature-tenant-context-policy]]
- [[raw/branch-notes/feature-repository-access-permission-contract]]
- [[raw/project-notes/ca-skeleton-operational-contract]] (§18 Control Plane Contract)
- 대안 그룹: **Topic 6 — Multi-tenancy** (대안 6종). 본 source 의 위치: 대안 1 — subdomain-based resolution.
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,85 @@
---
title: company-tech-blog / Netflix Tudum — CQRS Architecture Evolution (Kafka→RAW Hollow)
source_type: company-tech-blog
url: https://netflixtechblog.com/netflix-tudum-architecture-from-cqrs-with-kafka-to-cqrs-with-raw-hollow-86d141b72e52
archive_url:
status: raw
confidence: medium
tags: [cqrs, read-model, separate-read-store, kafka, cassandra, eventual-consistency, netflix, ca-skeleton]
related_branches: [feature-application-query-bypass-contract]
related_projects: [ca-skeleton]
created: 2026-06-04
last_reviewed: 2026-06-04
---
# Netflix Tudum — CQRS Architecture Evolution (Kafka → RAW Hollow)
> Layer: `raw/company-tech-blogs/` — Netflix TechBlog (2025) 에서 Netflix Tudum 팀이 Full CQRS (Kafka + Cassandra separate read store) 를 채택했다가 operational friction 으로 인해 RAW Hollow (in-memory) 로 대체한 사례. Full CQRS (Alt 3) 의 **현실적 운영 비용과 eventual consistency 문제** 의 production evidence.
>
> **출처 신뢰도**: Netflix TechBlog (official engineering blog). company-tech-blog 등급. official best practice 로 승격 금지 — Netflix 의 특정 use case (CMS-driven content site, 20M 사용자, editorial preview latency 문제) 에 특화된 결정.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-application-query-bypass-contract]] | D3 (Full CQRS — separate data stores) 의 operational cost + eventual consistency 문제 의 production evidence. "언제 escalation 해야 하는가" 의 반대 사례 (escalation 후 다시 simpler 로 돌아간 케이스) |
## 출처 / Source
- 원본 URL: https://netflixtechblog.com/netflix-tudum-architecture-from-cqrs-with-kafka-to-cqrs-with-raw-hollow-86d141b72e52
- 아카이브 URL:
- 저자 / 조직: Netflix Technology Blog — Tudum Engineering Team
- 발행일: 2025 (exact date per TechBlog post)
- 마지막 확인일: 2026-06-04
- **검증 한계**: netflixtechblog.com SSL 인증서 오류로 직접 WebFetch 불가. 아래 인용은 ByteByteGo 가 인용한 Netflix TechBlog 내용 기반 (secondary source, confidence: medium). bytebytego.com 에서 WebFetch 검증됨.
- 보조 확인: https://blog.bytebytego.com/p/how-netflix-tudum-supports-20-million (summary, WebFetch 검증됨), InfoQ 뉴스 보도 https://www.infoq.com/news/2025/08/netflix-tudum-cqrs-raw-hollow/
## 왜 저장했는지 / Why archived
Full CQRS (separate read store) 를 production 에서 실제로 채택했다가 복잡성·eventual consistency·preview latency 문제로 simpler architecture 로 전환한 사례. ca-tmpl skeleton 이 Full CQRS 를 "escalation only" 로 분류하는 결정의 반대 사례(counterargument source). "언제 Full CQRS 가 부적합한가" 의 production evidence.
## 핵심 인용 / Key quotes (verbatim, secondary source via ByteByteGo)
> [§Architecture rationale] "To keep these workflows independent and allow each to scale according to its needs, Netflix adopted a CQRS (Command Query Responsibility Segregation) architecture."
> [§Operational problem — eventual consistency] "Every time an editor made a change in the CMS, that change had to travel through a long chain before it appeared in a preview environment or on the live site."
> [§Operational problem — preview latency] "editors had to sometimes wait minutes to see their changes reflected in a preview, even though the system had already processed and stored the update."
> [§Migration rationale — complexity] "Removing Kafka, the external key-value store, and near-cache layers from the read path reduced moving parts and failure points, while eliminating cache-invalidation headaches."
> [§RAW Hollow result] "RAW Hollow distributes that update to all Hollow clients across service instances...each instance has the full dataset in memory, any request...is served immediately without cache checks or datastore queries."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| NETFLIX-TUDUM-C1 | Netflix Tudum 은 write path (editorial CMS) 와 read path (20M+ user site) 의 독립 scaling 을 위해 Full CQRS (Kafka + Cassandra separate read store) 를 채택했음 | "To keep these workflows independent and allow each to scale according to its needs, Netflix adopted a CQRS (Command Query Responsibility Segregation) architecture." | `company-case-study` | write/read 부하가 극단적으로 비대칭인 시스템 (editorial write 소수 vs 20M user read 다수) | 단순 Java/Spring skeleton 애플리케이션에서 동일 근거로 Full CQRS 가 필요하다는 근거는 아님 |
| NETFLIX-TUDUM-C2 | Full CQRS 의 separate store 구조는 "긴 체인" 을 통한 eventual consistency 지연을 유발 — editor 가 변경 후 preview 에서 확인하기까지 "때로는 수 분" 대기 | "Every time an editor made a change in the CMS, that change had to travel through a long chain before it appeared in a preview environment" + "editors had to sometimes wait minutes to see their changes reflected" | `company-case-study` | Kafka + separate store 를 통해 read model 을 갱신하는 Full CQRS 시스템 | 이 eventual consistency 지연이 모든 Full CQRS 시스템에서 나타난다는 뜻은 아님 — Netflix 의 Kafka pipeline 구성 특화 문제일 수 있음 |
| NETFLIX-TUDUM-C3 | separate store CQRS 의 이동 부품 (Kafka, external key-value store, near-cache) 제거가 장애 지점 감소와 운영 단순화를 가져옴 | "Removing Kafka, the external key-value store, and near-cache layers from the read path reduced moving parts and failure points, while eliminating cache-invalidation headaches." | `company-case-study` | Full CQRS 에서 더 단순한 아키텍처로 migration 결정의 근거 | "Kafka + separate store 가 항상 이런 문제를 낳는다" 는 일반화 불가 — Netflix 의 전환 이유가 부분적으로 in-memory store (RAW Hollow) 의 등장 덕분 |
| NETFLIX-TUDUM-C4 | in-memory read store 로 전환 후 page construction time 이 약 1.4s → 0.4s 로 단축 (InfoQ 보도) | (InfoQ 보조 인용) "Home page construction time dropped from roughly 1.4 seconds to about 0.4 seconds once all read-path services consumed Hollow in-memory state." | `company-case-study` (secondary — InfoQ via search summary) | in-memory 기반 read store 로 전환한 read-heavy production system | 일반 Java/Spring skeleton 에서 in-memory store 없이도 이 수준 성능을 달성해야 한다는 기준은 아님 |
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `NETFLIX-TUDUM-C1`: 극단적 write/read 비대칭 (소수 편집자 vs 20M 사용자) 이 Full CQRS separate store 채택 동기가 될 수 있음
- `NETFLIX-TUDUM-C2`~`C3`: separate store CQRS 의 운영 현실 — eventual consistency 지연 + "긴 체인" + 이동 부품 증가 = 운영 부담
- 이 자료가 증명하지 않는 것:
- Full CQRS 가 항상 eventual consistency 문제를 유발한다는 일반 규칙 — Netflix 의 특정 pipeline 구성 특화
- ca-tmpl skeleton 에서 Full CQRS 를 배제해야 한다는 직접 근거 — Netflix 는 Full CQRS 를 채택했고 다시 다른 방식으로 전환했을 뿐 (CQRS 자체를 폐기한 게 아님, RAW Hollow 도 CQRS)
- CQRS-lite (single store) 가 Full CQRS 보다 우월하다는 직접 비교 (Netflix 는 CQRS-lite 를 채택하지 않았음)
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- ca-tmpl skeleton 이 도달할 부하 수준과 Netflix Tudum (20M users) 의 비교 — 비교가 유효한지
- eventual consistency 허용 여부 — skeleton 의 기본 사용 도메인이 strong consistency 를 요구하는지
## 메모 / Notes
- Netflix 의 "CQRS → RAW Hollow" 전환은 "Full CQRS 는 나쁘다" 가 아니라 "더 단순한 read store 가 생겼으니 이동 부품을 줄이자" 의 실용적 결정
- ca-tmpl skeleton 의 escalation rule 에서: "read/write 부하가 명확히 비대칭이고 read store 기술 선택이 명확할 때" 만 Full CQRS 로 escalation 하는 조건의 반례(counterargument) 로 활용 가능
- **confidence: medium** — netflixtechblog.com 직접 접근 불가로 ByteByteGo/InfoQ secondary source 기반. 직접 접근 시 quotes 재검증 필요.
## Related / 관련
- [[raw/official-docs/cqrs-pattern-azure-architecture-center]] — Full CQRS separate store 의 공식 정의 + complexity 경고
- [[raw/official-docs/cqrs-fowler-bliki]] — CQRS caution 경고
- [[raw/branch-notes/feature-application-query-bypass-contract]] — 본 자료를 소비하는 branch
@@ -0,0 +1,100 @@
---
title: Onion Architecture (Allegro Tech Blog)
source_type: company-tech-blog
url: https://blog.allegro.tech/2023/02/onion-architecture.html
archive_url:
status: raw
confidence: medium
tags: [ca-architecture-layout, onion, allegro, dependency-inversion, hexagonal-comparison]
related_branches: [feature-architecture-enforcement-rules, feature-skeleton-package-blueprint-contract, feature-domain-feature-onboarding-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Onion Architecture (Allegro Tech Blog)
> Layer: `raw/company-tech-blogs/` — Allegro (폴란드 e-commerce) 의 Onion Architecture 해설 + Hexagonal 비교.
> 공식 표준 아닌 **회사 사례**. ca-tmpl 의 architecture-layout 대안 5종 중 "onion" 대안의 reference.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-architecture-enforcement-rules]] | Onion 의 명시적 layer 분리 + dependency direction (outside → inside) 가 ArchUnit 규칙으로 표현될 때의 reference |
| [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] | 패키지 blueprint 결정 시 layer-first (domain/application/infrastructure) 어휘의 사례 |
| [[raw/branch-notes/feature-domain-feature-onboarding-contract]] | 신규 도메인 추가 시 layer-first vs feature-first 분할 priority 비교 근거 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §19 Domain Application Readiness, §20 Skeleton Blueprint 의 onion 대안 reference |
## 출처 / Source
- 원본 URL: https://blog.allegro.tech/2023/02/onion-architecture.html
- 아카이브 URL: (미수집)
- 저자: Tomasz Tarczyński
- 조직: Allegro (폴란드 최대 e-commerce 플랫폼)
- 발행일: 2023-02-13
- 마지막 확인일: 2026-05-27
## 왜 저장했는지 / Why archived
ca-tmpl 의 feature-first 결정에 대한 **대안 5: Onion Architecture** 의 대기업 실 적용 관점 + Hexagonal 과의 명시적 비교 자료. Palermo 원문이 .NET 맥락이라 Java/Spring 적용 관점이 부족한 점을 보강.
## 핵심 인용 / Key quotes (verbatim)
> [§Definition] "Onion Architecture is a software architectural style which strongly promotes the separation of concerns between the most important part of a business application — the domain code — and its technical aspects like HTTP or database."
> [§Comparison with Hexagonal] "It can be successfully used as an alternative to a popular Hexagonal / Ports and Adapters architecture, and as such is predominantly used in the backend, business applications and services."
> [§Comparison with Hexagonal] "The main difference I've found in the implementations of Hexagonal Architecture and Onion Architecture lies mostly in the overall, more structured approach to the code layout of the latter."
> [§Core Objective] "They all have the same objective, which is the separation of concerns. They all achieve this separation by dividing the software into layers."
> [§Layer Structure] "There are three main layers in Onion Architecture: The domain layer, The application layer, The infrastructure layer each of which has its responsibilities."
> [§Dependency Direction] "Every outer layer sees classes from all inner layers, not only the one directly below. Moreover, the dependency direction always goes from the outside to the inside, never the other way around."
> [§Dependency Coupling] "Coupling is towards the centre of The Onion — expressed by the relationship between the layers."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| ALLEGRO-ONION-C1 | Onion Architecture 는 도메인 코드와 기술적 측면 (HTTP, DB) 의 관심사 분리를 강력하게 추구하는 아키텍처 스타일 | [§Definition] "Onion Architecture is a software architectural style which strongly promotes the separation of concerns between the most important part of a business application — the domain code — and its technical aspects like HTTP or database." | `company-case-study` | 비즈니스 어플리케이션의 도메인 중심 설계 | Onion 만이 SoC 를 달성할 수 있다는 뜻 아님 — Hexagonal, Clean, Modulith 등도 동일 목표 |
| ALLEGRO-ONION-C2 | Onion 은 Hexagonal/Ports & Adapters 의 대안으로 사용 가능하며 backend 비즈니스 어플리케이션에 주로 사용됨 | [§Comparison] "It can be successfully used as an alternative to a popular Hexagonal / Ports and Adapters architecture, and as such is predominantly used in the backend, business applications and services." | `company-case-study` | backend 비즈니스 어플리케이션 | Onion 이 Hexagonal 보다 우월하다는 뜻 아님 — 저자는 두 스타일을 alternative 로 표현 |
| ALLEGRO-ONION-C3 | Onion 과 Hexagonal 의 주된 차이는 Onion 이 코드 레이아웃에 대해 더 구조화된 접근을 제공한다는 점 | [§Comparison] "The main difference I've found in the implementations of Hexagonal Architecture and Onion Architecture lies mostly in the overall, more structured approach to the code layout of the latter." | `company-case-study` | 코드 레이아웃 의사 결정 | 저자 1인의 견해 ("I've found") — 업계 합의가 아님 |
| ALLEGRO-ONION-C4 | Onion 은 3 layer 구조 (domain / application / infrastructure) 를 가짐 | [§Layer Structure] "There are three main layers in Onion Architecture: The domain layer, The application layer, The infrastructure layer each of which has its responsibilities." | `company-case-study` | layer-first 패키지 구조 설계 | 일부 다른 Onion 해석은 4 layer (domain model / domain services / application / infrastructure) 를 가짐 — 본 자료는 3 layer 변형 |
| ALLEGRO-ONION-C5 | 의존성 방향은 항상 outside → inside, 외부 layer 는 모든 내부 layer 의 클래스를 볼 수 있음 (인접 layer 만이 아님) | [§Dependency Direction] "Every outer layer sees classes from all inner layers, not only the one directly below. Moreover, the dependency direction always goes from the outside to the inside, never the other way around." | `company-case-study` | Onion 의 의존성 규칙 ArchUnit 변환 시 | 이 규칙이 "엄격한 layer architecture" 보다 완화된 형태 — 인접 layer 만 허용하는 strict layered 와 다름 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `ALLEGRO-ONION-C1` ~ `C5`: Allegro 엔지니어의 Onion 정의, Hexagonal 과의 비교, 3 layer 구조, 의존성 방향 규칙
- **이 자료가 증명하지 않는 것**:
- Allegro 가 prod 에서 Onion 을 채택했다는 사실 — 본문은 해설 글로, 채택 사례 numeric metrics 없음
- Onion 이 Hexagonal/Clean 대비 운영 성능 / 개발 속도에서 우월하다는 정량 비교
- 본 글의 3 layer 가 Palermo 원본 Onion 의 정통 해석이라는 권위 — 저자 개인의 표현
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 4 layer (presentation / application / domain / infrastructure) 와 Allegro 의 3 layer (domain / application / infrastructure) 차이가 실제 운영에 미치는 영향
- "outside → inside, 모든 내부 layer 접근 가능" 규칙을 ArchUnit 으로 표현 시의 정확한 룰 (인접 layer 한정 vs 모든 내부 layer 허용)
## 메모 / Notes
- 적용 시나리오: Hexagonal 보다 layer 가 명시적인 가이드가 필요한 팀. 신규 개발자 온보딩 비용 절감이 중요할 때.
- 장점: layer 이름이 직관적 (domain/application/infrastructure) → ca-tmpl 의 4-layer 와 거의 동일한 어휘.
- 단점: layer 안에서 feature 를 어떻게 자를지는 본문에서 가이드 없음. 도메인 폭증 시 같은 문제 발생.
- ca-tmpl(feature-first) 와의 차이: Allegro 사례는 **layer 최상위 + feature 분할 가이드 없음**. ca-tmpl 의 4-layer 이름 (presentation/application/domain/infrastructure) 이 Onion 의 어휘를 차용한 것으로 보일 만큼 유사하나, **분할 우선순위가 정반대** — Onion 은 layer 우선, ca-tmpl 은 feature 우선.
- 신뢰도: `company-tech-blog` / `company-case-study` — Allegro 1명 저자의 사례/해설로만 인용. **"Onion 표준" 이라 부르지 않음**.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/modulith-kakaobank-techblog-2025]]
- [[raw/company-tech-blogs/domain-woowahan-ddd-aggregate-techblog]]
- 인용하는 branch:
- [[raw/branch-notes/feature-architecture-enforcement-rules]]
- [[raw/branch-notes/feature-skeleton-package-blueprint-contract]]
- [[raw/branch-notes/feature-domain-feature-onboarding-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§19, §20)
- 대안 그룹: **Topic 1 — Architecture Layout** (대안 5종: feature-first / layer-first / hexagonal / modulith / onion) — 본 자료는 대안 4 (onion)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,103 @@
---
title: Stripe Engineering — rate limiting, idempotency retry, exponential backoff
source_type: company-tech-blog
status: raw
confidence: high
url: https://stripe.com/blog/rate-limiters
archive_url:
related_branches: [feature-outbound-http-client-baseline, feature-rate-limit-idempotency-contract]
related_projects: [ca-tmpl]
tags: [ca-outbound-http, stripe, rate-limit, retry, backoff, idempotency, circuit-breaker]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Stripe Engineering — rate limiting, idempotency retry, exponential backoff
> Layer: `raw/company-tech-blogs/` — Stripe Engineering blog "Scaling your API with rate limiters" 의 4가지 rate limiter 분류 발췌. ca-tmpl outbound retry/timeout 결정의 **사례 근거** (company-case-study). 공식 best practice 로 격상 금지.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-outbound-http-client-baseline]] | retry default-disabled vs default-enabled 의 비교 사례 (Stripe 는 SDK 측 enabled-by-default, ca-tmpl 은 conservative default-disabled — 비교 reference) |
| [[raw/branch-notes/feature-rate-limit-idempotency-contract]] | idempotency-key + retry 결합의 산업 사례 + 4가지 rate limiter 분류의 부분 사례 |
## 컨텍스트
ca-tmpl outbound retry/timeout 결정의 **사례 근거**. Stripe 는 retry 정책과 idempotency 를 결합한 대표 사례 — 단, company-case-study 강도. 공식 best practice 로 격상 금지 (CLAUDE.md §5).
## 출처 / Source
- Stripe Engineering blog "Scaling your API with rate limiters": https://stripe.com/blog/rate-limiters
- 아카이브 URL: (미수집)
- 저자 / 조직: Paul Tarjan (Stripe Engineering)
- 발행일: 2017-08-31 (블로그 메타데이터 기준)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§Request rate limiter] "This rate limiter restricts each user to _N_ requests per second."
> [§Concurrent requests limiter] "Instead of 'You can use our API 1000 times a second', this rate limiter says 'You can only have 20 API requests in progress at the same time'."
> [§Fleet usage load shedder] "We always reserve a fraction of our infrastructure for critical requests."
> [§Worker utilization load shedder] "If a box is too busy to handle its request volume, it will slowly start shedding less-critical requests."
> [§Intro paragraph (idempotency context)] "If you're providing an API, chances are you've already experienced sudden increases in traffic that affect the quality of your service, potentially even leading to a service outage for all your users."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| STRIPE-RL-C1 | Stripe 는 production 에서 **request rate limiter** 를 운용하며 사용자별 초당 N requests 제한 | [§Request rate limiter] "This rate limiter restricts each user to _N_ requests per second." | `company-case-study` | Stripe API gateway 의 inbound 제어 사례 | 모든 API 가 동일한 1차원 rate limiting 만 쓴다는 뜻 아님 — 본 blog 가 4종 병행 명시 |
| STRIPE-RL-C2 | Stripe 는 **concurrent requests limiter** 도 운용 — "동시 진행 중인 API request 수" 를 사용자별로 제한 (예: 20 in progress) | [§Concurrent requests limiter] "Instead of 'You can use our API 1000 times a second', this rate limiter says 'You can only have 20 API requests in progress at the same time'." | `company-case-study` | 장시간 outbound 호출 (large LIST 등) 의 amplification 차단 사례 | "20" 이 universal default 라는 뜻 아님 — Stripe 내부 운영 수치 |
| STRIPE-RL-C3 | Stripe 는 **fleet usage load shedder** 로 critical request 용 infrastructure fraction 을 항상 예약 | [§Fleet usage load shedder] "We always reserve a fraction of our infrastructure for critical requests." | `company-case-study` | critical/non-critical traffic 분리 운영 사례 | "어떤 비율로 예약" 또는 "어떻게 critical 을 구분" 의 정확한 메커니즘은 본 인용에 없음 |
| STRIPE-RL-C4 | Stripe 는 **worker utilization load shedder** 로 box 가 과부하 시 less-critical request 부터 점진적으로 shed | [§Worker utilization load shedder] "If a box is too busy to handle its request volume, it will slowly start shedding less-critical requests." | `company-case-study` | per-instance overload 대응 사례 | "less-critical" 의 자동 분류 메커니즘은 본 인용에 없음 — 별도 출처 (Stripe API ref 의 priority tier) 필요 |
| STRIPE-RL-C5 | 이전 메모의 "Stripe SDK 가 409/429/500/502/503/504 + idempotency-key 자동 + full jitter 0.5x~1.5x + 2-3 attempts" 주장은 본 WebFetch (rate-limiters blog) 에서 **확인 안 됨** — Stripe API reference 의 별도 페이지 또는 stripe-java SDK 코드에서 검증 필요 | (negative finding — rate-limiters blog 에 retry attempt 수치 / Idempotency-Key 헤더 / backoff 산식 명시 없음) | `needs-confirmation` | retry 정책 / idempotency-key 자동 첨부 / backoff jitter 의 정확한 정책 인용 시 | 이 부정 확인은 Stripe SDK 가 그렇게 동작하지 **않는다** 는 뜻이 아니라, **본 blog 만으로는 증명 안 됨** — 별도 출처 (https://stripe.com/docs/api 의 Retries 절, stripe-java repo 의 `StripeResponseGetter`) 필요 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `STRIPE-RL-C1` ~ `C4`: Stripe production 의 **4종 rate limiter 분류** (request / concurrent / fleet usage / worker utilization) — Stripe 사례 한정
- **이 자료가 증명하지 않는 것**:
- "Stripe's SDKs automatically retry network errors and certain HTTP status codes (409, 429, 500, 502, 503, 504) with exponential backoff" 의 정확한 문구 — 본 blog 에 없음 (`STRIPE-RL-C5`). Stripe API reference Retries 절 별도 확인 필요
- "Idempotency-Key header automatically generated by the SDK" — 본 blog 에 없음. stripe-java repo 코드 별도 확인 필요
- "retry interval is randomized between 0.5 and 1.5 times the baseline (full jitter)" — 본 blog 에 없음. backoff 산식 별도 출처 필요
- "Retries are bounded: 2-3 attempts" — 본 blog 에 없음. SDK 코드 별도 확인 필요
- 4종 rate limiter 의 정확한 구현 (token bucket / sliding window / semaphore 등) — 본 인용 범위 밖
- **공식 best practice 로 격상 금지** (CLAUDE.md §5) — company-case-study 강도. 산업 표준이라고 말하려면 IETF draft / RFC / 다른 official-vendor-doc 와 corroborate 필요
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 "retry default-disabled" 결정 정당화는 본 blog 로는 **반례 (Stripe enabled-by-default)** 만 확인됨. Stripe 의 default-enabled 가 가능한 이유 (idempotency-key 자동 첨부 가정) 는 needs-confirmation
- 429 Retry-After header honor 정책 — 본 blog 에 없음. Resilience4j default 동작 별도 확인 + Stripe 정책 별도 출처 필요
- 4종 rate limiter 가 ca-tmpl inbound 측에 적용될 수 있는지는 outbound baseline 결정과 직교
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- ca-tmpl 결정과의 매핑 (해석):
- "retry 기본값 disabled" ↔ Stripe SDK 는 enabled-by-default (해석, `STRIPE-RL-C5` needs-confirmation). **반례**. ca-tmpl 이 보수적인 이유: provider 별 retry 정책이 다른 mixed 환경에서 default-on 은 amplification 위험.
- "idempotent method (GET/HEAD/PUT/DELETE) 만 default retry" ↔ Stripe 는 POST 도 idempotency-key 가 있으면 retry (해석, needs-confirmation). ca-tmpl 과 같은 원칙 (key 없는 POST 는 retry 금지).
- "circuit breaker metric outcome tag 만" ↔ Stripe blog 의 "load shedder" 4종 분류 (`STRIPE-RL-C1` ~ `C4`) 와 동일한 사상: 상태를 단순 fail/success 이상으로 분리 (해석).
- backoff 정책 (해석, `STRIPE-RL-C5` needs-confirmation):
- Stripe: full jitter `random(0.5x, 1.5x baseline)` (별도 출처 필요). ca-tmpl 이 Resilience4j 도입 시 `IntervalFunction.ofExponentialRandomBackoff` 활용 가능.
- retry-after header 처리 (해석, needs-confirmation):
- Stripe 429 → `Retry-After` 헤더 honor. ca-tmpl outbound 매핑에서도 429 를 retryable 로 분류 시 retry-after 를 read 해야 함 (Resilience4j Retry 는 default 로 안 함, 커스텀 필요).
- **취급 주의** (CLAUDE.md §5 + §11):
- Stripe 엔지니어링 블로그는 **사례**. "Stripe 가 그러니까 best-practice" 는 금지.
- idempotency-key + retry 결합은 IETF draft / Stripe API ref / Square API 에서 동일하게 권장 → 사실상 산업 표준 (해석 — 본 raw 만으로는 corroboration 미달, **UNSUPPORTED_DECISION 으로 분류**).
- 시사점: ca-tmpl 이 default-disabled 를 택한 것은 **provider 별 정책 차이를 인지한 conservative default**. Stripe 처럼 idempotency 가 보장된 환경에서는 활성화 권장 (해석).
## Related / 관련
- 같은 주제 다른 official-doc:
- [[raw/official-docs/outbound-spring-restclient-baseline]]
- [[raw/official-docs/outbound-webclient-vs-restclient-spring]]
- [[raw/official-docs/outbound-openfeign-declarative-client]]
- [[raw/official-docs/outbound-resilience4j-vs-spring-retry]]
- 인용하는 branch:
- [[raw/branch-notes/feature-outbound-http-client-baseline]]
- [[raw/branch-notes/feature-rate-limit-idempotency-contract]]
- 인용하는 wiki: (미작성)
@@ -0,0 +1,120 @@
---
title: Confluent — Kafka Connect Single Message Transforms (SMT) for Outbox Pattern
source_type: company-tech-blog
url: https://www.confluent.io/blog/kafka-connect-single-message-transformation-tutorial-with-examples/
archive_url:
status: needs-confirmation
confidence: medium
tags: [ca-outbox-pattern, confluent, kafka-connect, smt, cdc, company-case-study]
related_branches: [feature-domain-event-outbox-contract, feature-background-job-async-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Confluent — Kafka Connect SMT for Outbox Pattern
> Layer: `raw/company-tech-blogs/` — Confluent 블로그 "Kafka Connect Deep Dive Single Message Transforms" 의 SMT 정의/한계 발췌. ca-tmpl outbox 6대안 중 **대안 2 (Kafka Connect SMT 기반 outbox)** 의 사례.
>
> **출처 신뢰도 경고**: company-tech-blog. 공식 best practice 로 취급 금지 — 특정 벤더(Confluent)의 사례·관점일 뿐. SMT 가 outbox 의 표준 해법이라는 일반화는 금지.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-domain-event-outbox-contract]] | outbox 6대안 비교에서 "Kafka Connect SMT" 대안의 정의·한계 근거 (light-weight 변환에만 적합, 복잡 enrichment 는 stream processor 필요) |
| [[raw/branch-notes/feature-background-job-async-contract]] | outbox → topic 매핑을 application 코드 polling 으로 할지 vs Kafka Connect SMT 변환 layer 로 할지의 분기 근거 |
특정 branch 없이 foundational 조사로 수집한 경우:
- [[raw/project-notes/ca-skeleton-operational-contract]] — Contract #19 의 Domain Event / Outbox 항목에서 Confluent 스택 채택 안 함의 trade-off 근거 자료
## 컨텍스트 / 왜 저장했는지
ca-tmpl 의 SKIP LOCKED 결정에 대한 **대안 2: Kafka Connect 의 outbox SMT 를 이용한 변형**. Debezium 과 유사하지만 connector 선택지가 다르고, Confluent 가 권장하는 production pattern 확인용. 단 SMT 는 light-weight 변환에 한정됨을 본 자료가 직접 명시.
## 출처 / Source
- 원본 URL: https://www.confluent.io/blog/kafka-connect-single-message-transformation-tutorial-with-examples/
- 아카이브 URL: (미수집)
- 저자 / 조직: Confluent
- 발행일: rolling (Confluent blog)
- 마지막 확인일: 2026-05-27
- 보조 참고 (404 — 페이지 제거됨, 직접 검증 불가):
- `https://www.confluent.io/blog/messaging-microservices-mongodb-transactional-outbox/` — MongoDB transactional outbox (현재 404)
- `https://www.confluent.io/blog/event-driven-microservices-with-apache-kafka-the-transactional-outbox-pattern/` — outbox pattern (현재 404)
## 핵심 인용 / Key quotes (verbatim)
> [§SMT 정의] "Single Message Transforms (SMTs), and as the name suggests, it operates on every single message in your data pipeline as it passes through the Kafka Connect connector."
> [§SMT 동작 위치] "Source connectors pass records through the transformation before writing to the Kafka topic, and sink connectors pass records through the transformation before writing to the sink."
> [§Common uses] "Some common uses for transforms are: Renaming fields, Masking values, Routing records to topics based on a value, Converting or inserting timestamps into the record, Manipulating keys."
> [§한계 — 명시적 경고] "Transforms are a powerful concept, but they should only be used for simple, limited mutations of the data. Don't call out to external APIs or store state, and don't attempt any heavy processing."
> [§한계 — stream processor 권고] "Heavier transforms and data integrations should be handled in the stream processing layer between connectors using a stream processing solution such as Kafka Streams or KSQL."
> [§한계 — split/join 불가] "Transforms cannot split one message into many, nor can they join other streams for enrichment or do any kinds of aggregations. Such activities should be left to stream processors."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| OUTBOX-CFL-C1 | SMT 는 Kafka Connect connector 를 통과하는 모든 single message 에 동작하는 변환 메커니즘 | [§SMT 정의] "Single Message Transforms (SMTs)... operates on every single message in your data pipeline as it passes through the Kafka Connect connector." | `company-case-study` | Kafka Connect 기반 데이터 파이프라인의 message-level 변환 layer | "SMT 가 outbox 패턴의 표준 구현" 이라는 뜻은 아님 — 본 인용은 SMT 일반 정의 |
| OUTBOX-CFL-C2 | SMT 는 source connector 에서 Kafka topic 쓰기 전, sink connector 에서 sink 쓰기 전 적용된다 (양방향 hook 지점) | [§SMT 동작 위치] "Source connectors pass records through the transformation before writing to the Kafka topic, and sink connectors pass records through the transformation before writing to the sink." | `company-case-study` | Kafka Connect 의 source/sink connector 양쪽에서의 변환 시점 | "outbox row 를 topic 으로 변환하는 SMT 의 구체 예제" 본 인용에 미포함 |
| OUTBOX-CFL-C3 | SMT 의 일반적 용도: 필드 rename, 값 masking, value 기반 topic routing, timestamp 변환/삽입, key 조작 | [§Common uses] "Renaming fields, Masking values, Routing records to topics based on a value, Converting or inserting timestamps into the record, Manipulating keys." | `company-case-study` | SMT 의 적합 use case 카탈로그 | "outbox aggregate_type → topic name routing" 이 SMT 의 공식 예제라는 뜻은 아님 — 본 인용은 일반 카탈로그 |
| OUTBOX-CFL-C4 | SMT 는 simple/limited mutation 에만 사용해야 한다 — external API 호출, state 저장, heavy processing 금지 (벤더 명시 경고) | [§한계 — 명시적 경고] "Transforms are a powerful concept, but they should only be used for simple, limited mutations of the data. Don't call out to external APIs or store state, and don't attempt any heavy processing." | `company-case-study` | SMT 의 설계 한계 (Confluent 자체 권고) | "outbox 패턴이 SMT 만으로 완결된다" 는 뜻은 아님 — enrichment 필요 시 별도 stream processor 필수 |
| OUTBOX-CFL-C5 | Heavier transform / data integration 은 Kafka Streams 또는 KSQL 같은 stream processing layer 에서 처리해야 한다 (Confluent 권고) | [§한계 — stream processor 권고] "Heavier transforms and data integrations should be handled in the stream processing layer between connectors using a stream processing solution such as Kafka Streams or KSQL." | `company-case-study` | Confluent 스택 내 책임 분리 — SMT vs stream processor | "stream processor 없이 outbox 가 동작 불가" 는 아님 — 단순 변환은 SMT 로 충분 |
| OUTBOX-CFL-C6 | SMT 는 1 message → N messages split 불가, stream join 불가, aggregation 불가 (구조적 제약) | [§한계 — split/join 불가] "Transforms cannot split one message into many, nor can they join other streams for enrichment or do any kinds of aggregations." | `company-case-study` | SMT 의 구조적 한계 | outbox 의 1 row → 1 event 매핑이 항상 가능하다는 뜻은 아님 — 도메인에 따라 1:N 필요 시 SMT 부적합 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `OUTBOX-CFL-C1` ~ `C3`: Kafka Connect SMT 의 정의·동작 위치·일반 use case
- `OUTBOX-CFL-C4` ~ `C6`: SMT 의 명시적 한계 (Confluent 자체가 stream processor 와 책임 분리 권고)
- **이 자료가 증명하지 않는 것**:
- "outbox 패턴 = Kafka Connect SMT" 라는 등치 (본 페이지는 SMT 의 일반 튜토리얼, outbox 전용 가이드 아님)
- dual-write 문제의 정의 (본 인용은 SMT 한정)
- MongoDB / Postgres outbox 구체 구현 (보조 URL 404)
- Confluent Platform 의 EOS (exactly-once semantics) 보장 메커니즘
- SMT 가 application polling 보다 운영 비용이 낮다는 일반화
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 outbox row 변환이 simple mutation 범위인지 (`OUTBOX-CFL-C4` 기준)
- Kafka Connect cluster 운영 인력/지식 (Schema Registry 포함)
- aggregate_type → topic routing 패턴의 SMT 구체 config (`io.debezium.transforms.outbox.EventRouter` 별도 확인 필요)
- Confluent Cloud 라이선스/비용 vs self-hosted Kafka Connect 비용 비교
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- 적용 시나리오: Confluent Cloud / Confluent Platform 사용 조직, Debezium 외 다른 source connector(MongoDB Source Connector 등) 를 쓰는 경우.
- 장점:
- Kafka 생태계 안에서 outbox → topic 매핑이 깔끔 (`OUTBOX-CFL-C2` 의 hook 지점 활용)
- SMT 가 표준화돼 있어 connector 변경 시에도 변환 로직 재사용
- Schema Registry / Avro 같은 Confluent 스택과 자연스럽게 결합
- 단점:
- Confluent / Kafka Connect 종속도 증가
- SMT 는 light-weight 변환용 (`OUTBOX-CFL-C4` 벤더 명시). 복잡한 enrichment 는 별도 stream processor (ksqlDB / Kafka Streams) 필요 (`OUTBOX-CFL-C5`)
- 라이센스 / 비용 (Confluent Platform 일부 기능)
- ca-tmpl(SKIP LOCKED polling) 과의 차이:
- Debezium 케이스와 사실상 동일한 trade-off (CDC 기반, polling 제거)
- 추가로 Confluent 스택에 더 깊이 결합됨
- 운영 복잡도: 중상. Kafka Connect + Schema Registry 운영 부담.
- exactly-once / at-least-once 보장 수준: **at-least-once** 기본 (본 인용에 미명시 — 별도 확인 필요). Kafka transactions / idempotent producer 조합으로 EOS 시도 가능하나 outbox + SMT end-to-end EOS 는 별도 검증 필요.
- 외부 의존성 추가 여부: Kafka, Kafka Connect, (Schema Registry).
- 출처 신뢰도 재확인: 보조 URL 두 개가 404 (Confluent 페이지 제거). 인용 가능한 것은 SMT 튜토리얼 본문만 — 따라서 "Confluent 가 outbox 를 권장한다" 는 진술 자체가 본 자료로 증명 안 됨. **needs-confirmation** 유지.
## Related / 관련
- 같은 주제 다른 raw 자료:
- [[raw/official-docs/event-sourcing-vs-outbox-microservices-io]] (대안 4: event sourcing)
- [[raw/company-tech-blogs/outbox-netflix-domain-events-cdc]] (대안 6: Netflix DBLog)
- [[raw/company-tech-blogs/outbox-wix-engineering-debezium]] (대안 1 사례: Wix Debezium)
- [[raw/company-tech-blogs/domain-event-sourcing-vs-cqrs-greg-young]] (event/CQRS 정의 정리)
- 인용하는 branch:
- [[raw/branch-notes/feature-domain-event-outbox-contract]]
- [[raw/branch-notes/feature-background-job-async-contract]]
- 인용하는 wiki: (미작성)
@@ -0,0 +1,119 @@
---
title: Netflix — DBLog Generic CDC Framework (Domain Events / CDC at scale)
source_type: company-tech-blog
url: https://netflixtechblog.com/dblog-a-generic-change-data-capture-framework-69351fb9099b
archive_url: https://arxiv.org/abs/2010.12597
status: needs-confirmation
confidence: medium
tags: [ca-outbox-pattern, netflix, cdc, dblog, large-scale, company-case-study]
related_branches: [feature-domain-event-outbox-contract, feature-background-job-async-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Netflix — DBLog / CDC 기반 이벤트 전파 (극단 사례)
> Layer: `raw/company-tech-blogs/` — Netflix Tech Blog "DBLog: A Generic Change-Data-Capture Framework" + 동일 저자 arXiv 논문(2010.12597). ca-tmpl outbox 6대안 중 **대안 6 (Netflix DBLog — 극단 self-built CDC)** 의 사례.
>
> **출처 신뢰도 경고**: company-tech-blog. Netflix 사례는 **극단 규모 reference** 일 뿐 공식 best practice 아님. 일반 서비스에서 Netflix 식 결정을 모방할 이유 없음.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-domain-event-outbox-contract]] | outbox 6대안 비교에서 "극단 반대쪽 사례" 위치 — Netflix 조차 Debezium 부족하다고 판단해 self-built CDC framework 를 만들었다는 사실로 "ca-tmpl 의 단순 polling 으로 충분" 결정을 거꾸로 정당화 |
| [[raw/branch-notes/feature-background-job-async-contract]] | polling 부담의 상한선 — Netflix 규모에서 polling 이 비현실적이라는 reference |
특정 branch 없이 foundational 조사로 수집한 경우:
- [[raw/project-notes/ca-skeleton-operational-contract]] — Contract #19 의 outbox 결정 trade-off 매트릭스의 "초대규모" 끝점
## 컨텍스트 / 왜 저장했는지
ca-tmpl 의 SKIP LOCKED 결정에 대한 **극단 반대쪽 사례**: 초대규모에서는 polling 이 비현실적이고 self-built CDC framework 까지 만든 곳이 있음을 확인. "단순 polling 이면 충분" 을 거꾸로 증명하는 reference.
## 출처 / Source
- 원본 URL: https://netflixtechblog.com/dblog-a-generic-change-data-capture-framework-69351fb9099b (WebFetch 시 TLS 인증서 오류 — 직접 검증 실패, 본 인용은 arXiv 미러 기반)
- 아카이브 URL (arXiv preprint, 동일 저자): https://arxiv.org/abs/2010.12597
- 저자 / 조직: Andreas Andreakis, Ioannis Papapanagiotou (Netflix)
- 발행일: 2019-12 (블로그) / 2020-10 (arXiv)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [arXiv §Abstract / Introduction] "utilize Change-Data-Capture (CDC) in order to capture changed rows from a database's transaction log"
> [arXiv §Watermark approach] "DBLog utilizes a watermark based approach that allows us to interleave transaction log events with rows"
> [arXiv §Lock-free dump] "The watermark approach does not use locks and has minimum impact on the source"
> [arXiv §Flexible capture] "Selects can be triggered at any time on all tables, a specific table, or for specific primary keys"
> [arXiv §Chunked progress] "DBLog executes selects in chunks and tracks progress, allowing them to pause and resume"
> [arXiv §Production deployment] "DBLog is currently used in production by tens of microservices at Netflix"
원래 블로그 직접 인용(WebFetch 실패 → 인용 wording 미검증, **needs-confirmation**):
> [블로그 — 미검증] "DBLog is a Java-based framework that captures changes committed to a database from the transaction log and delivers them to consumers."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| NETFLIX-DBLOG-C1 | DBLog 는 CDC 를 활용해 데이터베이스 transaction log 에서 변경된 row 를 capture 한다 | [arXiv §Abstract] "utilize Change-Data-Capture (CDC) in order to capture changed rows from a database's transaction log" | `company-case-study` | log-based CDC 의 Netflix 자체 구현 정의 | "CDC 가 polling 보다 항상 우수" 라는 일반화 금지 — Netflix 규모 한정 |
| NETFLIX-DBLOG-C2 | DBLog 는 watermark 기반 방식으로 transaction log event 와 (dump 된) row 를 interleave 한다 | [arXiv §Watermark approach] "DBLog utilizes a watermark based approach that allows us to interleave transaction log events with rows" | `company-case-study` | log + dump 결합 시점의 일관성 보장 메커니즘 | watermark 방식이 다른 CDC 도구(Debezium 등) 의 기본 동작이라는 뜻은 아님 |
| NETFLIX-DBLOG-C3 | DBLog 의 watermark 방식은 lock 을 사용하지 않으며 source DB 에 최소한의 영향을 준다 | [arXiv §Lock-free dump] "The watermark approach does not use locks and has minimum impact on the source" | `company-case-study` | 초기 dump (bootstrap) 시 source DB 운영 영향 최소화 | "모든 CDC 가 lock-free" 라는 뜻은 아님 — Netflix 자체 구현 한정 |
| NETFLIX-DBLOG-C4 | DBLog 는 모든 테이블 / 특정 테이블 / 특정 primary key 에 대해 언제든지 select 를 트리거할 수 있다 | [arXiv §Flexible capture] "Selects can be triggered at any time on all tables, a specific table, or for specific primary keys" | `company-case-study` | DBLog 의 dump-on-demand 능력 | dump-on-demand 가 outbox 패턴의 일반적 요구사항이라는 뜻은 아님 |
| NETFLIX-DBLOG-C5 | DBLog 는 select 를 chunk 단위로 실행하고 progress 를 tracking 하여 pause/resume 가능 | [arXiv §Chunked progress] "DBLog executes selects in chunks and tracks progress, allowing them to pause and resume" | `company-case-study` | 장시간 dump 의 운영 안정성 메커니즘 | "chunk size 자동 조절" 또는 "back-pressure 자동 처리" 라는 뜻은 아님 |
| NETFLIX-DBLOG-C6 | DBLog 는 현재 Netflix 내부 수십 개의 microservice 에서 production 사용 중 (사례 규모의 reference) | [arXiv §Production deployment] "DBLog is currently used in production by tens of microservices at Netflix" | `company-case-study` | Netflix 내부 production 사례 규모 | "다른 회사에서 동일하게 운영 가능" 이라는 뜻은 아님 — Netflix 인프라 결합 가정 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `NETFLIX-DBLOG-C1` ~ `C5`: Netflix DBLog 의 정의·watermark·lock-free dump·chunked progress 등 기술 메커니즘
- `NETFLIX-DBLOG-C6`: Netflix 내부 production 규모 (수십 microservice) 의 사례 reference
- **이 자료가 증명하지 않는 것**:
- "CDC 가 polling 보다 모든 환경에서 우수" 라는 일반화 (본 자료는 Netflix 규모 사례 한정)
- at-least-once delivery semantics 의 명시적 보장 (arXiv abstract 에 직접 인용 없음 — blog 본문 미검증)
- DBLog 의 오픈소스 가용성 / 외부 조직 채택 가능성
- Kafka 와의 통합 디테일 (consumer 측 구체 구현)
- 일반 기업이 Debezium 으로 동일 효과를 달성할 수 있는지의 직접 비교
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 write throughput 이 polling 부담을 일으키는 임계점인지 (Netflix 규모와 거리)
- 본 자료를 "polling 의 비현실성" 의 reference 로 인용할 때 ca-tmpl 규모와의 명시적 차이 표기
- 블로그 원문 wording 검증 (현재 WebFetch TLS 실패 → archive.org 재시도 필요)
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- 적용 시나리오: write throughput 이 극단적으로 크고 (수십만 TPS), downstream fan-out 이 매우 많은 환경. polling 은 DB 자체에 부담.
- 장점:
- polling 부하 0
- lock-free dump (`NETFLIX-DBLOG-C3`) — 기존 row 초기 적재도 source DB 부담 최소화
- 자체 framework 이므로 Netflix 인프라(Kafka, EVCache 등) 와 깊게 결합
- 단점:
- **자체 framework 유지가 가능한 조직 규모가 전제** (Debezium 조차 부족하다고 판단한 케이스)
- 일반 기업이 이 패턴을 모방하는 것은 비현실적
- ca-tmpl(SKIP LOCKED polling) 과의 차이:
- 스케일 차이가 3-4 자릿수. ca-tmpl 은 단순 polling 으로 충분한 영역.
- "polling 은 안 쓴다 / CDC 도 부족해서 직접 만든다" 라는 극단 위치
- 운영 복잡도: 매우 높음.
- exactly-once / at-least-once 보장 수준: 일반 CDC 통념 상 at-least-once 가정 (본 인용에서는 직접 증명 안 됨 — needs-confirmation).
- 외부 의존성 추가 여부: 자체 CDC framework + Kafka. 사실상 자체 인프라 스택.
- 시사점: ca-tmpl 같은 일반 서비스에서 Netflix 식 결정을 모방할 이유 없음. **"단순 polling 이면 충분" 임을 거꾸로 증명** 하는 reference.
## Related / 관련
- 같은 주제 다른 raw 자료:
- [[raw/official-docs/event-sourcing-vs-outbox-microservices-io]] (대안 4: event sourcing)
- [[raw/company-tech-blogs/outbox-confluent-kafka-connect-smt]] (대안 2: Confluent SMT)
- [[raw/company-tech-blogs/outbox-wix-engineering-debezium]] (대안 1 사례: Wix Debezium)
- [[raw/company-tech-blogs/domain-event-sourcing-vs-cqrs-greg-young]] (event/CQRS 정의 정리)
- 인용하는 branch:
- [[raw/branch-notes/feature-domain-event-outbox-contract]]
- [[raw/branch-notes/feature-background-job-async-contract]]
- 인용하는 wiki: (미작성)
@@ -0,0 +1,125 @@
---
title: Wix Engineering — Debezium / CDC production 사례 (인용 검증 실패)
source_type: company-tech-blog
url: https://medium.com/wix-engineering/how-wix-uses-debezium-and-kafka-for-data-replication-and-cdc-cdce0c6b3cd1
archive_url:
status: needs-confirmation
confidence: low
tags: [ca-outbox-pattern, wix, debezium, cdc, production-case, company-case-study, unverified-source]
related_branches: [feature-domain-event-outbox-contract, feature-background-job-async-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Wix Engineering — Debezium / CDC 사례 (직접 검증 실패)
> Layer: `raw/company-tech-blogs/` — Wix Engineering Medium blog. ca-tmpl outbox 6대안 중 **대안 1 (Debezium CDC) 의 production 사례** 로 보관.
>
> **출처 신뢰도 경고 — 중요**: company-tech-blog + **원본 URL 4개 모두 404 (페이지 제거됨)**. WebFetch 시점(2026-05-27) 에 medium.com Wix Engineering 의 해당 글 + 보조 검색 결과(wix.engineering/post/scaling-to-the-moon-mysql-debezium-kafka, /post/exactly-once-message-delivery-from-mysql-to-kafka, /post/wix-greyhound-debezium-kafka) 가 모두 404. wix.engineering/blog 메인의 최근 5페이지에도 Debezium/CDC 관련 article 부재. 따라서 본 문서의 인용은 **모두 미검증 (needs-confirmation)**, 본 자료를 outbox 결정 근거로 인용 시 별도 archive.org 스냅샷 또는 컨퍼런스 발표 자료로 보강 필요.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-domain-event-outbox-contract]] | outbox 6대안 비교에서 "Debezium CDC production 사례" 위치 — 단, 본 자료가 미검증이므로 인용 시 보조 자료 필수 |
| [[raw/branch-notes/feature-background-job-async-contract]] | application polling vs CDC-based propagation 의 production 운영 비용 비교 reference (미검증) |
특정 branch 없이 foundational 조사로 수집한 경우:
- [[raw/project-notes/ca-skeleton-operational-contract]] — Contract #19 의 outbox 결정에서 "CDC 가 dual-write 를 제거한다" 일반 주장의 사례 후보 (검증 미달로 1차 근거 부적격)
## 컨텍스트 / 왜 저장했는지
ca-tmpl 의 SKIP LOCKED 결정에 대한 **CDC 기반 outbox 의 실제 production 운영 사례** 후보. Debezium 이 단순 토이가 아니라 대규모 production 에서 어떻게 굴러가는지 확인 목적. 단 원본 URL 이 모두 제거되어 wording 검증 불가.
## 출처 / Source
- 원본 URL: https://medium.com/wix-engineering/how-wix-uses-debezium-and-kafka-for-data-replication-and-cdc-cdce0c6b3cd1 (HTTP 404 — 2026-05-27 확인)
- 시도한 보조 URL (모두 404):
- https://medium.com/wix-engineering/scaling-to-the-moon-mysql-debezium-kafka-event-streaming-9a07ade5410d
- https://www.wix.engineering/post/scaling-to-the-moon-mysql-debezium-kafka
- https://www.wix.engineering/post/exactly-once-message-delivery-from-mysql-to-kafka
- https://www.wix.engineering/post/wix-greyhound-debezium-kafka
- https://www.wix.engineering/post/wix-architecture-at-scale-mysql
- https://www.wix.engineering/post/event-driven-architecture-5-pitfalls-to-avoid
- wix.engineering/blog 메인 (페이지 1) 에서도 Debezium/CDC/Kafka outbox 관련 최근 글 부재 (2026-05-27 확인)
- 아카이브 URL: (미수집 — archive.org 재시도 필요)
- 저자 / 조직: Wix Engineering (Medium)
- 발행일: 불명 (페이지 제거)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim — 모두 미검증)
> **경고**: 아래 인용은 원본 URL 이 검증 시점에 404 인 상태에서 보관 중인 사전 정리본. 원문 wording 검증 불가 → strength = `needs-confirmation`.
> [§미검증 — 보관용 wording] "We use Debezium to capture changes from our MySQL databases and stream them to Kafka, decoupling write paths from downstream consumers."
> [§미검증 — 보관용 wording] "Application services do not publish to Kafka directly; they write to their own database, and Debezium handles propagation."
> [§미검증 — 보관용 wording] "This avoids dual-writes and ensures that any change persisted in the source DB will eventually appear in Kafka."
## Claims Extracted / 추출된 주장
> **중요**: 본 자료는 원본 URL 404 로 인용 검증 실패 상태. 아래 claim 들은 모두 strength `needs-confirmation` — 적용 결정의 근거로 단독 인용 금지. 별도 검증된 자료 (`raw/company-tech-blogs/outbox-confluent-kafka-connect-smt`, `raw/official-docs/event-sourcing-vs-outbox-microservices-io`, 또는 Debezium 공식 문서) 와 조합 필요.
| Claim ID | Claim (이 자료가 직접 말한다고 보관된 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| WIX-DEBEZIUM-C1 | Wix 는 Debezium 으로 MySQL 변경을 capture 하여 Kafka 로 stream 하고, 이를 통해 write path 와 downstream consumer 를 decouple 한다 (미검증) | [§미검증 — 보관용 wording] "We use Debezium to capture changes from our MySQL databases and stream them to Kafka, decoupling write paths from downstream consumers." | `needs-confirmation` | (조건부) Wix 의 production 아키텍처 사례 — wording 검증 후 `company-case-study` 로 승급 가능 | "Debezium 이 outbox 의 표준 해법" 이라는 일반화 금지. 본 자료가 검증되어도 단일 사례. |
| WIX-DEBEZIUM-C2 | Wix 의 application service 는 Kafka 에 직접 publish 하지 않고 자신의 DB 에만 쓰며, Debezium 이 propagation 을 처리 (미검증) | [§미검증 — 보관용 wording] "Application services do not publish to Kafka directly; they write to their own database, and Debezium handles propagation." | `needs-confirmation` | (조건부) outbox/CDC 패턴의 "DB-only write" 원칙의 production 적용 reference | application 측 idempotency 요구사항이 사라진다는 뜻은 아님 — at-least-once 기본 가정 별도 |
| WIX-DEBEZIUM-C3 | 이 방식이 dual-writes 를 회피하며 source DB 에 persist 된 변경이 결국 Kafka 에 나타나는 것을 보장 (미검증) | [§미검증 — 보관용 wording] "This avoids dual-writes and ensures that any change persisted in the source DB will eventually appear in Kafka." | `needs-confirmation` | (조건부) CDC 기반 outbox 의 dual-write 회피 효과 사례 | "exactly-once" 가 아니라 "eventually" — 본 wording 도 eventual consistency 한정 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- **없음** (원본 URL 404 — 모든 claim 이 미검증 상태)
- **이 자료가 증명하지 않는 것**:
- Debezium 이 outbox 의 표준 해법이라는 일반화
- dual-write 회피의 일반론 (Wix 사례 한정, 게다가 검증 실패)
- Debezium connector 운영의 구체 trade-off (schema migration, WAL 적체 등)
- Wix 가 application polling 대신 CDC 를 선택한 의사결정 과정
- "조직 규모 → polling vs CDC 결정" 의 일반 규칙
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- **원본 wording 의 archive.org 스냅샷 수집** (필수 — 인용 검증)
- Wix 의 컨퍼런스 발표 (KubeCon, Devoxx 등) 에서 동일 주장 보강
- Debezium 공식 문서 (`debezium.io/documentation/reference/`) 의 outbox EventRouter 섹션과 비교
- ca-tmpl 의 write throughput 이 Wix 사례와 같은 CDC 도입 임계점인지 평가
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석. **단, 본 자료 자체가 미검증이므로 아래 메모도 보강 자료 없이 단독 사용 금지.**
- 적용 시나리오: 마이크로서비스 다수, write 트래픽이 크고 downstream consumer 가 많은 조직.
- 장점 (Wix 가 언급했다고 보관된 것 — 미검증):
- dual-write 제거 → 신뢰성 향상
- downstream 추가가 쉬움 (새 consumer 만 붙이면 됨, source 코드 무변경)
- 분석/검색 인덱스 등 secondary store 에 자동 sync
- 단점 (production 운영하며 드러나는 것 — 일반 통념):
- Debezium connector 자체의 운영 (offset, schema, HA) 부담이 큼
- schema migration 시 connector 영향 검토 필요
- large transactions / long-running transactions 가 WAL 적체 → lag 유발
- ca-tmpl(SKIP LOCKED polling) 과의 차이:
- Wix 규모면 polling overhead 가 비현실적 → CDC 가 사실상 필수
- ca-tmpl 규모(템플릿 수준) 에서는 Wix 식 인프라가 **과투자**
- 운영 복잡도: 높음. Kafka Connect cluster 전담 운영 인력/지식 필요.
- exactly-once / at-least-once 보장 수준: at-least-once. consumer idempotency 전제 (본 인용에서 직접 증명 안 됨).
- 외부 의존성 추가 여부: Kafka, Kafka Connect, Debezium, (Schema Registry).
- 시사점: "조직 규모와 downstream fan-out 수" 가 polling vs CDC 선택의 결정 변수 — 단, 본 자료가 검증 실패이므로 이 주장의 근거로는 Netflix DBLog (`outbox-netflix-domain-events-cdc`) + Debezium 공식 문서 조합을 사용해야 함.
## Related / 관련
- 같은 주제 다른 raw 자료:
- [[raw/official-docs/event-sourcing-vs-outbox-microservices-io]] (대안 4: event sourcing — 검증됨)
- [[raw/company-tech-blogs/outbox-confluent-kafka-connect-smt]] (대안 2: Confluent SMT — 부분 검증)
- [[raw/company-tech-blogs/outbox-netflix-domain-events-cdc]] (대안 6: Netflix DBLog — arXiv 검증)
- [[raw/company-tech-blogs/domain-event-sourcing-vs-cqrs-greg-young]] (event/CQRS 정의 정리)
- 인용하는 branch:
- [[raw/branch-notes/feature-domain-event-outbox-contract]]
- [[raw/branch-notes/feature-background-job-async-contract]]
- 인용하는 wiki: (미작성)
## Followup TODO
- [ ] archive.org 에서 원본 4개 URL 스냅샷 검색 → wording 검증
- [ ] Wix 의 컨퍼런스 발표 (YouTube / SlideShare) 검색하여 동일 주장 보강
- [ ] Debezium 공식 문서 outbox EventRouter 섹션을 별도 `raw/official-docs/debezium-outbox-event-router.md` 로 분리하여 1차 근거 확보
@@ -0,0 +1,117 @@
---
title: 우아한형제들 — 도메인 이벤트 발행 / Outbox 패턴 적용 사례
source_type: company-tech-blog
url: https://techblog.woowahan.com/
archive_url:
status: raw
confidence: low
tags: [ca-outbox-pattern, woowahan, korean-techblog, polling, company-tech-blog]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-domain-event-outbox-contract, feature-background-job-async-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# 우아한형제들 — Outbox 패턴 사례
> Layer: `raw/company-tech-blogs/` — 우아한형제들 기술블로그의 outbox 패턴 사례 **원문 발췌·출처 기록**.
> ca-tmpl 이 채택한 **DB polling + SKIP LOCKED 방식**과 가장 가까운 한국 사례 후보. 같은 결정을 한 조직이 어떤 trade-off 를 인정하고 갔는지 확인하는 corroboration 자료.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-domain-event-outbox-contract]] | Topic 3 — Outbox Pattern baseline (SKIP LOCKED polling) 의 한국 사례 corroboration — 단 인용 wording 미확인 시 corroboration 강도 제한 |
| [[raw/branch-notes/feature-background-job-async-contract]] | Background job 발행에서 JPA + Spring Boot + Kafka publisher 조합의 사례 자료 (정확 URL 보강 필요) |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §18 / §19 의 outbox 채택 — 한국 production 환경에서 동일 결정을 한 사례 reference |
## 컨텍스트
ca-tmpl 이 채택한 **DB polling + SKIP LOCKED 방식**과 가장 가까운 한국 사례 후보. 같은 결정을 한 조직이 어떤 trade-off 를 인정하고 갔는지 확인. 단, 본 raw 의 인용은 2026-05-22 작성 시점에 정확한 글 URL 을 확정하지 못한 상태로 다수 글의 공통 메시지 요약 형태이며, 2026-05-27 재검증에서도 정확 wording 확인이 불가하여 **company-tech-blog 사례로서의 가치보다 corroboration 한계가 더 크다**.
## 출처 / Source
- 원본 URL (블로그 메인): https://techblog.woowahan.com/
- 대상 글 URL: **미확정** — "MSA 환경에서의 이벤트 발행 / 트랜잭션 아웃박스" 류 글 다수에서 반복되는 메시지를 요약한 형태
- 아카이브 URL: (미수집)
- 저자 / 조직: 우아한형제들 기술블로그 (Woowahan Tech Blog)
- 발행일: 미확인 (글 URL 미확정)
- 마지막 확인일 (capture): 2026-05-22
- 마지막 재검증 시도: 2026-05-27
- **[2026-05-25 capture]**: user 가 2026-05-22 수집한 paraphrase 요약 원형 유지.
- **재검증 결과 [2026-05-27 verified attempt]**: 블로그 landing page `https://techblog.woowahan.com/` WebFetch 성공. 그러나 landing 에 노출된 최신 featured 글 (RAG chatbot / AI harness / multilingual / React 19 / review LLM / MCP stdio / incident lifecycle) 중 outbox / 도메인 이벤트 발행 / SKIP LOCKED / Kafka publisher / 이벤트 발행 키워드와 직접 매칭되는 글 **없음**. 단일 글 URL 확정 실패 — 카테고리 archive (Backend / Infra) 또는 검색 API 필요.
- **재검증 한계 + Strength 정책**: 원본 글 URL 여전히 미확정 → 본 자료는 source_type 을 `company-tech-blog` 로 분류하지만 **단일 글 인용으로 corroborate 불가**. 추출된 모든 claim 은 `needs-confirmation` Strength **유지** (Strength 상향 없음). 본 자료는 ca-tmpl 결정의 official 정당화로 사용 불가 — microservices.io / Postgres 공식이 1차, 본 자료는 단일 글 + verbatim 확보 전까지 보조 corroboration 으로도 사용 보류.
- **company-tech-blog evidence 는 official best practice 가 아님**: 본 자료는 official-standard / official-vendor-doc / official-reference 가 아니므로 "우아한형제들이 채택했으므로 best practice" 라는 추론 금지.
## 핵심 인용 / Key quotes (paraphrase / 요약, 2026-05-22 user 수집본 — verbatim 아님)
> **주의**: 아래는 verbatim 인용이 아니라 우아한형제들 기술블로그 다수 글에서 반복되는 메시지의 user paraphrase 요약. wiki 승급 전 단일 글 URL + verbatim 확보 필수.
> (paraphrase) "단일 트랜잭션 안에서 비즈니스 변경과 이벤트 저장을 묶어 두고, 별도 publisher 가 그 이벤트를 외부로 발행한다."
> (paraphrase) "Kafka 에 직접 publish 하지 않는 이유는 dual-write 문제 때문이다."
> (paraphrase) "polling 주기와 SKIP LOCKED 기반 다중 publisher 인스턴스로 처리량을 확보한다."
## Claims Extracted / 추출된 주장
> **중요**: 본 raw 는 verbatim 인용이 아니라 paraphrase 요약만 보유. 모든 claim 은 `needs-confirmation`. 단일 글 URL + verbatim 확보 전까지 corroboration 으로 사용 불가.
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| OUTBOX-WW-C1 | 우아한형제들의 일부 도메인은 단일 트랜잭션에서 비즈니스 변경 + 이벤트 저장 (outbox) 을 묶고 별도 publisher 가 외부 발행하는 패턴을 사용한다 (paraphrase) | (paraphrase) "단일 트랜잭션 안에서 비즈니스 변경과 이벤트 저장을 묶어 두고, 별도 publisher 가 그 이벤트를 외부로 발행한다." | `needs-confirmation` | 우아한형제들 일부 도메인 (정확한 글 / 시스템 범위 미확인) | "우아한형제들 전사 표준" 이라는 일반화는 본 자료로 보장 안 됨 — 단일 글 paraphrase 단계 |
| OUTBOX-WW-C2 | Kafka 직접 publish 를 피한 이유로 dual-write 문제를 언급 (paraphrase) | (paraphrase) "Kafka 에 직접 publish 하지 않는 이유는 dual-write 문제 때문이다." | `needs-confirmation` | outbox 도입 결정 논리 | dual-write 문제의 정의 / 실제 incident 가 있었는지 본 paraphrase 에 없음 — 일반적 reasoning 으로 추정 |
| OUTBOX-WW-C3 | polling interval + SKIP LOCKED 기반 다중 publisher 인스턴스로 처리량을 확보 (paraphrase) | (paraphrase) "polling 주기와 SKIP LOCKED 기반 다중 publisher 인스턴스로 처리량을 확보한다." | `needs-confirmation` | polling Message Relay 변형 | 정확한 interval / 인스턴스 수 / TPS 수치는 본 paraphrase 에 없음 |
### Strength 정책
본 문서의 모든 claim 은 `needs-confirmation`. 추가로 다음 두 제약:
1. verbatim 인용이 아닌 paraphrase → corroboration 강도가 일반 company-case-study 보다 약함
2. 단일 글 URL 미확정 → "우아한형제들이 X 라고 말했다" 라는 단정 자체가 불가, "다수 글의 공통 메시지로 보인다" 수준의 약한 진술만 가능
**company-tech-blog evidence 는 official best practice 가 아님** — 본 자료는 ca-tmpl 의 SKIP LOCKED polling 채택을 official 로 정당화하지 않으며, microservices.io / Postgres 공식 등 official-vendor-doc 으로 별도 정당화 필요.
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것** (단일 글 URL + verbatim 확보 시):
- 한국 production 환경의 일부 조직이 SKIP LOCKED polling 패턴을 채택한 사례가 존재한다는 약한 corroboration
- **이 자료가 증명하지 않는 것**:
- "우아한형제들 전사 표준" 또는 "한국 fintech / commerce 일반 표준" 같은 일반화
- 정확한 polling interval / 인스턴스 수 / TPS / lag 수치
- 우아한형제들이 dual-write 문제를 실제 incident 로 겪었는지 (이론적 reasoning vs 운영 경험 구분 불가)
- SKIP LOCKED polling 이 best practice 라는 명제 (company-tech-blog 는 official best practice 가 아님)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- 본 raw 를 corroboration 으로 활용하려면 **단일 글 URL + verbatim 인용 확보** 가 선행 — 현 상태로는 wiki 승급 불가
- corroboration 이 확보되더라도 official 정당화는 microservices.io / Postgres 공식 자료가 1차, 본 자료는 한국 사례 보조
## 메모 / Notes (내 프로젝트 해석 — 직접 인용 아님)
- 적용 시나리오 (사례 가설): Kafka 도입은 했으나 Debezium / Kafka Connect 까지는 운영하지 않는 조직. JPA / Spring Boot 기반 도메인이 많은 환경.
- 장점 (한국 기술블로그들이 공통적으로 강조 — paraphrase):
- 기존 RDB + JPA 스택 그대로 활용
- 운영 인력이 SQL 로 outbox 상태를 직접 진단 가능 (장애 시 큰 이점)
- Kafka Connect 운영 부담 없음
- 단점:
- polling lag (보통 수백 ms ~ 수 s — 사례 미검증 추정)
- outbox 테이블 hot row 관리 (archive, partition, vacuum)
- publisher 인스턴스 장애 시 lag 가시화 필요
- ca-tmpl (SKIP LOCKED polling) 과의 차이: **사실상 동일 패턴 추정**. ca-tmpl 이 같은 진영의 결정을 따르고 있다는 약한 corroboration (verbatim 확보 시).
- 운영 복잡도: 낮음~중간.
- exactly-once / at-least-once 보장 수준: at-least-once. consumer 측 idempotency 필수.
- 외부 의존성 추가 여부: Kafka (broker) 만. Kafka Connect / Debezium 불필요.
- 주의: 본 raw 는 인용 wording 이 paraphrase / `needs-confirmation` 이므로, `/ingest` 전에 실제 글 URL 1-2개를 찾아 verbatim 으로 보강 필요.
- 대안 그룹 (Topic 3 — Outbox Pattern, 대안 6종): **SKIP LOCKED polling** / Debezium CDC / Kafka Connect SMT / Dual-write [금지] / Event sourcing / Spring @TransactionalEventListener
- 본 source 의 위치: ca-tmpl baseline 사례 후보 — 우아한형제들 polling (단, verbatim 미확보로 약한 corroboration)
## Related / 관련
- 같은 주제 official-doc (이쪽이 1차 근거):
- [[raw/official-docs/outbox-skip-locked-microservices-io]] (baseline 정의)
- [[raw/official-docs/skip-locked-postgres-docs]] (메커니즘)
- [[raw/official-docs/outbox-debezium-official-docs]] (대안: CDC)
- [[raw/official-docs/dual-write-antipattern-microservices-io]] (negative reference)
- 인용하는 branch / project:
- [[raw/branch-notes/feature-domain-event-outbox-contract]]
- [[raw/branch-notes/feature-background-job-async-contract]]
- [[raw/project-notes/ca-skeleton-operational-contract]]
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,136 @@
---
title: company-tech-blog / Percona — Storing UUID Values in MySQL (2014, Karthik Appigatla)
source_type: company-tech-blog
url: https://www.percona.com/blog/store-uuid-optimized-way/
archive_url:
vendor: Percona
author: Karthik Appigatla
related_branches: [feature-resource-identifier-contract]
related_projects: [ca-skeleton]
tags: [company-tech-blog, ca-skeleton, persistence, mysql, uuid-storage, clustered-index]
created: 2026-05-31
---
# company-tech-blog / Percona — Storing UUID Values in MySQL
> Layer: `raw/company-tech-blogs/` — Percona 엔지니어링 블로그 원문 발췌·출처 기록.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/` 에 `source-summary-template` 형식으로 별도 작성. 원본은 raw에 영구 보관.
## Parent / 활용 branch
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-resource-identifier-contract]] | D10 (DB primary key 컬럼 정책): random UUID v4 를 `varchar(36)` 로 저장 시 InnoDB clustered index 단편화 + 디스크 비용이 `binary(16)` ordered UUID 대비 50% 더 크다는 정량 근거. D7 (timestamp leak): ordered UUID v1 reorder 방식의 시간 정보 노출 부작용 언급. |
## 출처 / Source
- 원본 URL: https://www.percona.com/blog/store-uuid-optimized-way/
- 대체 URL: https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/
- 아카이브 URL: (미보관)
- 저자 / 조직: Karthik Appigatla / Percona
- 발행일: 2014-12-19
- 마지막 확인일: 2026-05-31
- 후속 포스트 언급: "a more up-to-date follow-up post" — Storing UUID and Generated Columns (MySQL 8.0 `UUID_TO_BIN` / `BIN_TO_UUID` 함수 포함)
## 왜 저장했는지 / Why archived
Percona 는 MySQL 전문 컨설팅사로, InnoDB 내부 동작에 관한 정량 벤치마크 신뢰도가 높다.
`feature-resource-identifier-contract` 의 D10 결정(DB primary key 컬럼 타입)은 MySQL InnoDB clustered index 특성에 근거한 `binary(16)` vs `varchar(36)` 비교가 필요하며, 이 포스트가 25M 레코드 벤치마크로 그 근거를 제공한다.
단, 이 자료는 2014년 기준 UUID v1 재정렬 전략이며, MySQL 8.0 의 `UUID_TO_BIN(..., 1)` 내장 함수와 UUID v7 (RFC 9562, 2024) 은 후속 자료로 보강 필요.
## 핵심 인용 / Key quotes (verbatim, 5개 — Self-Grep 통과)
> [§Problems with UUID] "UUID has 36 characters which make it bulky."
> — 위치: clean text line 1, §Problems with UUID 단락
> [§Problems with UUID] "InnoDB stores data in the PRIMARY KEY order and all the secondary keys also contain PRIMARY KEY. So having UUID as PRIMARY KEY makes the index bigger which cannot be fit into the memory"
> — 위치: clean text line 1, §Problems with UUID 단락
> [§Benchmarking / Total Size] "The size of the UUID table is almost 50% bigger than Ordered UUID table and 30% bigger than the table with BIGINT as PRIMARY KEY."
> — 위치: clean text line 1, §Benchmarking 결과 요약 단락
> [§Benchmarking / Time taken] "For the table with UUID as PRIMARY KEY, you can notice that as the table grows big, the time taken to insert rows is increasing almost linearly. Whereas for other tables, the time taken is almost constant."
> — 위치: clean text line 1, §Time taken 단락
> [§Benchmarking / Total Size] "Comparing the Ordered UUID table BIGINT table, the time is taken to insert rows and the size are almost the same. But they may vary slightly based on the index structure."
> — 위치: clean text line 1, §Benchmarking 결과 비교 단락
### Self-Grep Verification 결과
임시 파일: `/tmp/percona-uuid-clean.txt` (HTML에서 추출한 단일 행 plain text)
```bash
grep -oF "UUID has 36 characters which make it bulky" /tmp/percona-uuid-clean.txt | wc -l
# Observed: 1 (PASS)
grep -oF "InnoDB stores data in the PRIMARY KEY order and all the secondary keys also contain PRIMARY KEY" /tmp/percona-uuid-clean.txt | wc -l
# Observed: 1 (PASS)
grep -oF "The size of the UUID table is almost 50% bigger than Ordered UUID table and 30% bigger than the table with BIGINT as PRIMARY KEY" /tmp/percona-uuid-clean.txt | wc -l
# Observed: 1 (PASS)
grep -oF "the time taken to insert rows is increasing almost linearly" /tmp/percona-uuid-clean.txt | wc -l
# Observed: 1 (PASS)
grep -oF "Comparing the Ordered UUID table BIGINT table, the time is taken to insert rows and the size are almost the same" /tmp/percona-uuid-clean.txt | wc -l
# Observed: 1 (PASS)
```
검증 V: 5 | 일치 P: 5 | 폐기 D: 0 | 정정 C: 0
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| PERCONA-UUID-C1 | UUID 를 `char(36)` 로 저장하면 36자 크기 때문에 인덱스가 커진다 | [§Problems with UUID] "UUID has 36 characters which make it bulky." | `company-case-study` | MySQL InnoDB, UUID v1/v4 를 char 형식으로 저장하는 경우 | varchar(36) 과 char(36) 의 차이; PostgreSQL uuid native type 의 저장 비용; binary(16) 의 명시적 크기 비교(이 문장만으로는 미증명) |
| PERCONA-UUID-C2 | InnoDB 는 PRIMARY KEY 순서로 데이터를 저장하고, 모든 secondary key 는 PRIMARY KEY 를 포함한다 — UUID PK 는 모든 secondary index 를 크게 만들어 메모리에 올리기 어렵게 한다 | [§Problems with UUID] "InnoDB stores data in the PRIMARY KEY order and all the secondary keys also contain PRIMARY KEY. So having UUID as PRIMARY KEY makes the index bigger which cannot be fit into the memory" | `company-case-study` | MySQL InnoDB clustered index 구조 (MySQL 5.x/8.x) | MariaDB / PostgreSQL / TokuDB 등 다른 엔진의 동일 동작; secondary index 크기의 정확한 배율(인용만으로는 수치 없음) |
| PERCONA-UUID-C3 | 25M 레코드 벤치마크: random UUID PK 테이블의 총 크기는 ordered UUID 테이블보다 50% 크고, BIGINT PK 테이블보다 30% 크다 | [§Benchmarking] "The size of the UUID table is almost 50% bigger than Ordered UUID table and 30% bigger than the table with BIGINT as PRIMARY KEY." | `company-case-study` | MySQL 5.x InnoDB, 25M 행, 특정 스키마(events 테이블 구조 명시됨) | 다른 스키마·데이터 분포·MySQL 버전에서의 재현 보장; PostgreSQL 에서의 동일 수치; UUID v7 (RFC 9562) 에서의 동일 수치(이 포스트는 v1 재정렬 전략) |
| PERCONA-UUID-C4 | random UUID PK 에서는 테이블이 커질수록 삽입 시간이 거의 선형적으로 증가하는 반면, ordered UUID / BIGINT PK 에서는 삽입 시간이 거의 일정하다 | [§Time taken] "For the table with UUID as PRIMARY KEY, you can notice that as the table grows big, the time taken to insert rows is increasing almost linearly. Whereas for other tables, the time taken is almost constant." | `company-case-study` | MySQL InnoDB, 25K 행 단위 배치 삽입, 25M 레코드까지 측정 | SSD vs HDD 환경 차이; buffer pool 크기 설정 영향; 동시 write 부하 환경; 단건 INSERT vs batch INSERT 차이 |
| PERCONA-UUID-C5 | Ordered UUID 테이블과 BIGINT 테이블은 삽입 시간과 크기가 거의 동일하다 (index 구조에 따라 약간 차이 가능) | [§Benchmarking] "Comparing the Ordered UUID table BIGINT table, the time is taken to insert rows and the size are almost the same. But they may vary slightly based on the index structure." | `company-case-study` | MySQL InnoDB, 동일 벤치마크 조건 | ordered UUID 가 BIGINT 와 완전히 동등하다는 보장; MySQL 8.0 의 `UUID_TO_BIN(..., 1)` 빌트인 함수 사용 시의 동작; UUID v7 (RFC 9562) 을 binary(16) 으로 저장한 경우의 동작 |
### Strength 적용 이유
이 자료는 Percona 엔지니어링 블로그다. Percona 는 MySQL 전문 컨설팅사로 신뢰도가 높지만, 이 포스트는:
- 2014년 작성 (MySQL 5.x 기준, MySQL 8.0 이전)
- 특정 스키마 + 특정 하드웨어 환경의 단일 벤치마크
- 동료 검토(peer review) 된 공식 표준이 아님
따라서 모든 Claim 은 `company-case-study` 로 분류한다. MySQL InnoDB clustered index 구조(C2) 는 MySQL 공식 레퍼런스 매뉴얼로 별도 보강 시 `official-vendor-doc` 로 격상 가능.
## Usage Boundaries / 적용 경계
### 이 자료가 직접 증명하는 것
- `PERCONA-UUID-C2`: MySQL InnoDB 에서 secondary index 가 PK 를 포함한다는 구조적 사실 (D10 결정의 핵심 전제)
- `PERCONA-UUID-C3`: 25M 행 벤치마크에서 random UUID PK `binary(16)` vs ordered UUID `binary(16)` 의 50% 크기 차이 (D10 정량 근거)
- `PERCONA-UUID-C4`: random UUID 의 삽입 성능이 테이블 크기 증가와 함께 선형 저하하는 경향 (D10 index fragmentation 경고)
- `PERCONA-UUID-C5`: ordered UUID 와 BIGINT PK 의 성능·크기가 거의 동등함 (D10 trade-off: uuid 유니크성을 유지하면서 BIGINT 수준 성능 가능)
### 이 자료가 증명하지 않는 것
- **`varchar(36)` vs `binary(16)` 의 직접 크기 비교**: 벤치마크의 `events_uuid` 테이블은 이미 `binary(16)` 을 사용함 — char(36) 의 정량 비교는 이 포스트 범위 밖
- **PostgreSQL uuid native type 의 동작**: PostgreSQL 은 HEAP 기반 + 별도 MVCC 구조로 InnoDB clustered index 와 다름
- **MySQL 8.0 `UUID_TO_BIN(..., 1)` / `BIN_TO_UUID()` 빌트인 함수의 동작**: 2014년 포스트이며, 후속 포스트 참조 권고
- **UUID v7 (RFC 9562, 2024) 의 InnoDB 에서의 성능**: 이 포스트는 UUID v1 재정렬 전략. v7 은 native time-ordered 이므로 동일 원리가 적용되나, 벤치마크 미제공
- **`varchar(36)` vs `char(36)` 의 차이**: 이 포스트는 문제 제기에서 `char(36)` 을 언급하나 실제 벤치마크는 `binary(16)` 비교
- **TSID (64bit) vs binary(16) 의 성능 차이**: 이 포스트는 BIGINT vs binary(16) 비교는 있으나 TSID 의 ID 구조는 다름
### ca-skeleton D10 결정에 적용하려면 추가 확인이 필요한 것
- MySQL 8.0+ 에서의 `UUID_TO_BIN(UUID(), 1)` 를 사용한 UUID v7 저장 성능 (후속 Percona 포스트 또는 별도 벤치마크)
- PostgreSQL uuid native type 성능은 별도 PostgreSQL 레퍼런스 필요
- 실제 ca-skeleton 스키마에서 secondary index 수를 고려한 PK 비용 계산
## 메모 / Notes
- 이 포스트는 UUID v1 의 timestamp 부분을 재정렬하는 수동 방식을 제안함. MySQL 8.0 이후에는 `UUID_TO_BIN(UUID(), 1)` 가 동일 효과를 내장 함수로 제공.
- UUID v7 (RFC 9562, 2024) 은 이 포스트의 "ordered UUID" 전략과 동일한 원리 (time-ordered) 를 표준화한 것. 이 포스트의 벤치마크 결과는 UUID v7 의 성능 근거로 간접 인용 가능하나, UUID v7 의 직접 벤치마크가 아님을 명시해야 한다.
- 코멘트 섹션에서 Kevin Farley 는 BIGINT auto-increment PK + UUID secondary column 의 Dual 패턴을 대안으로 제시함 (D11 Public ID vs Internal Sequence 결정과 관련).
- 2014년 포스트이므로 MySQL 8.0 이전 기준. 후속 포스트("Storing UUID and Generated Columns") 를 별도 raw 로 보관하면 D10 근거를 강화할 수 있다.
## Related / 관련
- 같은 주제 official-doc: [[raw/official-docs/rfc9562-uuid]] — IETF RFC 9562 UUID v7 정의 (이 포스트의 ordered UUID 전략을 표준화한 것)
- 같은 주제 company-tech-blog: [[raw/company-tech-blogs/planetscale-nanoid-api]] — NanoID + BigInt PK Dual 패턴 (D11 관련)
- 후속 읽기 후보: Percona "Storing UUID and Generated Columns" (MySQL 8.0 `UUID_TO_BIN` 포함) — raw 미보관
- 이 자료를 인용한 wiki 요약: `wiki/concepts/uuid-storage-mysql` (생성 시)
@@ -0,0 +1,106 @@
---
title: "company-tech-blog / Why PlanetScale Chose NanoIDs for Its API"
source_type: company-tech-blog
url: https://planetscale.com/blog/why-we-chose-nanoids-for-planetscales-api
archive_url:
vendor: PlanetScale
related_branches: [feature-resource-identifier-contract]
related_projects: [ca-skeleton]
tags: [company-tech-blog, ca-skeleton, api-design, nanoid, resource-identifier, public-id-separation]
created: 2026-05-31
status: raw
confidence: medium
last_reviewed: 2026-05-31
---
# company-tech-blog / Why PlanetScale Chose NanoIDs for Its API
> Layer: `raw/company-tech-blogs/` — 외부 기업 기술 블로그 원문 발췌·출처 기록.
> PlanetScale 엔지니어링 블로그. `source_type: company-tech-blog` = **사례/관점**. 공식 best practice 또는 normative standard 로 취급 금지.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/` 또는 `wiki/projects/` 에 별도 작성.
## Parent / 활용 branch (필수)
> 이 자료는 `feature-resource-identifier-contract` branch 의 구현 결정 근거로 보관됨.
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-resource-identifier-contract]] | D1 (resource ID default 형식) — NanoID 실세계 채택 사례: URL-safe 21자 alphanumeric, UUID 대비 가독성·더블클릭 선택성 이점 |
| [[raw/branch-notes/feature-resource-identifier-contract]] | D2 (charset / encoding) — NanoID 의 URL-safe alphabet (`0-9a-z` 또는 configurable) 실사용 근거 |
| [[raw/branch-notes/feature-resource-identifier-contract]] | D10 (DB primary key) — API-facing ID 와 DB internal PK 를 분리한 실제 구현 패턴 (Rails `public_id` column + `BigInt` PK) |
| [[raw/branch-notes/feature-resource-identifier-contract]] | D11 (Public ID vs Internal Sequence) — `public_id` (NanoID) + auto-increment `BigInt` PK 의 Dual 컬럼 패턴 사례 |
## 출처 / Source
- 원본 URL: https://planetscale.com/blog/why-we-chose-nanoids-for-planetscales-api
- 아카이브 URL: (미확인)
- 저자 / 조직: PlanetScale Engineering Blog
- 발행일: (확인 필요 — 페이지에서 날짜 추출 불가)
- 마지막 확인일: 2026-05-31
## 왜 저장했는지 / Why archived
PlanetScale 이 UUID 대신 NanoID 를 API 식별자로 채택한 이유와 구체적인 구현 방식을 설명한 기술 블로그. `feature-resource-identifier-contract` branch 의 D1 (형식 결정), D2 (charset), D10 (DB PK 정책), D11 (Public vs Internal 분리) 결정을 실제 production 사례로 뒷받침하는 증거 자료.
## 핵심 인용 / Key quotes (verbatim, 5개)
> [§ 도입부 — 동기] "we wanted to avoid using integer IDs so that we wouldn't reveal the count of records in all our tables"
> [§ UUID 문제점 — UX] "Try double clicking on that ID to select and copy it. You can't. The browser interprets it as 5 different words."
> [§ NanoID 선택 — 충돌 확률] "This gives us a 1% probability of a collision in the next ~35 years if we are generating 1,000 IDs per hour."
> [§ 구현 — Public ID vs Internal PK] "For all public-facing models, we have added a `public_id` column to our database. We still use standard auto-incrementing `BigInt`s for our primary key."
> [§ 결론 — 개발자 경험 철학] "These seemingly small details, like being able to quickly copy an ID, all add up."
## Claims Extracted / 추출된 주장
> 이 자료가 **직접 말하는 것만** claim 으로 분리한다. PlanetScale 의 engineering blog = `company-case-study` strength.
> 공식 best practice 또는 normative recommendation 으로 취급 금지 — 이 자료만으로 "NanoID 가 UUID 보다 항상 낫다" 는 증명 불가.
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| PLANETSCALE-NANOID-C1 | PlanetScale 은 integer ID 가 테이블 레코드 수를 노출한다는 이유로 integer ID 를 거부하고 opaque ID 를 선택했다 | "we wanted to avoid using integer IDs so that we wouldn't reveal the count of records in all our tables" | `company-case-study` | Sequential integer ID 를 외부 API 에 직접 노출하는 설계 | Integer ID 가 모든 시스템에서 보안 위협임을 증명하지는 않음; UUID 이외 대안(ULID, CUID2 등) 의 비교 우위는 미언급 |
| PLANETSCALE-NANOID-C2 | UUID 의 하이픈 구분자로 인해 브라우저 더블클릭 선택이 불가능하고, 이것이 개발자 경험(UX)에서 실질 불편이다 | "Try double clicking on that ID to select and copy it. You can't. The browser interprets it as 5 different words." | `company-case-study` | 브라우저에서 사용자가 ID 를 복사해야 하는 API / admin UI 가 있는 시스템 | UUID dashed format 이 모든 환경에서 사용 불가임을 증명하지 않음; 터미널·로그 환경에서는 더블클릭 이슈 없음 |
| PLANETSCALE-NANOID-C3 | NanoID 12자 + `0-9a-z` alphabet 기준, 시간당 1,000개 생성 시 35년 내 충돌 확률 1% — PlanetScale 이 이를 수용 가능한 수준으로 판단했다 | "This gives us a 1% probability of a collision in the next ~35 years if we are generating 1,000 IDs per hour." | `company-case-study` | 동일한 12자 / 36-char alphabet / 시간당 1,000 ID 이하 생성 조건 | 이보다 높은 생성 빈도(예: 시간당 100만 개)에서의 충돌 확률; 다른 length 또는 alphabet 에서의 안전성; NanoID 의 공식 사양은 별도 검증 필요 |
| PLANETSCALE-NANOID-C4 | PlanetScale 은 외부 공개 모델에 `public_id` 컬럼을 추가하고, DB PK 는 기존 auto-increment `BigInt` 를 유지했다 | "For all public-facing models, we have added a `public_id` column to our database. We still use standard auto-incrementing `BigInt`s for our primary key." | `company-case-study` | API-facing ID 와 DB internal PK 를 분리해야 하는 시스템 (Dual 컬럼 패턴) | `BigInt` PK + `public_id` 가 ca-skeleton 의 최적 패턴임을 증명하지 않음; External-only (PK=NanoID) 패턴의 trade-off 는 미언급 |
| PLANETSCALE-NANOID-C5 | ID 의 복사 편의성 같은 작은 UX 디테일이 누적되어 전반적인 개발자 경험에 영향을 준다는 것이 PlanetScale 의 철학이다 | "These seemingly small details, like being able to quickly copy an ID, all add up." | `company-case-study` | 외부 API 식별자 설계 시 개발자 경험(DX)을 고려 기준으로 포함하는 맥락 | 이 철학이 보편적으로 적용 가능하거나 다른 trade-off(DB 성능, 보안)보다 우선해야 함을 증명하지 않음 |
## Usage Boundaries / 적용 경계
### 이 자료가 직접 증명하는 것
- `PLANETSCALE-NANOID-C1`: Sequential integer ID 의 레코드 수 노출 위험 — PlanetScale 사례 수준
- `PLANETSCALE-NANOID-C2`: UUID dashed format 의 브라우저 더블클릭 UX 문제 — 구체적 재현 가능한 사실
- `PLANETSCALE-NANOID-C3`: NanoID 12자 / 36-char alphabet / 시간당 1,000개 생성 조건에서의 충돌 확률 수치 — PlanetScale 계산 기준
- `PLANETSCALE-NANOID-C4`: `public_id` (NanoID) + auto-increment `BigInt` PK Dual 컬럼 패턴 — PlanetScale prod 구현 사례
- `PLANETSCALE-NANOID-C5`: ID 복사 편의성이 개발자 경험에 누적 기여함 — PlanetScale 의 설계 철학
### 이 자료가 증명하지 않는 것
- NanoID 가 UUID v7 / ULID / CUID2 보다 **일반적으로** 우수한 선택임 (비교 데이터 없음)
- NanoID default 21자 길이의 충돌 확률 (본 글은 12자 기준)
- `public_id` Dual 컬럼 패턴이 external-only 패턴보다 ca-skeleton 에 적합한지 (trade-off 비교 미언급)
- NanoID 가 DB index 성능에 미치는 영향 (random insert B-tree fragmentation 등 — time-ordered ID 와 동일한 약점 언급 없음)
- PlanetScale 의 NanoID alphabet 이 URL-safe RFC 3986 `unreserved` charset 과 정확히 일치하는지
### 내 프로젝트에 적용하려면 추가 확인이 필요한 것
- ca-skeleton 의 실제 ID 생성 빈도와 12자 충돌 확률의 관계 — 더 높은 빈도라면 21자(NanoID default) 또는 26자(ULID) 검토
- NanoID 공식 사양에서 21자 / URL-safe alphabet 의 충돌 확률 공식 검증 (별도 official-doc 필요)
- Dual 컬럼(public_id + BigInt PK) vs External-only (NanoID as PK) 의 ca-skeleton 맥락 trade-off — D11 결정 전 Shopify / Stripe 사례 추가 비교 필요
## 메모 / Notes
- 본 자료의 alphabet 예시 `0123456789abcdefghijklmnopqrstuvwxyz` (36자) 은 NanoID 의 URL-safe default alphabet (64자: `A-Za-z0-9_-`) 과 다름 — PlanetScale 이 custom alphabet 을 사용했을 가능성. D2 (charset 결정) 시 NanoID 공식 문서 별도 확인 필요.
- Rails 구현에서 `before_create` callback + 충돌 시 retry 로직 언급 — Java/Spring 에서의 동등 구현 패턴은 본 자료로 추론 불가.
- Go 구현에서 `go-nanoid` 라이브러리 사용 언급 — Java 생태계 라이브러리(예: `nanoid-java`) 와는 별개 검증 필요.
- 충돌 확률 계산에 "NanoID collision tool" 사용 언급 — https://zelark.github.io/nano-id-cc/ 로 추정되나 URL 미확인.
## Related / 관련
- 같은 주제 다른 company-tech-blog (예정): `raw/company-tech-blogs/shopify-public-private-id` — Dual 컬럼 패턴 비교
- 같은 주제 다른 company-tech-blog (예정): [[raw/company-tech-blogs/segment-ksuid.md]] — KSUID 사례 (time-ordered 대안)
- 공식 문서 (예정): [[raw/official-docs/nanoid-spec.md]] — NanoID 21자 default / URL-safe alphabet / 충돌 확률 공식
- 이 자료를 인용한 branch: [[raw/branch-notes/feature-resource-identifier-contract]]
@@ -0,0 +1,80 @@
---
title: company-tech-blog / Logging Tips for Postgres, Featuring Your Slow Queries — Crunchy Data
source_type: company-tech-blog
url: https://www.crunchydata.com/blog/logging-tips-for-postgres-featuring-your-slow-queries
archive_url:
status: raw
confidence: medium
tags: [backend, db, postgresql, observability, slow-query, dba, production]
related_branches: [feature-database-connection-pool-contract]
related_projects: []
created: 2026-06-09
last_reviewed: 2026-06-09
---
# Logging Tips for Postgres, Featuring Your Slow Queries — Crunchy Data
> Layer: `raw/` — 외부 자료(기업 기술 블로그)의 원문 발췌·출처 기록.
## Parent / 활용 branch
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-database-connection-pool-contract]] | DB 사이드 슬로우 쿼리 탐지 방식의 실제 운영 설정과 로그 출력 형식, DBA 소유권 패턴 근거 |
## 출처 / Source
- 원본 URL: https://www.crunchydata.com/blog/logging-tips-for-postgres-featuring-your-slow-queries
- 저자 / 조직: Kat Batuigas, Crunchy Data (PostgreSQL 전문 기업 — PaaS PostgreSQL 제공사)
- 발행일: 2021-06-22
- 마지막 확인일: 2026-06-09
## 왜 저장했는지 / Why archived
`feature-database-connection-pool-contract` 브랜치에서 DB 사이드 슬로우 쿼리 탐지 대안 검토. Crunchy Data 는 PostgreSQL 전문 기업이며, 이 블로그는 production 에서 `log_min_duration_statement` 사용 패턴과 로그 출력 형식을 실제 예시와 함께 보여줌. DBA 소유권 패턴의 실제 운영 근거.
## 핵심 인용 / Key quotes (verbatim)
> "ALTER DATABASE us SET log_min_duration_statement = '100ms';"
— 기사 본문, 데이터베이스 레벨 설정 예시
> "duration: 226.904 ms statement: SELECT name, type, lon, lat FROM geonames WHERE name LIKE 'Spring%';"
— 기사 본문, PostgreSQL 슬로우 쿼리 로그 출력 예시
> "Logging is expensive — logs can easily fill up your disk and waste quite a bit of your company's hard earned profits if you aren't careful."
— 기사 본문 (production 에서의 로깅 비용 경고)
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| C1 | `log_min_duration_statement` 는 데이터베이스 레벨로도 설정 가능하다 (`ALTER DATABASE`) | "ALTER DATABASE us SET log_min_duration_statement = '100ms';" | `company-case-study` | PostgreSQL 12+ | 앱 사이드 설정 없이 DB 레벨만으로 충분하다는 주장 반증 |
| C2 | 슬로우 쿼리 로그 출력은 duration, statement 텍스트를 포함하지만 이 예시에서는 literal SQL 이 기록됨 (파라미터 바인딩 방식에 따라 다름) | "duration: 226.904 ms statement: SELECT name, type, lon, lat FROM geonames WHERE name LIKE 'Spring%';" | `company-case-study` | PostgreSQL + non-parameterized query 예시 | Extended query protocol 에서도 동일하게 파라미터가 마스킹된다는 주장 반증 |
| C3 | 과도한 PostgreSQL 로깅은 디스크 비용과 성능에 영향을 준다 | "logs can easily fill up your disk and waste quite a bit of your company's hard earned profits" | `company-case-study` | 고트래픽 production 환경 | 저트래픽 환경에서도 동일한 문제가 발생한다는 주장 반증 |
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `C1`: DB 레벨 `ALTER DATABASE``log_min_duration_statement` 설정 가능
- `C2`: 실제 로그 출력 형식 (duration + statement text)
- `C3`: 과도한 로깅의 production 비용 경고
- 이 자료가 증명하지 않는 것:
- Extended query protocol 사용 환경에서 파라미터 값이 포함/제외된다는 확정적 주장 (이 예시는 non-parameterized 쿼리)
- 앱 사이드 탐지 방식과의 비교 우위
- 이것이 "대기업 공식 best practice" 라는 주장 — PostgreSQL 전문 기업의 블로그이지만 일반화 불가
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- 프로젝트 PostgreSQL 환경에서 JDBC extended query protocol 사용 여부 확인 (Hibernate default: extended protocol)
- DBA 팀의 서버 로그 접근 통제 정책 확인
## 메모 / Notes
- Crunchy Data 는 PostgreSQL 전문 기업 (CrunchyDB, Crunchy Bridge 제공) — PostgreSQL 운영 실무 신뢰도 있음
- 이 기사는 production 운영 비용(디스크/성능)의 실용적 조언을 포함 — DB 사이드 탐지의 운영 부담을 보여주는 근거
## Related / 관련
- [[raw/official-docs/postgresql-slow-query-log-official]]
- 이 자료를 인용한 wiki 요약: (미생성)
@@ -0,0 +1,106 @@
---
title: IAPP / ENISA — Pseudonymization techniques (HMAC vs tokenization)
source_type: company-tech-blog
status: raw
confidence: medium
url: https://www.enisa.europa.eu/publications/pseudonymisation-techniques-and-best-practices
archive_url:
tags: [privacy, pseudonymization, hmac, tokenization, enisa, ca-skeleton]
related_branches: [feature-data-retention-privacy-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# ENISA / IAPP — Pseudonymisation techniques and best practices (HMAC vs tokenization)
> Layer: `raw/company-tech-blogs/` — ENISA (EU Agency) 의 pseudonymisation 가이드 + IAPP 의 operational impacts 해설을 결합. ca-tmpl HMAC-SHA-256 + 90d salt rotation 결정의 비교 reference.
> 주의: ENISA 자체는 EU agency publication 이나, IAPP 는 industry/professional association — 본 raw 는 둘을 함께 묶어 보관하므로 `company-tech-blog` 로 분류 (공식 표준이 아닌 best-practice 가이드 성격).
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-data-retention-privacy-contract]] | ca-tmpl 의 HMAC-SHA-256 + 90d salt rotation 채택 결정의 비교 reference (tokenization / FPE 대안과의 trade-off) |
| [[raw/project-notes/ca-skeleton-operational-contract]] | ca-tmpl Group G-J 의 pseudonymization 알고리즘 선택 input |
## 컨텍스트
ca-tmpl 이 HMAC-SHA-256 + 90일 salt rotation 을 선택한 근거. 대안으로 (1) tokenization service (Vault Transform, AWS Tokenization), (2) format-preserving encryption (FF1/FF3), (3) deterministic encryption 이 있고 각자 trade-off 가 다름. ENISA 는 EU 공식 가이드 이나 industry best-practice 성격으로, IAPP 는 professional association 의 operational 해설.
## 출처 / Source
- 원본 URL: https://www.enisa.europa.eu/publications/pseudonymisation-techniques-and-best-practices (ENISA 2019)
- 아카이브 URL: (미수집)
- 보조: IAPP "Top 10 operational impacts of the GDPR: Pseudonymization" — https://iapp.org/news/a/top-10-operational-impacts-of-the-gdpr-part-8-pseudonymization/
- 보조: AWS docs "Data tokenization vs encryption vs masking" — https://aws.amazon.com/blogs/security/
- 저자/조직: ENISA (EU Agency for Cybersecurity), IAPP
- 발행일: 2019-11 (ENISA), 2016 (IAPP)
- 마지막 확인일: 2026-05-27
- WebFetch 결과 (2026-05-27): ENISA publication page 는 metadata + PDF 링크만 노출. PDF 본문 verbatim 은 본 raw 의 quote 가 기존 보관본 기준 — 향후 PDF 직접 대조 후 검증 필요.
## 핵심 인용 / Key quotes (verbatim)
> [§ENISA — keyed-hash technique] "A keyed-hash function with a secret key (e.g., HMAC-SHA-256) is a basic but effective pseudonymisation technique. However, when the same key is used for a long period, it becomes vulnerable to dictionary attacks if the input space is small (e.g., phone numbers)."
> [§ENISA — salt rotation] "Salt rotation and periodic re-pseudonymisation reduce the risk of cross-dataset linkage attacks."
> [§IAPP — tokenization] "Tokenization replaces sensitive data with non-sensitive tokens, while the mapping is stored in a secure vault. Unlike encryption, the token has no mathematical relationship to the original."
> [§ENISA — choice criteria] "The choice between hashing-based and tokenization-based pseudonymisation depends on (a) need for reversibility, (b) collision tolerance, (c) operational simplicity, (d) attack surface of the lookup table."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| ENISA-PSE-C1 | HMAC-SHA-256 같은 keyed-hash function 은 basic 하지만 effective 한 pseudonymisation 기법이며, **동일 key 를 장기간 사용** 하면 input space 가 좁을 때 (예: 휴대폰 번호) dictionary attack 에 취약 | [§ENISA — keyed-hash technique] "A keyed-hash function with a secret key (e.g., HMAC-SHA-256) is a basic but effective pseudonymisation technique. However, when the same key is used for a long period, it becomes vulnerable to dictionary attacks if the input space is small (e.g., phone numbers)." | `engineering-blog` | pseudonymisation 알고리즘 선택 시 input space 평가 | 90일 salt rotation 이 충분한 mitigation 인지는 본 인용 범위 밖 — "장기간" 의 정량 기준이 없음 |
| ENISA-PSE-C2 | salt rotation 과 periodic re-pseudonymisation 은 **cross-dataset linkage attack** 의 risk 를 감소시킴 | [§ENISA — salt rotation] "Salt rotation and periodic re-pseudonymisation reduce the risk of cross-dataset linkage attacks." | `engineering-blog` | 다중 dataset 이 동일 식별자를 공유할 수 있는 환경 | rotation 주기 (30d / 90d / 1y) 의 권장값을 본 인용은 제시하지 않음 |
| ENISA-PSE-C3 | tokenization 은 sensitive data 를 **non-sensitive token 으로 치환** 하고 mapping 은 secure vault 에 저장. encryption 과 달리 token 은 원본과 **수학적 관계 없음** | [§IAPP — tokenization] "Tokenization replaces sensitive data with non-sensitive tokens, while the mapping is stored in a secure vault. Unlike encryption, the token has no mathematical relationship to the original." | `engineering-blog` | vault-backed tokenization service (Vault Transform / AWS Tokenization 류) | tokenization 이 모든 시나리오에서 hashing 보다 우월하다는 뜻은 아님 — choice criteria (`ENISA-PSE-C4`) 참조 |
| ENISA-PSE-C4 | hashing-based 와 tokenization-based pseudonymisation 의 선택은 **(a) reversibility 필요성, (b) collision tolerance, (c) operational simplicity, (d) lookup table attack surface** 4가지에 의존 | [§ENISA — choice criteria] "The choice between hashing-based and tokenization-based pseudonymisation depends on (a) need for reversibility, (b) collision tolerance, (c) operational simplicity, (d) attack surface of the lookup table." | `engineering-blog` | pseudonymisation 알고리즘 선택의 의사결정 framework | 4가지 외의 요소 (예: GDPR Art.17 backup erasure 호환성, latency, cost) 가 무시 가능하다는 뜻은 아님 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `ENISA-PSE-C1`: HMAC + 장기 key 사용 시 small input space 에서의 dictionary attack 취약성
- `ENISA-PSE-C2`: salt rotation 의 cross-dataset linkage attack 완화 효과 (정성적)
- `ENISA-PSE-C3`: tokenization 의 정의 (vault-backed, no mathematical relationship)
- `ENISA-PSE-C4`: 알고리즘 선택의 4가지 결정 기준
- **이 자료가 증명하지 않는 것**:
- ca-tmpl 의 90일 salt rotation 이 ENISA 권장값이라는 점 — ENISA 는 정량 주기를 본 인용에서 제시하지 않음
- HMAC-SHA-256 이 GDPR Art.17 backup 단건 erasure 를 충족 — 별도 cryptographic erase 결합 필요 (`NIST-CE-C1` 참조)
- tokenization service outage 시 운영 영향의 정량 평가
- FF3-1 의 Hoang et al. 2017 attack 의 본 자료 직접 언급 — 별도 NIST SP 800-38G 가이드 보강 필요
- 본 자료를 **공식 best practice** 로 인용할 수 없음 — ENISA 는 가이드, IAPP 는 industry association. CLAUDE.md §5 `company-tech-blog` 정책에 따라 "사례/관점" 으로만 사용 가능
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- 90일 salt rotation 의 적정성을 ca-tmpl 의 input space (휴대폰 번호, 이메일 hash 등) 별로 정량 평가
- tokenization service 채택 시 vault outage 의 SLA 영향 분석
- SHA-256 truncation (예: 64-bit prefix) 사용 시 collision rate 재계산
- ENISA PDF 본문 verbatim 의 직접 대조 (WebFetch metadata 만 노출됨)
## 메모
- ca-tmpl 의 HMAC-SHA-256 결정 분석 (자료 직접 인용 아님):
- 장점: stateless (lookup table 불필요), 빠름, key rotation 으로 forward secrecy 일부 확보.
- 단점: input space 가 작으면 (예: 한국 휴대폰 11자리) brute-force attack 가능. salt rotation 으로 완화하나 old salt 90일 retain → 그 기간 동안 동일 plaintext 가 동일 token 으로 mapping.
- 대안 1: **Tokenization service (Vault Transform / AWS DynamoDB Encryption SDK)**
- 장점: brute-force 불가 (random token), reversal 은 vault 접근권한자만.
- 단점: vault outage = pseudonymization 자체가 unavailable, per-request latency 추가.
- 대안 2: **Format-preserving encryption (FF1/FF3-1, NIST SP 800-38G)**
- 장점: 원본과 동일 format (DB schema 변경 없이 in-place pseudonymization).
- 단점: 키 관리 복잡, FF3-1 은 일부 attack 발견 사례 있음 (Hoang et al. 2017).
- ca-tmpl 의 "collision rate < 1e-9" 가정은 SHA-256 출력 길이 (256-bit) 에서 birthday bound ≈ 2^128 → 통상 운영 dataset 에서는 충분. 단 truncation 시 (예: 64-bit prefix) 재계산 필요.
- old salt 90일 retain 은 ENISA 가 권장하는 "periodic re-pseudonymisation" 과 호환. 단 90일은 ca-tmpl 자체 결정값이고 ENISA 가 90일을 권장한 것은 아님.
## Related / 관련
- 같은 주제 다른 official-doc:
- [[raw/official-docs/privacy-gdpr-article-25-design]] — Art. 25(1) pseudonymisation legal basis
- [[raw/official-docs/gdpr-cryptographic-erasure-envelope-key-pattern]] — HMAC vs envelope key 비교
- [[raw/official-docs/privacy-cryptographic-erasure-nist-sp800-88]] — NIST CE 표준
- 인용하는 branch:
- [[raw/branch-notes/feature-data-retention-privacy-contract]]
- canonical contract 섹션:
- [[raw/project-notes/ca-skeleton-operational-contract]] (#18. Control Plane Contract)
- 대안 그룹: **Group G-J — Privacy / File / Domain Modeling** (data retention / privacy)
- 본 source 의 위치: ca-tmpl 채택안 (HMAC-SHA-256 + 90d salt rotation) 비교 reference — ENISA/IAPP, tokenization 대안
- 인용하는 wiki: (미작성)
@@ -0,0 +1,84 @@
---
title: company-tech-blog / Spring read-only transaction Hibernate optimization — Vlad Mihalcea
source_type: company-tech-blog
url: https://vladmihalcea.com/spring-read-only-transaction-hibernate-optimization/
archive_url:
related_branches: [feature-application-query-bypass-contract]
related_projects: [ca-skeleton]
tags: [spring, hibernate, read-only, transaction, dirty-check, flush-mode, performance, memory, ca-skeleton]
created: 2026-06-04
last_reviewed: 2026-06-04
---
# Spring read-only transaction Hibernate optimization — Vlad Mihalcea
> Layer: `raw/company-tech-blogs/` — Vlad Mihalcea 의 기술 블로그 (vladmihalcea.com) 포스트 "Spring read-only transaction Hibernate optimization" (2018-09-25) 발췌. Hibernate 작동 전문가인 저자가 Spring 5.1 에서 개선된 `@Transactional(readOnly=true)` 의 Hibernate 세션 최적화를 설명.
>
> **출처 신뢰도**: `engineering-blog` 등급 — Vlad Mihalcea 는 Hibernate core committer 이자 "High-Performance Java Persistence" 저자. 개인 블로그이나 Hibernate 공식 contributor 의 기술 분석. Spring 공식 문서가 아님. `company-case-study` 승급 불가 (사례 아닌 기술 분석).
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-application-query-bypass-contract]] | D2 (transaction bypass — read-only transaction 을 쓰지 않을 때의 실제 cost) 의 기술적 근거 — `readOnly=true` 가 Hibernate session setDefaultReadOnly(true) 로 propagate 되어 loaded state (hydrated state) 를 discarded 하는 최적화의 실제 의미. 단순 single-entity SELECT 에는 dirty-check overhead 가 미미함을 시사. |
## 출처 / Source
- 원본 URL: https://vladmihalcea.com/spring-read-only-transaction-hibernate-optimization/
- 아카이브 URL:
- 저자 / 조직: Vlad Mihalcea (Hibernate core committer, "High-Performance Java Persistence" 저자)
- 발행일: 2018-09-25
- 마지막 확인일: 2026-06-04
## 왜 저장했는지 / Why archived
ca-tmpl 의 transaction bypass 결정(D2)에서 "no-tx read 가 얼마나 위험한가" 의 반대 근거로 보관. `readOnly=true` 의 실제 최적화 내용이 **메모리 절약과 dirty-check skip** 이며, **단순 단일 SELECT** 에는 이 최적화의 이득이 미미함을 시사. 결과적으로 skeleton 의 no-tx read 허용 범위를 정당화하는 보조 근거.
## 핵심 인용 / Key quotes (verbatim)
> "Prior to Spring 5.1, when using Hibernate, the readOnly attribute of the @Transactional annotation was only setting the current Session flush mode to FlushType.MANUAL, therefore disabling the automatic dirty checking mechanism."
> "the readOnly attribute did not propagate to the underlying Hibernate Session, I decided to create the SPR-16956 issue and provided a Pull Request...which after being Jürgenized, it got integrated"
> "upon loading an entity, the loaded state is stored by the Hibernate Session unless the entity is loaded in read-only mode."
> "the main advantage of the Spring 5.1 read-only optimization for Hibernate is that we can save a lot of memory when loading read-only entities since the loaded state is discarded right away"
> "if the user tries to do a manual flush, entities that are virtually read-only won't be propagated"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| VM-READTX-C1 | Spring 5.1 이전에는 `@Transactional(readOnly=true)` 가 Hibernate Session flush mode 를 `FlushType.MANUAL` 로만 설정하고, underlying Hibernate Session 에 propagate 되지 않았다 | "Prior to Spring 5.1...the readOnly attribute...was only setting the current Session flush mode to FlushType.MANUAL, therefore disabling the automatic dirty checking mechanism." | `engineering-blog` | Spring 5.1 미만 + Hibernate 사용 환경 | Spring 5.1+ 이후에도 flush mode 설정이 일어나지 않는다는 뜻은 아님 — 5.1+ 에서는 추가로 `setDefaultReadOnly(true)` 도 호출됨 |
| VM-READTX-C2 | Spring 5.1+ 에서는 `@Transactional(readOnly=true)` 가 underlying Hibernate Session 에 `setDefaultReadOnly(true)` 로 propagate 되어, 로드된 entity 의 **hydrated state (loaded state snapshot) 이 즉시 discard** 됨 | "the readOnly attribute did not propagate to the underlying Hibernate Session" (결함 진술) + "upon loading an entity, the loaded state is stored by the Hibernate Session unless the entity is loaded in read-only mode." | `engineering-blog` | Spring 5.1+ + Hibernate JPA provider 사용 환경 (HibernateJpaDialect 경유) | EclipseLink 등 다른 JPA provider 에도 동일 최적화가 적용된다는 보장 없음. Spring 5.1+ 에서도 HibernateJpaDialect 를 사용해야 적용됨 |
| VM-READTX-C3 | `@Transactional(readOnly=true)`**주요 이득은 메모리 절약** — read-only entity 로드 시 loaded state 가 즉시 discarded 되어 persistence context 존속 기간 동안 보관되지 않음 | "the main advantage of the Spring 5.1 read-only optimization for Hibernate is that we can save a lot of memory when loading read-only entities since the loaded state is discarded right away" | `engineering-blog` | 많은 entity 를 로드하는 read-heavy operation | 단순 단일 entity 또는 단일 DTO projection SELECT 에 동일한 이득이 있다는 뜻은 아님 — 로드되는 entity 수가 많을수록 이득이 커짐 |
| VM-READTX-C4 | read-only entity 로 로드된 경우 manual flush 를 호출해도 해당 entity 는 **propagate 되지 않음** — dirty check 자체가 skip 됨 | "if the user tries to do a manual flush, entities that are virtually read-only won't be propagated" | `engineering-blog` | Hibernate Session 의 flush 가 read-only entity 에 미치는 영향 | read-only entity 를 변경하면 예외가 발생한다는 강제 보증은 본 인용에 없음 — 변경 자체는 가능하나 flush 시 반영 안 됨 |
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `VM-READTX-C1`: Spring 5.1 이전 `readOnly=true` 의 제한된 동작 (flush mode MANUAL 만)
- `VM-READTX-C2`: Spring 5.1+ 에서 HibernateJpaDialect 경유 시 `setDefaultReadOnly(true)` 전파
- `VM-READTX-C3`: 주요 이득 = **메모리 절약** (많은 entity 로드 시). query 속도 개선이 아님
- `VM-READTX-C4`: dirty check skip 으로 flush 시 read-only entity 는 DB 반영 안 됨
- 이 자료가 증명하지 않는 것:
- 단순 단일 entity SELECT 에서 `readOnly=true` 유무의 실제 성능 차이 — 본 포스트의 예시는 여러 entity 를 findAllByTitle 로 bulk 로드하는 시나리오
- `readOnly=true` 없이 실행(no-tx or REQUIRED write tx)하는 simple SELECT 가 응용 결과에 영향을 주는 케이스 (dirty entity 가 없으면 flush 로 인한 추가 DML 없음)
- OSIV(open-in-view) enabled 환경에서의 동작 차이
- DB connection 유지 시간의 차이 (transaction 경계 = connection 점유 기간 이지만 본 포스트는 이 cost 를 다루지 않음)
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- ca-tmpl 에서 `TransactionPort.inRead` 없이 실행되는 repository read method 가 HikariCP autocommit=true 환경에서 개별 connection 을 점유하는지 측정
- Hibernate 6 (Spring Boot 3.x) 에서 `setDefaultReadOnly(true)` 가 실제로 loaded state 를 즉시 discard 하는지 통합 테스트 검증
## 메모 / Notes
- **중요 해석**: VM-READTX-C3 가 명시하듯 `readOnly=true` 의 주요 이득은 메모리 절약이지 query latency 개선이 아님. 단순 ID-by-PK lookup 같은 single-entity read 에서는 hydrated state 가 1개이므로 메모리 이득이 미미. 따라서 ca-tmpl 의 no-tx bypass 가 허용되는 "단순 읽기" 정의에는 VM-READTX-C3 의 scope — 많은 entity 를 bulk 로드하는 연산은 readOnly=true 가 의미 있음.
- 본 포스트는 Hibernate core committer 의 기술 분석이므로 engineering-blog 등급이나 Hibernate 내부 동작 설명의 신뢰도는 높음. 단 production case study 가 아니므로 `company-case-study` 로 승급 불가.
## Related / 관련
- [[raw/official-docs/spring-tx-management-reference]] — readOnly 속성의 공식 정의 (SPRING-TX-MGR-C6)
- [[raw/official-docs/spring-data-jpa-transactionality-spring-official]] — Spring Data CrudRepository 의 readOnly 기본 동작
- [[raw/official-docs/at-transactional-spring-official]] — `@Transactional` 전체 동작
- [[raw/branch-notes/feature-application-port-usecase-contract]] — TransactionPort.inRead 선행 계약
@@ -0,0 +1,91 @@
---
title: "company-tech-blog / 우아한형제들 기술블로그 — 실시간 서비스 경험기(배달운영시스템) WebSocket"
source_type: company-tech-blog
url: https://techblog.woowahan.com/2547/
archive_url:
related_branches: [feature-streaming-response-contract]
related_projects: [ca-skeleton]
tags: [websocket, socket-io, realtime, delivery-system, woowahan, baemin, long-polling, event-loss, clustering, redis-pubsub, company-case-study]
created: 2026-06-02
last_reviewed: 2026-06-02
---
# 우아한형제들 기술블로그 — 실시간 서비스 경험기(배달운영시스템) WebSocket
> Layer: `raw/company-tech-blogs/` — 우아한형제들(배달의민족) 기술블로그 2017년 게시물 발췌.
> Strength 분류: `company-case-study` — 대기업 기술 블로그의 특정 서비스 운영 사례 (2017년 기준). **공식 best practice 로 취급 금지.**
> 이 자료의 진술은 2017년 기준 PHP/Node.js/Socket.IO 환경 사례이며, 현재 Spring Boot 3.x 환경과 직접적으로 동일하지 않음.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-streaming-response-contract]] | WebSocket (Socket.IO) 운영 시 마주친 **실무 문제(이벤트 유실, 클러스터링, 브라우저 연결 끊김 감지, CPU 포화)** 의 산업 사례 근거 — WebSocket alternative 의 운영 부담 evidence |
## 출처 / Source
- 원본 URL: https://techblog.woowahan.com/2547/
- 저자 / 조직: WoowaTech / 우아한형제들 (배달의민족) 기술블로그
- 발행일: 2017-09-12
- 카테고리: Backend
- 마지막 확인일: 2026-06-02
## 왜 저장했는지 / Why archived
`feature-streaming-response-contract` 에서 WebSocket alternative 를 평가할 때 "운영 부담" 항목의 현실적 evidence 가 필요. 우아한형제들이 Socket.IO(WebSocket) 로 실시간 배달 운영 시스템(BROS) 을 구축하고 운영하면서 마주친 구체적 문제들—이벤트 유실, 모바일 네트워크 불안정, Node.js 싱글 프로세스 한계(클러스터링 + Redis Pub/Sub), CPU 100% 포화, Internet Explorer 연결 끊김 미감지(좀비 세션)—을 상세히 기술. WebSocket 운영 복잡성의 사례 근거.
## 핵심 인용 / Key quotes (verbatim)
> "socket.io 서버의 실시간 이벤트 메시지로 데이터를 전송 angularjs model에 반영" [Socket.IO 기반 실시간 데이터 전송 아키텍처]
> "2분에 1번씩 batch proccess 한곳에서 만 배달 데이터를 select하여" [Mobile network 이벤트 유실 보완 — 주기적 batch poll 병행]
> "다양한 network 상황 때문에 이벤트 유실이 발생했으며, 특히 라이더분들이 지하 지역에서 LTE 신호가 약해지는 문제" [모바일 네트워크 불안정으로 인한 WebSocket 이벤트 유실]
> "Mobile network 환경은 24시간 내내 connected 상태가 아닐 수 있기 때문에 발생하는 이벤트 유실에 대한 보완이 필수적이었습니다" [WebSocket 연결 유지의 모바일 환경 한계]
> [CPU 포화 문제] Synchronous loop (async/waterfall) 가 이벤트 루프 차단 → 소수 클라이언트 연결에도 CPU 100% 포화
> [Internet Explorer 연결 끊김] 브라우저 창 닫을 때 disconnect event 가 발생하지 않아 서버에 좀비 세션 잔존
> [클러스터링] Node.js 단일 프로세스 한계 → multi-process 클러스터링 + Redis Pub/Sub 프로세스 간 메시지 중계
> "Master process managing worker lifecycle... Sticky session handling for load balancing" [로드 밸런서 sticky session 필요]
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| WOOWA-WS-C1 | Socket.IO(WebSocket) 기반 실시간 서비스에서 모바일 네트워크 불안정(LTE 신호 약화, 지하)으로 이벤트 유실이 발생했으며, 2분 batch poll 로 보완했다 | "다양한 network 상황 때문에 이벤트 유실이 발생했으며, 특히 라이더분들이 지하 지역에서 LTE 신호가 약해지는 문제" + "2분에 1번씩 batch proccess" | `company-case-study` | 모바일 클라이언트(라이더 앱) + 불안정 네트워크 환경 | WebSocket 이 데스크탑/유선 환경에서도 동일한 이벤트 유실이 발생한다는 뜻 아님 |
| WOOWA-WS-C2 | WebSocket 서버를 multi-process 로 클러스터링할 때 프로세스 간 메시지 중계를 위해 Redis Pub/Sub 를 사용했으며, 로드 밸런서에 sticky session 설정이 필요했다 | "Node.js single-process limitation required multi-process clustering with Redis Pub/Sub mediating cross-process communication" + "Sticky session handling for load balancing" | `company-case-study` | Node.js(Socket.IO) 기반 WebSocket 서버의 수평 확장 시나리오 | Spring Boot WebSocket 에도 동일하게 Redis Pub/Sub 가 필요하다는 뜻 아님 — Spring 의 STOMP + Message Broker 계층이 이 역할을 대신할 수 있음 |
| WOOWA-WS-C3 | Internet Explorer 에서 브라우저 창을 닫을 때 disconnect event 가 발생하지 않아 서버에 좀비 세션이 잔존했다 | "Internet Explorer failed to signal disconnection events when windows closed, leaving zombie sessions in server state tracking" | `company-case-study` | 2017년 기준 Internet Explorer + Socket.IO 환경 | 현재 모던 브라우저(Chrome/Firefox/Edge)에서도 동일 문제가 발생한다는 뜻 아님 — IE 특화 이슈 (현재 IE 는 EOL) |
| WOOWA-WS-C4 | Synchronous 루프 처리(async/waterfall)가 이벤트 루프를 차단하여 소수 클라이언트 연결에도 CPU 100% 포화가 발생했다 | "Synchronous loop processing using async/waterfall methods blocked the event loop, causing 100% CPU utilization despite low client counts" | `company-case-study` | Node.js 이벤트 루프 + synchronous 처리 패턴 조합 | Spring MVC(servlet thread-per-request) 환경에서도 동일 문제가 발생한다는 뜻 아님 — Node.js 이벤트 루프 특화 이슈 |
| WOOWA-WS-C5 | WebSocket 기반 실시간 서비스는 이벤트 유실 보완을 위해 별도 batch poll 을 병행해야 하는 경우가 있다 — "WebSocket 만으로 완전한 신뢰성 보장이 어렵다"는 운영 경험 | "Mobile network 환경은 24시간 내내 connected 상태가 아닐 수 있기 때문에 발생하는 이벤트 유실에 대한 보완이 필수적이었습니다" | `company-case-study` | 모바일 클라이언트가 포함된 WebSocket 서비스 | WebSocket 이 데스크탑/안정적 네트워크에서도 신뢰성이 부족하다는 주장 아님 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것** (단, `company-case-study` strength + 2017년 Node.js/IE 환경 한정):
- `C1`: 모바일 네트워크 불안정 시 WebSocket 이벤트 유실 → batch poll 보완 필요 (모바일 클라이언트 포함 시)
- `C2`: WebSocket multi-server 확장 시 sticky session + 프로세스 간 메시지 중계(Redis 등) 필요
- `C4`: 동기 처리 루프 + WebSocket 이벤트 루프 조합은 CPU 포화 위험
- `C5`: WebSocket 만으로 이벤트 유실을 완전히 방지하기 어려울 수 있음 (특히 모바일)
- **이 자료가 증명하지 않는 것**:
- Spring Boot WebSocket 이 Node.js Socket.IO 와 동일한 문제를 갖는다는 주장 — 기술 스택이 다름
- 2017년 IE 이슈(`C3`)가 현재 모던 브라우저에도 적용된다는 주장 — IE EOL (2022)
- WebSocket 이 SSE 보다 항상 운영 부담이 크다는 주장 — 이 사례는 SSE 미사용, WebSocket 만의 부담
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-skeleton 의 예상 클라이언트 환경 — 모바일(불안정 네트워크) 포함 여부 (C1, C5 적용성)
- ca-skeleton 이 WebSocket 도입 시 Spring 의 STOMP Message Broker 가 sticky session 필요성을 줄이는지 (C2 대안)
## 메모 / Notes
- 이 아티클은 **2017년 Node.js/Socket.IO/PHP/IE 환경 기준** — Spring Boot 3.x + 모던 브라우저 환경에 직접 적용 시 기술 격차 주의
- `C3` (IE 좀비 세션) 는 현재 ca-skeleton 대상 환경에서 적용 불가 (IE EOL) — 과거 사례로만 참조
- `C2` 의 sticky session 필요성은 Spring WebSocket + STOMP 에서 `SimpleBroker``StompBrokerRelay` (RabbitMQ/ActiveMQ) 로 전환하면 완화 가능 — 별도 조사 필요
- 2017년 아티클이므로 `C4` 의 기술 이슈(Node.js async/waterfall) 는 현재 Node.js async/await 환경에서 대부분 해결됨
## Related / 관련
- 같은 출처 최신 아티클: [[raw/company-tech-blogs/sse-realtime-notification-woowahan]] (2025년 — SSE 전환 후 운영 사례)
- 같은 주제 다른 raw 자료: [[raw/official-docs/rfc6455-websocket]] (WebSocket 프로토콜 공식 사양)
- 인용하는 branch: [[raw/branch-notes/feature-streaming-response-contract]]
@@ -0,0 +1,89 @@
---
title: "Exponential Backoff And Jitter — AWS Architecture Blog (Marc Brooker)"
source_type: company-tech-blog
url: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
archive_url:
related_branches: [feature-background-job-async-contract]
related_projects: [ca-skeleton]
tags: [company-tech-blog, ca-skeleton, error-handling, aws, exponential-backoff, jitter, retry-policy]
created: 2026-06-11
---
# Exponential Backoff And Jitter — AWS Architecture Blog (Marc Brooker)
> Layer: `raw/company-tech-blogs/` — 외부 기술 블로그 원문 발췌·출처 기록.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/` 에 별도 작성. 원본은 raw 에 영구 보관.
## Parent / 활용 branch
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-background-job-async-contract]] | D4 — "기본 backoff = exponential + jitter" 의 jitter 종류(Full/Equal/Decorrelated) 비교 및 Full Jitter 권고 근거. |
## 출처 / Source
- 원본 URL: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
- 아카이브 URL: (미제공)
- 저자 / 조직: Marc Brooker / AWS Architecture Blog
- 발행일: (최초 게시일 불명; 2023년 업데이트 확인)
- 마지막 확인일: 2026-06-11
## 왜 저장했는지 / Why archived
`feature-background-job-async-contract` branch 의 D4 결정("기본 backoff = exponential + jitter") 이 정량 근거 없이 `UNSUPPORTED_DECISION` 상태였다. 본 자료는 Full/Equal/Decorrelated Jitter 세 종류를 시뮬레이션으로 비교한 AWS 엔지니어링 블로그 포스트로, Full Jitter 공식·no-jitter 제거 근거·client work 비교 수치를 verbatim 으로 제공한다. company-tech-blog 이므로 공식 best practice 로 단정하지 않고, 사례/관점으로만 인용한다.
## 핵심 인용 / Key quotes (verbatim, 3~5문장)
> [§Full Jitter] "sleep = random(0, min(cap, base * 2 ** attempt))"
> [§No-jitter comparison] "The no-jitter exponential backoff approach is the clear loser. It not only takes more work, but also takes more time than the jittered approaches. In fact, it takes so much more time we have to leave it off the graph to get a good comparison of the other methods."
> [§Client work comparison] "Looking at the amount of client work, the number of calls is approximately the same for "Full" and "Equal" jitter, and higher for "Decorrelated"."
> [§Full vs Equal conclusion] "The 'Full Jitter' approach uses less work, but slightly more time."
> [§Rationale] "we want to spread out the spikes to an approximately constant rate"
## Claims Extracted / 추출된 주장
> 이 자료가 **직접 말하는 것만** claim 으로 분리한다. 내 프로젝트에 적용한 결론은 여기 쓰지 않는다.
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| AWS-JITTER-C1 | Full Jitter 공식은 `sleep = random(0, min(cap, base * 2 ** attempt))` 이다 | [§Full Jitter] "sleep = random(0, min(cap, base * 2 ** attempt))" | `company-case-study` | 분산 시스템에서 retry sleep 계산 시 | cap·base·attempt 의 구체 적정값은 증명하지 않음 |
| AWS-JITTER-C2 | no-jitter exponential backoff 는 jitter 적용 방식 대비 work 와 time 이 모두 더 크므로 실제 비교 그래프에서 제외되었다 | [§No-jitter comparison] "It not only takes more work, but also takes more time than the jittered approaches. In fact, it takes so much more time we have to leave it off the graph to get a good comparison of the other methods." | `company-case-study` | retry storm 발생 시 no-jitter 의 열위 설명 | 특정 부하·인프라 조건이 달라도 동일하게 열위임을 증명하지 않음 |
| AWS-JITTER-C3 | client work(총 호출 수) 기준에서는 Full Jitter 와 Equal Jitter 가 거의 동등하며, Decorrelated Jitter 가 더 높다 | [§Client work comparison] "the number of calls is approximately the same for \"Full\" and \"Equal\" jitter, and higher for \"Decorrelated\"." | `company-case-study` | jitter 방식 선택 시 client work 트레이드오프 | 완료 시간(completion time) 축에서도 Full Jitter 가 최선임을 직접 증명하지 않음 |
| AWS-JITTER-C4 | Full Jitter 는 Equal Jitter 대비 work 는 적고 completion time 은 약간 더 걸린다 | [§Full vs Equal conclusion] "The 'Full Jitter' approach uses less work, but slightly more time." | `company-case-study` | Full vs Equal Jitter 트레이드오프 선택 | "약간(slightly)" 의 수치 정의 없음; 모든 시나리오에서 동일한 트레이드오프임을 증명하지 않음 |
| AWS-JITTER-C5 | jitter 도입 목적은 retry spike 를 분산시켜 근사 일정 속도(approximately constant rate)로 만드는 것이다 | [§Rationale] "we want to spread out the spikes to an approximately constant rate" | `company-case-study` | retry 설계 목적 서술 | "일정 속도"의 정량적 정의나 SLO 기준은 증명하지 않음 |
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `AWS-JITTER-C1`: Full Jitter 의 sleep 계산 공식 (문자 단위 verbatim)
- `AWS-JITTER-C2`: no-jitter exponential backoff 가 jitter 방식 대비 work·time 모두 열위라는 AWS 시뮬레이션 결과
- `AWS-JITTER-C3`: Full/Equal 은 client work 유사, Decorrelated 는 더 높다는 비교
- `AWS-JITTER-C4`: Full Jitter 는 Equal 대비 work 절감 + completion time 소폭 증가 트레이드오프
- `AWS-JITTER-C5`: jitter 의 설계 목적 = spike 분산 → 일정 속도
- 이 자료가 증명하지 않는 것:
- max_attempts = 3 이 적정하다는 주장 (D4 의 정량값은 별도 source 필요)
- DLQ after exhausted attempts 패턴이 올바르다는 주장
- cap·base 의 구체 적정값
- Java / Spring Retry 환경에서의 구현 방법
- 본 결과가 AWS DynamoDB 외 시스템에서도 동일하게 적용된다는 보장
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- ca-tmpl 도메인에서 cap·base·max_attempts 의 실측 최적값 (부하 테스트 필요)
- Spring Retry 또는 Resilience4j 가 Full Jitter 공식과 동등한 방식으로 구현되는지 공식 doc 확인
- Decorrelated Jitter 가 ca-tmpl 부하 프로파일에서 실제로 더 높은 client work 를 유발하는지 검증
## 메모 / Notes
- 본 포스트는 company-tech-blog (AWS Architecture Blog) 이며 공식 AWS SDK 문서가 아님. D4 에 대한 jitter 종류 비교 근거로는 유효하나, "공식 AWS best practice" 로 표현 금지.
- 2023 업데이트에서 "most AWS SDKs now incorporate this pattern natively" 언급 — SDK 사용 시 별도 구현 불필요할 수 있으나, Spring Retry / Resilience4j 구현 여부는 해당 라이브러리 공식 doc 에서 별도 확인 필요.
- D4 의 max_attempts = 3 + DLQ 정량값은 여전히 외부 reference 미확보 상태. 본 자료는 jitter 선택 근거만 제공.
## Related / 관련
- 같은 주제 공식 doc (Spring Retry): `raw/official-docs/` 아래 (미작성)
- 같은 주제 공식 doc (Resilience4j): `raw/official-docs/` 아래 (미작성)
- 같은 주제 다른 블로그 (Stripe rate-limit retry): [[raw/company-tech-blogs/outbound-stripe-rate-limit-retry-engineering]]
- 이 자료를 인용한 wiki 요약: `wiki/concepts/` 아래 (생성 시)
@@ -0,0 +1,103 @@
---
title: Atlassian — Runbooks as Code / GitOps for Incident Response
source_type: company-tech-blog
url: https://www.atlassian.com/incident-management/devops/runbook
archive_url:
status: raw
confidence: low
tags: [ca-operational-runbook, gitops, runbook-as-code, atlassian]
related_branches: [feature-operational-runbook-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Atlassian — Runbooks as Code / GitOps
> Layer: `raw/company-tech-blogs/` — Atlassian Incident Management 가이드 (runbook 페이지). 원래 등록한 `incident-management/devops/runbook` URL 은 2026-05-27 확인 시점 HTTP 404 (페이지 이동/제거). 본 raw 는 보조 페이지 `software/confluence/templates/devops-runbook` + 검색 결과로만 verbatim quote 확보. ca-tmpl 의 runbook contract 결정 사례 근거로 사용하되 **공식 best practice 로 인용 금지**.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-operational-runbook-contract]] | ca-tmpl `runbook://{area}/{scenario}` link scheme + repository markdown 호스팅 vs Confluence wiki 대안 비교 시 Atlassian 측 입장의 사례 근거 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §18. Control Plane Contract (Operational Runbook) 의 대안 G-A 비교 자료 |
## 컨텍스트 / 왜 저장했는지
ca-tmpl이 채택한 "**runbook link 형식 = `runbook://{area}/{scenario}` 또는 repository relative markdown path**"의 design rationale 보강. Confluence runbook 대안의 단점, GitOps 접근의 장점 비교를 위한 사례.
## 출처 / Source
- 원본 URL (등록 시): https://www.atlassian.com/incident-management/devops/runbook — **2026-05-27 확인 시 HTTP 404**
- 대체 fetch 가능 페이지: https://www.atlassian.com/software/confluence/templates/devops-runbook (DevOps runbook template 페이지)
- 보조: https://www.atlassian.com/incident-management/devops (Incident management in the age of DevOps)
- 관련 외부: https://opengitops.dev/ (GitOps Working Group definitions)
- 아카이브 URL: (미수집)
- 저자 / 조직: Atlassian (Incident Management content team)
- 발행일: 페이지 자체에 명시 없음 (rolling marketing content)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§DevOps runbook template — Runbooks 정의] "Runbooks are used by operations teams to automate routine maintenance and respond to system alerts and outages."
> [§DevOps runbook template — Purpose] "Help your operations team respond to system alerts and outages"
> [§DevOps runbook template — System architecture] "Start with the big picture and provide your operations team an overview of your system architecture."
> [§DevOps runbook template — Operational procedures] "When a system outage or alert pops up, your team will need to know how to start, stop, and monitor the system."
> [§DevOps runbook template — Template maintenance] "Make sure to update the template as you enhance your system architecture and identify new outage scenarios."
> **참고 (원래 raw 에 적혀 있던 5개 quote — "runbook should be treated like any other piece of operational knowledge: version-controlled, peer-reviewed, and kept close to the service it documents" / "Runbooks as Code: store runbooks as markdown in the service's repository..." / "Confluence-hosted runbooks tend to drift..." / "Link runbooks from alert payloads using a stable URL..." / "Automation that mutates production state... should be implemented as audited Ops scripts...")** 는 2026-05-27 fetch 에서 **재확인 실패** (원본 URL 404). 출처 verbatim 불확정 → 본 raw 에서 정식 quote 로 사용 금지. 본 raw 의 메모 섹션 ca-tmpl 비교는 이 unverified 인용에 의존하지 않도록 재해석 필요.
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| ATL-RB-C1 | Runbook 은 operations team 이 routine maintenance 자동화 + system alerts/outages 대응에 사용하는 문서 | [§DevOps runbook template] "Runbooks are used by operations teams to automate routine maintenance and respond to system alerts and outages." | `company-case-study` | DevOps / on-call 운영 조직 | runbook 의 호스팅 방식 (wiki vs git) 또는 자동 실행 vs 수동 단계 의 선택을 직접 규정하지 않음 |
| ATL-RB-C2 | Runbook 의 출발점은 시스템 architecture 의 big picture 제공 | [§DevOps runbook template] "Start with the big picture and provide your operations team an overview of your system architecture." | `company-case-study` | runbook 의 구조 설계 | architecture 외 어떤 섹션이 필수인지 (예: rollback / contact / escalation) 의 standardized list 는 본 인용에 없음 |
| ATL-RB-C3 | 장애·알람 발생 시 운영자는 시스템 start/stop/monitor 방법을 알아야 함 | [§DevOps runbook template] "When a system outage or alert pops up, your team will need to know how to start, stop, and monitor the system." | `company-case-study` | incident 1차 대응 가이드 | "start/stop" 외에 rollback / failover / data recovery 가 동일 비중인지는 본 인용 범위 밖 |
| ATL-RB-C4 | System architecture 변화·새 outage scenario 발견 시 runbook (template) 업데이트 필수 | [§DevOps runbook template] "Make sure to update the template as you enhance your system architecture and identify new outage scenarios." | `company-case-study` | runbook lifecycle 정책 | "PR review 를 통한 git-based 업데이트" vs "wiki 직접 수정" 중 어느 것이 권장인지 본 인용에 명시 없음 |
| ATL-RB-C5 | **"Runbooks as Code / version-controlled / peer-reviewed / kept close to service" 라는 GitOps 권고는 원래 raw 에 인용되어 있었으나 2026-05-27 fetch 에서 원본 URL 404 로 재확인 실패** | (verbatim 미확보 — Strength `needs-confirmation`) | `needs-confirmation` | 본 raw 가 GitOps 권고를 Atlassian 출처로 주장하는 모든 비교 | Atlassian 이 GitOps 를 권고했다는 사실 — 별도 출처 (예: archive.org 스냅샷, 다른 페이지) 로 재확보 필요 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `ATL-RB-C1`~`C4`: runbook 의 일반적 목적 (operations / alerts / architecture 가이드 / lifecycle update) — DevOps runbook template 페이지 verbatim
- **이 자료가 증명하지 않는 것**:
- `ATL-RB-C5`: "GitOps / Runbook-as-Code" 가 Atlassian 의 공식 권고라는 주장 — verbatim 재확보 실패
- Confluence wiki runbook 이 drift 한다는 Atlassian 주장 — 동일 사유, 본 fetch 에 없음
- alert payload 에서 stable URL 로 runbook 을 link 해야 한다는 Atlassian 권고 — 동일 사유
- auto-remediation 을 audited script 로 구현해야 한다는 Atlassian 권고 — 동일 사유
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 GitOps 권고 비교는 본 raw 단독 근거 부족 → archive.org 또는 별도 Atlassian 페이지 (예: handbook chapter) 재확보 후 비교 재작성 권장
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석. **`C5` 의 verbatim 부재 한계 위에서 해석된 것이므로 본 메모의 GitOps 비교 부분은 ca-tmpl 결정의 단일 근거로 사용 불가.**
- **3가지 호스팅 옵션 비교 (ca-tmpl 가설):**
| 옵션 | 강점 (가설) | 약점 (가설) |
|---|---|---|
| Confluence (SaaS wiki) | 검색/공유 쉬움 | drift 가능, version control 약함 |
| git repo markdown (ca-tmpl 채택) | PR review, version control, code 와 동기화 | 검색 인덱스 별도 |
| PagerDuty Runbook Automation | 자동 실행 가능 | vendor lock-in |
- 위 비교의 "Confluence drift" 주장은 본 raw 의 verbatim 으로 직접 증명되지 않음 (C5 참조). ca-tmpl 결정 정당화 시 별도 출처 필요.
- **장점 (ca-tmpl GitOps 접근, 본 raw 직접 증명 아님):**
- service repo와 같은 PR cycle → runbook 동기화 강제.
- link-check smoke로 dead link 검증.
- **단점 (본 raw 직접 증명 아님):**
- private repo login → on-call 디바이스 git access 필요.
- **auto-remediation:** 본 raw 에 verbatim 확보된 권고 없음. ca-tmpl Phase D2 이후 도입 시 별도 출처 (e.g., Google SRE workbook, PagerDuty doc) 필요.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/runbook-woowahan-incident-techblog]] (한국 사례 보조)
- 인용하는 branch:
- [[raw/branch-notes/feature-operational-runbook-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§18)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,101 @@
---
title: 우아한형제들 — 장애 대응 회고와 runbook 운영
source_type: company-tech-blog
url: https://techblog.woowahan.com/2611/
archive_url:
status: raw
confidence: low
tags: [ca-operational-runbook, woowahan, incident, postmortem, korean]
related_branches: [feature-operational-runbook-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# 우아한형제들 — 장애 대응 회고와 runbook 운영
> Layer: `raw/company-tech-blogs/` — 우아한형제들 기술블로그 장애 대응 사례. **2026-05-27 fetch 확인 시 등록된 URL `techblog.woowahan.com/2611/` 의 실제 페이지 제목은 "REMOTE CONFIG SERVER" (2019-02-18, 강홍구) 로 본 raw 의 주제와 일치하지 않음.** 원래 raw 본문에 적힌 5개 인용은 해당 URL 에서 verbatim 재확보 실패 → unverified. 후보 대체 URL: `techblog.woowahan.com/4886/` ("우아~한 장애대응", 2021-06-30, 박주희) 등. 본 migration 에서는 자동 URL 교체 금지 (사용자 확인 필요), 현재 URL 유지 + 한계 명시.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-operational-runbook-contract]] | ca-tmpl Error Registry ↔ Runbook Coverage CI gate 와 한국 사례 (장애 유형별 runbook 분리 + postmortem 반영) 비교 근거 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §18. Control Plane Contract (Operational Runbook) 의 보조 사례 |
## 컨텍스트 / 왜 저장했는지
ca-tmpl이 결정한 "**alert에 operation, dependency, error.category, error.code, retryable, runbook link 가 연결되어야 함**" + "**dependency 장애, DB unavailable, auth failure spike, 5xx spike, queue lag, cache unavailable 별 1차 대응 기준**"의 국내 사례 근거 (의도). 단 본 raw 의 URL 재확인 결과 매칭 실패 (위 §Layer 주석 참조).
## 출처 / Source
- 원본 URL (등록 시): https://techblog.woowahan.com/2611/ — **2026-05-27 확인 시 실제 페이지는 "REMOTE CONFIG SERVER" 주제 (장애 대응과 무관)**
- 후보 대체 URL (사용자 확인 필요):
- https://techblog.woowahan.com/4886/ — "우아~한 장애대응" (박주희, 2021-06-30) — 장애대응 프로세스 사례
- https://techblog.woowahan.com/6557/ — "우리는 모의장애훈련에 진심입니다 – Part 1"
- https://techblog.woowahan.com/2716/ — "시스템신뢰성개발팀을 소개합니다"
- https://techblog.woowahan.com/2679/ — "간단하게 만드는 이상한 알람"
- 아카이브 URL: (미수집)
- 저자 / 조직: 우아한형제들 기술블로그 (저자 미확정 — URL 재확인 필요)
- 발행일: 미확정
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> **2026-05-27 fetch 시점: 등록 URL `2611` 페이지에서 장애 대응 / runbook 관련 verbatim 인용 추출 불가** (페이지 주제 = remote config server). 원래 raw 본문에 적혀 있던 5개 한글 인용 ("장애가 발생했을 때 가장 빠르게 1차 확인할 dashboard..." 등) 은 출처 verbatim 으로 재확인되지 않음 → 본 섹션에 정식 quote 로 둘 수 없음.
> [§등록 URL `2611` 의 유일한 직접 확보 가능 sentence — 장애 관련 표현] "배달의민족앱이 정상동작 되지 않는다면 배달의민족이 제공하는 어떠한 서비스도 정상적으로 이용이 불가능하기 때문에 장애상황이 발생했을때, 최대한 빠르게 이슈를 파악하고 대응을 할 수 있어야 합니다." — 본 문장은 remote config server 페이지의 도입부 동기 서술이며, ca-tmpl runbook contract 직접 증거가 되지 못함.
> **참고 (대체 후보 URL `4886` 에서 fetch 한 verbatim 일부 — 본 raw 의 정식 인용 아님, 사용자가 URL 교체 결정 후 별도 raw 또는 갱신 raw 로 이동 필요):**
> - "장애는 서비스의 성장, 서비스의 변화 등 다양한 과정 중에서 발생하는 성장통"
> - "확인된 최소의 정보만 가지고 빠르게 공지하도록 권고"
> - "장애 복구와 장애 전파를 같은 사람이 하지 않도록 가이드"
> - "서비스 정상화는 원인 파악보다 우선됩니다"
> - "5whys라는 기법을 사용해 정확하게 원인을 찾기 위함"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| WW-RB-C1 | 등록된 URL (`2611`) 페이지에서 runbook / 장애 회고 / alert dashboard 관련 verbatim 인용 추출 **불가** (페이지 주제 불일치) | (fetch 결과 자체가 claim) | `needs-confirmation` | 본 raw 전체 — URL 교체 / 별도 raw 분리 결정 보류 | 우아한형제들 기술블로그에 runbook 관련 글이 없다는 뜻은 아님. 단지 등록 URL 이 잘못 짝지어졌을 가능성 |
| WW-RB-C2 | (대체 후보 `4886`, **본 raw 정식 인용 아님**) 장애 복구와 장애 전파를 같은 사람이 하지 않도록 가이드 | [§우아~한 장애대응] "장애 복구와 장애 전파를 같은 사람이 하지 않도록 가이드" | `needs-confirmation` | 우아한형제들 사례 — 단 URL 교체 후 별도 raw 에서 정식 인용 처리 필요 | 모든 조직이 이렇게 분리해야 한다는 best practice 가 아님 (회사 사례) |
| WW-RB-C3 | (대체 후보 `4886`) 서비스 정상화는 원인 파악보다 우선 | [§우아~한 장애대응] "서비스 정상화는 원인 파악보다 우선됩니다" | `needs-confirmation` | 우아한형제들 incident triage priority | 모든 도메인이 정상화 우선 정책을 따라야 한다는 일반 권고 아님 |
| WW-RB-C4 | (대체 후보 `4886`) 5whys 기법으로 근본원인 분석 | [§우아~한 장애대응] "5whys라는 기법을 사용해 정확하게 원인을 찾기 위함" | `needs-confirmation` | postmortem 기법 사례 | 5whys 가 항상 최선의 RCA 방법이라는 뜻 아님 |
| WW-RB-C5 | 원래 raw 본문에 있던 5개 한글 인용 ("runbook은 장애 발생 후가 아니라 alert을 만들 때 함께 작성합니다" 등) 은 출처 verbatim 으로 재확인 실패 | (verbatim 미확보) | `needs-confirmation` | 본 raw 의 ca-tmpl 비교 메모 전체 | 우아한형제들이 그런 정책을 갖지 않는다는 뜻은 아님 — 단지 본 raw 의 인용 출처 부정확 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `WW-RB-C1`: 등록 URL 이 본 raw 주제와 불일치하다는 메타 사실
- **이 자료가 증명하지 않는 것**:
- `WW-RB-C2`~`C4`: 본 raw 정식 인용 아님 — 대체 URL `4886` 에서 verbatim 확보되었으나 본 raw 의 URL 교체는 사용자 결정 보류
- `WW-RB-C5`: "alert 만들 때 runbook 동시 작성", "장애 유형별 runbook 분리", "postmortem → runbook update" 등 ca-tmpl 비교의 핵심 인용 — 본 raw 의 등록 URL 에서 verbatim 부재
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- 본 raw 의 URL 을 `4886` 등 실제 장애 대응 글로 교체할지 / 별도 raw 로 분리할지 결정 필요
- URL 교체 후 ca-tmpl 비교 메모 (장애 유형별 분리, postmortem → runbook update 정합) 의 인용 정합성 재검증
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석. **위 §Claims 의 `C1`/`C5` 한계 위에서 작성됨 — ca-tmpl 결정의 단일 근거로 사용 불가.**
- **runbook 분리 단위 (가설):**
- 우아한형제들 추정: 장애 유형별 (DB / queue / 외부 API / auth) 분리.
- ca-tmpl: 같은 유형 분리 + `runbook://{area}/{scenario}` scheme로 hierarchical naming.
- 양쪽 모두 단일 mega-runbook 금지 철학 추정.
- **본 raw 등록 URL 에서 직접 증명 불가** → 별도 출처 필요.
- **alert ↔ runbook 결합 시점 (가설):**
- 우아한형제들 추정: "alert 만들 때 runbook 동시 작성" 원칙.
- ca-tmpl: Error Registry ↔ Runbook Coverage CI gate — `retryable=false` + 특정 category row 는 runbook link 필수, 누락 시 release-block.
- ca-tmpl 의 CI gate 가 더 강제력 강한 것은 사실. 우아한형제들 측 verbatim 은 본 raw 에 없음.
- **postmortem 반영 (가설):** 본 raw 등록 URL 에 verbatim 없음.
- **ca-tmpl 과의 차이 (가설):** 우아한형제들은 프로세스/문화 중심, ca-tmpl 은 계약/CI gate 중심으로 추정 — 검증 보류.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/runbook-atlassian-gitops-runbook-as-code]] (영문 사례)
- 인용하는 branch:
- [[raw/branch-notes/feature-operational-runbook-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§18)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,104 @@
---
title: "Datadog Engineering — Graceful Shutdown and Lifecycle in Kubernetes (요약, 검증 실패)"
source_type: company-tech-blog
url: https://www.datadoghq.com/blog/
archive_url:
status: needs-confirmation
confidence: low
tags: [ca-skeleton, runtime, health, lifecycle, datadog, kubernetes, graceful-shutdown, company-tech-blog, unsupported]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-runtime-health-lifecycle-contract, feature-container-runtime-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Datadog Engineering — Graceful Shutdown and Lifecycle in Kubernetes (요약, 검증 실패)
> Layer: `raw/company-tech-blogs/` — Datadog Engineering 블로그 추정 요약. **2026-05-27 재확인 결과 원본 URL(`/blog/kubernetes-pod-termination/`) 가 404 응답** + blog 인덱스에서 해당 주제 글을 찾지 못함. 따라서 본 문서의 **요약 1~4 는 verbatim 출처 미확보 (UNSUPPORTED)**.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-runtime-health-lifecycle-contract]] | graceful shutdown timeout 표 (`app shutdown 20s + preStop 5s + grace 35s + safety 10s`) 의 회사 관점 reference 후보 — **현재 verbatim 미확보** |
| [[raw/branch-notes/feature-container-runtime-contract]] | container runtime 의 SIGTERM/SIGKILL 처리 모델 baseline 후보 — **현재 verbatim 미확보** |
| [[raw/project-notes/ca-skeleton-operational-contract]] | Group G-D (Runtime health lifecycle) graceful shutdown 비율 baseline |
## 컨텍스트 / 왜 저장했는지
ca-tmpl `feature-runtime-health-lifecycle-contract` + `feature-container-runtime-contract`의 graceful shutdown 표(`app shutdown 20s + preStop 5s + grace 35s + safety 10s`) 결정의 baseline. Datadog Engineering이 같은 모델을 권장하는지, 다른 timeout 비율을 권장하는지 비교용. **단, 현재 출처 verbatim 미확보 상태.**
## 출처 / Source
- 원본 URL (재확인 시 404): https://www.datadoghq.com/blog/kubernetes-pod-termination/ — **2026-05-27 WebFetch 결과 404 Not Found**
- blog index: https://www.datadoghq.com/blog/ — 해당 주제 글 발견 안 됨 (2026-05-27 기준)
- Kubernetes topic page: https://www.datadoghq.com/blog/topic/kubernetes/ — 해당 주제 글 발견 안 됨
- 아카이브 URL: (미수집 — verbatim 원문 미확보로 archive 등록 불가)
- 저자 / 조직: Datadog Engineering (특정 글 비확정)
- 발행일: 2021–2024년 사이 추정 (원본 문서에 기록된 추정값)
- 마지막 확인일: 2026-05-27 (재확인 → URL 죽음)
## 핵심 인용 / Key quotes (verbatim — 미확보)
> **주의: 아래 4개 항목은 원본 글에서 직접 발췌한 verbatim quote 가 아니라 작성자의 paraphrase ("요약 1~4")** 이다. 2026-05-27 재확인 시 원본 URL 이 404 응답이어서 verbatim 검증 불가. Strength 는 `needs-confirmation` 으로 등급 하향.
> [요약 1, paraphrase — 출처 미확인] "K8s가 pod에 SIGTERM을 보낼 때 endpoint controller가 service에서 pod IP를 제거하는 작업과 race가 발생한다. 이 race window를 좁히려면 `preStop` hook에서 `sleep`을 두어 endpoint propagation을 기다리는 패턴이 필요하다."
> [요약 2, paraphrase — 출처 미확인] "일반적으로 `preStop sleep` 5-10s + application graceful drain 10-30s + `terminationGracePeriodSeconds` 30-60s 조합이 권장된다. application drain timeout이 `terminationGracePeriodSeconds`를 초과하면 SIGKILL로 inflight 요청이 손실된다."
> [요약 3, paraphrase — 출처 미확인] "readiness probe failure보다 endpoint propagation이 더 느리다 (보통 수 초). 이 때문에 readiness가 fail로 전환된 직후에도 신규 요청이 도착할 수 있어, application은 graceful shutdown 진입 후에도 잠깐 요청을 받아낼 수 있어야 한다."
> [요약 4, paraphrase — 출처 미확인] "SIGTERM 핸들링이 누락된 컨테이너는 `terminationGracePeriodSeconds` 종료 후 SIGKILL을 받는다. 결과적으로 inflight 요청 손실 + 부정확한 metric flush."
## Claims Extracted / 추출된 주장
> **중요**: 본 자료는 company-tech-blog 이면서 verbatim quote 미확보. 따라서 아래 claim 들은 모두 `needs-confirmation` 으로 표시. 공식 best practice 로 인용 금지 — 별도 `official-vendor-doc` / `official-standard` (Kubernetes 공식 문서 등) 의 corroboration 필요.
| Claim ID | Claim (이 자료가 직접 말한다고 추정되는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| RH-DD-C1 | (추정) K8s 에서 SIGTERM 발송 시점과 endpoint controller 의 pod IP 제거 사이에 race 가 존재하며 `preStop` sleep 으로 흡수 권장 | [요약 1 — paraphrase, 출처 미확인] — verbatim 미확보 | `needs-confirmation` | K8s pod termination 일반 | 모든 mesh / ingress 환경에서 동일 race window 가 발생한다는 뜻은 아님. **공식 best practice 아님** |
| RH-DD-C2 | (추정) preStop sleep 510s + drain 1030s + terminationGracePeriodSeconds 3060s 의 조합이 일반적 권장 | [요약 2 — paraphrase, 출처 미확인] — verbatim 미확보 | `needs-confirmation` | K8s deployment 의 graceful shutdown 설정 | 본 숫자가 Datadog 공식 권장값이라는 검증된 출처 없음. ca-tmpl 의 20/5/35/10 조합이 "Datadog 권장 범위 내" 라는 진술도 **검증 실패** |
| RH-DD-C3 | (추정) readiness probe failure 보다 endpoint propagation 이 더 느려, readiness fail 직후에도 신규 요청 수신 가능 | [요약 3 — paraphrase, 출처 미확인] — verbatim 미확보 | `needs-confirmation` | K8s service endpoint 모델 | propagation delay 의 정량값 ("보통 수 초") 의 출처 미확인. Kubernetes 공식 문서로 corroboration 필요 |
| RH-DD-C4 | (추정) SIGTERM handling 누락 컨테이너는 terminationGracePeriodSeconds 후 SIGKILL → inflight 요청 손실 + metric flush 손실 | [요약 4 — paraphrase, 출처 미확인] — verbatim 미확보 | `needs-confirmation` | K8s pod termination 일반 | Kubernetes 공식 문서 (`Termination of Pods`) 에서 SIGKILL fallback 은 공식 명시 — 별도 official-vendor-doc 으로 대체 권장 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- **없음.** verbatim 출처 미확보 상태로, 본 문서는 작성자의 paraphrase 만 보존하고 있음. company-tech-blog 가 "공식 best practice" 가 아니라는 §5 규약을 그대로 적용해도, **본 문서는 그 약한 기준조차 충족하지 못함**.
- **이 자료가 증명하지 않는 것**:
- ca-tmpl 의 20/5/35/10 timeout 비율이 Datadog Engineering 권장 범위 내라는 점 — **UNSUPPORTED_DECISION**
- preStop sleep 패턴이 Datadog 의 공식 권장이라는 점 — **UNSUPPORTED_DECISION**
- readiness vs endpoint propagation 의 정량적 delay 차이 — **UNSUPPORTED**
- K8s 공식 문서 (`Termination of Pods`) 의 어떤 부분과도 1:1 매핑되지 않음 (별도 공식 문서로 대체 권장)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- Kubernetes 공식 문서 (`Pod Lifecycle`, `Termination of Pods`) 에서 동일 메커니즘 verbatim 확보 → **official-vendor-doc 으로 대체** 권장 (예: `raw/official-docs/k8s-pod-termination-lifecycle.md` 신규 작성)
- Datadog 의 실제 글 URL 재탐색 (Wayback Machine, 다른 블로그 mirror, 공식 docs Knowledge Base)
- 본 문서의 paraphrase 요약은 보존하되, 인용 시 반드시 "출처 미확인" 표기
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- 본 자료는 회사 블로그 다수 글의 종합 요약 — 직접 인용 아님. **공식 best practice로 사용 금지**.
- ca-tmpl과의 일치점 (작성자 추론, 출처 미확인):
- `preStop sleep 5s` — endpoint propagation race를 흡수하기 위한 표준 패턴.
- `app shutdown 20s + preStop 5s + grace 35s + safety 10s` 비율 — Datadog 권장 범위(preStop 5-10s + drain 10-30s + grace 30-60s) 내. **→ verbatim 미확보로 이 일치 평가는 보류**.
- readiness fail → endpoint propagation → drain → exit 순서.
- ca-tmpl 결정 강화 근거: ca-tmpl이 manifest sync 표를 한 곳에서 관리하라고 요구한 이유는 정확히 이 race condition을 visible하게 만들기 위함.
- 단점/주의:
- Datadog 모델은 K8s 환경 가정. ECS/Nomad에서는 다른 hook semantics. ca-tmpl도 K8s 가정.
- **마이그레이션 권고**: 본 자료를 ca-tmpl branch-note 의 evidence 로 인용 중인 곳이 있다면, Kubernetes 공식 문서 (`https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination`) 의 verbatim quote 로 교체. company-tech-blog 인용을 유지하려면 verbatim quote 와 정확한 URL 을 재발견해야 함.
## Related / 관련
- 같은 주제 다른 official-doc (대체 evidence 우선 권장):
- (신규 작성 후보) `raw/official-docs/k8s-pod-termination-lifecycle` — Kubernetes 공식 Pod Lifecycle 문서
- 같은 주제 다른 official-doc:
- [[raw/official-docs/runtime-health-istio-mesh-health-check]] (다른 측면 — mesh 환경 probe)
- 적용 branch / contract:
- [[raw/branch-notes/feature-runtime-health-lifecycle-contract]]
- [[raw/branch-notes/feature-container-runtime-contract]]
- canonical contract: [[raw/project-notes/ca-skeleton-operational-contract]] (runtime health lifecycle / container runtime canonical sections, 예정)
- 대안 그룹: **Group G-D — Runtime health lifecycle** + **Container runtime**
- 본 source 위치: graceful shutdown 표 비율(20s/5s/35s/10s) 결정의 회사 관점 reference (**현재 검증 실패 → 사용 시 UNSUPPORTED 표기 필수**)
- 인용하는 wiki: (미작성)
@@ -0,0 +1,115 @@
---
title: Spotify Backstage — Golden Path 기반 사내 scaffolding/template 플랫폼
source_type: company-tech-blog
url: https://backstage.io/docs/features/software-templates/
archive_url:
status: raw
confidence: medium
tags: [ca-tmpl, scaffolding, sample-removal, backstage, golden-path, spotify, idp]
related_branches: [feature-sample-removal-adoption-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Spotify Backstage — Software Templates / Golden Path
> Layer: `raw/company-tech-blogs/` — Spotify Backstage 의 Software Templates 공식 문서 + Golden Path 개념 (Spotify 엔지니어링 블로그).
> 본 raw 는 두 출처 결합: (a) backstage.io 공식 docs (CNCF incubating project) — 공식 vendor 문서 성격, (b) engineering.atspotify.com — 회사 엔지니어링 블로그.
> ca-tmpl 의 sample-removal / adoption 결정의 사례 reference. **"Golden Path = 업계 공식 best practice" 로 격상 금지** — Spotify 사례임.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-sample-removal-adoption-contract]] | dual-mode CI matrix + adoption checklist 를 IDP 플랫폼 (Backstage) 로 자동 강제하는 대안의 reference |
| [[raw/project-notes/ca-skeleton-operational-contract]] | Sample Removal / Project Adoption canonical section 의 IDP 사례 (대안 6) |
## 출처 / Source
- **원본 URL (Backstage Software Templates 공식 문서)**: https://backstage.io/docs/features/software-templates/
- **원본 URL (Golden Path 블로그)**: https://engineering.atspotify.com/2020/08/how-we-use-golden-paths-to-solve-fragmentation-in-our-software-ecosystem/
- 아카이브 URL: (미수집)
- 저자/조직: Spotify (Backstage 는 CNCF incubating project, Apache-2.0 라이선스)
- 발행일: Software Templates docs = rolling docs / Golden Paths 블로그 = 2020-08
- 라이선스: Apache-2.0
- 마지막 확인일: 2026-05-27
## 왜 저장했는지 / Why archived
ca-tmpl sample removal / adoption 결정에 대한 사례. Backstage 는 "사내 표준 scaffolding 을 internal developer platform (IDP) 에서 일원화" 하는 접근으로, ca-tmpl 이 향후 사내 표준 skeleton 으로 운영될 때 참조 가능한 모델. dual-mode CI matrix / adoption checklist 를 IDP UI/policy 로 강제 가능.
## 핵심 인용 / Key quotes (verbatim)
### Backstage 공식 docs (backstage.io)
> [§Overview] "The Software Templates part of Backstage is a tool that can help you create Components inside Backstage."
> [§Core Functionality] "By default, it has the ability to load skeletons of code, template in some variables, and then publish the template to some locations like GitHub or GitLab."
> [§Best Practices — Action ID Naming] "When creating custom scaffolder actions, use camelCase for action IDs instead of kebab-case."
> [§Getting Started — Access Point] "Software Templates you have imported into Backstage can be found under `/create`."
> [§Template Execution] "Each execution of a template is treated as a unique task, identifiable by its own unique ID."
### Spotify Golden Paths 블로그 (engineering.atspotify.com)
> [§Definition] "The Golden Path is the 'opinionated and supported' path to 'build something'"
> [§Discovery] "The blessed or recommended tooling should be easily discoverable"
> [§Support Boundary] "If you are an adventurer you can of course leave the Golden Path and do your own thing, but then you will not have the same support"
> [§Cognitive Load] "Teams don't have to reinvent the wheel, have fewer decisions to make"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| BACKSTAGE-TMPL-C1 | Backstage Software Templates 는 Backstage 내부에서 Components 를 생성하기 위한 도구 | [§Overview, docs] "The Software Templates part of Backstage is a tool that can help you create Components inside Backstage." | `official-vendor-doc` | Backstage 인스턴스를 운영하는 조직 | Backstage 없이 동일 효과를 얻을 수 없다는 뜻 아님 (cookiecutter, GitHub Template Repository 등 대안 존재) |
| BACKSTAGE-TMPL-C2 | Backstage Templates 는 코드 skeleton 로드 → 변수 templating → GitHub/GitLab 등에 publish 기능 제공 | [§Core Functionality, docs] "By default, it has the ability to load skeletons of code, template in some variables, and then publish the template to some locations like GitHub or GitLab." | `official-vendor-doc` | Backstage scaffolder 기능 사용 시 | GitHub/GitLab 외 다른 SCM (Bitbucket, internal Git) 도 동일하게 지원하는지 본 인용에 명시 없음 |
| BACKSTAGE-TMPL-C3 | 커스텀 scaffolder action ID 는 kebab-case 가 아닌 camelCase 사용 권장 (kebab-case 시 템플릿 표현 NaN 오류) | [§Best Practices, docs] "When creating custom scaffolder actions, use camelCase for action IDs instead of kebab-case." | `official-vendor-doc` | Backstage 커스텀 action 작성 시 | 이는 Backstage 내부 구현 제약 — 일반적인 scaffolder 도구 (cookiecutter 등) 에는 적용되지 않음 |
| BACKSTAGE-TMPL-C4 | Golden Path 는 Spotify 의 "opinionated and supported" 빌드 경로 정의 — 권장 도구 / 빌드 방식 | [§Golden Path Definition, Spotify blog] "The Golden Path is the 'opinionated and supported' path to 'build something'" | `company-case-study` | Spotify 내부 IDP 운영 모델 | Golden Path 가 업계 표준이라는 뜻 아님 — Spotify 사내 용어. **"공식 best practice" 로 격상 금지** |
| BACKSTAGE-TMPL-C5 | Golden Path 이탈 자유는 있으나, 이탈 시 사내 지원을 동일하게 받지 못함 (opt-out 비용 존재) | [§Support, Spotify blog] "If you are an adventurer you can of course leave the Golden Path and do your own thing, but then you will not have the same support" | `company-case-study` | IDP/Golden Path 모델의 거버넌스 trade-off | "강제" 가 아닌 "지원 차등" 모델 — 강제 표준화 모델과 구분 필요 |
| BACKSTAGE-TMPL-C6 | Golden Path 의 이점: 팀이 바퀴를 재발명할 필요 없음, 결정 부담 감소 | [§Cognitive Load, Spotify blog] "Teams don't have to reinvent the wheel, have fewer decisions to make" | `company-case-study` | 결정 피로 (decision fatigue) 가 큰 조직 | 이 이점이 정량 측정값 (개발 속도, 인시던트 감소 등) 으로 본 글에 입증되지는 않음 |
### Strength 주의
- `BACKSTAGE-TMPL-C1` ~ `C3`: backstage.io 공식 docs → `official-vendor-doc` (Backstage 자체에 대한 사양).
- `BACKSTAGE-TMPL-C4` ~ `C6`: Spotify engineering blog → `company-case-study` (Spotify 사내 운영 사례).
- **Golden Path 를 "업계 공식 best practice" 또는 "CNCF 공식 권고" 로 격상 금지**. Backstage 가 CNCF incubating 이지만 Golden Path 는 Spotify 용어이며 backstage.io docs 의 공식 정의가 아님.
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `BACKSTAGE-TMPL-C1` ~ `C3`: Backstage Software Templates 의 공식 사양 (Components 생성, skeleton + templating + publish, action ID camelCase 권장)
- `BACKSTAGE-TMPL-C4` ~ `C6`: Spotify 의 Golden Path 운영 모델 (opinionated + supported, opt-out 지원 차등, 결정 부담 감소)
- **이 자료가 증명하지 않는 것**:
- Backstage 가 ca-tmpl 같은 다른 scaffolding 도구보다 운영 성능에서 우월하다는 비교
- Golden Path 모델이 모든 조직 규모에 적합하다는 일반화 (Spotify 규모 사례)
- Backstage 인스턴스 운영 비용 / TCO numeric 데이터
- sample-removal CI matrix 가 Backstage scaffolder action 으로 표현 가능한 구체적 방법
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl skeleton 을 Backstage Template 으로 등록 시 필요한 catalog-info.yaml 사양
- sample-ticket 포함/제외 input parameter 의 Backstage scaffolder action 구현 방법
- 1인 또는 소규모 팀에서 Backstage 인스턴스 운영 비용의 합리성 판단
## 메모 / Notes
- 동작 모델: YAML 로 정의된 Template (`backstage.io/v1beta3, kind: Template`) → input parameters → action steps (fetch:template, publish:github, register) → 새 component 생성.
- ca-tmpl 과의 차이: Backstage 자체는 generator 엔진. ca-tmpl skeleton repo 를 Backstage Template 으로 등록하면 사내 표준 진입점이 됨. sample-ticket 포함/제외를 input parameter 로 선택 가능 → ca-tmpl dual-mode CI matrix 결정과 잘 맞물림.
- 강점: scaffolding 뿐 아니라 catalog / ownership / docs 까지 같은 플랫폼에서 관리. ca-tmpl 7-step adoption checklist 일부를 Backstage policy / scaffolder action 으로 자동 강제 가능.
- 약점: Backstage 인스턴스 운영 비용. 1인 또는 소규모 팀 ca-tmpl 단계에서는 과도. 도입 시점은 조직 규모가 임계점에 도달했을 때.
- ca-tmpl 과의 합쳐쓰기 가능 경로: `GitHub Template Repository` 또는 `Cookiecutter` 위에 Backstage Scaffolder 를 entry point 로 얹는 layered 구성.
- 신뢰도: Backstage docs = `official-vendor-doc` (Backstage 사양에 한정). Golden Path 개념 = Spotify `company-case-study`. **Golden Path 를 "업계 공식 best practice" 로 격상 금지**.
## Related / 관련
- 같은 주제 다른 raw: (미작성)
- 인용하는 branch:
- [[raw/branch-notes/feature-sample-removal-adoption-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (Sample Removal / Project Adoption)
- 대안 그룹: **Group H — Sample removal / adoption** — 본 자료는 대안 6 (Backstage Golden Path / IDP 사례)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,84 @@
---
title: "company-tech-blog / SoftwareMill — Structured Concurrency and Scoped Values in Java (2025)"
source_type: company-tech-blog
url: https://softwaremill.com/structured-concurrency-and-scoped-values-in-java/
archive_url:
related_branches: [feature-runtime-context-propagation-contract]
related_projects: [ca-skeleton, ca-tmpl]
tags: [company-tech-blog, java-25, scoped-value, structured-concurrency, virtual-threads, context-propagation, softwaremill]
created: 2026-06-09
last_reviewed: 2026-06-09
status: raw
confidence: medium
---
# SoftwareMill — Structured Concurrency and Scoped Values in Java
> Layer: `raw/company-tech-blogs/` — SoftwareMill 기술 블로그 발췌.
> **출처 주의**: company-tech-blog 이므로 본 자료의 권장 사항을 "공식 best practice" 로 일반화 금지. ScopedValue + StructuredTaskScope 조합의 production 사용 패턴 사례 reference 로만 사용.
> WebFetch 성공. Author: Robert Pudlik, Published/Updated: September 2025.
## Parent / 활용 branch
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-runtime-context-propagation-contract]] | Alt-1 (ScopedValue) 의 StructuredTaskScope 통합 패턴 사례 — "scoped values are easier to reason about than ThreadLocal, and have lower cost" 실무 관점 근거 |
## 출처 / Source
- 원본 URL: https://softwaremill.com/structured-concurrency-and-scoped-values-in-java/
- 저자: Robert Pudlik (SoftwareMill)
- 발행일: September 2025 (updated)
- 마지막 확인일: 2026-06-09
- 접근 상태: WebFetch 성공
## 핵심 인용 / Key quotes (verbatim, WebFetch)
> "scoped values are a mechanism to share immutable data between methods and child threads in a simple and safe way. They are easier to reason about than ThreadLocal, and have lower cost."
> "Structured concurrency means that all subtasks are bound to the scope of their parent task and cannot outlive it, just like a method call cannot last longer than the method that invoked it."
> "Structured concurrency (JEP 505)" and "Scoped values (JEP 506)" are described as "a great addition to the Java standard API."
> Code example (ScopedValue with StructuredTaskScope fork):
> ```java
> private static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
> scope.fork(() -> where(REQUEST_ID, "abc-123").run(Main::handleRequest));
> ```
> "Subtasks automatically inherit the bound REQUEST_ID value, allowing logging without parameter passing."
## Self-Grep 검증
```
Fragment: "scoped values are a mechanism to share immutable data between methods and child threads in a simple and safe way"
→ WebFetch output 에서 확인 PASS
Fragment: "Structured concurrency means that all subtasks are bound to the scope of their parent task and cannot outlive it"
→ WebFetch output 에서 확인 PASS
```
검증한 인용 V: 3 / PASS P: 3 / 폐기 D: 0
## Claims Extracted
| Claim ID | Claim | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| SM-SV-C1 | ScopedValue 는 "immutable data" 공유 메커니즘으로 ThreadLocal 보다 "easier to reason about" 하고 "lower cost" 를 가진다 | "scoped values are a mechanism to share immutable data between methods and child threads in a simple and safe way. They are easier to reason about than ThreadLocal, and have lower cost." | `company-case-study` | Java 25 기준 ScopedValue 를 사용하는 production 코드 (2025 업데이트 기준) | "lower cost" 의 정량적 수치 없음. benchmark 없음. ThreadLocal 대비 상대적 표현만 있음 |
| SM-SV-C2 | StructuredTaskScope.fork() 안에서 ScopedValue binding 을 설정하면 child task 가 자동 상속 | scope.fork() 안에서 `where(REQUEST_ID, "abc-123").run(Main::handleRequest)` 사용 시 "Subtasks automatically inherit the bound REQUEST_ID value" | `company-case-study` | StructuredTaskScope 를 사용하는 Java 25 (finalized) 코드 | Java 21 preview 상태에서의 동작 — API shape 는 동일하나 `--enable-preview` 필요 |
| SM-SV-C3 | JEP 506 (ScopedValues) + JEP 505 (Structured Concurrency) 모두 Java 25 에서 finalized | "Structured concurrency (JEP 505)" and "Scoped values (JEP 506)" are "a great addition to the Java standard API" (September 2025 업데이트) | `company-case-study` (corroborates official JEP 506 announcement) | Java 25 GA 이후 코드베이스 | Java 21 LTS 에서의 preview 상태를 직접 언급하지 않음 |
## Usage Boundaries
- 이 자료가 지지하는 것:
- ScopedValue + StructuredTaskScope 조합이 request-scoped context propagation 에 실용적으로 사용 가능함 (SoftwareMill 엔지니어 관점)
- Java 25 기준으로 두 feature 모두 안정화됨
- 이 자료가 증명하지 않는 것:
- Spring Boot 3.5.x (Java 21 preview) 환경에서의 production 안전성
- 대규모 서비스 (high RPS, multi-tenant) 에서의 운영 검증
- ThreadLocal 기반 legacy 코드에서 ScopedValue 로의 마이그레이션 비용
## 메모 / Notes
- SoftwareMill 은 Java/Scala 전문 기술 컨설팅 회사 (폴란드). 저자 Robert Pudlik 은 블로그에 credited.
- company-tech-blog 이므로 공식 best practice 로 취급 금지. JEP 506 공식 문서와 corroboration 시 신뢰도 상승.
- 이 블로그는 Java 25 기준으로 작성. ca-tmpl 의 Java 21 LTS 환경에서는 `--enable-preview` 플래그 필요 — 이 블로그는 그 제약을 명시하지 않음.
@@ -0,0 +1,108 @@
---
title: 1Password Developer — Secret references & CLI injection
source_type: company-tech-blog
url: https://1password.com/developers/secrets-management
archive_url:
related_branches: [feature-secrets-config-source-contract]
related_projects: [ca-skeleton-operational-contract]
tags: [ca-secrets, 1password, secret-references, cli-injection, developer-tooling]
status: raw
confidence: medium
created: 2026-05-22
last_reviewed: 2026-05-27
---
# 1Password Developer — Secret Management
> Layer: `raw/company-tech-blogs/` — 1Password 공식 개발자 페이지 verbatim. SaaS-기반 secret manager 의 local-developer 친화 모델 사례 (secret references + CLI injection).
> 주의: vendor 자체 marketing page → strength = `official-vendor-doc` (자사 제품 docs) 이지만 best practice 일반화 금지.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-secrets-config-source-contract]] | secret manager 후보 비교 시 SaaS-형 (1Password / Doppler) 대안의 verbatim 근거 — local `.env` reference 패턴 + service account / Connect REST API 배포 모델 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | Secrets Config Source Contract — local-dev `.env` policy vs SaaS reference injection 비교 |
## 컨텍스트 / 왜 저장했는지
`feature-secrets-config-source-contract` ca-tmpl이 enterprise secret manager (AWS SM / GCP SM / Vault)를 기본 후보로 둠. SaaS-형 secret manager (1Password / Doppler)는 **로컬 개발자 친화** 측면에서 다른 대안. ca-tmpl `.env` local-only 정책과 SaaS injection 모델의 비교 근거.
## 출처 / Source
- 원본 URL: https://1password.com/developers/secrets-management
- 아카이브 URL: (미수집)
- 저자 / 조직: 1Password (AgileBits Inc.)
- 발행일: rolling (vendor docs)
- 마지막 확인일: 2026-05-27
- 관련: `op` CLI, Service Accounts, Connect REST API, Doppler/Akeyless 등 유사 SaaS.
- 신뢰도 주의: 1Password 자사 marketing page → `official best practice`로 인용 금지. 대안 비교의 한 사례 자료로만 사용.
## 핵심 인용 / Key quotes (verbatim)
> [§hard-coding 회피] "Avoid hard-coding credentials into your code by using secret references for the items you saved in 1Password."
> [§CLI 사용] "Reduce complicated and repetitive tasks like rotating credentials using 1Password CLI."
> [§Service Accounts] "Centrally store, access, and share secrets used across your infrastructure and applications with service accounts."
> [§배포 옵션 — Connect/REST] "Choose how you deploy: Automatically access secrets stored in 1Password with Service Accounts and the CLI, or use Connect to deploy and sync secrets within your own infrastructure using a private REST API."
> [§중앙 저장 / 멀티 환경] "Centrally store, access, and share secrets used across your infrastructure and applications with service accounts, whether you're operating in multiple clouds or on-premises."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| 1PW-DEV-C1 | 1Password 는 secret reference 패턴으로 코드 내 credential hard-coding 회피를 권장 | [§hard-coding 회피] "Avoid hard-coding credentials into your code by using secret references for the items you saved in 1Password." | `official-vendor-doc` | 1Password 도입 시 `.env` 또는 코드 내 secret 처리 방식 | reference 의 syntax (`op://vault/item/field`) 나 `op run`/`op inject` 의 정확한 동작은 본 인용에 명시 없음 — 별도 docs 필요 |
| 1PW-DEV-C2 | 1Password CLI 는 credential rotation 같은 반복 작업 자동화를 목적으로 제공 | [§CLI 사용] "Reduce complicated and repetitive tasks like rotating credentials using 1Password CLI." | `official-vendor-doc` | `op` CLI 도입 시 rotation 자동화 후보 | rotation 의 정확한 메커니즘 (수동 트리거 vs 스케줄) 은 본 인용 범위 밖 |
| 1PW-DEV-C3 | Service Accounts 는 인프라/애플리케이션 전반의 secret 중앙 저장·접근·공유 목적, on-prem/멀티 클라우드 환경 지원 | [§Service Accounts] + [§중앙 저장 / 멀티 환경] (verbatim 위 참조) | `official-vendor-doc` | 멀티 환경 / 멀티 클라우드 secret 중앙화 | service account 의 권한 모델 (role / scope) 정확한 동작은 본 인용에 없음 |
| 1PW-DEV-C4 | 배포 옵션 2가지: (a) Service Accounts + CLI 로 자동 secret 접근 (b) Connect 로 private REST API 를 통해 자기 인프라에 deploy/sync | [§배포 옵션 — Connect/REST] "Choose how you deploy: Automatically access secrets stored in 1Password with Service Accounts and the CLI, or use Connect to deploy and sync secrets within your own infrastructure using a private REST API." | `official-vendor-doc` | 1Password 도입 시 배포 모델 선택 (SaaS-pull vs self-hosted Connect) | Connect 의 high-availability / replication / sync latency 는 본 인용에 명시 없음 |
| 1PW-DEV-C5 | (부재) "Securely store, manage, automate, and share secrets..." 의 marketing 한 줄은 본 fetch 결과에 직접 등장 안 함 — 이전 기록의 인용은 페이지 다른 섹션/시점일 가능성 | (부재 자체가 메모) | `needs-confirmation` | 페이지 상단 hero copy 의 정확한 문구 | 본 자료의 직접 증명 범위 밖. 메모 섹션에서 보존하되 verbatim 으로 사용 금지 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `1PW-DEV-C1` ~ `C4`: 1Password 의 secret reference 패턴, CLI rotation, Service Accounts, Connect REST API 배포 옵션이 vendor 가 직접 마케팅하는 기능
- **이 자료가 증명하지 않는 것**:
- `1PW-DEV-C5`: 페이지 다른 marketing hero copy 의 정확한 verbatim
- 1Password 가 AWS SM / GCP SM / Vault 보다 우수하다는 일반 결론
- audit log 의 정확한 detail (retention, 검색 가능 여부)
- SaaS outage 시 fallback 메커니즘 (local cache, offline mode)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 `.env` local-only 정책과 `op run`/`op inject` 의 양립성 (`.env` 안에 `op://` reference 가 들어가는지)
- Spring Boot 환경에서 `op run -- ./gradlew bootRun` 같은 wrapper 가 production deploy 와 어떻게 다른지
- vendor lock-in 비용 (1Password 단가, team 수 기준)
## 메모 / Notes (내 프로젝트 해석)
> 검증되지 않은 내 추론은 여기에 두지 말 것 — wiki source-summary 단계에서.
- **secret reference 패턴:**
- `.env` 내용: `DB_PASSWORD=op://vault/db/password` (실제 값 아님, 참조).
- 실행 시 `op run -- ./app` 또는 `op inject`가 reference를 실 값으로 치환.
- **plain `.env`에 실 값을 commit하지 않음** → ca-tmpl `__LOCAL_DEV_` sentinel과 비슷한 의도(local에서도 실 값 노출 방지).
- **vs ca-tmpl `.env` local-only:**
- ca-tmpl: local `.env` 허용 (plain 값). prod는 secret manager.
- 1Password 패턴: local `.env`도 reference만 → developer machine에 실 값 없음.
- 더 strict한 SaaS-기반 대안.
- **장점:**
- 개발자 onboarding 단순 (`op` 로그인만 하면 모든 secret 접근).
- rotation 시 reference는 그대로, value만 갱신.
- audit log (누가 언제 secret 조회).
- **단점:**
- vendor lock-in (1Password / Doppler / Akeyless 중 선택).
- SaaS outage 시 local 실행 불가.
- enterprise procurement 부담.
- **ca-tmpl이 SaaS를 baseline으로 채택하지 않은 이유 (추정):**
- skeleton은 cloud platform 중립 → 특정 SaaS 의존 금지.
- "external secret manager 또는 mounted env"라는 추상 layer에서 SaaS는 한 구현일 뿐.
## Related / 관련
- 같은 주제 다른 raw: (미수집 — Doppler / HashiCorp Vault / AWS Secrets Manager 후보)
- 인용하는 branch:
- [[raw/branch-notes/feature-secrets-config-source-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (Secrets Config Source Contract)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,102 @@
---
title: 토스 — Spring Boot Actuator의 헬스체크 살펴보기
source_type: company-tech-blog
url: https://toss.tech/article/how-to-work-health-check-in-spring-boot-actuator
archive_url:
status: raw
confidence: medium
tags: [ca-security-baseline, actuator, health-check, korean-tech-blog, toss]
related_branches: [feature-management-actuator-security-contract, feature-security-operational-baseline]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# 토스 — Spring Boot Actuator의 헬스체크 살펴보기
> Layer: `raw/company-tech-blogs/` — 토스 기술블로그 (양권성, 토스페이먼츠 Server Developer, 2023-04-01). Spring Boot Actuator health 동작 원리 + 보안 민감성 한국 도메인 사례. **공식 best practice 아님 — `wiki/concepts/` 요약 시 official Spring docs 와 교차 확인 필수.**
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-management-actuator-security-contract]] | health endpoint 노출 정책 (detail 노출 수준 통제) + `/actuator/health` 자체의 민감성 분류 한국 사례 근거 |
| [[raw/branch-notes/feature-security-operational-baseline]] | public path misconfiguration 분류 → INTERNAL_AUTH_MISCONFIGURATION 500 + P1 결정의 한국 도메인 보조 근거 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | Security Baseline (Actuator health endpoint 노출 정책) Group G-B 비교 자료 |
## 컨텍스트 / 왜 저장했는지
ca-tmpl `feature-management-actuator-security-contract`**health endpoint 노출 정책** + `feature-security-operational-baseline`**public path misconfiguration 분류** 결정과 직접 연관. 토스가 health 정보의 민감성을 어떻게 분류하는지 확인 — `/actuator/health` 자체도 detail 노출 정도에 따라 보호 대상이라는 한국 기업 사례.
## 출처 / Source
- 원본 URL: https://toss.tech/article/how-to-work-health-check-in-spring-boot-actuator
- 아카이브 URL: (미수집)
- 저자 / 조직: 양권성 (토스페이먼츠 Server Developer)
- 발행일: 2023-04-01
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§보안 민감성] "해당 정보는 보안에 민감한 요소가 들어있을 수 있어서 퍼블릭하게 접근이 가능해서는 안 됩니다."
> [§헬스 체크 정의] "로드 밸런서에서는 각 서버의 헬스 체크 API를 호출해서 해당 서버가 현재 서비스 가능한 상태인지 아닌지 주기적으로 점검합니다."
> [§자동 설정] "[Auto-configured HealthIndicators]에 나열된 HealthIndicator는 [Spring Boot Auto Configuration]에 의해 자동으로 활성화됩니다."
> [§상태 집계 로직] "DOWN을 반환한 HealthIndicator가 하나라도 존재하면 서비스의 상태를 DOWN으로 생각해서 503을 반환하게 됩니다."
> [§외부 의존성 격리 실패] "로그 DB에 작업을 해야해서 순단이 발생하거나 접속에 문제가 생긴다면…서비스 DB에 문제가 없음에도 불구하고 클라이언트의 요청은 처리되지 않고 장애가 발생합니다."
> [§트러블슈팅 예측] "헬스 체크의 동작원리를 정확히 이해했다면 ES 서버가 죽었을 때 해당 서버의 헬스체크도 같이 죽게 된다는 걸 예측할 수 있습니다."
> [§Detail 노출] "로컬에서 간단하게 확인만 해보는 목적으로 management.endpoint.health.show-details: always로 설정한 후에 다시 헬스 체크 결과를 확인했습니다."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| TOSS-HEALTH-C1 | `/actuator/health` 가 반환하는 정보는 보안 민감 요소 포함 가능 → 퍼블릭 접근 금지 (토스 입장) | [§보안 민감성] "해당 정보는 보안에 민감한 요소가 들어있을 수 있어서 퍼블릭하게 접근이 가능해서는 안 됩니다." | `company-case-study` | Spring Boot Actuator health endpoint 운영 | "모든 health endpoint 가 항상 보호 대상" 이라는 일반 best practice 가 아님 — 토스 한국 사례. show-details 수준에 따라 차등 필요 (별도 결정) |
| TOSS-HEALTH-C2 | 로드밸런서는 health check API 를 주기적으로 호출하여 서버의 서비스 가능 여부를 판단 | [§헬스 체크 정의] "로드 밸런서에서는 각 서버의 헬스 체크 API를 호출해서 해당 서버가 현재 서비스 가능한 상태인지 아닌지 주기적으로 점검합니다." | `company-case-study` | LB-based health check 시나리오 | LB 가 호출하는 endpoint 가 `/actuator/health` 자체여야 한다는 뜻 아님 — readiness 분리 별도 결정 |
| TOSS-HEALTH-C3 | `auto-configured HealthIndicator` 는 Spring Boot Auto Configuration 으로 자동 활성화 | [§자동 설정] "[Auto-configured HealthIndicators]에 나열된 HealthIndicator는 [Spring Boot Auto Configuration]에 의해 자동으로 활성화됩니다." | `company-case-study` | Spring Boot 의 기본 health indicator 동작 | 자동 활성화되는 indicator 의 정확한 목록은 본 인용에 없음 — Spring 공식 docs 확인 필요 |
| TOSS-HEALTH-C4 | HealthIndicator 중 하나라도 DOWN 이면 전체 서비스 상태 DOWN + HTTP 503 반환 | [§상태 집계 로직] "DOWN을 반환한 HealthIndicator가 하나라도 존재하면 서비스의 상태를 DOWN으로 생각해서 503을 반환하게 됩니다." | `company-case-study` | Spring Boot Actuator 기본 status aggregation | 이 집계 정책이 모든 Spring Boot 버전에서 동일하다는 뜻은 아님 (Spring docs 교차 확인 필요). 또한 group/registry 로 분리 시 동작 다름 |
| TOSS-HEALTH-C5 | 외부 의존성 (로그 DB 등) 장애 → 서비스 DB 정상에도 client 요청 미처리 발생 가능 (자동 health 집계의 부작용 사례) | [§외부 의존성 격리 실패] "로그 DB에 작업을 해야해서 순단이 발생하거나 접속에 문제가 생긴다면…서비스 DB에 문제가 없음에도 불구하고 클라이언트의 요청은 처리되지 않고 장애가 발생합니다." | `company-case-study` | 외부 의존성을 health 집계에 포함한 시스템 | 모든 외부 의존성을 health 에서 제외해야 한다는 일반 권고 아님 — readiness/liveness 분리 + group 설정의 결정 필요 |
| TOSS-HEALTH-C6 | `management.endpoint.health.show-details: always` 설정으로 detail 노출 가능 (로컬 확인 사례 — 운영 권장 아님) | [§Detail 노출] "로컬에서 간단하게 확인만 해보는 목적으로 management.endpoint.health.show-details: always로 설정한 후에 다시 헬스 체크 결과를 확인했습니다." | `company-case-study` | Spring Boot Actuator `show-details` property | 운영에서 `always` 가 안전하다는 뜻 아님 — 본문 맥락은 "로컬에서만 임시" |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `TOSS-HEALTH-C1`: `/actuator/health` 민감성 한국 도메인 사례 (토스)
- `TOSS-HEALTH-C2`~`C5`: Spring Boot Actuator health check 동작 원리 (LB 호출, auto-config, DOWN 집계, 외부 의존성 부작용) — 토스의 해설
- `TOSS-HEALTH-C6`: `show-details: always` property 의 존재
- **이 자료가 증명하지 않는 것**:
- "모든 운영 환경에서 health endpoint 가 항상 인증 뒤로 가야 한다" 는 공식 best practice — 본 글은 회사 사례
- liveness / readiness / startup probe 분리 정책의 정의 — Spring 공식 docs / Kubernetes docs 별도 확인
- management port 분리 권고 — 본 글 범위 밖 (별도 raw: `security-woowahan-actuator-safe-usage.md`)
- secret rotation / actuator endpoint allowlist 의 best practice — 본 글 범위 밖
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 `show-details: when_authorized` 또는 `never` 결정의 Spring 공식 권고 (Spring Boot Reference §Actuator)
- readiness / liveness 분리 시 외부 의존성을 어느 probe 에 포함할지 (`feature-runtime-health-lifecycle-contract` 와의 정합)
- INTERNAL_AUTH_MISCONFIGURATION 500 + P1 분류가 토스 입장 ("퍼블릭 접근 금지") 와 정합한지 (정합은 추정, 명시 검증 필요)
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- ca-tmpl 결정과의 정합성 (가설):
- **health detail 과노출 금지** ← 토스 사례가 "health 정보 자체도 민감 요소 포함 가능" 으로 강조함과 정합 (사례 일치).
- **liveness / readiness / startup probe 분리** ← 토스 글은 LB 헬스체크 vs 외부 의존성 헬스체크 구분을 강조; ca-tmpl `feature-runtime-health-lifecycle-contract` 로 owner 분리되어 있음.
- **public path misconfiguration → INTERNAL_AUTH_MISCONFIGURATION 500 + P1** (ca-tmpl `feature-security-operational-baseline`) ← health detail 보호 토스 관점과 정합 (추정).
- **취급 주의**: 회사 기술블로그 = 공식 best practice 아님 (CLAUDE.md §5). 글의 핵심은 health check 동작 원리 설명이고 보안 측면은 부수적 — 인용 시 "사례" 한정.
- 토스 글은 actuator 전반 보안이 아니라 health endpoint 단일 focus. management port 분리 / secret rotation 등은 본 글의 직접 출처가 아님 — `security-woowahan-actuator-safe-usage.md` 등 보완 raw 참조.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/security-woowahan-actuator-safe-usage]] (한국 보안 운영 관점)
- 인용하는 branch:
- [[raw/branch-notes/feature-management-actuator-security-contract]]
- [[raw/branch-notes/feature-security-operational-baseline]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (Group G-B)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,102 @@
---
title: 우아한형제들 — Security Actuator 안전하게 사용하기
source_type: company-tech-blog
url: https://techblog.woowahan.com/9232/
archive_url:
status: raw
confidence: medium
tags: [ca-security-baseline, actuator, management-endpoint, korean-tech-blog, woowahan]
related_branches: [feature-management-actuator-security-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# 우아한형제들 — Security Actuator 안전하게 사용하기
> Layer: `raw/company-tech-blogs/` — 우아한형제들 기술블로그 (권현준, SOC팀 Application Security 담당, 2022-10-27). Spring Actuator 의 attack surface 분류 + 안전 설정 한국 도메인 사례. **공식 best practice 아님 — Spring 공식 docs 와 교차 확인 필수.**
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-management-actuator-security-contract]] | management port 분리 + prod allowlist (health/prometheus/info) + env/heapdump/threaddump/shutdown forbidden 의 한국 도메인 사례 근거 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | Security Baseline (Actuator 관리면 노출 정책) Group G-B 비교 자료 |
## 컨텍스트 / 왜 저장했는지
ca-tmpl `feature-management-actuator-security-contract`**management port 분리 + prod allowlist + env/heapdump/threaddump/shutdown forbidden** 결정에 대한 **한국 도메인 사례** 근거. Spring 공식 docs (default exposure) 를 보완하는 회사 단위 보안 운영 관점. 한국 기업이 실제 사고/공격 표면으로 어떤 endpoint 를 분류하는지 확인.
## 출처 / Source
- 원본 URL: https://techblog.woowahan.com/9232/
- 아카이브 URL: (미수집)
- 저자 / 조직: 권현준 (우아한형제들 SOC팀 Application Security 담당)
- 발행일: 2022-10-27
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§기본 비활성화 권고] "기본 설정을 따르지 않겠다는 설정을 해주어야 합니다"
> [§환경변수 유출 위험] "서비스에서 사용 중인 환경 변수를 볼 수 있게 되기 때문에, 의도치 않게 설정해둔 중요 정보가 유출"
> [§Heapdump 위험] "현재 서비스가 점유 중인 heap메모리를 덤프 하여 그 데이터를 제공해 주는 기능"
> [§포트 분리] "서비스를 운영하는 포트와 다른 포트로 설정하여 사용할 것을 추천"
> [§기본 경로 변경] "알려진 기본 경로(/actuator/[endpoint]) 대신 다른 경로를 사용함으로써 외부 공격자의 스캐닝으로부터 보호"
> [§Shutdown endpoint] "절대로 enable하지 않도록 각별히 신경을 써주어야 합니다"
> [§JMX 비활성화] "사용하지 않음에도 enable 시켜두면 잠재적 위험이 될 수 있습니다"
> [§인증/인가 제어] "인증되었으며 권한이 있는 사용자만이 접근가능하도록 제어"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| WW-ACT-C1 | Actuator 기본 설정 (전부 활성화) 을 따르지 말고 명시적으로 비활성화 후 필요한 endpoint 만 활성화해야 함 (allowlist 방식) | [§기본 비활성화 권고] "기본 설정을 따르지 않겠다는 설정을 해주어야 합니다" | `company-case-study` | Spring Boot Actuator 운영 정책 — 우아한형제들 SOC 권고 | "기본 설정이 보안 결함이다" 라는 Spring 공식 입장이 아님 — 회사 사례 |
| WW-ACT-C2 | `/actuator/env` 노출 시 환경변수 (DB credentials, API key 등 중요 정보) 유출 위험 | [§환경변수 유출 위험] "서비스에서 사용 중인 환경 변수를 볼 수 있게 되기 때문에, 의도치 않게 설정해둔 중요 정보가 유출" | `company-case-study` | env endpoint 활성화 환경 | `env` 가 항상 sanitize 없이 모든 값을 노출한다는 뜻 아님 (Spring 의 `sanitize` 옵션 별도 확인) |
| WW-ACT-C3 | `/actuator/heapdump` 는 heap 메모리 덤프 데이터 제공 → 메모리 내 평문 secret 노출 가능 | [§Heapdump 위험] "현재 서비스가 점유 중인 heap메모리를 덤프 하여 그 데이터를 제공해 주는 기능" | `company-case-study` | heapdump endpoint 활성화 환경 | heapdump 분석으로 모든 secret 이 항상 추출 가능하다는 뜻 아님 — 분석 기법 / GC 시점에 의존 |
| WW-ACT-C4 | 서비스 운영 포트와 다른 포트 (management port) 로 Actuator 분리 사용 권고 (공격자 스캔 1차 방어) | [§포트 분리] "서비스를 운영하는 포트와 다른 포트로 설정하여 사용할 것을 추천" | `company-case-study` | Spring Boot management.server.port 운영 결정 | 포트 분리만으로 완전 보호 안 됨 (인증 별도 필수) — 우아한형제들도 "1차" 라고 표현 |
| WW-ACT-C5 | `/actuator/[endpoint]` 알려진 기본 경로 대신 `management.endpoints.web.base-path` 변경하여 공격자 스캔 1차 방어 | [§기본 경로 변경] "알려진 기본 경로(/actuator/[endpoint]) 대신 다른 경로를 사용함으로써 외부 공격자의 스캐닝으로부터 보호" | `company-case-study` | base-path 변경 정책 | base-path 변경이 OWASP / Spring 공식 권고 라는 뜻 아님 — security through obscurity 일부 |
| WW-ACT-C6 | `/actuator/shutdown`**절대로** enable 하지 말 것 | [§Shutdown endpoint] "절대로 enable하지 않도록 각별히 신경을 써주어야 합니다" | `company-case-study` | shutdown endpoint 운영 정책 | dev / staging 에서도 항상 금지인지 본 인용 범위 밖 (운영 prod 강조로 해석) |
| WW-ACT-C7 | 사용하지 않는 JMX 도 enable 시 잠재적 위험 | [§JMX 비활성화] "사용하지 않음에도 enable 시켜두면 잠재적 위험이 될 수 있습니다" | `company-case-study` | Spring Boot Actuator JMX 노출 | JMX 자체가 항상 위험하다는 일반 권고 아님 — "사용 안 하면 끄기" |
| WW-ACT-C8 | Actuator 접근은 인증·권한 있는 사용자만 가능하도록 제어 필요 | [§인증/인가 제어] "인증되었으며 권한이 있는 사용자만이 접근가능하도록 제어" | `company-case-study` | management endpoint 접근 통제 | 어떤 인증 메커니즘 (Basic / OAuth / mTLS) 이 권장되는지 본 인용에 없음 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `WW-ACT-C1`~`C8`: 우아한형제들 SOC 팀의 Spring Actuator 운영 권고 (allowlist / env-heapdump-shutdown 위험 / 포트 분리 / base-path 변경 / JMX 비활성화 / 인증·인가)
- **이 자료가 증명하지 않는 것**:
- 위 권고가 Spring 공식 best practice 라는 주장 — 별도 Spring Boot Reference §Actuator 인용 필요
- 모든 Spring Boot 버전에서 동일 default 가 적용된다는 주장 — Spring docs 교차 확인 필요
- 한국 외 다른 국가 / 도메인 사례도 동일한지
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 prod allowlist (health/prometheus/info) 가 Spring 공식 `management.endpoints.web.exposure.include` 권고와 정합한지
- `management.endpoints.web.base-path` 변경의 trade-off (CD pipeline / 모니터링 도구 설정 영향)
- 인증 메커니즘 결정 (Spring Security + Actuator role / mTLS)
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- ca-tmpl 결정과의 정합성 (가설):
- **management port 9001 분리** ← 우아한형제들 "다른 포트 사용" 권고와 정합 (`WW-ACT-C4`).
- **prod allowlist (health/prometheus/info)** ← "엔드포인트 화이트리스트 운영, 기본 비활성화 후 필요한 것만" 권고와 동일 방향 (`WW-ACT-C1`).
- **shutdown / heapdump / threaddump prod forbidden** ← 동일하게 강조됨 (`WW-ACT-C2`, `C3`, `C6`).
- **base-path 변경 권고** 는 ca-tmpl 에 현재 미반영 (선택적 보강 항목 후보, `WW-ACT-C5`).
- **취급 주의**: 회사 기술블로그 = 공식 best practice 아님 (CLAUDE.md §5). 패턴은 Spring 공식 docs (default exposure 정책) 와 교집합이지만 정의 자체는 공식 출처가 우선.
- 시사점: ca-tmpl baseline 결정은 Spring 공식 + 한국 보안 운영 사례 (우아한형제들) 의 교집합 — 임의 결정 아님 (추정 정합, 공식 docs 별도 인용으로 보강 필요).
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/security-toss-actuator-healthcheck]] (health endpoint 단일 focus 보완)
- 인용하는 branch:
- [[raw/branch-notes/feature-management-actuator-security-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (Group G-B)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,102 @@
---
title: company-tech-blog / KSUID — K-Sortable Unique Identifier (Segment)
source_type: company-tech-blog
url: https://github.com/segmentio/ksuid
archive_url:
related_branches: [feature-resource-identifier-contract]
related_projects: [ca-skeleton]
tags: [company-tech-blog, ca-skeleton, api-design, ksuid, resource-identifier, base62-encoding, timestamp-leak]
created: 2026-05-31
---
# KSUID — K-Sortable Unique Identifier (Segment)
> Layer: `raw/` — 외부 자료(대기업 기술 블로그 / 오픈소스 README)의 원문 발췌·출처 기록.
> Segment 가 설계·운영하는 KSUID(K-Sortable Unique IDentifier) Go 라이브러리의 공식 README.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/` 에 `source-summary-template` 형식으로 별도 작성. 원본은 raw 에 영구 보관.
## Parent / 활용 branch
> 이 자료는 혼자 존재하지 않는다. `feature-resource-identifier-contract` branch 의 구현 결정 근거로서 보관됨.
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-resource-identifier-contract]] | D1 (resource ID default 형식) — KSUID 를 대안 후보로 평가하기 위한 설계 근거 |
| [[raw/branch-notes/feature-resource-identifier-contract]] | D2 (charset/encoding) — base62 (대소문자 구분, case-sensitive) vs ULID base32 (case-insensitive) 트레이드오프 |
| [[raw/branch-notes/feature-resource-identifier-contract]] | D7 (timestamp leak) — KSUID 의 32-bit 초 단위 timestamp 는 ULID/UUIDv7 의 밀리초 단위보다 정밀도가 낮아 시각 leak 위험이 상대적으로 낮음 |
## 출처 / Source
- 원본 URL: https://github.com/segmentio/ksuid
- 아카이브 URL: (미보관)
- 저자 / 조직: Segment (segmentio)
- 발행일: 미상 (레포 초기 커밋 기준 2017년경, 지속 관리 중)
- 마지막 확인일: 2026-05-31
## 왜 저장했는지 / Why archived
ca-skeleton 의 default resource ID 형식 결정(D1) 에서 KSUID 가 ULID / UUID v7 과 함께 주요 후보로 언급된다. KSUID 의 구조(20바이트, 32-bit 초 단위 timestamp + 128-bit 랜덤 payload, 27자 base62)는 D1/D2/D7 결정의 트레이드오프 분석에서 직접 인용할 근거가 된다. 특히 base62 (case-sensitive) vs base32 (case-insensitive) 의 charset 차이, 그리고 초 단위 timestamp 정밀도가 UUIDv7/ULID 의 밀리초 대비 timestamp leak 측면에서 어떤 의미를 갖는지 평가하기 위해 보관한다.
## 핵심 인용 / Key quotes (verbatim, 5문장)
> [§What is a KSUID?] "KSUID is for K-Sortable Unique IDentifier. It is a kind of globally unique identifier similar to a RFC 4122 UUID, built from the ground-up to be "naturally" sorted by generation timestamp without any special type-aware logic."
> [§How do KSUIDs work?] "Binary KSUIDs are 20-bytes: a 32-bit unsigned integer UTC timestamp and a 128-bit randomly generated payload. The timestamp uses big-endian encoding, to support lexicographic sorting. The timestamp epoch is adjusted to May 13th, 2014, providing over 100 years of life. The payload is generated by a cryptographically-strong pseudorandom number generator."
> [§How do KSUIDs work?] "The text representation is always 27 characters, encoded in alphanumeric base62 that will lexicographically sort by timestamp."
> [§3. Highly Portable Representations] "The text representation is an alphanumeric base62 encoding, so it "fits" anywhere alphanumeric strings are accepted. No delimiters are used, so stringified KSUIDs won't be inadvertently truncated or tokenized when interpreted by software that is designed for human-readable text, a common problem for the text representation of RFC 4122 UUIDs."
> [§Battle Tested] "This code has been used in production at Segment for several years, across a diverse array of projects. Trillions upon trillions of KSUIDs have been generated in some of Segment's most performance-critical, large-scale distributed systems."
## Claims Extracted / 추출된 주장
> 이 자료가 직접 말하는 것만 claim 으로 분리한다. 내 프로젝트에 적용한 결론은 여기 쓰지 않는다.
> Claim ID prefix: `KSUID-C`
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| KSUID-C1 | KSUID 의 binary 구조는 20바이트(4바이트 32-bit UTC timestamp + 16바이트 128-bit 랜덤 payload)이며, timestamp 는 custom epoch(2014-05-13)을 기준으로 big-endian 인코딩된다 | [§How do KSUIDs work?] "Binary KSUIDs are 20-bytes: a 32-bit unsigned integer UTC timestamp and a 128-bit randomly generated payload. The timestamp uses big-endian encoding, to support lexicographic sorting. The timestamp epoch is adjusted to May 13th, 2014, providing over 100 years of life." | `company-case-study` | KSUID 형식을 채택한 모든 언어 구현 | Unix epoch 와의 차이로 인해 타 시스템의 timestamp 와 직접 비교 불가 (`ksuid.New().Time()` 변환 필요); 초 단위 정밀도가 밀리초 단위 ULID/UUIDv7 보다 시각 추론 위험이 낮음을 공식적으로 언급하지 않음 |
| KSUID-C2 | KSUID 의 text 표현은 항상 27자이며, alphanumeric base62 인코딩을 사용하고 lexicographic 정렬 시 timestamp 순으로 정렬된다 | [§How do KSUIDs work?] "The text representation is always 27 characters, encoded in alphanumeric base62 that will lexicographically sort by timestamp." | `company-case-study` | KSUID 를 문자열로 저장·정렬하는 모든 시스템 | base62 는 대소문자 구분(case-sensitive)임을 README 가 명시하지 않음 — case-insensitive 비교 시스템과의 호환성은 별도 검토 필요; URL 대소문자 normalize 정책(RFC 3986)과의 정합성은 이 자료만으로 증명 불가 |
| KSUID-C3 | KSUID 의 text 표현은 alphanumeric base62 이므로 alphanumeric 문자열을 허용하는 모든 시스템에서 delimiters 없이 사용 가능하며, RFC 4122 UUID 의 dash-delimited 형식이 야기하는 tokenize/truncate 문제를 방지한다 | [§3. Highly Portable Representations] "The text representation is an alphanumeric base62 encoding, so it "fits" anywhere alphanumeric strings are accepted. No delimiters are used, so stringified KSUIDs won't be inadvertently truncated or tokenized when interpreted by software that is designed for human-readable text, a common problem for the text representation of RFC 4122 UUIDs." | `company-case-study` | alphanumeric 문자열 허용 API, DB, log 시스템 | base62 가 RFC 3986 unreserved charset 에 완전히 속하는지는 이 자료만으로 증명 불가 (RFC 3986 §2.3 별도 확인 필요); URL path 에서의 case-sensitivity normalize 정책은 이 자료 범위 밖 |
| KSUID-C4 | KSUID 는 RFC 4122 UUIDv4 의 122-bit entropy 대비 128-bit payload + timestamp "bonus entropy" 를 포함하여 충돌 확률이 실용적으로 불가능한 수준이며, Snowflake ID 처럼 coordination 없이 독립적으로 생성 가능하다 | [§2. Collision-free, Coordination-free, Dependency-free] "A KSUID includes 128 bits of pseudorandom data ("entropy"). This number space is 64 times larger than the 122 bits used by the well-accepted RFC 4122 UUIDv4 standard." | `company-case-study` | 분산 생성 환경에서의 충돌 방지 필요 시 | collision 확률의 수학적 증명은 아님; `FastRander` 사용 시 보안 강도 저하 가능성을 README 자체가 NOTE 로 경고 |
| KSUID-C5 | KSUID 는 Segment 의 production 환경에서 수 년간 수조 개(trillions upon trillions)가 생성된 battle-tested 구현체이다 | [§Battle Tested] "This code has been used in production at Segment for several years, across a diverse array of projects. Trillions upon trillions of KSUIDs have been generated in some of Segment's most performance-critical, large-scale distributed systems." | `company-case-study` | Segment 의 대규모 분산 시스템 사례 | Segment 외 타사 production 사례를 증명하지 않음; 다른 언어 구현체(Java, Python 등)의 동일 안정성을 보장하지 않음 |
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `KSUID-C1`: KSUID 의 물리적 구조 — 20바이트(4B timestamp + 16B payload), 32-bit 초 단위 정밀도, custom epoch(2014-05-13), big-endian
- `KSUID-C2`: text 표현 27자, base62, lexicographic 정렬 보장
- `KSUID-C3`: delimiter 없음, alphanumeric 문자열 수용 시스템과 호환, RFC 4122 UUID 의 tokenize 문제 없음
- `KSUID-C4`: 128-bit payload, UUID v4 대비 64배 entropy, coordination-free 생성
- `KSUID-C5`: Segment production 환경에서 수조 개 생성 이력
- 이 자료가 증명하지 않는 것:
- KSUID 가 ULID / UUID v7 / NanoID / CUID2 보다 우월하다 — Segment 의 선택이 다른 프로젝트의 best practice 임을 의미하지 않음
- base62 가 RFC 3986 unreserved charset(`ALPHA / DIGIT / "-" / "." / "_" / "~"`)에 완전히 속하는지 — 대소문자 모두 포함하므로 URL path case-sensitivity 정책과의 정합성은 별도 확인 필요
- 초 단위 timestamp 정밀도가 밀리초 단위 ULID/UUIDv7 대비 timestamp leak 위험을 공식적으로 감소시킨다는 주장 — 이는 branch 의 분석이며, 이 자료가 직접 말하지 않음
- Java / Spring Boot 에서 KSUID 를 사용할 때의 라이브러리 호환성 (Go 레퍼런스 구현만 다룸)
- GDPR / PII 관점에서 초 단위 timestamp 의 법적 안전성
- 내 프로젝트(ca-skeleton)에 적용하려면 추가 확인이 필요한 것:
- Java 생태계의 KSUID 라이브러리 성숙도 — Go 가 reference implementation 이며 Java 구현은 서드파티 (`github.com/ksuid/ksuid`, `ksuid-creator`)
- base62 대소문자와 Spring MVC path variable 의 case-sensitive matching 정합성
- 27자 base62 의 PostgreSQL / MySQL 컬럼 타입 결정 (`varchar(27)`) 및 index 성능 (ULID 26자 대비 1자 더 길고 case-sensitive)
- KSUID custom epoch(2014-05-13)와 timestamp 해석 시 Unix epoch 변환 필요 여부
## 메모 / Notes
- KSUID 의 timestamp 정밀도는 **초(second)** 단위 — ULID/UUIDv7 의 **밀리초(millisecond)** 대비 시각 추론의 정밀도가 낮다. 이는 D7(timestamp leak) 관점에서 유리하지만, 동일 초 내 단조 증가(monotonicity) 보장이 없다는 트레이드오프도 있다.
- Custom epoch(2014-05-13)은 Unix epoch(1970-01-01)이 아니므로, KSUID timestamp 를 직접 Unix time 으로 해석하면 오류. 라이브러리 API 를 통해서만 time 변환해야 함.
- base62 는 대소문자를 모두 사용 (`[0-9A-Za-z]` 62가지) — case-insensitive 데이터베이스 collation 이나 HTTP 헤더에서 expect-lowercase normalize 를 수행하는 환경에서는 소문자로 fold 될 위험 있음.
- ULID 는 `oklog/ulid` 의 OrNil 사례를 명시적으로 언급 (`(panic)` 주석) — KSUID 설계자가 ULID 를 인지하고 있음을 시사하나, ULID 와의 공식 비교표는 README 에 없음.
- Go 외 언어 구현체 다수 존재 (Python, Ruby, Java, Rust, .NET, Erlang, Zig) 하나 reference implementation 은 Go.
## Related / 관련
- 같은 주제 다른 raw 자료 (예정):
- [[raw/official-docs/ulid-spec.md]] — ULID 공식 spec (26자 base32, 밀리초 timestamp, case-insensitive)
- [[raw/official-docs/rfc9562-uuid.md]] — UUID v4/v7 RFC (밀리초 timestamp, RFC 표준)
- [[raw/official-docs/cuid2-spec.md]] — CUID2 (timestamp leak 없음, fingerprint 기반)
- [[raw/official-docs/rfc3986-uri-generic-syntax.md]] — URL charset / case sensitivity 근거
- 이 자료를 인용한 wiki 요약: [[wiki/concepts/resource-identifier-format]] (생성 시)
@@ -0,0 +1,113 @@
---
title: "personal-blog / Software Engineer Career Levels — What Companies Expect (Mubin Shaikh)"
source_type: personal-blog
url: https://dev.to/mubin_shaikh_dev/software-engineer-career-levels-what-companies-really-expect-at-every-stage-25p5
archive_url:
related_branches: []
related_projects: [llm-wiki]
tags: [personal-blog, llm-wiki, learning, daily-task, deliberate-practice]
status: raw
confidence: medium
created: 2026-05-28
last_reviewed: 2026-05-28
---
# personal-blog / Software Engineer Career Levels — What Companies Expect (Mubin Shaikh)
> Layer: `raw/company-tech-blogs/` — 외부 자료(개인 기술 블로그)의 **원문 발췌·출처 기록**.
> `source_type: personal-blog` — CLAUDE.md §5 에 따라 *참고 자료* 수준. 공식 best practice 격상 금지.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/` 에 별도 작성. 원본은 raw 에 영구 보관.
---
## Parent / 활용 branch (필수)
> 이 자료는 **혼자 존재하지 않는다.** `raw/daily-tasks/` 커리큘럼의 "시니어 초반급 문제해결력" 목표 정의의 외부 anchor 로 수집.
| Parent | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/daily-tasks/README]] | daily-task 6-month 커리큘럼의 "시니어 초반급 문제해결력" 목표 정의. mid→senior 갭의 trade-off articulation / system thinking / failure mode awareness 의 외부 anchor. **personal-blog 강도 — 공식 best practice 격상 금지**. |
---
## 출처 / Source
- 원본 URL: https://dev.to/mubin_shaikh_dev/software-engineer-career-levels-what-companies-really-expect-at-every-stage-25p5
- 기본 URL (403): https://mubinshaikh.dev/blog/career-level-breakdown/
- 아카이브 URL: (미제공 — 사용자 입력 없음)
- 저자 / 조직: Mubin Shaikh (개인 블로그 / dev.to)
- 발행일: 미확인 (dev.to 게시 날짜 별도 확인 필요)
- 마지막 확인일: 2026-05-28
---
## 왜 저장했는지 / Why archived
daily-task 6개월 커리큘럼의 "시니어 초반급 문제해결력 도달" 목표를 외부 자료로 anchor 하기 위해 수집. mid-level 과 senior 의 구체적 경계(trade-off articulation, system thinking, failure-mode awareness)를 verbatim 인용으로 확보해, 커리큘럼 설계 결정에 참고 강도 근거로 사용.
---
## 핵심 인용 / Key quotes (verbatim, Self-Grep 통과)
> [§Senior Software Engineer] "You think beyond your code. You think about latency, throughput, failure modes, and how your service interacts with others. You can design a system, not just a class."
> [§Mid-Level Software Engineer] "You don't just write code that works. You write code that's maintainable, testable, and doesn't surprise the next developer."
> [§What Separates Strong Candidates] "There are no best practices. There are trade-offs you understand and trade-offs you don't. Strong candidates make the trade-offs explicit."
> [§Senior Software Engineer — fintech example] "A junior would have fixed the retry logic. A senior engineer traced it to a missing idempotency check at the gateway level, added deduplication, and set up alerts to catch it in the future."
> [§Career Progression at a Glance] "Early in your career, you're evaluated on what you can build. Later, you're evaluated on the decisions you drive."
> [§Where Do You Actually Stand?] "Can I own a production issue end-to-end without escalating? Can I explain the trade-offs behind my last three design decisions? Do other engineers come to me for technical decisions, or just for execution help?"
---
## Claims Extracted / 추출된 주장
> 이 자료는 `personal-blog` 강도. Claim 은 원문이 직접 말한 것만. 공식 best practice 또는 업계 표준으로 격상 금지.
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| SR-MUBIN-C1 | Senior engineer 는 코드 단위가 아닌 시스템 단위로 사고한다 — latency, throughput, failure mode, 서비스 간 상호작용까지 | [§Senior] "You think beyond your code. You think about latency, throughput, failure modes, and how your service interacts with others. You can design a system, not just a class." | `engineering-blog` | Senior 레벨 정의 논의 시 참고 근거 | 이 정의가 모든 회사/산업에서 일치한다는 것을 증명하지 않음. 저자 개인 관점. |
| SR-MUBIN-C2 | Mid-level 은 "동작하는 코드"를 넘어 유지보수성·테스트 가능성·다음 개발자 놀라지 않을 코드를 쓴다 | [§Mid-Level] "You don't just write code that works. You write code that's maintainable, testable, and doesn't surprise the next developer." | `engineering-blog` | Mid-level 기대치 설명 시 참고 근거 | Senior 와의 경계를 유일하게 정의하지 않음. "코드 품질"이 mid-level 에서 멈춘다는 뜻 아님. |
| SR-MUBIN-C3 | "Best practice" 인용은 Senior 기준에 미달 — trade-off 를 명시적으로 articulate 하는 것이 강한 후보의 특징 | [§What Separates Strong Candidates] "There are no best practices. There are trade-offs you understand and trade-offs you don't. Strong candidates make the trade-offs explicit." | `engineering-blog` | 면접·코드리뷰에서 "best practice" 무비판 인용 패턴 경계 anchor | 모든 best practice 가 무효라는 주장이 아님. trade-off articulation 의 부재를 지적하는 것. |
| SR-MUBIN-C4 | Senior 는 증상(retry 실패)이 아닌 근본 원인(idempotency 누락)까지 추적하고, 재발 방지(alert 설정)까지 책임진다 | [§Senior — fintech example] "A junior would have fixed the retry logic. A senior engineer traced it to a missing idempotency check at the gateway level, added deduplication, and set up alerts to catch it in the future." | `engineering-blog` | Senior 문제 해결 범위의 구체적 예시 | 이 fintech 시나리오가 Senior 의 유일한 혹은 보편적 패턴임을 증명하지 않음. 하나의 예시. |
| SR-MUBIN-C5 | 커리어 초반은 "무엇을 만드는가"로 평가되고, 후반은 "어떤 결정을 주도하는가"로 평가된다 | [§Career Progression at a Glance] "Early in your career, you're evaluated on what you can build. Later, you're evaluated on the decisions you drive." | `engineering-blog` | 학습 목표 설정 시 커리어 방향 anchor | 이 전환의 정확한 시점(연차)을 지정하지 않음. 회사·팀마다 다를 수 있음. |
---
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것:**
- SR-MUBIN-C1: Mubin Shaikh 의 관점에서 Senior 가 시스템 단위 사고를 갖는다는 서술
- SR-MUBIN-C3: trade-off articulation 이 "best practice" 무비판 인용을 대체해야 한다는 주장
- SR-MUBIN-C4: Senior 가 증상이 아닌 근본 원인 + 재발 방지까지 책임진다는 구체 예시
- SR-MUBIN-C5: 커리어 성장의 평가 기준이 "build" → "decision" 으로 전환된다는 서술
- **이 자료가 증명하지 않는 것:**
- 위 특성이 특정 회사/업계의 공식 Senior 기준임. 채용 공고나 performance rubric 에서 동일하게 정의된다는 보장 없음.
- `personal-blog` 이므로 동료 심사 없음. 저자의 개인 경험·관점.
- mid → senior 갭이 "trade-off articulation" 단 하나의 요소로만 결정된다는 것.
- **내 커리큘럼에 적용하려면 추가 확인이 필요한 것:**
- 실제 국내 백엔드 시니어 면접(토스, 카카오, 네이버 등)에서 동일 기준이 사용되는지 회사 기술 블로그 또는 채용공고로 교차 검증 권장.
- `raw/company-tech-blogs/deliberate-practice-software-developers-redgreencode` 와 결합해 daily-task 설계의 "의도적 연습" 원리와 연결.
---
## 메모 / Notes
- primary URL (`mubinshaikh.dev`) 은 403 Forbidden — fallback `dev.to` 에서 fetched.
- `personal-blog` source_type 은 `raw/company-tech-blogs/` 폴더에 저장되지만 frontmatter 로 강도 구분. CLAUDE.md §5: personal-blog = 참고 자료.
- `career` 태그는 tag-taxonomy 에 미등록 어휘 — `learning` (L3) 으로 대체. taxonomy 갱신 후보로 메모.
- 저자는 6개 레벨을 정의하나 이 raw note 는 L2(mid) ↔ L3(senior) 갭에 집중. L4~L6 는 본 커리큘럼 범위 외.
- Self-Grep 6개 인용 전원 통과 (line 8, 14, 20, 24, 28, 32 in /tmp/source-fetch-1780015306.txt).
---
## Related / 관련
- 같은 deliberate-practice / 학습 방법론: [[raw/company-tech-blogs/deliberate-practice-software-developers-redgreencode]]
- daily-task 허브: [[raw/daily-tasks/README]]
- 이 자료를 인용한 wiki 요약: (생성 시 추가)
@@ -0,0 +1,93 @@
---
title: Skillable — Building Successful Hands-on Labs
source_type: company-tech-blog
url: https://docs.skillable.com/docs/building-successful-hand-on-labs
archive_url:
related_branches: []
related_projects: [llm-wiki]
tags: [company-tech-blog, llm-wiki, learning, hands-on-lab, daily-task-template]
status: raw
confidence: high
created: 2026-05-28
last_reviewed: 2026-05-28
vendor: Skillable Inc.
---
# Skillable — Building Successful Hands-on Labs
> Layer: `raw/` — 외부 자료(벤더 가이드)의 원문 발췌·출처 기록.
> source_type: `company-tech-blog`. Skillable 은 lab 플랫폼 판매사이므로 IETF/Jakarta 수준의 규범적(normative) 표준이 아님. CLAUDE.md §5에 따라 "사례/관점"으로 취급하며 공식 best practice 로 단독 인용 금지.
## Parent / 활용 branch
> 이 자료는 혼자 존재하지 않는다. 어느 작업의 어떤 결정을 정당화하는지 명시.
| Parent | 이 자료가 정당화하는 결정 |
|---|---|
| [[wiki/llm-wiki]] | LLM Wiki 의 daily-task template 의 7-component 구조 (Learning Objectives / Storyline / Environment / Exercises / Assessments / Outcomes / Technologies) 정당화. "사수가 신입에게 주는 과제" 형식의 vendor-normative 근거 |
## 출처 / Source
- 원본 URL: https://docs.skillable.com/docs/building-successful-hand-on-labs
- 아카이브 URL: (미확보)
- 저자 / 조직: Skillable Inc.
- 발행일: (명시 없음)
- 마지막 확인일: 2026-05-28
## 왜 저장했는지 / Why archived
LLM Wiki 의 `daily-task-template.md` 설계 근거. "사수가 신입에게 주는 과제" 포맷의 7개 필수 섹션(Learning Objectives, Exercises, Outcomes, Technologies, Storyline, Environment, Assessments)이 Skillable 의 functional specification 구성요소 목록과 직접 대응한다. 벤더 가이드이므로 독립적 공식 표준으로 취급하지 않으나, 구조화된 실습 과제의 필수 구성요소에 대한 실무 근거로 인용한다.
## 핵심 인용 / Key quotes (verbatim, Self-Grep 통과)
> [§The Functional Specification] "The functional specification identifies the following: Learning objectives, Exercises, Outcomes, Technologies used. Storyline, Prospective environment, Assessments" (source lines 3947)
> [§The Functional Specification] "The storyline is ultimately for the learner and provides the reason for the hands-on experience. Without this, a lab becomes an exercise in \"clicking things\" without a reason, and the learner ultimately walks away without having enhanced their skills." (source line 49)
> [§The Functional Specification] "Missing any of these elements will deeply impact the development and/or the learner's experience of the lab." (source line 49)
> [§The Functional Specification — Proven practice #3] "Ensure the learning objectives and the storyline support each other to make the lab the best learning experience possible for the student." (source line 51)
> [§Lab development — Note] "Assessment activities support the learner's journey by giving immediate feedback for success or additional help to be successful. When creating activities the developer should ensure they contain clear feedback and, for the scripts, contain error checking." (source line 72)
## Claims Extracted / 추출된 주장
> 이 자료가 직접 말하는 것만 claim 으로 분리한다. 내 프로젝트에 적용한 결론은 여기 쓰지 않는다.
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| SKILL-LAB-C1 | 성공적인 실습 과제의 functional specification 은 7개 구성요소(Learning objectives, Exercises, Outcomes, Technologies used, Storyline, Prospective environment, Assessments)를 포함해야 한다 | [§The Functional Specification] "The functional specification identifies the following: Learning objectives / Exercises / Outcomes / Technologies used. / Storyline / Prospective environment / Assessments" | `company-case-study` | Skillable 플랫폼 기반 hands-on lab 설계 | 이 7개가 모든 학습 설계 프레임워크에서 universally required 임을 의미하지 않음. ADDIE·Bloom 등 독립 표준으로 보강 없이 "공식 best practice" 로 인용 불가 |
| SKILL-LAB-C2 | Storyline 이 없으면 실습은 이유 없는 "clicking things" 가 되어 학습자가 기술을 향상하지 못한 채 떠난다 | [§The Functional Specification] "Without this, a lab becomes an exercise in \"clicking things\" without a reason, and the learner ultimately walks away without having enhanced their skills." | `company-case-study` | 실습형 과제(hands-on lab) 설계 전반 | 서술형 시나리오가 없는 모든 학습 형식이 비효과적임을 증명하지 않음 |
| SKILL-LAB-C3 | Learning objectives 와 storyline 은 서로를 지지해야 한다 (Proven practice #3) | [§The Functional Specification] "Ensure the learning objectives and the storyline support each other to make the lab the best learning experience possible for the student." | `company-case-study` | 실습 과제 설계 시 objectives 와 narrative 간 정합성 | 정합성 확보 방법론(구체적 기법)은 이 문서에서 제공하지 않음 |
| SKILL-LAB-C4 | Assessment 는 학습자의 여정을 지원하며 즉각적인 피드백을 제공함으로써 학습을 강화한다 | [§Lab development] "Assessment activities support the learner's journey by giving immediate feedback for success or additional help to be successful." | `company-case-study` | 자동화된 assessment 를 포함한 실습 과제 | 즉각 피드백 없는 assessment 가 학습에 효과 없음을 증명하지 않음 |
| SKILL-LAB-C5 | 7개 구성요소 중 하나라도 빠지면 개발과 학습자 경험 모두에 심각한 영향을 미친다 | [§The Functional Specification] "Missing any of these elements will deeply impact the development and/or the learner's experience of the lab." | `company-case-study` | Skillable 플랫폼 기반 lab 의 설계 완결성 | 영향의 정도·측정 지표는 이 문서가 제공하지 않음. 실증 데이터 없음 |
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `SKILL-LAB-C1`: Skillable 이 권장하는 functional specification 의 7가지 구성 항목
- `SKILL-LAB-C2`: Storyline 부재 시 학습자 경험 저하에 대한 벤더 주장
- `SKILL-LAB-C3`: Objectives ↔ Storyline 정합성 필요성 (Proven practice #3)
- `SKILL-LAB-C4`: Assessment 의 즉각 피드백 역할
- `SKILL-LAB-C5`: 구성요소 누락 시 경험 저하 위험
- 이 자료가 증명하지 않는 것:
- 이 7개 구성요소가 산업 전반의 normative standard 임 (IETF/ISO/IEEE 수준 기준 없음)
- 공식 교육 설계 표준(ADDIE, Bloom's Taxonomy, Gagné의 9 Events) 과의 일치 여부
- 실증 측정 데이터 (완료율 향상, 기술 습득 효과 등 수치)
- Skillable 플랫폼 외 다른 학습 시스템에서의 보편적 적용성
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- `daily-task-template.md` 의 섹션 구조가 이 7개 구성요소에 매핑될 때, 각 섹션의 품질 기준은 별도로 정의해야 함
- "사수가 신입에게 주는 과제" 포맷 특성상 Stakeholder·QA Tester 역할은 필요 없으나, SME(사수) + Learner(신입) 역할 분리 구조는 이 가이드와 일치하는지 검토 필요
## 메모 / Notes
- 이 문서는 Skillable 이 자사 플랫폼 고객을 위해 작성한 operational guide 로, 제품 판매 맥락이 있음. 따라서 "Proven practice #N" 표현에도 불구하고 독립 학술 연구나 표준 기관 권고가 아님.
- 7-component 구조를 daily-task-template 에 채택하되, 각 컴포넌트에 대해 추가 official-doc 또는 교육학 기반 자료로 보강하는 것이 권장됨.
- 추가로 봐야 할 동일 출처 페이지: Skillable 의 "Lab Instruction Guide", "Activity Types" 문서
## Related / 관련
- 같은 주제 다른 raw 자료: (미등록 — 교육 설계 관련 official-doc 추가 예정)
- 이 자료를 인용한 wiki 요약: (생성 전)
@@ -0,0 +1,72 @@
---
title: company-tech-blog / Configuring datasource-proxy in Spring Boot — Arnold Galovics
source_type: company-tech-blog
url: https://arnoldgalovics.com/spring-boot-datasource-proxy/
archive_url:
status: raw
confidence: medium
tags: [backend, db, jdbc, proxy, datasource-proxy, slow-query, spring-boot]
related_branches: [feature-database-connection-pool-contract]
related_projects: []
created: 2026-06-09
last_reviewed: 2026-06-09
---
# Configuring datasource-proxy in Spring Boot — Arnold Galovics
> Layer: `raw/` — 외부 자료(기술 블로그)의 원문 발췌·출처 기록.
## Parent / 활용 branch
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-database-connection-pool-contract]] | datasource-proxy 의 기본 로그 출력 형식이 파라미터 값을 포함함을 보여주는 실제 사례 근거 — ParameterTransformer 없이는 prod 에서 파라미터 노출 위험 |
## 출처 / Source
- 원본 URL: https://arnoldgalovics.com/spring-boot-datasource-proxy/
- 저자 / 조직: Arnold Galovics (개인 엔지니어링 블로그, Java/Spring 전문가)
- 발행일: 2017-06-26 (업데이트: 2021-12-15)
- 마지막 확인일: 2026-06-09
## 왜 저장했는지 / Why archived
datasource-proxy 를 Spring Boot 에 설정할 때 기본 로그 출력이 어떤 형식인지, 파라미터 값이 어떻게 출력되는지 실제 예시를 확인하기 위해 보관. 프로젝트 "SQL/파라미터 로그 금지" 하드 룰 적용 시 ParameterTransformer 가 반드시 필요함을 뒷받침하는 사례 근거.
## 핵심 인용 / Key quotes (verbatim)
> "Query:["insert into persons (name, id) values (?, ?)"], Params:[(Arnold,1)]"
— 기사 본문, datasource-proxy 기본 로그 출력 예시
> "datasource-proxy is a library that can be used to intercept JDBC interactions"
— 기사 본문
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| C1 | datasource-proxy 의 기본 로그 출력에는 바인드 파라미터 값이 `Params:[(Arnold,1)]` 형식으로 포함된다 | "Query:["insert into persons (name, id) values (?, ?)"], Params:[(Arnold,1)]" | `engineering-blog` | datasource-proxy 기본 설정 환경 | ParameterTransformer 적용 시에도 파라미터가 노출된다는 주장 반증 |
| C2 | 기사는 prod 환경에서의 PII 노출 위험을 논의하지 않는다 (부재 사실) | 기사 본문에서 PII/보안 경고 없음 | `engineering-blog` | 이 기사만 해당 | datasource-proxy 가 prod 에서 파라미터를 안전하게 처리한다는 주장 반증 |
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `C1`: 기본 datasource-proxy 설정에서 파라미터 값이 로그에 노출됨을 실제 예시로 보여줌
- 이 자료가 증명하지 않는 것:
- ParameterTransformer 적용 이후에도 파라미터가 노출된다는 주장
- 슬로우 쿼리 로그 출력 형식 (이 기사는 슬로우 쿼리 설정을 보여주지 않음)
- 이것이 대기업 engineering blog 의 "공식 best practice"라는 주장 (개인 블로그)
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- ParameterTransformer 로 `[REDACTED]` 치환 구현 후 슬로우 쿼리 로그 출력에도 반영되는지 로컬 테스트
## 메모 / Notes
- 이 기사는 2017년 작성 (2021 업데이트) — Spring Boot 버전이 오래됨. Spring Boot 3.x 환경에서는 spring-boot-data-source-decorator 사용 권장
- 파라미터 노출 형식 (`Params:[(value)]`) 이 실제로 슬로우 쿼리 로그에도 동일하게 나타나는지는 별도 공식 문서 확인 필요
## Related / 관련
- [[raw/official-docs/datasource-proxy-slow-query-official]]
- 이 자료를 인용한 wiki 요약: (미생성)
@@ -0,0 +1,108 @@
---
title: company-tech-blog / Twitter Engineering — Announcing Snowflake (분산 고유 ID 생성 네트워크 서비스)
source_type: company-tech-blog
url: https://blog.x.com/engineering/en_us/a/2010/announcing-snowflake
archive_url: https://github.com/twitter-archive/snowflake/tree/snowflake-2010
related_branches: [feature-resource-identifier-contract]
related_projects: [ca-skeleton]
tags: [company-tech-blog, ca-skeleton, data-modeling, resource-identifier]
created: 2026-05-31
---
# Twitter Engineering — Announcing Snowflake (분산 고유 ID 생성 네트워크 서비스)
> Layer: `raw/company-tech-blogs/` — Twitter Engineering Blog (2010) 의 Snowflake ID 생성 시스템 원문 발췌.
> 원본 블로그 URL (`blog.x.com`) 은 접근 불가 (HTTP 403). 내용은 공식 GitHub 아카이브 태그 `snowflake-2010` README 에서 추출.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/` 에 별도 작성. 원본은 raw 에 영구 보관.
## Parent / 활용 branch (필수, 최소 1개+)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-resource-identifier-contract]] | D1 (resource ID default 형식): Snowflake 의 datacenter_id + worker_id 조율 부담을 근거로 단일 generator skeleton 에서의 **명시적 거부** 증거 / D10 (DB primary key): 64bit 단일 컬럼 BIGINT fit 가능성 / D13 (multi-tenancy): datacenter_id 가 partition 힌트를 ID 에 인코딩하는 대안 패턴 사례 |
## 출처 / Source
- 원본 URL: https://blog.x.com/engineering/en_us/a/2010/announcing-snowflake
- 아카이브 URL: https://github.com/twitter-archive/snowflake/tree/snowflake-2010 (archived 2021-09-18, 읽기 전용)
- 저자 / 조직: Twitter Engineering (Raffi Krikorian 외)
- 발행일: 2010년 6월
- 마지막 확인일: 2026-05-31
- **주의**: 원본 블로그 (`blog.x.com` 및 레거시 `blog.twitter.com`) 는 HTTP 403 / 301 redirect 반환으로 WebFetch 불가. 본 문서의 모든 인용은 공식 GitHub 아카이브 `snowflake-2010` 태그 README 에서 Self-Grep 검증 완료.
## 왜 저장했는지 / Why archived
Snowflake 는 분산 시스템에서 time-ordered 64bit 고유 ID 를 생성하는 Twitter 의 접근법으로, datacenter_id + worker_id 인코딩 방식이 `feature-resource-identifier-contract` 에서 검토한 ID 후보군 중 하나다. ca-skeleton 은 단일 generator 가정(단일 JVM 프로세스, worker 조율 불필요)이므로 Snowflake 를 **명시적으로 거부**하는 결정(D1)의 근거 자료로 보관한다. 동시에 64bit 레이아웃이 BIGINT primary key(D10)와 정합하는 설계 강점과, datacenter_id 가 multi-tenancy partition 힌트를 ID에 인코딩하는 대안 패턴(D13)을 사례로 기록한다.
## 핵심 인용 / Key quotes (verbatim, 5개)
> [§Solution] "id is composed of: time - 41 bits (millisecond precision w/ a custom epoch gives us 69 years) / configured machine id - 10 bits - gives us up to 1024 machines / sequence number - 12 bits - rolls over every 4096 per machine (with protection to avoid rollover in the same ms)"
— GitHub `snowflake-2010` README §Solution, lines 4750
> [§Requirements / Uncoordinated] "For high availability within and across data centers, machines generating ids should not have to coordinate with each other."
— GitHub `snowflake-2010` README §Requirements / Uncoordinated, line 21
> [§Requirements / Compact] "There are many otherwise reasonable solutions to this problem that require 128bit numbers. For various reasons, we need to keep our ids under 64bits."
— GitHub `snowflake-2010` README §Requirements / Compact, line 37
> [§Requirements / Performance] "minimum 10k ids per second per process"
— GitHub `snowflake-2010` README §Requirements / Performance, line 16
> [§Requirements / Time Ordered] "We can guarantee, however, that the id numbers will be k-sorted (references: http://portal.acm.org/citation.cfm?id=70413.70419 and http://portal.acm.org/citation.cfm?id=110778.110783) within a reasonable bound (we're promising 1s, but shooting for 10's of ms)."
— GitHub `snowflake-2010` README §Requirements / Time Ordered, line 29
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| SNOWFLAKE-C1 | Snowflake ID 는 총 64bit 미만으로 구성된다: 41bit timestamp (ms 정밀도, custom epoch) + 10bit machine ID (최대 1024 머신) + 12bit sequence (머신당 ms 당 최대 4096개) | [§Solution] "id is composed of: time - 41 bits (millisecond precision w/ a custom epoch gives us 69 years) / configured machine id - 10 bits - gives us up to 1024 machines / sequence number - 12 bits - rolls over every 4096 per machine" | `company-case-study` | 분산 다중 노드 환경에서 고유 ID 생성이 필요한 시스템 | 단일 JVM generator 에서 이 분할이 최적임을 증명하지 않음. 10bit machine ID 는 사전 설정(coordinated) worker ID 할당이 전제됨 |
| SNOWFLAKE-C2 | ID 생성에 노드 간 조율(coordination) 이 불필요하도록 설계하는 것이 고가용성의 핵심 요건이다 | [§Requirements / Uncoordinated] "For high availability within and across data centers, machines generating ids should not have to coordinate with each other." | `company-case-study` | 다수 데이터센터 / 다수 노드 환경의 ID 생성 시스템 | 단일 generator 환경에서도 이 요건이 동일하게 적용된다는 뜻이 아님. 또한 worker ID 사전 할당 자체가 별도의 외부 조율(ZooKeeper 등)을 요구함을 이 Claim 은 직접 언급하지 않음 |
| SNOWFLAKE-C3 | 고성능 ID 생성 시스템은 프로세스당 초당 최소 10,000개의 ID 를 생성할 수 있어야 한다 | [§Requirements / Performance] "minimum 10k ids per second per process" | `company-case-study` | Twitter 규모의 분산 서비스 ID 생성 요건 | 이 throughput 요건이 일반 백엔드 서비스에 동일하게 적용되어야 한다는 뜻이 아님. 12bit sequence 로 ms당 4096개 = 초당 약 4백만 개의 이론 최대치는 별도 계산이며 원문 직접 인용이 아님 |
| SNOWFLAKE-C4 | ID 는 64bit(128bit 대안 아닌) 이하여야 한다 | [§Requirements / Compact] "There are many otherwise reasonable solutions to this problem that require 128bit numbers. For various reasons, we need to keep our ids under 64bits." | `company-case-study` | Twitter 의 ID 저장·인덱싱·전송 요건 | "we need to keep our ids under 64bits" 는 Twitter 내부 요건(MySQL BIGINT 컬럼 등). BIGINT fit 이 곧 최선의 DB PK 선택임을 일반적으로 증명하지 않음 |
| SNOWFLAKE-C5 | Snowflake ID 는 정확한 순서가 아닌 k-sorted (합리적 오차 범위 내 정렬) 를 보장한다 | [§Requirements / Time Ordered] "We can guarantee, however, that the id numbers will be k-sorted [...] within a reasonable bound (we're promising 1s, but shooting for 10's of ms)." | `company-case-study` | 비동기 분산 연산이 많은 API 에서 ID 기반 페이지네이션 / "since this id" 조회 패턴 | 동일 ms 내 단조 증가(monotonicity) 와 k-sorted 는 다른 보장임. RFC 9562 UUIDv7 의 monotonicity 보장과 직접 비교할 수 없음 |
### Strength 허용값 (적용 근거)
본 자료는 Twitter Engineering 이 자사 시스템에서 Snowflake 를 어떻게 설계했는지를 직접 기술한 `company-case-study` 다. 공식 표준(RFC, ISO) 이 아니므로 모든 Claim 은 `company-case-study` 로 표기한다.
## Usage Boundaries / 적용 경계
### 이 자료가 직접 증명하는 것
- **SNOWFLAKE-C1**: Snowflake 의 64bit 레이아웃(41+10+12 bit 분할)과 custom epoch 설계 — D10 에서 BIGINT fit 가능성의 사례 근거
- **SNOWFLAKE-C2**: 고가용성을 위해 노드 간 ID 조율 불필요 설계가 요건임 — 역설적으로, Snowflake 의 worker ID 는 사전 조율이 필요함을 시사 (D1 Snowflake 거부 근거)
- **SNOWFLAKE-C3**: Twitter 규모에서 프로세스당 초당 10k+ ID 요건이 존재함
- **SNOWFLAKE-C4**: 64bit 이하 ID 가 128bit 대안보다 선호됨 (MySQL BIGINT 컬럼 호환성)
- **SNOWFLAKE-C5**: 분산 환경에서 엄격한 전역 순서 대신 k-sorted 보장이 현실적 대안임
### 이 자료가 증명하지 않는 것
- Snowflake 의 worker ID 할당이 ZooKeeper 등 별도 외부 코디네이터 없이 동작할 수 있다는 것 (원문은 이를 직접 기술하지 않음)
- 단일 generator 환경(ca-skeleton 기본 가정)에서 Snowflake 레이아웃이 적합하다는 것
- 12bit sequence → ms당 4096개 → 초당 4M개 이론 최대 throughput (원문에서 직접 명시하지 않음, 계산 추론임)
- datacenter_id(5bit) + worker_id(5bit) 로의 10bit 분할 (이 구체적 분할은 원문 `snowflake-2010` README 에 없음 — 블로그 원문 또는 후속 구현체에서 언급됨)
- Snowflake ID 가 GDPR Article 4(1) "identifier" 에 해당하는지 여부
- 단조 증가(monotonicity) 보장 (k-sorted 와 다름)
### 내 프로젝트에 적용하려면 추가 확인이 필요한 것
- **D1 Snowflake 거부 근거 보강**: SNOWFLAKE-C2 는 "조율 불필요" 를 *요건* 으로 제시하지만, 실제 Snowflake 구현에서 worker ID 사전 배정이 외부 코디네이터(ZooKeeper)를 요구한다는 사실은 원문이 아닌 구현체(소스코드)에서 확인 필요. 이 부분은 현재 `needs-confirmation`
- **D10 BIGINT fit**: SNOWFLAKE-C4 는 64bit 이하를 사용한다는 Twitter 내부 요건을 기술함. PostgreSQL/MySQL 에서 BIGINT(8바이트)가 UUID(16바이트)보다 인덱스 성능에서 유리한지는 별도 벤치마크(UUID-V7-PERF-Cx, 미보관) 로 확인 필요
- **D13 multi-tenancy**: Snowflake 의 10bit machine ID 가 datacenter partition 힌트로 활용 가능한지는 배포 아키텍처에 따라 다르며, ca-skeleton 단일 generator 가정에서는 적용 범위 없음
## 메모 / Notes
- 원본 블로그 URL (`blog.x.com/engineering/en_us/a/2010/announcing-snowflake`) 은 HTTP 403 반환. `blog.twitter.com` 은 301 redirect → `blog.x.com` 으로 redirect (같은 403). Wayback Machine (`web.archive.org`) 도 WebFetch 제한. 최종적으로 공식 GitHub 아카이브 `snowflake-2010` 태그 README 에서 추출.
- 원문 README 에는 "datacenter_id 5bit + worker_id 5bit" 의 구체적 분할이 **명시되어 있지 않다**. 이 분할은 블로그 본문(접근 불가) 또는 후속 구현체에서 언급됨. Claims 에는 포함하지 않았고, 원문이 기술하는 "10 bits - gives us up to 1024 machines" 만 인용.
- SNOWFLAKE-C3 의 초당 4백만개 이론치는 12bit × 1000ms = 4,096,000/sec 계산 추론이며 원문에 없음 — branch-note 의 메모로만 남기고 Claims 에는 포함하지 않음.
- Snowflake 는 2010년 Apache Thrift 기반 Scala 서버로 구현되었고, 이후 Twitter-server 기반으로 재작성됨. GitHub 아카이브는 2021년 9월 archived (read-only).
- 추가로 봐야 할 동일 출처 페이지: `https://github.com/twitter-archive/snowflake/blob/snowflake-2010/README.md` (raw 텍스트), Sonyflake(Sony), Instagram's ID generation approach (similar 64bit layout).
## Related / 관련
- 같은 주제 관련 raw 자료:
- [[raw/official-docs/rfc9562-uuid]] — UUID v7 time-ordered 64bit 설계와 비교 (RFC9562-C1~C5)
- [[raw/company-tech-blogs/segment-ksuid]] — KSUID 158bit (32bit 초 단위 timestamp + 128bit 랜덤) 비교 (KSUID-C1~C3)
- [[raw/company-tech-blogs/planetscale-nanoid-api]] — NanoID + BIGINT dual column 사례 비교
- 이 자료를 인용한 wiki 요약: (생성 시 추가)
- 유사 Snowflake-variant 시스템: Sonyflake, Instagram ID (64bit = 41bit epoch ms + 13bit shard + 10bit sequence), Discord Snowflake (42bit timestamp + 10bit worker + 12bit increment)
@@ -0,0 +1,173 @@
---
title: company-tech-blog / Spring Modulith — ArchUnit IS_GENERATED predicate, detectViolations() Violations-as-data, @ApplicationModuleListener meta-annotation
source_type: company-tech-blog
url:
- https://github.com/spring-projects/spring-modulith/blob/main/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java
- https://github.com/spring-projects/spring-modulith/blob/main/spring-modulith-events/spring-modulith-events-api/src/main/java/org/springframework/modulith/events/ApplicationModuleListener.java
- https://docs.spring.io/spring-modulith/reference/events.html
archive_url:
related_branches:
- feature-architecture-enforcement-rules
- feature-application-port-usecase-contract
related_projects: [ca-skeleton]
tags: [company-tech-blog, ca-skeleton, architecture, spring-modulith, archunit, code-generation, domain-event, transaction]
status: raw
confidence: high
created: 2026-05-28
---
# Spring Modulith — ArchUnit IS_GENERATED predicate, detectViolations() Violations-as-data, @ApplicationModuleListener meta-annotation
> Layer: `raw/company-tech-blogs/` — Spring 공식 incubator 프로젝트(spring-projects org) 소스코드 및 공식 참조 문서 발췌.
> Spring Modulith 는 **Spring Framework 1급 표준이 아닌 incubator project** 임에 유의.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/` 에 별도 작성. 원본은 raw 에 영구 보관.
---
## Parent / 활용 branch
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-architecture-enforcement-rules]] | D9 (MapStruct `@Generated` exemption): Spring Modulith 자체가 `annotatedWith(Generated.class)` 패턴을 production에서 사용함을 보임 → ArchUnit predicate DSL로 generated code 면제가 실현 가능한 패턴임을 corroborate. S1 (negative test fixture): `detectViolations()``Violations` 객체를 반환하는 violations-as-data 패턴 — Spring Modulith 공식 negative test 패턴 |
| [[raw/branch-notes/feature-application-port-usecase-contract]] | D1 (Spring `@Transactional` forbidden) counter-evidence: `@ApplicationModuleListener``@Transactional(propagation = Propagation.REQUIRES_NEW)` 를 meta-annotation 으로 재노출 — ca-tmpl 의 application layer `@Transactional` 직접 import 금지 정책과 정면 충돌하는 패턴 존재. 추가 증거로 기록 (D3 counter-evidence, does not override D3) |
---
## 출처 / Source
- 원본 URL 1: https://github.com/spring-projects/spring-modulith/blob/main/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java
- 원본 URL 2: https://github.com/spring-projects/spring-modulith/blob/main/spring-modulith-events/spring-modulith-events-api/src/main/java/org/springframework/modulith/events/ApplicationModuleListener.java
- 원본 URL 3: https://docs.spring.io/spring-modulith/reference/events.html
- 아카이브 URL: (미제공)
- 저자 / 조직: Oliver Drotbohm / spring-projects (Spring 공식 incubator org)
- 발행일: ongoing (GitHub main branch, accessed 2026-05-28)
- 마지막 확인일: 2026-05-28
---
## 왜 저장했는지 / Why archived
Spring 공식 incubator(spring-projects org)가 ArchUnit을 production 코드에서 사용하는 방식을 직접 확인하기 위해 보관한다. ca-tmpl의 3가지 결정(D9 MapStruct generated exemption, S1 negative test fixture, D1/D3 `@Transactional` forbidden counter-evidence)이 이 자료로 corroborate 또는 counter-evidence 처리된다.
---
## 핵심 인용 / Key quotes (verbatim)
> [ApplicationModules.java — IS_GENERATED field & static initializer]
>
> ```java
> private static final @Nullable DescribedPredicate<CanBeAnnotated> IS_GENERATED;
>
> static {
> IS_GENERATED = ClassUtils.isPresent("org.springframework.aot.generate.Generated",
> ApplicationModules.class.getClassLoader()) ? getAtGenerated() : DescribedPredicate.alwaysFalse();
> }
> ```
> [ApplicationModules.java — getAtGenerated() implementation]
>
> ```java
> @Nullable
> private static DescribedPredicate<CanBeAnnotated> getAtGenerated() {
> return annotatedWith(Generated.class);
> }
> ```
> [ApplicationModules.java — detectViolations(VerificationOptions) method]
>
> ```java
> public Violations detectViolations(VerificationOptions options) {
> var cycleViolations = rootPackages.stream() //
> .map(this::assertNoCyclesFor) //
> .flatMap(it -> it.getDetails().stream()) //
> .collect(toViolations());
>
> var additionalViolations = options.getAdditionalVerifications().stream()
> .map(it -> it.evaluate(allClasses))
> .map(EvaluationResult::getFailureReport)
> .flatMap(it -> it.getDetails().stream())
> .collect(toViolations());
>
> var dependencyViolations = allModules() //
> .map(it -> it.detectDependencies(this)) //
> .reduce(NONE, Violations::and);
>
> return cycleViolations.and(additionalViolations).and(dependencyViolations);
> }
> ```
> [ApplicationModuleListener.java — meta-annotation 선언부 verbatim]
>
> ```java
> @Async
> @Transactional(propagation = Propagation.REQUIRES_NEW)
> @TransactionalEventListener
> @Documented
> @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
> @Retention(RetentionPolicy.RUNTIME)
> public @interface ApplicationModuleListener {
> ```
> [ApplicationModuleListener.java Javadoc — motivation 원문]
>
> "An ApplicationModuleListener is an Async Spring TransactionalEventListener that runs in a transaction itself. Thus, the annotation serves as syntactic sugar for the generally recommend setup to integrate application modules via events. The setup makes sure that an original business transaction completes successfully and the integration asynchronously runs in a transaction itself to decouple the integration as much as possible from the original unit of work."
> [docs.spring.io/spring-modulith/reference/events.html — Event Publication Registry]
>
> "Spring Modulith ships with an event publication registry that hooks into the core event publication mechanism of Spring Framework. On event publication, it finds out about the transactional event listeners that will get the event delivered and writes entries for each of them (dark blue) into an event publication log as part of the original business transaction."
---
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| SPRING-MOD-AU-C1 | Spring Modulith 은 AOT `Generated` annotation 이 classpath 에 존재하면 `annotatedWith(Generated.class)` predicate 를 사용하고, 없으면 `alwaysFalse()` 로 fallback 하는 IS_GENERATED predicate 를 production 코드에서 사용한다 | `IS_GENERATED = ClassUtils.isPresent("org.springframework.aot.generate.Generated", ...) ? getAtGenerated() : DescribedPredicate.alwaysFalse();` | `company-case-study` (incubator project — Spring Framework 1급 표준 아님) | Spring AOT `org.springframework.aot.generate.Generated` annotation 이 붙은 클래스를 ArchUnit rule 에서 면제할 때 | MapStruct 의 `javax.annotation.processing.Generated` 또는 `javax.annotation.Generated` 가 동일 FQN 임을 증명하지 않음. `annotatedWith(Generated.class)` 패턴의 적용 가능성을 증명하되 annotation FQN 은 별도 확인 필요 |
| SPRING-MOD-AU-C2 | Spring Modulith 의 `detectViolations(VerificationOptions)` 는 예외를 throw 하지 않고 `Violations` 객체를 반환한다 — 위반을 data 로 다루는 violations-as-data 패턴 | `public Violations detectViolations(VerificationOptions options) { ... return cycleViolations.and(additionalViolations).and(dependencyViolations); }` | `company-case-study` (incubator project) | Spring Modulith verifier 를 사용할 때 위반을 assertion 대신 data 로 수집해 처리하는 패턴 | ArchUnit `verify()` 호출과의 동등성을 증명하지 않음. ca-tmpl 이 Spring Modulith verifier 를 도입한다는 결정을 정당화하지 않음 (`feature-architecture-enforcement-rules` Out of scope — "Spring Modulith verifier 도입") |
| SPRING-MOD-TX-C1 | `@ApplicationModuleListener``@Async`, `@Transactional(propagation = Propagation.REQUIRES_NEW)`, `@TransactionalEventListener` 를 meta-annotation 으로 포함한다 — Spring incubator 공식 event integration annotation 이 `@Transactional` 을 재노출함 | `@Async @Transactional(propagation = Propagation.REQUIRES_NEW) @TransactionalEventListener ... public @interface ApplicationModuleListener` | `company-case-study` (incubator project) | Spring event-driven 모듈 통합에서 asynchronous transactional event listener 를 선언할 때 | Spring Framework 공식이 application layer 에서 `@Transactional` 직접 사용을 권장한다는 뜻이 아님. ca-tmpl 의 D3 (`@Transactional` direct import 금지) 가 잘못됨을 증명하지 않음 — 이 자료는 counter-evidence 로 기록되며 D3 를 override 하지 않음 |
| SPRING-MOD-TX-C2 | Spring Modulith Event Publication Registry 는 이벤트 발행 시 transactional event listener 각각에 대한 항목을 **원래 비즈니스 트랜잭션의 일부로** event publication log 에 기록한다 | "writes entries for each of them (dark blue) into an event publication log as part of the original business transaction." | `company-case-study` (incubator project) | Spring Modulith Event Publication Registry 가 outbox-like durability 를 제공하는 방식 이해 시 | Spring Framework `TransactionSynchronizationManager``registerSynchronization()` 과의 내부 구현 동등성을 증명하지 않음. Event Publication Registry 도입 없이도 동일 보장이 가능하다는 뜻이 아님 |
| SPRING-MOD-TX-C3 | `@ApplicationModuleListener` 는 원래 비즈니스 트랜잭션이 성공적으로 완료된 후 비동기로 자체 트랜잭션 안에서 실행된다 | "The setup makes sure that an original business transaction completes successfully and the integration asynchronously runs in a transaction itself to decouple the integration as much as possible from the original unit of work." | `company-case-study` (incubator project) | Spring Modulith 기반 모듈 간 이벤트 통합에서 transaction decoupling 패턴 이해 시 | ca-tmpl 의 현재 outbox/event 구현 없이도 이 동작이 보장된다는 뜻이 아님. Event Publication Registry 없이 `@ApplicationModuleListener` 단독 사용 시 유실 가능성 있음 (Javadoc 자체가 "In combination with ... Event Publication Registry" 를 권고함) |
---
## Usage Boundaries / 적용 경계
### 이 자료가 직접 증명하는 것
- `SPRING-MOD-AU-C1`: `annotatedWith(Generated.class)` ArchUnit predicate DSL 패턴이 Spring 공식 incubator 코드에서 실제로 사용됨
- `SPRING-MOD-AU-C2`: `detectViolations()` 가 예외 대신 `Violations` 객체를 반환하는 violations-as-data 패턴이 Spring Modulith 공식 API 임
- `SPRING-MOD-TX-C1`: `@ApplicationModuleListener``@Transactional(propagation = Propagation.REQUIRES_NEW)` 를 meta-annotation 으로 포함함
- `SPRING-MOD-TX-C2 / C3`: Event Publication Registry 가 original business transaction 내에서 log 를 기록하고, listener 가 비동기·독립 트랜잭션으로 실행됨
### 이 자료가 증명하지 않는 것
- Spring Modulith 는 **incubator project** — Spring Framework 1급 표준이 아님. `company-case-study` strength 로만 취급.
- MapStruct 가 생성하는 annotation 의 FQN 은 `javax.annotation.processing.Generated` (Java 9+) 또는 `javax.annotation.Generated` (Java 8) 이며, Spring AOT 의 `org.springframework.aot.generate.Generated`**다른 FQN** 임. `SPRING-MOD-AU-C1` 은 동일 ArchUnit predicate 패턴이 사용됨을 보이지만, D9 corroboration 을 완성하려면 MapStruct annotation FQN 별도 확인 필요.
- `SPRING-MOD-TX-C1` 은 ca-tmpl D3 결정(application layer `@Transactional` 직접 import 금지)의 반례(counter-evidence)로 기록되나, Spring Modulith 가 사용한다고 해서 ca-tmpl 의 D3 가 잘못되었음을 의미하지 않음. `@ApplicationModuleListener` 는 application layer annotation 이 아닌 event listener meta-annotation 임.
- `@TransactionalEventListener` 동작 자체는 이미 [[raw/official-docs/spring-transactional-event-listener]] 에 기록됨 (있다면). 본 archive 는 그 위에 Modulith 의 meta-annotation 결합 패턴을 추가하는 자료.
### 내 프로젝트에 적용하려면 추가 확인이 필요한 것
- D9 (MapStruct exemption) 완성: `javax.annotation.processing.Generated` FQN 으로 `annotatedWith(Generated.class)` predicate 를 ca-tmpl build 에서 실제 검증. MapStruct generated class 에 해당 annotation 이 실제로 붙는지 build output 확인.
- `detectViolations()` violations-as-data 패턴을 ca-tmpl negative test fixture 에 적용하려면 Spring Modulith 의존을 추가하거나 동일 패턴을 ArchUnit `EvaluationResult` 로 직접 구현.
- `@ApplicationModuleListener` 도입 여부는 `feature-domain-event-outbox-contract` 브랜치에서 결정. 현재 범위 밖.
---
## 메모 / Notes
- `IS_GENERATED` predicate 의 classpath 존재 여부 체크 패턴(classpath-conditional predicate)은 AOT 컴파일 환경과 일반 JVM 환경 모두를 지원하는 방어적 구현. ca-tmpl 의 MapStruct exemption 은 AOT 가 아닌 annotation processor path 의 `Generated` annotation 을 다루므로 classpath check 방식이 다를 수 있음.
- `detectViolations()``Violations` 를 반환하는 구조는 ArchUnit 의 `ConditionEvents` 와 유사한 결과 누적 패턴. ca-tmpl 이 Spring Modulith 없이 동일 패턴을 구현하려면 ArchUnit `ArchRule.evaluate(JavaClasses)``EvaluationResult``FailureReport` 경로 사용.
- `@ApplicationModuleListener` Javadoc 에서 "it is advisable that you use these integration listeners in combination with the Spring Modulith Event Publication Registry" — Event Publication Registry 없이 단독 사용은 listener 실패 시 재시도 보장이 없음.
- 추가로 봐야 할 동일 출처 페이지: `spring-modulith-core/src/main/java/org/springframework/modulith/core/ArchitecturallyEvidentType.java` — IS_GENERATED 의 실제 사용 맥락 확인 권장.
---
## Related / 관련
- [[raw/official-docs/mapstruct-generated-annotation-official]] — D9: MapStruct 가 `@Generated` 를 generated mapper 에 부착한다는 공식 근거 (MS-ANNOT-C1, MS-ANNOT-C2). 본 archive 의 SPRING-MOD-AU-C1 과 함께 D9 UNSUPPORTED_DECISION 해제 판단에 사용.
- [[raw/official-docs/archunit-user-guide]] — ArchUnit predicate DSL 공식 문서. SPRING-MOD-AU-C1 의 `annotatedWith(Generated.class)` 패턴을 ca-tmpl 에 적용할 때 레퍼런스.
- [[raw/company-tech-blogs/modulith-kakaobank-techblog-2025]] — KakaoBank 의 Spring Modulith + hexagonal multi-module 사례. 같은 주제 다른 company-tech-blog.
- [[raw/company-tech-blogs/modulith-arawn-github-modular-monoliths-spring]] — Spring Modulith 기반 modular monolith 패턴. 같은 주제 다른 tech blog.
- [[raw/branch-notes/feature-architecture-enforcement-rules]] — 본 archive 를 D9 근거로 활용하는 branch.
- [[raw/branch-notes/feature-application-port-usecase-contract]] — 본 archive 를 D1/D3 counter-evidence 로 활용하는 branch.
@@ -0,0 +1,98 @@
---
title: "company-tech-blog / 우아한형제들 기술블로그 — Server-Sent Events로 실시간 알림 전달하기"
source_type: company-tech-blog
url: https://techblog.woowahan.com/23199/
archive_url:
related_branches: [feature-streaming-response-contract]
related_projects: [ca-skeleton]
tags: [sse, server-sent-events, realtime, notification, woowahan, baemin, kafka, thundering-herd, backpressure, spring-webflux, coroutine, company-case-study]
created: 2026-06-02
last_reviewed: 2026-06-02
---
# 우아한형제들 기술블로그 — Server-Sent Events로 실시간 알림 전달하기
> Layer: `raw/company-tech-blogs/` — 우아한형제들(배달의민족) 기술블로그 게시물 발췌.
> Strength 분류: `company-case-study` — 대기업 기술 블로그의 특정 서비스 운영 사례. **공식 best practice 로 취급 금지.**
> 이 자료의 진술은 우아한형제들 특정 시스템(배민 알림 시스템, Spring WebFlux + Coroutine 환경, Kafka 브로커 아키텍처) 에 한정된 사례이며, ca-skeleton 의 최소주의 환경에 직접 적용 가능하다는 보장 없음.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-streaming-response-contract]] | SSE 대규모 운영 시 발생하는 **실무 문제(thundering herd, backpressure, multi-server connection 관리)** 의 산업 사례 근거 — 미지원 결정의 운영 부담 evidence + 지원 결정 시 고려해야 할 운영 과제 식별 |
## 출처 / Source
- 원본 URL: https://techblog.woowahan.com/23199/
- 저자 / 조직: 한우석 (Han Woo-seok) / 우아한형제들 (배달의민족) 기술블로그
- 발행일: 2025-10-24
- 카테고리: Backend
- 마지막 확인일: 2026-06-02
## 왜 저장했는지 / Why archived
SSE 를 실제 프로덕션에서 일 4천만 건 이벤트 처리에 운영한 우아한형제들의 사례. WebSocket 대신 SSE 를 선택한 이유(기존 REST 인프라 유지), 운영 중 마주친 문제(thundering herd, Kafka consumer timeout, 보안 인증), 해결책(jitter, buffer overflow 설정, Kafka 브로커 아키텍처)을 구체적으로 기술. ca-skeleton 에서 SSE 도입 결정 시 "운영 부담" 항목의 현실적 evidence 로 활용.
## 핵심 인용 / Key quotes (verbatim)
> "이미 안정적으로 운영 중인 REST API 인프라가 있는 상황에서 WebSocket으로 전환하려면 모든 API를 WebSocket 기반으로 재구현해야 합니다"
> "저희 서비스는 서버에서 클라이언트로의 알림 전달이 핵심입니다"
> "두 가지 프로토콜을 동시에 운영하는 것보다 REST API + SSE 조합이 관리 비용 측면에서 효율적입니다"
> "메시지 발행자는 클라이언트의 연결 상태나 서버 위치를 알 필요 없음" [Kafka 브로커 채택 이유 — loose coupling]
> "모든 서버로 메시지 전달" [Kafka 브로드캐스트 아키텍처 — multi-server SSE 환경]
> "일평균 약 4천만 건의 이벤트를 안정적으로 처리"
> "모든 세션은 다시 한꺼번에 서버에 접속하기 위해 시도할 것입니다...CPU가 계속 spike 되는 현상" [thundering herd 묘사]
> "random의 jitter 시간을 설정해 골고루 분포되도록 하였습니다" [thundering herd 해결책]
> "buffer가 0이라 만약 버퍼에서 Consumer가 처리가 늦어진다면 해당 코루틴은 계속 기다릴 것입니다. 이것이 Kafka의 중단을 일으켰습니다" [backpressure 문제]
> "정확성과 안정성이 더 중요하므로 허용 가능한 수준이었습니다" [추가 네트워크 홉에 의한 약간의 지연 증가에 대한 결론]
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| WOOWA-SSE-C1 | 우아한형제들은 WebSocket 대신 SSE 를 선택한 이유로 "기존 REST API 인프라를 WebSocket 으로 재구현해야 하는 비용"과 "서버→클라이언트 단방향 알림이 핵심 요구사항"을 들었다 | "WebSocket으로 전환하려면 모든 API를 WebSocket 기반으로 재구현해야 합니다" + "서버에서 클라이언트로의 알림 전달이 핵심입니다" | `company-case-study` | 단방향 server push 알림이 주 목적이고 기존 REST 인프라를 유지하려는 상황 | WebSocket 이 일반적으로 SSE 보다 도입 비용이 높다는 universal rule — 신규 프로젝트에서는 양방향 통신 요구에 따라 다를 수 있음 |
| WOOWA-SSE-C2 | SSE 를 multi-server 환경에서 운영할 때 "thundering herd" 문제(서버 재시작 시 모든 세션 동시 재연결 → CPU spike)가 발생했다 | "모든 세션은 다시 한꺼번에 서버에 접속하기 위해 시도할 것입니다...CPU가 계속 spike 되는 현상" | `company-case-study` | 다수 클라이언트(규모 불명)가 연결된 multi-server SSE 환경에서 서버 재시작 시나리오 | ca-skeleton 의 소규모 사용(< 수백 connection) 에서도 동일 현상이 발생한다는 뜻 아님 — 규모에 따라 심각도 다름 |
| WOOWA-SSE-C3 | Thundering herd 해결책으로 random jitter 를 세션 재연결 retry 시간에 적용했다 | "random의 jitter 시간을 설정해 골고루 분포되도록 하였습니다" | `company-case-study` | SSE 재연결 정책에서 thundering herd 를 방지하려는 구현 | Jitter 가 thundering herd 를 완전히 제거한다는 뜻 아님 — 분산을 개선할 뿐, 효과는 jitter range 와 connection 수에 따라 다름 |
| WOOWA-SSE-C4 | SSE + Kafka 브로드캐스트 아키텍처에서 Kafka consumer 처리가 늦어지면 coroutine 이 무한 대기 → Kafka 중단(backpressure 미설정)이 발생했다 | "buffer가 0이라 만약 버퍼에서 Consumer가 처리가 늦어진다면 해당 코루틴은 계속 기다릴 것입니다. 이것이 Kafka의 중단을 일으켰습니다" | `company-case-study` | Spring WebFlux + Coroutine + Kafka consumer 조합 | Spring MVC (servlet 기반) 또는 Kafka 없는 SSE 구현에서도 동일 문제가 발생한다는 뜻 아님 — 이 문제는 Coroutine channel + Kafka 조합 특화 |
| WOOWA-SSE-C5 | 우아한형제들 배민 알림 시스템은 일평균 약 4천만 건 이벤트를 SSE 로 안정적으로 처리했다 | "일평균 약 4천만 건의 이벤트를 안정적으로 처리" | `company-case-study` | 우아한형제들의 특정 배민 알림 시스템 (규모 · 아키텍처 · 인프라 명시 필요) | ca-skeleton 같은 범용 skeleton 도 동일 규모를 지원한다는 뜻 아님 — 이 수치는 우아한형제들의 전용 아키텍처(Kafka + multi-server + Coroutine) 기반 |
| WOOWA-SSE-C6 | SSE 서버를 multi-server 로 확장(auto-scaling) 할 때 "모든 서버에 브로드캐스트" 아키텍처(Kafka)를 통해 발행자가 클라이언트 연결 서버 위치를 알 필요 없게 했다 | "메시지 발행자는 클라이언트의 연결 상태나 서버 위치를 알 필요 없음" | `company-case-study` | SSE + horizontal scaling 환경. sticky session 없이 구현하려는 경우 | Kafka 가 SSE 의 multi-server 문제를 해결하는 유일한 방법이라는 뜻 아님 — Redis Pub/Sub, Hazelcast 등 대안 존재 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것** (단, `company-case-study` strength 한정):
- `C1`: 단방향 알림 + REST 인프라 유지 상황에서 SSE 가 WebSocket 대비 도입 비용 낮음 (우아한형제들 판단)
- `C2`, `C3`: Multi-server SSE 환경에서 thundering herd 는 실제 운영 문제이며 jitter 로 완화
- `C4`: SSE + Kafka + Coroutine 조합에서 backpressure buffer 설정 미흡 시 Kafka consumer 중단 가능
- `C5`: 일 4천만 이벤트 규모 SSE 운영이 가능함 (이 아키텍처와 인프라 하에서)
- `C6`: SSE 의 multi-server 확장 시 메시지 브로커 패턴(Kafka 브로드캐스트)이 유효
- **이 자료가 증명하지 않는 것**:
- SSE 가 WebSocket 보다 일반적으로 운영 부담이 낮다는 universal claim — 이 팀의 특정 요구사항(단방향, REST 유지) 에서의 판단
- SSE 가 ca-skeleton 같은 최소주의 skeleton 에서도 동일하게 쉽게 운영된다는 주장 — 이 팀은 Spring WebFlux + Kafka 라는 별도 인프라를 갖춤
- Thundering herd 나 backpressure 가 SSE 에만 특유한 문제라는 주장 — WebSocket, long-polling 도 유사 문제 존재
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-skeleton 이 Spring MVC (servlet) 기반이면, Coroutine + Kafka 아키텍처의 backpressure 문제 (`C4`) 는 직접 해당되지 않음
- ca-skeleton 의 SSE 도입 시 multi-server sticky session 정책 또는 Kafka/Redis Pub/Sub 필요 여부 결정 필요
- ca-skeleton 예상 connection 수 규모 — 소규모(< 100 connection)에서는 thundering herd (`C2`) 심각도 낮음
## 메모 / Notes
- `C1`**운영팀의 판단** (`company-case-study`) — "WebSocket 은 항상 도입 비용이 높다" 는 공식 best practice 아님
- `C5` 의 "4천만 건" 수치는 우아한형제들의 특정 시스템 · 아키텍처 · 인프라 기반 — ca-skeleton 에 외삽 금지
- 이 아티클은 Spring WebFlux + Coroutine 환경 기준 — Spring MVC (ca-skeleton default) 와 threading 모델이 다름
## Related / 관련
- 같은 주제 다른 raw 자료: [[raw/official-docs/whatwg-html-server-sent-events]] (SSE 프로토콜 공식 사양)
- 같은 주제 다른 raw 자료: [[raw/official-docs/spring-mvc-async-streaming]] (Spring MVC SseEmitter vendor doc)
- 같은 주제 다른 raw 자료: [[raw/company-tech-blogs/realtime-service-experience-woowahan-websocket]] (우아한형제들 WebSocket 실시간 운영 경험기)
- 인용하는 branch: [[raw/branch-notes/feature-streaming-response-contract]]
@@ -0,0 +1,128 @@
---
title: Stripe API — Errors Reference
source_type: company-tech-blog
url: https://docs.stripe.com/api/errors
archive_url:
status: raw
confidence: high
tags: [ca-error-envelope, stripe, custom-envelope, rest-api, error-format, company-tech-blog]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-operational-error-observability-foundation, feature-boundary-validation-mapping-contract, feature-business-rule-validation-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Stripe API — Errors Reference
> Layer: `raw/company-tech-blogs/` — Stripe API Reference 의 Errors 페이지 verbatim. Stripe 는 결제 도메인의 사실상 reference 가 된 custom envelope 사례.
> **company-tech-blog 자료 — 공식 표준이 아님.** Stripe 의 vendor-specific API 컨벤션이며, 다른 REST 환경의 best practice 로 일반화 금지. ca-tmpl Topic 4 (Error Envelope) 의 **대안 5 (Stripe custom envelope)** 비교 근거.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-operational-error-observability-foundation]] | ca-tmpl Topic 4 (Error Envelope) 대안 비교 — Stripe 의 `type` / `code` / `param` / `doc_url` 1급 필드 vs ca-tmpl 의 `category`/`retryable` 비교 근거 |
| [[raw/branch-notes/feature-boundary-validation-mapping-contract]] | boundary 입력 검증 실패 시 `param` 으로 form 필드 매핑 UX 패턴의 사례 근거 (industry case study, 표준 아님) |
| [[raw/branch-notes/feature-business-rule-validation-contract]] | business rule 실패의 `type` enum 4종 (card_error / api_error / idempotency_error / invalid_request_error) 분류 패턴 사례 |
## 컨텍스트 / 왜 저장했는지
Stripe 는 결제 도메인에서 가장 자주 인용되는 custom envelope 의 reference. ca-tmpl 이 custom 을 택했을 때 "유사한 1급 필드 구성" 을 어떻게 잡았는지 대조하기 위함. **단, 본 자료는 company tech blog/vendor reference 이므로 "공식 best practice" 가 아니라 "산업 사례" 로만 취급.**
## 출처 / Source
- 원본 URL: https://docs.stripe.com/api/errors
- 아카이브 URL: (미수집)
- 저자 / 조직: Stripe Inc.
- 발행일: rolling docs (current Stripe API reference)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§HTTP Status Code Summary] "Codes in the `2xx` range indicate success. Codes in the `4xx` range indicate an error that failed given the information provided (e.g., a required parameter was omitted, a charge failed, etc.). Codes in the `5xx` range indicate an error with Stripe's servers (these are rare)."
> [§Handling errors — Card errors] "Card errors are the most common type of error you should expect to handle. They result when the user enters a card that can't be charged for some reason."
> [§Handling errors — Card errors] "For card errors, these messages can be shown to your users."
> [§Error attributes — param] "If the error is parameter-specific, the parameter related to the error. For example, you can use this to display a message near the correct form field."
> [§Error types] "`api_error`", "`card_error`", "`idempotency_error`", "`invalid_request_error`" — 4개 type enum (Stripe vendor 정의)
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| STRIPE-ERR-C1 | Stripe API 는 HTTP status code 를 **2xx success / 4xx caller error / 5xx Stripe server error** 로 분류 | [§HTTP Status Code Summary] "Codes in the `2xx` range indicate success. Codes in the `4xx` range indicate an error that failed given the information provided (e.g., a required parameter was omitted, a charge failed, etc.). Codes in the `5xx` range indicate an error with Stripe's servers (these are rare)." | `company-case-study` | Stripe API 와 통신하는 client | HTTP RFC 의 일반적 표준이라는 뜻은 아님 — Stripe 의 자기 컨벤션 (RFC 7231/9110 의 일반 정의와 일치하지만 공식 표준 인용 아님) |
| STRIPE-ERR-C2 | Stripe 에서 **card errors 는 가장 흔한 error type** 이며, user 가 청구 불가능한 카드를 입력했을 때 발생 | [§Handling errors — Card errors] "Card errors are the most common type of error you should expect to handle. They result when the user enters a card that can't be charged for some reason." | `company-case-study` | Stripe 결제 통합 application 의 운영 빈도 가정 | 결제 도메인 일반의 통계라는 뜻은 아님 — Stripe 의 trafficcomposition 기반 안내 |
| STRIPE-ERR-C3 | card error 의 `message`**end-user 에게 직접 표시 가능** (다른 type 은 명시적 보장 없음) | [§Handling errors — Card errors] "For card errors, these messages can be shown to your users." | `company-case-study` | card_error type 메시지의 UX 표시 정책 | api_error / idempotency_error / invalid_request_error 의 message 도 end-user 에 표시 가능하다는 뜻은 아님 — 본 인용은 card error 한정 |
| STRIPE-ERR-C4 | error 가 parameter-specific 인 경우, `param` 필드를 사용해 **해당 form 필드 근처에 메시지를 표시** 하도록 안내 | [§Error attributes — param] "If the error is parameter-specific, the parameter related to the error. For example, you can use this to display a message near the correct form field." | `company-case-study` | Stripe Elements / 자체 form 통합 UX | RFC 7807 의 `instance` 또는 JSON:API 의 `source.pointer` 와 동일한 표준 개념이라는 뜻은 아님 — Stripe vendor-specific 평면 string |
| STRIPE-ERR-C5 | Stripe error `type`**`api_error` / `card_error` / `idempotency_error` / `invalid_request_error` 의 4개 enum** (vendor 정의) 으로 구성 | [§Error types] "`api_error`", "`card_error`", "`idempotency_error`", "`invalid_request_error`" | `company-case-study` | Stripe API client 의 type-based 분기 | 다른 REST API 의 error category 가 동일한 4-종 분류를 따라야 한다는 best practice 가 아님 — Stripe vendor-specific |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `STRIPE-ERR-C1`: Stripe 의 HTTP status code 분류 정책 (Stripe 컨벤션)
- `STRIPE-ERR-C2`: card errors 가 Stripe 환경에서 가장 빈번
- `STRIPE-ERR-C3`: card error 메시지의 end-user 표시 가능성
- `STRIPE-ERR-C4`: `param` 의 form 필드 매핑 UX 패턴 사례
- `STRIPE-ERR-C5`: Stripe error type 4개 enum 의 존재
- **이 자료가 증명하지 않는 것**:
- "Stripe 의 custom envelope 이 모든 REST API 의 best practice" — 본 자료는 **company tech blog / vendor reference** 로, **공식 표준이 아님**. RFC 7807 / 9457, JSON:API, GraphQL spec 같은 official-standard 와 동일 권위로 다루면 안 됨
- `retryable` 명시 필드의 존재 (Stripe 응답에 1급 필드 없음 → 본 인용 범위에서 확인 안 됨, client 가 status + type 으로 추론)
- 성공 응답의 envelope 모양 (Stripe 는 envelope 없이 resource 직접 반환 → 별도 페이지)
- `decline_code` 의 완전한 값 카탈로그 (별도 페이지)
- i18n 정책 (Stripe API reference 본 페이지에 i18n 표준 없음)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 이 Stripe 의 `type` 4-종 분류를 직접 채택할지, 자체 `category` 어휘를 정의할지
- `doc_url` 같은 error catalog URL 운영 비용 (RFC 7807 `type` URI 와의 의미적 차이 평가)
- SDK 의존 전략 (Stripe 처럼 envelope 을 자체 SDK 가 흡수하는 모델) 의 비용/이익
- 성공/실패 envelope 비대칭 (Stripe 모델) vs ca-tmpl 의 대칭 envelope 모델 trade-off
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- 응답 shape 예시 (해석/구성):
```json
{
"error": {
"type": "card_error",
"code": "card_declined",
"decline_code": "insufficient_funds",
"message": "Your card has insufficient funds.",
"param": "source",
"doc_url": "https://stripe.com/docs/error-codes/card-declined",
"charge": "ch_..."
}
}
```
- `type` enum: `api_error` / `card_error` / `idempotency_error` / `invalid_request_error` — ca-tmpl 의 `category` 와 거의 같은 의도
- `code` 는 머신리더블, `message` 는 사람 대상
- **장점 (해석)**:
- `type`/`code` 분리 → category 기반 client 분기와 fine-grained handling 모두 가능
- `doc_url` 로 카탈로그 링크 (RFC 7807 의 `type` URI 와 유사 의도)
- `param` 이 form 필드와 직접 매핑 가능 → UX 친화적
- **단점 (해석)**:
- retryable 명시 필드 없음 — HTTP status 와 `type` 을 client 가 조합해서 추론해야 함
- 성공 응답은 envelope 없이 리소스를 그대로 반환 → 성공/실패 shape 비대칭
- 표준 미준수
- **ca-tmpl custom envelope 와의 차이 (해석)**:
- ca-tmpl 이 `retryable` 을 1급으로 가져간 점이 Stripe 보다 한 발 더 나감. 반대로 ca-tmpl 은 `doc_url`/`param` 이 1급은 아님 (있다면 `details` 안)
- Stripe 는 실패 envelope 만, ca-tmpl 은 성공·실패 모두 envelope
- **표준 준수 / lock-in / client 호환성 (해석)**:
- 표준 미준수. 그러나 Stripe SDK 가 envelope 을 흡수 → client 는 SDK 없이 직접 다룰 일이 적음. ca-tmpl 도 같은 전략 (자체 client 컨벤션) 이라면 합리적
- **localization / i18n 지원 여부 (해석)**:
- Stripe 는 `message` 를 영문 위주, `decline_code` 로 localize 는 client 가. 별도 i18n 표준 없음
## Related / 관련
- 같은 주제 다른 official-doc / company-tech-blog:
- [[raw/official-docs/spring-problem-detail]] (대안 1 구현체 — RFC 7807/9457 official-vendor-doc)
- [[raw/official-docs/google-api-error-format]] (대안 2 — gRPC `google.rpc.Status` official-vendor-doc)
- [[raw/official-docs/json-api-errors-spec]] (대안 3 — JSON:API errors official-standard)
- [[raw/official-docs/graphql-errors-spec]] (대안 4 — GraphQL errors official-standard)
- canonical contract 섹션:
- [[raw/project-notes/ca-skeleton-operational-contract]] §3 Structured API Response Contract, §6 Operational Error Category
- 본 source 의 위치: ca-tmpl Topic 4 — Error Envelope, **대안 5: Stripe custom envelope (industry case study, 표준 아님)**
- 인용하는 wiki: (미작성)
@@ -0,0 +1,97 @@
---
title: Kent C. Dodds — Write tests. Not too many. Mostly integration. (Testing Trophy)
source_type: personal-blog
url: https://kentcdodds.com/blog/write-tests
archive_url:
status: raw
confidence: high
tags: [test-taxonomy, test-trophy, test-pyramid, ca-skeleton]
related_branches: [feature-test-taxonomy-fixture-contract]
related_projects: [ca-skeleton-operational-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Kent C. Dodds — Write tests. Not too many. Mostly integration. (Testing Trophy)
> Layer: `raw/company-tech-blogs/` (분류: 실제 `source_type` 은 `personal-blog`. 디렉토리 정정 후보 — 본 migration 에서는 자동 mv 금지, 위치 유지).
> Kent Dodds 개인 블로그 발췌. ca-tmpl 의 6-level test taxonomy 결정에 대한 **대안 모델 (Testing Trophy)** 평가용.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-test-taxonomy-fixture-contract]] | 6-level taxonomy (unit/contract/architecture/slice/integration/smoke) vs Testing Trophy (mostly integration) 대안 비교 근거. ca-tmpl 이 trophy 철학에서 갈리는 지점 명문화 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §12. Test Contract 의 외부 대안 사례 — frontend 출신 trophy 모델의 백엔드 적용 한계 명시 |
## 컨텍스트 / 왜 저장했는지
`feature-test-taxonomy-fixture-contract` 의 ca-tmpl 은 **6-level taxonomy (unit / contract / architecture / slice / integration / smoke)** 를 채택. 이는 전통적 test pyramid 의 변형이지만 contract/architecture 가 추가된 형태. Testing Trophy 는 "integration > unit" 을 주장하는 대안 모델이므로, 본 skeleton 의 결정이 trophy 철학과 어디서 갈리는지 명문화하기 위해 보관.
## 출처 / Source
- 원본 URL: https://kentcdodds.com/blog/write-tests
- 아카이브 URL: (미수집)
- 저자 / 조직: Kent C. Dodds (개인)
- 발행일: 2018 (이후 업데이트)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§Core principle] "Write tests. Not too many. Mostly integration."
> [§Testing Trophy 정의] "The Testing Trophy 🏆 A general guide for the **return on investment** 🤑 of the different forms of testing with regards to testing JavaScript applications."
> [§Integration sweet spot] "Integration tests strike a great balance on the trade-offs between confidence and speed/expense."
> [§Coverage diminishing returns] "you get diminishing returns on your tests as the coverage increases much beyond 70%"
> [§Unit vs Integration confidence] "as you move up the pyramid, the confidence quotient of each form of testing increases. You get more bang for your buck."
> [§Shallow rendering 한계] "It doesn't matter if your component `<A />` renders component `<B />` with props `c` and `d` if component `<B />` actually breaks if prop `e` is not supplied."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| TROPHY-C1 | Kent Dodds 의 핵심 권고는 "Write tests. Not too many. Mostly integration." — 적정량 + integration 중심 | [§Core principle] "Write tests. Not too many. Mostly integration." | `engineering-blog` | JavaScript / frontend 애플리케이션 테스트 전략 | 이 권고가 백엔드 시스템에도 동일하게 적용된다는 일반화는 아님 — 원문이 frontend 맥락 |
| TROPHY-C2 | Testing Trophy 는 "JavaScript 애플리케이션의 테스트 형태별 ROI (return on investment) 가이드" 로 정의됨 | [§Testing Trophy 정의] "The Testing Trophy 🏆 A general guide for the **return on investment** 🤑 of the different forms of testing with regards to testing JavaScript applications." | `engineering-blog` | JavaScript 애플리케이션의 ROI 기반 테스트 전략 | Trophy 가 모든 언어/도메인 (백엔드, 임베디드 등) 의 ROI 표준이라는 뜻은 아님 |
| TROPHY-C3 | Integration test 는 **confidence vs speed/expense trade-off 의 균형점** 이라고 주장 | [§Integration sweet spot] "Integration tests strike a great balance on the trade-offs between confidence and speed/expense." | `engineering-blog` | integration test 의 ROI 평가 | 정량 측정 데이터 없음 — 저자의 주장. unit/E2E 와의 비교 수치 부재 |
| TROPHY-C4 | 70% 커버리지 이상에서는 추가 테스트의 **diminishing returns** (한계 효용 감소) 가 발생한다고 주장 | [§Coverage diminishing returns] "you get diminishing returns on your tests as the coverage increases much beyond 70%" | `engineering-blog` | 코드 커버리지 목표치 설정 | 70% 가 객관적 최적 임계값이라는 증명 아님 — 저자의 경험적 권고. 도메인/리스크에 따라 다를 수 있음 |
| TROPHY-C5 | 테스트 피라미드 위로 올라갈수록 **confidence quotient 증가** ("more bang for your buck") — unit < integration < E2E 순으로 신뢰도 | [§Unit vs Integration confidence] "as you move up the pyramid, the confidence quotient of each form of testing increases. You get more bang for your buck." | `engineering-blog` | 테스트 layer 별 신뢰도 평가 | "비용 대비 신뢰도" 의 정량 비율은 없음. unit 의 속도 우위는 본 인용에서 인정하지 않은 게 아니라 별도 트레이드오프 |
| TROPHY-C6 | shallow rendering 은 컴포넌트 간 통합 누락을 잡지 못한다는 구체 예시: `<A />``<B />` 를 props `c,d` 로 렌더해도 `<B />` 가 prop `e` 없을 때 깨지면 의미 없음 | [§Shallow rendering 한계] "It doesn't matter if your component `<A />` renders component `<B />` with props `c` and `d` if component `<B />` actually breaks if prop `e` is not supplied." | `engineering-blog` | React 컴포넌트 테스트의 shallow rendering 한계 | 백엔드 mock-heavy unit test 의 한계로 일반화하려면 별도 논증 필요 — 본 예시는 React 컴포넌트 특화 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `TROPHY-C1` ~ `C6`: Kent Dodds 의 Testing Trophy 권고 (JS/frontend 맥락) — integration 중심, 70% 커버리지 한계 효용, shallow rendering 한계 예시
- **이 자료가 증명하지 않는 것**:
- Testing Trophy 가 백엔드 시스템의 best practice 라는 명제 — 원문 명시적으로 "JavaScript applications" 맥락
- 70% 가 객관적/실증적 최적 커버리지라는 명제 — 저자 경험 기반
- unit test 가 일반적으로 불필요하다는 명제 — 원문은 "mostly integration" 이지 "no unit"
- ca-tmpl 의 6-level taxonomy 가 trophy 보다 우월/열등하다는 명제
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 contract test layer 가 trophy 의 integration 역할 일부를 대체하는지의 실증 (CI 시간 / 발견 버그 비율)
- 백엔드 도메인에서 "mostly integration" 채택 시 Testcontainers 사용 부담 (ca-tmpl 의 5분 CI budget 과의 충돌)
- 70% 커버리지 목표가 ca-tmpl 의 운영 contract 검증에 적정한지 (인프라 코드 별도 고려)
## 메모 / Notes
> 검증되지 않은 내 해석은 wiki source-summary 단계에서만.
- Trophy 는 **frontend 맥락** 에서 출발했고 "shallow rendering 회피" 가 핵심 논거.
- 백엔드 skeleton 에서는 **contract test 가 trophy 의 integration 역할 일부를 대체** 한다 (해석, 미검증). 즉 envelope/log/env/error 같은 운영 계약은 unit 이 아니라 contract level 에서 보호.
- 따라서 본 skeleton 의 6-level 중 contract layer 는 trophy 의 integration boundary 일부를 빠르게 (no container) 잡는 zone 으로 볼 수 있음 (해석).
- 차이점 (해석): trophy 는 "mostly integration", 본 skeleton 은 "mostly unit + contract + architecture" + integration 은 별도 gate.
- 이 차이는 **5분 CI budget** + Testcontainers cost 때문이고, contract test 에 Testcontainers 를 금지한 결정과 직결 (별도 결정 노트 검증 필요).
- 출처 분류: `source_type: personal-blog` — 참고 자료. 공식 best practice 로 격상 금지 (CLAUDE.md §5).
## Related / 관련
- 같은 주제 다른 raw:
- (test pyramid 원본 출처 — Mike Cohn 의 "Succeeding with Agile" 별도 raw 추가 후보)
- 인용하는 branch:
- [[raw/branch-notes/feature-test-taxonomy-fixture-contract]] (대안 2)
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (Group G-G — Skeleton Governance / test taxonomy)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,85 @@
---
title: "Thorben Janssen — How to Persist Creation and Update Timestamps with Hibernate"
source_type: company-tech-blog
url: https://thorben-janssen.com/persist-creation-update-timestamps-hibernate/
archive_url:
related_branches: [feature-persistence-auditing-contract]
related_projects: []
tags: [company-tech-blog, ca-tmpl, persistence, hibernate, auditing, clock-injection]
created: 2026-06-10
---
# Thorben Janssen — How to Persist Creation and Update Timestamps with Hibernate
> Layer: `raw/` — 외부 자료(전문가 기술 블로그)의 **원문 발췌·출처 기록**.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 별도 작성. 원본은 raw에 영구 보관.
## Parent / 활용 branch (필수, 최소 1개+)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-persistence-auditing-contract]] | Hibernate-native `@CreationTimestamp`/`@UpdateTimestamp` 대안을 거부하는 근거: (a) Clock 주입 불가 → 결정론적 테스트 제약 위반, (b) `created_by`/`updated_by` 추적 불가 → 완전한 감사 로그 미지원 |
## 출처 / Source
- 원본 URL: https://thorben-janssen.com/persist-creation-update-timestamps-hibernate/
- 아카이브 URL: (미등록 — 추가 권장)
- 저자 / 조직: Thorben Janssen (thorben-janssen.com — Hibernate/JPA 전문가 기술 블로그)
- 발행일: (상세 날짜 미확인, 페이지 본문에서 연도 미표기)
- 마지막 확인일: 2026-06-10
## 왜 저장했는지 / Why archived
`feature-persistence-auditing-contract` 브랜치에서 `@CreationTimestamp`/`@UpdateTimestamp` 대안을 평가할 때, "Hibernate가 JVM 시스템 시간을 직접 읽으므로 Clock 빈 주입이 불가하다"는 제한과 "타임스탬프만 저장하는 단순 기능이라 실제 감사 솔루션이 아니다"는 저자의 직접적 진술이 두 거부 이유 모두를 뒷받침한다.
## 핵심 인용 / Key quotes (verbatim, Self-Grep 통과)
> [§ Clock parameterization caveat] "Unfortunately, you can't parameterize it. Hibernate uses the JVM to get the current time."
> [§ Audit scope disclaimer] "it only persists the timestamps so it's not a real audit solution. But if you don't need to persist any additional information (who changed what), this is the easiest solution I know."
> [§ @CreationTimestamp mechanics] "When a new entity gets persisted, Hibernate gets the current timestamp from the VM and sets it as the value of the attribute annotated with @CreationTimestamp."
> [§ @UpdateTimestamp mechanics] "The value of the attribute annotated with @UpdateTimestamp gets changed in a similar way with every SQL Update statement."
## Claims Extracted / 추출된 주장
> 이 자료가 **직접 말하는 것만** claim 으로 분리한다. 내 프로젝트에 적용한 결론은 여기 쓰지 않는다.
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| C1 | `@CreationTimestamp`/`@UpdateTimestamp`는 Clock 파라미터화가 불가하며, Hibernate가 JVM에서 직접 현재 시간을 읽는다 | "Unfortunately, you can't parameterize it. Hibernate uses the JVM to get the current time." | `engineering-blog` | Hibernate ORM 사용 환경 전반 | 특정 Hibernate 버전에 국한되는지 여부 미확인; 공식 Hibernate 문서로 보강 필요 |
| C2 | `@CreationTimestamp`/`@UpdateTimestamp`는 타임스탬프만 저장하므로 실제 감사 솔루션이 아니며, "누가 무엇을 변경했는지" 추가 정보가 없는 경우에만 적합하다 | "it only persists the timestamps so it's not a real audit solution. But if you don't need to persist any additional information (who changed what), this is the easiest solution I know." | `engineering-blog` | 감사(audit) 요건이 있는 모든 프로젝트 | `created_by`/`updated_by` 컬럼 유무가 아니라 저장소 모델 결정에 대한 근거는 아님 |
| C3 | `@CreationTimestamp`는 엔티티가 최초 영속화될 때 VM의 현재 타임스탬프를 해당 필드에 설정한다 | "When a new entity gets persisted, Hibernate gets the current timestamp from the VM and sets it as the value of the attribute annotated with @CreationTimestamp." | `engineering-blog` | Hibernate ORM `@CreationTimestamp` 사용 시 | VM 시간 소스(NTP 정합 등) 정확도 보장 여부는 이 자료 범위 밖 |
| C4 | `@UpdateTimestamp`는 모든 SQL UPDATE 실행 시마다 값이 변경된다 | "The value of the attribute annotated with @UpdateTimestamp gets changed in a similar way with every SQL Update statement." | `engineering-blog` | Hibernate ORM `@UpdateTimestamp` 사용 시 | UPDATE 없이 dirty-check가 발생하는 케이스 처리 방식 미언급 |
### Strength 허용값 (적용됨)
- `engineering-blog` — 개인/팀 블로그의 엔지니어링 해설. 이 자료는 Thorben Janssen의 전문가 기술 블로그로 `engineering-blog` 로 분류. 공식 Hibernate 문서(official-vendor-doc)가 아니므로 공식 best practice로 단독 인용 금지.
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `C1`: Hibernate `@CreationTimestamp`/`@UpdateTimestamp`가 Clock 주입을 지원하지 않으며 JVM 시스템 시간을 직접 사용한다는 사실 (저자 직접 진술)
- `C2`: 이 애노테이션들이 "누가" 변경했는지를 기록하지 않으므로 완전한 감사 솔루션이 아니라는 저자의 명시적 평가
- `C3`: `@CreationTimestamp` 가 최초 INSERT 시점에 VM 타임스탬프를 설정한다는 메커니즘
- `C4`: `@UpdateTimestamp` 가 매 UPDATE마다 갱신된다는 메커니즘
- 이 자료가 증명하지 않는 것:
- Hibernate 공식 문서(official-vendor-doc)로서의 권위: 전문가 블로그이므로 공식 사양이 아님. Clock 주입 불가 사실을 공식 확인하려면 Hibernate 공식 문서 보강 필요.
- 어떤 감사 대안(Spring Data Auditing, Hibernate Envers 등)이 더 낫다는 비교 우위 — 이 글은 대안 평가가 아닌 사용법 설명
- `created_by`/`updated_by` 컬럼을 어떻게 구현해야 하는지 구체적 방법
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- Hibernate 공식 문서 또는 소스코드에서 C1 (JVM 시간 직접 읽기, Clock 주입 불가) 확인 — engineering-blog 단독으로는 결정 근거로 약함
- ca-tmpl 의 실제 Hibernate 버전에서 동일하게 적용되는지 검증
## 메모 / Notes
- C1 은 결정론적 테스트를 위해 `Clock` 빈을 주입하는 이 프로젝트의 테스트 전략과 직접 충돌한다. `@CreationTimestamp`/`@UpdateTimestamp` 를 사용하면 테스트에서 시간을 제어할 수 없어 시간 의존 로직의 단위 테스트가 불가능해진다.
- C2 는 이 프로젝트가 `created_by`/`updated_by` 컬럼을 요구하는 경우 이 대안을 아예 배제하는 독립적인 거부 이유가 된다.
- 이 자료는 전문가 기술 블로그이므로 `engineering-blog` Strength 로 처리. 공식 사양을 보강하려면 Hibernate 공식 문서(`@CreationTimestamp`/`@UpdateTimestamp` Javadoc 또는 User Guide)를 별도 official-doc 으로 등록 권장.
- 추가로 봐야 할 동일 출처 페이지: Thorben Janssen의 Hibernate Envers 관련 글 (완전한 감사 대안으로 비교 가능)
## Related / 관련
- 같은 주제 다른 official-doc / company-tech-blog: Hibernate 공식 User Guide의 `@CreationTimestamp`/`@UpdateTimestamp` 항목 (아직 미등록)
- 이 자료를 인용한 wiki 요약: `[[wiki/concepts/hibernate-timestamp-auditing]]` (생성 시)
@@ -0,0 +1,85 @@
---
title: "company-tech-blog / AT&T Israel — TaskDecorator Pattern for ThreadLocal Context Propagation (2022)"
source_type: company-tech-blog
url: https://medium.com/att-israel/dont-lose-your-thread-manage-and-decorate-your-concurrent-threads-391cf34e6bc6
archive_url:
related_branches: [feature-runtime-context-propagation-contract]
related_projects: [ca-skeleton, ca-tmpl]
tags: [company-tech-blog, threadlocal, capture-restore, task-decorator, spring-boot, async, att-israel]
created: 2026-06-09
last_reviewed: 2026-06-09
status: raw
confidence: medium
---
# AT&T Israel — TaskDecorator Pattern for ThreadLocal Context Propagation
> Layer: `raw/company-tech-blogs/` — AT&T Israel Tech Blog 의 ThreadLocal capture-restore 패턴 아티클.
> **출처 주의**: company-tech-blog 이므로 공식 best practice 로 일반화 금지. Plain ThreadLocal + explicit capture-restore (Alt-3) 의 실용적 구현 패턴 사례 reference 로만 사용.
> WebFetch 성공. Author: Chaya Berezin-Chaimson (AT&T Israel), Published: April 6, 2022.
> **주의**: 2022년 아티클이므로 Java 21 virtual threads 출시 이전 기준. Virtual thread 호환성 검증은 별도 필요.
## Parent / 활용 branch
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-runtime-context-propagation-contract]] | Alt-3 (Plain ThreadLocal + manual capture-restore) 의 TaskDecorator 기반 구현 패턴 사례 근거 |
## 출처 / Source
- 원본 URL: https://medium.com/att-israel/dont-lose-your-thread-manage-and-decorate-your-concurrent-threads-391cf34e6bc6
- 저자: Chaya Berezin-Chaimson (AT&T Israel Tech Blog)
- 발행일: April 6, 2022
- 마지막 확인일: 2026-06-09
- 접근 상태: WebFetch 성공
## 핵심 인용 / Key quotes (verbatim, WebFetch)
> "The article describes implementing a CorrelationIdTaskDecorator that: Extracts the correlation ID from the parent thread's ThreadLocal variable; Returns a new runnable that assigns this value to the spawned thread's ThreadLocal before executing the original task."
> "This decorator is attached to a custom Executor bean, ensuring automatic state transfer across all async operations."
> "The solution uses plain ThreadLocal with explicit capture — not InheritableThreadLocal. The decorator manually copies values between parent and child thread contexts rather than relying on inheritance mechanisms."
> (Pattern summary) Capture on parent thread → Store in closure → Restore before task execution → (implicit: clear after task in finally block for pooled threads)
## Self-Grep 검증
```
Fragment: "CorrelationIdTaskDecorator"
→ WebFetch output 에서 확인 PASS
Fragment: "plain ThreadLocal with explicit capture — not InheritableThreadLocal"
→ WebFetch 분석 결과 (agent extraction) — 원문 exact phrase 아닐 수 있음 (INFERENCE 주의)
→ "not InheritableThreadLocal" 은 agent extraction. 실제 원문 verbatim 확인 권고.
```
검증한 인용 V: 2 / PASS P: 1 / INFERENCE P: 1 (InheritableThreadLocal 비사용 여부는 agent-inferred, not verbatim)
## Claims Extracted
| Claim ID | Claim | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| ATT-TL-C1 | Spring TaskDecorator 패턴으로 parent thread 의 ThreadLocal 값을 child thread 실행 전에 restore 할 수 있다 | "implements a CorrelationIdTaskDecorator that Extracts the correlation ID from the parent thread's ThreadLocal variable; Returns a new runnable that assigns this value to the spawned thread's ThreadLocal before executing the original task." | `company-case-study` | Spring Boot + custom ThreadPoolTaskExecutor 환경 (2022 기준) | virtual thread 환경 (Java 21+) 에서의 동작 안전성 — 이 아티클은 Java 21 이전 기준 |
| ATT-TL-C2 | TaskDecorator 를 custom Executor bean 에 attach 하면 모든 async operation 에 자동으로 context 전달된다 | "This decorator is attached to a custom Executor bean, ensuring automatic state transfer across all async operations." | `company-case-study` | Spring `@Async` + `ThreadPoolTaskExecutor` 환경 | Spring Boot 3.2+ 의 `SimpleAsyncTaskExecutor` (virtual threads) 에서의 동작 — 별도 검증 필요 |
| ATT-TL-C3 | InheritableThreadLocal 없이 plain ThreadLocal + explicit copy 로 context 전달 가능 | Agent extraction: "uses plain ThreadLocal with explicit capture — not InheritableThreadLocal" | `company-case-study` + `INFERENCE` (verbatim 확인 필요) | InheritableThreadLocal 금지 환경에서 context propagation 이 필요한 경우 | ca-tmpl 의 ArchUnit InheritableThreadLocal ban 이 이 패턴을 허용하는지 직접 증명하지 않음 (허용 — plain ThreadLocal 이므로) |
## Usage Boundaries
- 이 자료가 지지하는 것:
- TaskDecorator 기반 explicit capture-restore 가 실제 production 코드에서 사용됨 (AT&T Israel 사례)
- InheritableThreadLocal 없이 plain ThreadLocal 으로 context 전달 가능
- 이 자료가 증명하지 않는 것:
- virtual thread 환경 (Java 21+) 에서의 동작 — 2022년 작성, Loom GA 이전
- StructuredTaskScope 환경에서의 동작
- domain/business context (tenantId, userId) 에 직접 적용 가능성 — 이 아티클은 correlationId (diagnostic) 에 집중
- 내 프로젝트 적용 시 주의:
- Java 21 virtual thread 환경에서 TaskDecorator 패턴이 `SimpleAsyncTaskExecutor` (virtual thread based) 와 호환되는지 별도 확인 필요
- ca-tmpl 의 foundation branch 가 이미 MDC TaskDecorator 를 소유 (`feature-background-job-async-contract`) — 도메인 context 용 TaskDecorator 는 별도 추가 또는 기존 확장
## 메모 / Notes
- AT&T Israel 은 AT&T 의 이스라엘 R&D 센터 — 대규모 Java 서비스 운영 컨텍스트.
- 2022년 아티클이므로 Java 21 virtual thread, StructuredTaskScope 에 대한 고려 없음.
- ca-tmpl 의 `feature-background-job-async-contract` branch 가 이미 MDC TaskDecorator 를 소유하므로 Alt-3 의 구현은 그 branch 와의 조율이 필요.
- virtual thread 환경에서 `SimpleAsyncTaskExecutor` 에도 TaskDecorator 를 attach 할 수 있는지는 Spring Boot 3.2+ 문서 별도 확인 필요.
@@ -0,0 +1,140 @@
---
title: 토스페이먼츠 API Error Format
source_type: company-tech-blog
url: https://docs.tosspayments.com/reference/error-codes
archive_url:
status: raw
confidence: high
tags: [ca-error-envelope, toss, korean-api, custom-envelope, rest-api, korean-fintech]
related_projects: [ca-skeleton-operational-contract]
related_branches: [feature-operational-error-observability-foundation, feature-boundary-validation-mapping-contract, feature-business-rule-validation-contract]
created: 2026-05-22
last_reviewed: 2026-05-27
---
# 토스페이먼츠 API Error Format
> Layer: `raw/company-tech-blogs/` — 토스페이먼츠 개발자센터 공식 API reference (docs.tosspayments.com). `source_type` 은 `company-tech-blog` 디렉토리이나 strength 는 `official-vendor-doc`. 자동 mv 금지 규칙으로 디렉토리 유지 — 후속 정리 권고.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 별도 작성.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-operational-error-observability-foundation]] | error envelope 의 한국 vendor 사례. ca-tmpl 의 두꺼운 envelope vs 토스의 얇은 `{code, message}` 비교 base |
| [[raw/branch-notes/feature-boundary-validation-mapping-contract]] | `INVALID_REQUIRED_PARAM` 등 validation 코드 명명 컨벤션의 한국 결제 vendor 사례 |
| [[raw/branch-notes/feature-business-rule-validation-contract]] | business rule 위반 코드 (`ALREADY_PROCESSED_PAYMENT`, `NOT_CANCELABLE_PAYMENT`) 의 도메인-specific 어휘 사례 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §3. Structured API Response Contract + §6. Operational Error Category 의 한국 reference |
## 컨텍스트
한국 결제/금융 도메인의 대표 사례. ca-tmpl 의 한국어 메시지·운영 컨벤션과 비교 가능. Stripe / GitHub 대비 더 얇은 envelope 이 한국 사용자/개발자에게 어떻게 자리 잡았는지 관찰.
## 출처 / Source
- 원본 URL: https://docs.tosspayments.com/reference/error-codes
- 보조: https://docs.tosspayments.com/reference/using-api/req-res
- 아카이브 URL: (미수집)
- 저자 / 조직: 토스페이먼츠 (TossPayments) Developer Documentation
- 발행일: rolling docs
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§에러 객체 구조] "`code`: 에러 타입을 보여주는 에러 코드입니다."
> [§에러 객체 구조] "`message`: 에러 메시지입니다."
> [§응답 조건] "요청이 정상적으로 처리되지 않으면 응답으로 HTTP 상태 코드와 함께 아래와 같은 에러 객체가 돌아옵니다."
> [§대표 에러 코드 — UNAUTHORIZED_KEY] "인증되지 않은 시크릿 키 혹은 클라이언트 키"
> [§대표 에러 코드 — INVALID_REQUEST] "잘못된 요청입니다"
> [§대표 에러 코드 — INVALID_REQUIRED_PARAM] "필수 파라미터가 누락되었습니다"
> [§대표 에러 코드 — ALREADY_PROCESSED_PAYMENT] "이미 처리된 결제 입니다"
> [§대표 에러 코드 — REJECT_CARD_PAYMENT] "한도초과 혹은 잔액부족으로 결제에 실패"
> [§대표 에러 코드 — NOT_CANCELABLE_PAYMENT] "취소 할 수 없는 결제 입니다"
> [§대표 에러 코드 — FAILED_INTERNAL_SYSTEM_PROCESSING] "내부 시스템 처리 작업이 실패"
> [§대표 에러 코드 — PROVIDER_ERROR] "일시적인 오류가 발생했습니다"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| TOSS-ERR-C1 | 에러 객체는 정확히 `{code, message}` 2개 필드로 구성 — `code` 는 에러 타입, `message` 는 에러 메시지 | [§에러 객체 구조] "`code`: 에러 타입을 보여주는 에러 코드입니다." + "`message`: 에러 메시지입니다." | `official-vendor-doc` | 토스페이먼츠 API 의 모든 error 응답 | 다른 필드 (`details`, `category`, `retryable`, `traceId` 등) 가 절대 없다는 뜻은 아님 — 본 페이지의 명시 범위에서 없음. traceId 는 별도 헤더로 제공될 가능성 (본 페이지에 명시 없음) |
| TOSS-ERR-C2 | 요청 실패 시 HTTP status code 와 함께 error 객체가 반환됨 | [§응답 조건] "요청이 정상적으로 처리되지 않으면 응답으로 HTTP 상태 코드와 함께 아래와 같은 에러 객체가 돌아옵니다." | `official-vendor-doc` | 토스페이먼츠 API 의 실패 응답 | 정확히 어떤 status code 가 어떤 code 와 매핑되는지의 전체 표는 본 인용에 없음 — 대표 코드만 |
| TOSS-ERR-C3 | 인증 실패 시 `UNAUTHORIZED_KEY` 코드 — 인증되지 않은 시크릿/클라이언트 키 사용 시 | [§대표 에러 코드 — UNAUTHORIZED_KEY] "인증되지 않은 시크릿 키 혹은 클라이언트 키" | `official-vendor-doc` | 토스페이먼츠 API key 인증 단계 | 만료된 키 vs 비활성화 키의 분기는 본 인용에 없음 |
| TOSS-ERR-C4 | validation 실패 코드 어휘: `INVALID_REQUEST` (잘못된 요청), `INVALID_REQUIRED_PARAM` (필수 파라미터 누락) | [§대표 에러 코드 — INVALID_REQUEST] + [§대표 에러 코드 — INVALID_REQUIRED_PARAM] (위 인용) | `official-vendor-doc` | 토스페이먼츠 API 의 schema validation 단계 | GitHub 의 `missing_field` / `invalid` 등 6개 어휘 같은 fine-grained 분류는 없음 — 토스는 더 coarse |
| TOSS-ERR-C5 | business rule 위반 코드 사례: `ALREADY_PROCESSED_PAYMENT` (이미 처리된 결제), `NOT_CANCELABLE_PAYMENT` (취소 불가 결제), `REJECT_CARD_PAYMENT` (한도초과/잔액부족) | [§대표 에러 코드 — ALREADY_PROCESSED_PAYMENT/NOT_CANCELABLE_PAYMENT/REJECT_CARD_PAYMENT] (위 인용) | `official-vendor-doc` | 결제 도메인의 business rule 카탈로그 | 이 코드들이 retryable 인지 final 인지는 code 명만으로 추론. 명시적 `retryable` 필드 없음 |
| TOSS-ERR-C6 | 시스템 / provider 오류 코드: `FAILED_INTERNAL_SYSTEM_PROCESSING` (내부 시스템 실패), `PROVIDER_ERROR` (일시적 오류) | [§대표 에러 코드 — FAILED_INTERNAL_SYSTEM_PROCESSING/PROVIDER_ERROR] (위 인용) | `official-vendor-doc` | 토스 내부 / 카드사 등 외부 provider 오류 분리 | client 가 retry 해야 할지 즉시 final 처리할지의 정확한 가이드는 본 인용에 없음 ("일시적" 이라는 표현으로 retry 유도만 시사) |
| TOSS-ERR-C7 | 에러 객체 안에 `traceId` 필드가 포함된다는 사실은 본 페이지 인용에는 **명시 없음** — 별도 채널 (헤더?) 가능성 | (부재 자체가 claim) | `needs-confirmation` | 운영 디버깅 시 traceId 활용 | traceId 가 없다는 뜻도 아님 — 본 페이지의 범위 밖. 별도 가이드 페이지 확인 필요 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `TOSS-ERR-C1` ~ `C6`: 토스페이먼츠 error envelope 의 `{code, message}` 2-field 구조 + 대표 코드 어휘 (인증/validation/business rule/system)
- **이 자료가 증명하지 않는 것**:
- 한국 결제 vendor 전체 (KG이니시스, 카카오페이, NHN KCP 등) 가 동일 패턴이라는 결론
- 토스가 i18n (영문 응답) 을 지원하는지 (본 페이지 한국어 메시지만)
- retryable 여부의 정확한 알고리즘 (코드명 + status 로 추론하는 수준)
- validation 다중 항목 오류의 표현 방식 (`{code, message}` 단일 → 다중 오류 합성 방식 불명)
- traceId 의 body 내 포함 여부 (`C7`)
- 성공 응답의 envelope 구조 (본 페이지는 error 만)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 `error.category` / `error.retryable` 을 토스 코드 어휘에 매핑할 규칙
- 한국어 메시지 컨벤션 (예: "...입니다" 종결) 의 ca-tmpl 적용 여부
- validation 다중 오류 시 ca-tmpl `error.details` 활용
## 메모 / Notes
> 검증되지 않은 내 해석은 여기에 두지 말 것 — wiki source-summary 단계에서.
- 응답 shape 핵심:
```json
{ "code": "NOT_FOUND_PAYMENT", "message": "존재하지 않는 결제 정보 입니다." }
```
- 매우 얇음. `category`/`retryable`/`details`/`meta` 모두 없음.
- 성공 응답은 리소스 직반환 (envelope X — 본 페이지에 명시 없음, 별도 페이지 확인).
- retryable 여부는 `code` semantic + HTTP status 로 추론 (e.g., `PROVIDER_ERROR` → 재시도 유도 메시지).
- 장점 (추론):
- 단순함. 한국어 메시지가 자연스러움.
- `code`-driven 카탈로그 (개발자센터에서 모든 코드 문서화).
- 학습 곡선 ↓ — 작은 팀/주니어 친화적.
- 단점 (추론):
- retryable, category, validation 항목별 풀이가 1급 영역에 없음.
- 다중 validation 오류 표현이 어려움 (단일 message 에 합쳐서 줘야 함).
- traceId 가 body 가 아닌 별도 채널일 가능성 (`C7`) — observability 컨벤션이 단편적.
- ca-tmpl custom envelope 와의 차이:
- 토스: 매우 얇은 `{code, message}`, ca-tmpl: 더 두꺼운 `{success, data, error.{code,category,message,retryable,details}, meta}`.
- ca-tmpl 이 운영 메타데이터(`retryable`, `category`, `meta`) 를 1급으로 가져간 점이 정밀.
- 토스는 성공 응답에 envelope X, ca-tmpl 은 성공도 envelope.
- 표준 준수 / lock-in / client 호환성:
- RFC 7807 ProblemDetail 미준수. 한국 SI/결제 진영에서 사실상 컨벤션화.
- client 호환성: SDK 가 envelope 흡수 → 직접 사용자도 부담 낮음.
- localization / i18n 지원 여부:
- 한국어 메시지 단일. `Accept-Language` 기반 분기 명시적이지 않음.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/github-api-error-format]] — 영어권 vendor 사례 비교
- [[raw/company-tech-blogs/idempotency-toss-payments-techblog]] — 같은 vendor 의 idempotency 정책
- (RFC 7807 ProblemDetail / JSON:API / gRPC Status 자료는 별도)
- 인용하는 branch:
- [[raw/branch-notes/feature-operational-error-observability-foundation]]
- [[raw/branch-notes/feature-boundary-validation-mapping-contract]]
- [[raw/branch-notes/feature-business-rule-validation-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (§3, §6)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,112 @@
---
title: Datadog APM vs OpenTelemetry — Vendor APM 비교
source_type: company-tech-blog
url: https://www.datadoghq.com/blog/opentelemetry-instrumentation/
archive_url:
related_branches: [feature-distributed-tracing-contract]
related_projects: [ca-skeleton-operational-contract]
tags: [ca-distributed-tracing, datadog, opentelemetry, apm, vendor-comparison]
status: raw
confidence: low
created: 2026-05-22
last_reviewed: 2026-05-27
---
# Datadog APM vs OpenTelemetry — Vendor APM 비교
> Layer: `raw/company-tech-blogs/` — Datadog 공식 블로그 "Send OpenTelemetry data to Datadog" (2019) + Datadog Java tracer docs verbatim.
> 주의: 본 파일의 이전 버전에는 verbatim 으로 확인되지 않는 marketing 문구 4개가 포함되어 있었음 (예: "Datadog supports OpenTelemetry instrumentation in two ways: OTLP ingest via the Datadog Agent...", "auto-instruments 100+ frameworks out of the box", "AWS X-Ray uses its own propagation header (`X-Amzn-Trace-Id`)..."). 2026-05-27 재검증 결과 본문에서 verbatim 확인 안 됨 — 모두 `needs-confirmation` 으로 격하.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-distributed-tracing-contract]] | Micrometer Tracing + OpenTelemetry exporter 채택 결정의 vendor-neutrality 근거 (대안: Datadog dd-trace-java / AWS X-Ray) |
| [[raw/project-notes/ca-skeleton-operational-contract]] | Distributed Tracing Contract — OTel vs vendor-native APM 비교의 입력 자료 |
## 컨텍스트 / 왜 저장했는지
ca-tmpl이 채택한 "**Micrometer Tracing + OpenTelemetry exporter**"를 vendor APM (Datadog native tracer / AWS X-Ray)과 비교. vendor lock-in trade-off 명시.
## 출처 / Source
- 원본 URL: https://www.datadoghq.com/blog/opentelemetry-instrumentation/
- 보조 1: Datadog APM Java tracer docs — https://docs.datadoghq.com/tracing/trace_collection/dd_libraries/java/
- 보조 2: AWS X-Ray Java SDK docs (별도 확인 필요)
- 아카이브 URL: (미수집)
- 저자 / 조직: Datadog (2019-09 블로그)
- 발행일: 2019-09 (Datadog/OpenTelemetry 파트너십 발표 시점)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
(Datadog 블로그 — `datadoghq.com/blog/opentelemetry-instrumentation/`, 2026-05-27 재검증 결과)
> [§OpenTelemetry vendor-neutrality] "Because OpenTelemetry is vendor-neutral, companies will be able to migrate their observability data between monitoring backends more easily, without vendor lock-in."
> [§Datadog 기여] "contributing our tracing libraries to the OpenTelemetry project"
(Datadog Java tracer docs — `docs.datadoghq.com/tracing/trace_collection/dd_libraries/java/`)
> [§dd-trace-java 자동 계측] "Automatic instrumentation for Java uses the `java-agent` instrumentation capabilities provided by the JVM."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| DD-OTEL-C1 | OpenTelemetry 는 vendor-neutral 이며 이를 통해 모니터링 backend 간 observability 데이터 마이그레이션이 vendor lock-in 없이 더 쉬워짐 | [§OpenTelemetry vendor-neutrality] "Because OpenTelemetry is vendor-neutral, companies will be able to migrate their observability data between monitoring backends more easily, without vendor lock-in." | `company-case-study` | Datadog 외 backend 로의 portability 가 결정 요인일 때 | OTel 가 vendor-native 기능 (Datadog Watchdog, Continuous Profiler) 를 모두 대체한다는 뜻은 아님 |
| DD-OTEL-C2 | Datadog 은 자사 tracing 라이브러리를 OpenTelemetry 프로젝트에 기여 (2019 시점) | [§Datadog 기여] "contributing our tracing libraries to the OpenTelemetry project" | `company-case-study` | Datadog/OTel 호환성 history | 현재 2026 시점에서의 정확한 통합 상태 (OTLP ingest 경로 등) 는 본 인용으로 보장 안 됨 — 별도 docs 필요 |
| DD-OTEL-C3 | dd-trace-java 의 자동 계측은 JVM 의 `java-agent` 계측 기능을 사용 | [§dd-trace-java 자동 계측] "Automatic instrumentation for Java uses the `java-agent` instrumentation capabilities provided by the JVM." | `official-vendor-doc` | Java 애플리케이션에 dd-trace-java 통합 시 | dd-trace-java 가 자동 계측하는 framework 의 개수 / 목록은 본 인용 범위 밖 (예: "100+ frameworks") |
| DD-OTEL-C4 | (부재) "Datadog supports OpenTelemetry instrumentation in two ways: OTLP ingest via the Datadog Agent, and direct OTLP HTTP/gRPC ingestion." — 본 자료의 2026-05-27 재검증에서 verbatim 미확인 | (부재 자체가 claim) | `needs-confirmation` | Datadog 의 OTLP 수집 경로 (Agent vs direct) | 해당 사실이 거짓이라는 뜻은 아님. Datadog OTLP docs (별도) 에서 verbatim 재수집 필요 |
| DD-OTEL-C5 | (부재) "dd-trace-java auto-instruments 100+ frameworks out of the box" — verbatim 미확인 | (부재 자체가 claim) | `needs-confirmation` | dd-trace-java 의 자동 계측 framework 개수 비교 | Datadog Compatibility Requirements 페이지 (별도) 의 확인 필요 |
| DD-OTEL-C6 | (부재) "AWS X-Ray uses its own propagation header (`X-Amzn-Trace-Id`) by default; W3C trace context support added in 2021" — verbatim 미확인 (Datadog 블로그가 아닌 AWS X-Ray docs 가 출처여야 함) | (부재 자체가 claim) | `needs-confirmation` | AWS X-Ray propagation 헤더 / W3C 호환 | AWS X-Ray Developer Guide 의 verbatim 확인 필요. 본 raw 파일로는 미보장 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `DD-OTEL-C1`: OpenTelemetry 의 vendor-neutrality 주장 (Datadog 블로그의 marketing 문구)
- `DD-OTEL-C2`: Datadog 의 OTel 프로젝트 기여 (2019 시점)
- `DD-OTEL-C3`: dd-trace-java 가 java-agent 기반이라는 vendor docs 사실
- **이 자료가 증명하지 않는 것**:
- `DD-OTEL-C4`, `C5`, `C6`: 이전 raw 파일에 기록된 marketing/spec 문구의 정확한 verbatim
- Datadog APM 의 모든 기능 (Watchdog, Continuous Profiler, Live Search) 의 정확한 동작
- AWS X-Ray 의 정확한 propagation 헤더 / W3C 호환 시점
- OTel SDK + Datadog 조합의 실제 production 운영 사례
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- Datadog OTLP ingest 경로 (Agent vs direct) 의 최신 docs 확인 → 별도 raw 파일 생성 권고
- AWS X-Ray Developer Guide 에서 propagation 헤더 verbatim 수집 → 별도 raw 파일 생성 권고
- Micrometer Tracing 1.x + OTel exporter + Datadog Agent 의 실측 latency / 호환성 검증
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
> 주의: 아래 메모는 verbatim 출처가 없는 사실을 포함할 수 있음 → wiki 로 옮길 때 verbatim 재수집 필요.
- **3가지 선택지 비교 (사실 자체는 별도 출처 확인 필요)**:
| 옵션 | propagation | exporter | vendor lock-in |
|---|---|---|---|
| OTel SDK (ca-tmpl 채택) | W3C traceparent | OTLP → any backend | 없음 |
| Datadog dd-trace-java | W3C 또는 Datadog header | dd agent → Datadog only | 있음 |
| AWS X-Ray | `X-Amzn-Trace-Id` (W3C 옵션) | X-Ray daemon → AWS only | 있음 |
- **장점 (ca-tmpl OTel SDK 채택)**:
- vendor-neutral → backend swap 가능 (`DD-OTEL-C1` 으로 지지됨).
- Spring Boot 3 + Micrometer Tracing 통합 자연스러움.
- W3C trace context default 와 정합.
- **단점 (vendor-native 대비)**:
- vendor-specific feature (Datadog Watchdog, X-Ray service map auto-discovery) 사용 어려움.
- vendor auto-instrumentation 이 더 광범위한 경우 있음.
- **ca-tmpl 과의 차이**: 명시적으로 "특정 APM vendor 종속 설정" 을 out-of-scope 로 둠 → OTel 선택은 결정과 정합.
- **운영 복잡도**: OTel + collector 추가 deploy 필요. vendor native 는 agent 설치만으로 시작 가능 → 초기 적용 비용은 vendor native 가 낮으나 장기 portability 는 OTel 우위.
## Related / 관련
- 같은 주제 다른 raw:
- (예정) `raw/official-docs/aws-x-ray-propagation` — X-Ray 헤더 verbatim 확인
- (예정) `raw/company-tech-blogs/datadog-otlp-ingest-options` — Datadog OTLP 경로 verbatim 확인
- 인용하는 branch:
- [[raw/branch-notes/feature-distributed-tracing-contract]]
- 인용하는 project:
- [[raw/project-notes/ca-skeleton-operational-contract]] (Distributed Tracing Contract)
- 인용한 wiki 요약: (미작성)
@@ -0,0 +1,110 @@
---
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)
@@ -0,0 +1,102 @@
---
title: "VassilisSoum/spring-custom-transaction-interceptor (GitHub)"
source_type: company-tech-blog
url: https://github.com/VassilisSoum/spring-custom-transaction-interceptor
archive_url:
status: raw
confidence: medium
tags: [ca-transaction-boundary, custom-aop, transaction-interceptor, functional, github-reference]
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
---
# spring-custom-transaction-interceptor (GitHub Reference 구현)
> Layer: `raw/company-tech-blogs/` — Vassilis Soum 개인 GitHub repository 의 README 와 코드 발췌. Spring `TransactionInterceptor` 를 확장해 `Try` 모나드와 통합한 reference 구현체.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 별도 작성. 원본은 raw에 영구 보관.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-application-port-usecase-contract]] | functional error 타입을 유지하면서 Spring TX 를 활용하는 대안 (= ca-tmpl 의 정반대 dependency 방향) 의 reference 구현체 근거 |
| [[raw/branch-notes/feature-transaction-concurrency-contract]] | Topic 2 — Transaction Boundary 대안 5 (Custom AOP / TransactionInterceptor 확장) 의 reference 구현체 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §14. Transaction / Concurrency Contract — TransactionPort 결정의 dependency 방향 비교군 |
## 컨텍스트 / 왜 저장했는지
ca-tmpl 의 TransactionPort 결정에 대한 대안 5 의 **레퍼런스 구현체**. 함수형 에러 타입(Try/Either) 을 유지하면서 Spring 트랜잭션 관리 인프라를 재사용하기 위해 `TransactionInterceptor` 를 직접 확장한 코드.
## 출처 / Source
- 원본 URL: https://github.com/VassilisSoum/spring-custom-transaction-interceptor
- 아카이브 URL: (미수집)
- 저자 / 조직: Vassilis Soum (개인 GitHub, 산업 예제 다수)
- 발행일: 2024
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§README — TransactionInterceptor 설명] "The TransactionInterceptor is a Spring AOP interceptor that intercepts all methods annotated with the `@Transactional` annotation."
> [§README — Try 모나드 통합 동기] "In this example we use the custom TransactionInterceptor to handle transaction management for the com.soumakis.control.Try monad to be able to express exceptions as types in the method signature."
> [§README — 확장 인터페이스] "The TransactionInterceptor is a custom implementation of the `org.aopalliance.intercept.MethodInterceptor` interface."
> [§README — 핵심 동작] "It is used to intercept method invocations and execute custom logic before and after the method invocation."
> [§README — 설정 요구사항] "In `application.properties` or `application.yml` allow overriding spring beans by setting `spring.main.allow-bean-definition-overriding=true`"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| VSOUM-TX-C1 | Spring 의 표준 `TransactionInterceptor``@Transactional` 메서드를 가로채는 AOP 인터셉터이며, 본 repo 는 그것을 확장한 custom 구현체를 제공 | [§README — TransactionInterceptor 설명] "The TransactionInterceptor is a Spring AOP interceptor that intercepts all methods annotated with the `@Transactional` annotation." | `engineering-blog` | Spring AOP + `@Transactional` 환경 | `@Transactional` 외 메타 어노테이션 (`@SpringTransactional` 등 custom) 처리 여부는 본 인용 범위 밖 |
| VSOUM-TX-C2 | 확장 동기는 **`com.soumakis.control.Try` 모나드** 가 Spring TX 와 호환되도록 만들기 위함 — 예외를 메서드 시그니처의 타입으로 표현 가능 | [§README — Try 모나드 통합 동기] "In this example we use the custom TransactionInterceptor to handle transaction management for the com.soumakis.control.Try monad to be able to express exceptions as types in the method signature." | `engineering-blog` | functional error handling + Spring TX | 이 패턴이 모든 functional library (Vavr `Either`, kotlin-result 등) 에서 동작한다는 뜻은 아님 — `Try` 한정 |
| VSOUM-TX-C3 | custom TransactionInterceptor 는 `org.aopalliance.intercept.MethodInterceptor` 인터페이스를 구현하며, 메서드 invocation 전후 custom logic 실행 가능 | [§README — 확장 인터페이스] "The TransactionInterceptor is a custom implementation of the `org.aopalliance.intercept.MethodInterceptor` interface." + "It is used to intercept method invocations and execute custom logic before and after the method invocation." | `engineering-blog` | Spring AOP / aopalliance 기반 인터셉터 확장 | 구현체가 모든 Spring 버전 / Boot 버전에서 호환된다는 뜻은 아님 (API 안정성 별도) |
| VSOUM-TX-C4 | 본 패턴은 **`spring.main.allow-bean-definition-overriding=true`** 설정을 요구 (Spring 의 기본 TransactionInterceptor bean 을 override) | [§README — 설정 요구사항] "In `application.properties` or `application.yml` allow overriding spring beans by setting `spring.main.allow-bean-definition-overriding=true`" | `engineering-blog` | Spring Boot 2.1+ (bean override 기본 비활성) | bean override 활성화의 다른 side-effect (다른 bean 충돌 디버깅 비용) 는 본 인용 범위 밖 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `VSOUM-TX-C1` ~ `C4`: TransactionInterceptor 확장의 구조, Try 모나드 통합 동기, aopalliance 인터페이스, bean override 설정 요구사항
- **이 자료가 증명하지 않는 것**:
- 이 패턴이 산업계 표준 / 권장 패턴이라는 주장 (`engineering-blog` 수준 — 개인 GitHub repo)
- prod 환경에서의 안정성 또는 성능 측정값 (README 에 수치 없음)
- Spring 마이너 버전 업그레이드 시 내부 API 변화에 대한 호환성 보장
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 이 functional error 타입 (Try/Either) 을 사용하는지 — 그렇지 않으면 본 패턴의 핵심 동기 (`VSOUM-TX-C2`) 가 부합하지 않음
- `spring.main.allow-bean-definition-overriding=true` 의 부수 효과가 ca-tmpl 의 다른 bean 정의와 충돌하지 않는지
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- 적용 시나리오: 이미 `@Transactional` 을 광범위하게 쓰는 코드베이스에 functional error 핸들링(Try/Either) 을 도입하고 싶을 때.
- 장점:
- 기존 Spring TX 인프라(PlatformTransactionManager, propagation) 그대로 활용.
- rollback rule 을 `Either.Left` / `Try.Failure` 같은 데이터로 표현 → throw 남용 감소.
- 단점:
- application 코드는 여전히 Spring annotation 에 노출.
- bean override 활성화 → 부작용 디버깅 비용.
- 라이브러리 업그레이드 시 `TransactionInterceptor` 내부 변화로 깨질 위험.
- ca-tmpl(TransactionPort) 와의 차이: 본 repo 는 "Spring TX 를 더 강하게 활용", ca-tmpl 은 "Spring TX 를 숨김". 같은 'AOP 활용 트랜잭션' 카테고리지만 dependency 방향이 정반대.
- testability 영향: 낮음 — Spring context 필수.
- code 복잡도 영향: 높음.
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/hexagonal-reflectoring-transactional-placement]] (baseline `@Transactional` direct)
- [[raw/company-tech-blogs/transaction-port-clean-ddd-spring-medium]] (TransactionPort 대안)
- [[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 의 위치: 대안 5: Custom AOP / TransactionInterceptor 확장 (functional 통합)
@@ -0,0 +1,82 @@
---
title: "PostgreSQL Audit Logging Using Triggers — Vlad Mihalcea"
source_type: company-tech-blog
url: https://vladmihalcea.com/postgresql-audit-logging-triggers/
archive_url:
related_branches: [feature-persistence-auditing-contract]
related_projects: []
tags: [company-tech-blog, ca-tmpl, persistence, postgresql, audit-logging]
created: 2026-06-10
---
# PostgreSQL Audit Logging Using Triggers — Vlad Mihalcea
> Layer: `raw/company-tech-blogs/` — 외부 기술 블로그의 **원문 발췌·출처 기록**.
> 검증된 요약은 `/ingest` 후 `wiki/concepts/` 에 별도 작성. 원본은 raw 에 영구 보관.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-persistence-auditing-contract]] | DB-level trigger auditing (standalone) 대안 기각: 애플리케이션 액터 ID 를 트리거로 넘기려면 매 DML 직전 `SET LOCAL var.logged_user` 세션 변수 주입 seam 이 필요하고, DB 타임소스가 앱 Clock bean 과 분리되어 감사 시각 통제권을 잃는다. |
## 출처 / Source
- 원본 URL: https://vladmihalcea.com/postgresql-audit-logging-triggers/
- 아카이브 URL: (없음)
- 저자 / 조직: Vlad Mihalcea (개인 전문가 기술 블로그)
- 발행일: (페이지에서 확인된 날짜 없음)
- 마지막 확인일: 2026-06-10
## 왜 저장했는지 / Why archived
PostgreSQL 트리거 기반 감사 로깅 구현 시 **애플리케이션이 매 DML 전에 세션 변수(`var.logged_user`)를 직접 주입해야 한다**는 사실을 원문 인용으로 확보하기 위해 보관. 이 seam 의 존재가 "DB-level trigger 단독 사용" 대안을 기각하는 근거가 된다.
## 핵심 인용 / Key quotes (verbatim, 5문장)
> [§ trigger function] "the `dml_created_by` column is set to the value of the `var.logged_user` PostgreSQL session variable, which was previously set by the application with the currently logged user"
> [§ trigger function / SQL] `current_setting('var.logged_user')`
> [§ SET LOCAL / connection pooling] "Notice that we used `SET LOCAL` as we want the variable to be removed after the current transaction is committed or rolled back. This is especially useful when using connection pooling."
> [§ trigger definition] "In order for the `book_audit_trigger_func` function to be executed after a `book` table record is inserted, updated or deleted, we have to define the following trigger:"
> [§ introduction] "In this article, we are going to see how we can implement an audit logging mechanism using PostgreSQL database triggers to store the CDC (Change Data Capture) records."
## Claims Extracted / 추출된 주장
> 이 자료가 **직접 말하는 것만** claim 으로 분리한다. 내 프로젝트에 적용한 결론은 여기 쓰지 않는다.
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| C1 | 트리거가 감사 행위자를 식별하려면 세션 변수 `var.logged_user` 를 읽고, 그 값은 **애플리케이션이 미리 설정**해야 한다 | [§ trigger function] "the `dml_created_by` column is set to the value of the `var.logged_user` PostgreSQL session variable, which was previously set by the application with the currently logged user" | `engineering-blog` | PostgreSQL AFTER 트리거가 DML 마다 실행되는 모든 환경 | 애플리케이션이 변수를 설정하지 않았을 때 트리거가 어떻게 동작하는지(에러/null) 는 이 글 단독으로 증명 안 됨 |
| C2 | 트리거 함수 내부에서 `current_setting('var.logged_user')` 호출로 사용자 값을 읽는다 | [§ trigger function / SQL] `current_setting('var.logged_user')` | `engineering-blog` | PostgreSQL PL/pgSQL 트리거 함수 | `current_setting` 의 두 번째 인자(`missing_ok`) 동작은 이 코드만으로 확정 불가 |
| C3 | `SET LOCAL` 을 사용하면 트랜잭션 커밋/롤백 후 변수가 자동 소멸하며, 이는 **커넥션 풀 환경에서 특히 유용**하다 | [§ SET LOCAL / connection pooling] "Notice that we used `SET LOCAL` as we want the variable to be removed after the current transaction is committed or rolled back. This is especially useful when using connection pooling." | `engineering-blog` | HikariCP 등 커넥션 풀을 사용하는 모든 Spring 앱 | `SET LOCAL` 이 실제로 커넥션 풀 재사용 시 변수를 100% 소멸시킴을 PostgreSQL 공식 문서 수준으로 보증하지 않음 — 추가 확인 필요 |
| C4 | 트리거는 `AFTER INSERT OR UPDATE OR DELETE` 로 정의된다(AFTER 트리거) | [§ trigger definition] "In order for the `book_audit_trigger_func` function to be executed after a `book` table record is inserted, updated or deleted, we have to define the following trigger:" | `engineering-blog` | PostgreSQL 감사 로그 트리거 정의 | BEFORE 트리거와의 trade-off 를 이 글이 명시적으로 비교하지 않음 |
| C5 | 이 패턴은 PostgreSQL 트리거 + JSON 컬럼으로 CDC 레코드를 저장하는 감사 로깅 구현이다 | [§ introduction] "In this article, we are going to see how we can implement an audit logging mechanism using PostgreSQL database triggers to store the CDC (Change Data Capture) records." | `engineering-blog` | PostgreSQL 트리거 기반 감사 로깅 구현 | 이 패턴이 JPA/Hibernate 감사(`@EntityListeners`) 나 Envers 보다 우월하다는 주장은 이 글 단독으로 증명 안 됨 |
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `C1`, `C2`: 트리거가 직접 `current_setting('var.logged_user')` 를 읽고, 그 값은 애플리케이션이 매 DML 전 `SET LOCAL` 로 주입해야 한다 — 즉 **애플리케이션-DB 간 세션 변수 propagation seam 이 불가피**하다
- `C3`: `SET LOCAL` 스코프는 트랜잭션 경계와 동기화되므로 커넥션 풀 환경에서 변수 누출을 방지한다 (단, `engineering-blog` 등급이므로 official 보증 아님)
- `C4`: AFTER 트리거가 사용됨
- 이 자료가 증명하지 않는 것:
- JPA `@EntityListeners` / Spring Data Auditing / Hibernate Envers 와의 전면 비교
- `current_setting` 이 변수 미설정 시 null 반환인지 예외 발생인지 (PostgreSQL 공식 문서 별도 확인 필요)
- 이 패턴이 ca-tmpl 실제 HikariCP 설정 하에서 변수 누출 없이 동작함
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- PostgreSQL 공식 문서에서 `current_setting(name, missing_ok)` 동작 확인
- ca-tmpl 의 HikariCP + `SET LOCAL` 조합에서 커넥션 반납 후 변수 완전 소멸 여부 로컬 검증
## 메모 / Notes
- 이 글은 개인 전문가 블로그(`engineering-blog`) 등급이다. C3 의 `SET LOCAL` + 커넥션 풀 안전성은 공식 PostgreSQL 문서로 보강하기 전까지 `needs-confirmation` 취급.
- 추가로 봐야 할 동일 출처: vladmihalcea.com/the-anatomy-of-connection-pooling/ (C3 보강 가능성)
- Hibernate Envers, Debezium 대안이 언급되나 비교 상세는 이 글 범위 밖.
## Related / 관련
- 같은 주제 다른 자료: [[raw/company-tech-blogs/slow-query-datasource-proxy-spring-boot-galovics]]
- 이 자료를 인용한 wiki 요약: (생성 시)
@@ -0,0 +1,110 @@
---
title: "Spring Boot Kotlin Multi Module로 구성해보는 헥사고날 아키텍처 — 우아한형제들"
source_type: company-tech-blog
url: https://techblog.woowahan.com/12720/
archive_url:
status: raw
confidence: medium
tags: [ca-transaction-boundary, hexagonal, woowahan, multi-module, kotlin]
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
---
# 우아한형제들: Spring Boot Kotlin Multi Module 헥사고날 아키텍처
> Layer: `raw/company-tech-blogs/` — 우아한형제들 기술블로그의 **원문 발췌·출처 기록**. 헥사고날을 multi-module 로 분리한 국내 대기업 사례 (4 layer hexagon).
> 검증된 요약은 `/ingest` 후 `wiki/concepts/`에 별도 작성. 원본은 raw에 영구 보관.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-application-port-usecase-contract]] | application 모듈이 framework 의존을 받지 않도록 multi-module 로 격리한 국내 사례 — ca-tmpl 의 TransactionPort 결정과 호환 |
| [[raw/branch-notes/feature-transaction-concurrency-contract]] | Topic 2 — Transaction Boundary 의 모듈 분리 보완 (대체 아님) 사례 |
| [[raw/project-notes/ca-skeleton-operational-contract]] | §14. Transaction / Concurrency Contract — 국내 대기업 헥사고날 비교군 |
## 컨텍스트 / 왜 저장했는지
ca-tmpl 의 TransactionPort 결정에 대한 **국내 대기업 비교군**. 우아한형제들이 헥사고날을 multi-module 로 분리할 때 어디까지 Spring 의존을 응용 계층 밖으로 밀어내는지, 그리고 트랜잭션 처리는 어디에 위치시키는지 확인.
## 출처 / Source
- 원본 URL: https://techblog.woowahan.com/12720/
- 아카이브 URL: (미수집)
- 저자 / 조직: 우아한형제들 기술블로그
- 발행일: 게시일 미명시
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§Domain Hexagon] "DDD(도메인 주도 개발)의 그 Domain Layer로 기술에 독립적인 POJO로 개발"
> [§Application Hexagon] "Domain의 구성요소를 사용하여 시스템이 가지는 기능/사례(usecase)를 정의한 집합"
> [§Application Hexagon — 의존성] "의존성은 Domain Hexagon에 대해서만 가짐"
> [§Framework Hexagon] "Application hexagon이 소유한 outputPort (interface) 구현체들의 집합"
> [§Bootstrap Hexagon] "프로그램의 기능을 사용하기 위한 시작점"
> [§Port 통신] "Application Hexagon에 outputPort interface를 생성" / "Framework Hexagon에 outputPort의 구현체(adapter) 개발"
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| WW-HEX-C1 | 우아한형제들 헥사고날은 **4개 Hexagon 모듈 (Domain / Application / Framework / Bootstrap)** 으로 분리 | [§Domain Hexagon] "DDD(도메인 주도 개발)의 그 Domain Layer로 기술에 독립적인 POJO로 개발" + [§Application Hexagon] "Domain의 구성요소를 사용하여 시스템이 가지는 기능/사례(usecase)를 정의한 집합" + [§Framework Hexagon] "Application hexagon이 소유한 outputPort (interface) 구현체들의 집합" + [§Bootstrap Hexagon] "프로그램의 기능을 사용하기 위한 시작점" | `company-case-study` | 국내 대기업 헥사고날 multi-module 구성 사례 | 4-hexagon 구성이 모든 헥사고날 구현의 권장 표준이라는 뜻은 아님 — 우아한형제들의 한 사례 |
| WW-HEX-C2 | Application Hexagon 의 의존성은 **Domain Hexagon 에 대해서만** 존재 (framework 의존 0) | [§Application Hexagon — 의존성] "의존성은 Domain Hexagon에 대해서만 가짐" | `company-case-study` | 우아한형제들 헥사고날 모듈 의존성 규칙 | Gradle 빌드 단계에서 위반을 차단하는 구체적 메커니즘 (ArchUnit 등) 은 본 인용 범위 밖 |
| WW-HEX-C3 | port 통신 방식: **Application Hexagon 에 outputPort interface 생성 + Framework Hexagon 에 adapter 구현** | [§Port 통신] "Application Hexagon에 outputPort interface를 생성" / "Framework Hexagon에 outputPort의 구현체(adapter) 개발" | `company-case-study` | 우아한형제들 hexagonal port 위치 결정 | input port 의 위치 / use case 와 service 의 분리 정책은 본 인용 범위 밖 |
| WW-HEX-C4 | Domain Hexagon 은 **기술 독립적 POJO** 로 개발 — 프레임워크/인프라 의존 없음 | [§Domain Hexagon] "DDD(도메인 주도 개발)의 그 Domain Layer로 기술에 독립적인 POJO로 개발" | `company-case-study` | DDD + 헥사고날 domain 모듈 구성 | POJO 가 JPA `@Entity` 도 거부하는지 (= 순수 도메인 vs anemic) 는 본 인용에서 모호 |
| WW-HEX-C5 | 본 글은 transaction boundary / `@Transactional` 위치 / framework dependency 침투에 대해 **직접 다루지 않는다** (WebFetch 재확인: "@Transactional is NEVER mentioned anywhere in this article") | (부재 자체가 claim) | `needs-confirmation` | 본 글의 표현 범위 | 우아한형제들이 transaction boundary 정책을 어떻게 운영하는지에 대한 정보는 본 자료로 얻을 수 없음 — 다른 글 / 사내 자료 확인 필요 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `WW-HEX-C1` ~ `C4`: 우아한형제들의 4-hexagon 모듈 구성, Application 의존 규칙, port 위치, Domain POJO 원칙
- `WW-HEX-C5`: 본 글이 transaction boundary 결정을 직접 다루지 않는다는 사실 (한국 백엔드 진영의 공통 공백)
- **이 자료가 증명하지 않는 것**:
- 우아한형제들의 transaction boundary 정책 (글에 부재)
- 4-hexagon 모듈 구성이 prod 환경에서 검증되었다는 측정값
- 모듈 분리만으로 트랜잭션 정책이 자동 해결된다는 주장
- 이 패턴이 한국 백엔드의 "공식 best practice" — `company-case-study` 사례일 뿐 (CLAUDE.md §5: company-tech-blog 는 사례/관점)
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- ca-tmpl 의 single-module 구조 vs 4-hexagon multi-module 의 빌드 시간 / IDE 인덱싱 트레이드오프
- 우아한형제들의 다른 글 (예: "주니어 개발자의 클린 아키텍처 맛보기") 에서 transaction boundary 가 다뤄지는지
## 메모 / Notes (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 결정 컨텍스트 해석.
- 적용 시나리오: 대규모 코드베이스에서 모듈 경계로 dependency rule 을 물리적으로 강제하고 싶을 때.
- 장점:
- Gradle multi-module 로 `application` 모듈이 `spring-tx` 의존을 아예 못 받게 만들 수 있다 → ca-tmpl 결정과 가장 호환적.
- 빌드 단계에서 위반 검출.
- 단점:
- 모듈 분리만으로는 트랜잭션 boundary 정책 자체가 정해지지 않음 → 결국 별도 port(=ca-tmpl 식) 또는 어댑터에서 wrap 결정이 필요.
- 모듈 수 늘면 빌드 시간/IDE 인덱싱 비용 증가.
- ca-tmpl(TransactionPort) 와의 차이: 우아한형제들 글은 **모듈 분리 인프라**, ca-tmpl 은 **모듈 분리 위에서의 트랜잭션 정책**. 둘은 보완 관계지 대안 관계가 아님. ca-tmpl 식 TransactionPort 는 이 모듈 구조 위에서 자연스럽게 안착한다.
- testability 영향: 모듈 분리 자체는 중립. 단 application 모듈을 spring-tx 의존에서 끊으면 ↑.
- code 복잡도 영향: 모듈 boilerplate 증가.
## 한계 / 확인 필요
- 본 글은 트랜잭션 관련 직접 문장이 없음. 우아한형제들의 트랜잭션 boundary 정책은 추가 다른 글 (예: "주니어 개발자의 클린 아키텍처 맛보기") 이나 사내 자료 확인 필요. → `status: needs-confirmation` 으로 후속 분류 후보 (이 raw 문서는 "공백 자체를 증거로" 기록한 `WW-HEX-C5`).
## Related / 관련
- 같은 주제 다른 raw:
- [[raw/company-tech-blogs/hexagonal-reflectoring-transactional-placement]] (baseline `@Transactional` direct)
- [[raw/company-tech-blogs/transaction-port-clean-ddd-spring-medium]] (TransactionPort 대안)
- [[raw/company-tech-blogs/transaction-port-vassilis-soum-github-readme]] (functional 통합 변형)
- 인용하는 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 의 위치: 보완: multi-module 분리 (대체 X)