48 KiB
title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, branch, parent_branch, related_projects, governing_docs, tags, created, target_merge, status_label, contract_packet_sha256
| title | source_type | status | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | branch | parent_branch | related_projects | governing_docs | tags | created | target_merge | status_label | contract_packet_sha256 | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-integration-adapter-templates | branch-note | raw | BR-CA-SKELETON-OPERATIONAL-CONTRACT-009 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-009 |
|
1 | feature-integration-adapter-templates |
|
|
|
2026-05-21 | in-progress | 1442f6124a72b8a5b62b10f02f014af447a26ecf28849e0baa7ca41a87de8703 |
branch: feature-integration-adapter-templates
Layer:
raw/branch-notes/— Kafka/Redis/Slack/Google Email 같은 선택형 adapter template와 실패 계약을 정의합니다.
[!note] 개정 예정 고지 (2026-07-28) 본 branch 는 owner 를 유지하되 내용을 코드 사실에 맞춰 개정한다. 근거:
docs/superpowers/specs/2026-07-28-ca-skeleton-production-capability-feature-decomposition-design.md§5.3.
- 개정 사유: "optional adapter 는 disabled env 가 기본" 을 capability descriptor 모델로 대체한다. 활성화 SSOT 는
feature-capability-provider-selection-contract로 이동하고 본 branch 는 소비자가 된다.- 참조 설계: ca-tmpl
docs/superpowers/specs/2026-07-26-production-capability-platform-design.md§7 (등급internal-design-doc— 공식 문서 아님)
부모 (필수)
- Parent project (canonical SSOT): raw/project-notes/ca-skeleton-operational-contract
ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: optional adapter template가 core broker abstraction을 침범하지 않는다
상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-EVENT-BROKER-001@1 |
core는 broker-agnostic outbox를 제공하고 Kafka는 optional adapter로 둔다 | Kafka를 포함한 optional adapter의 활성화·격리 template에 적용한다 | raw/project-notes/ca-skeleton-operational-contract |
브랜치 지역 결정
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|---|---|---|---|
| D1 | optional adapter를 disabled-default module로 제공한다 | local |
raw/project-notes/ca-skeleton-operational-contract | proposed |
| D2 | ConditionalOnProperty로 bean 등록을 제어한다 | local |
raw/project-notes/ca-skeleton-operational-contract | proposed |
| D3 | ArchUnit으로 application의 disabled adapter 의존을 검사한다 | local |
raw/project-notes/ca-skeleton-operational-contract | proposed |
| D4 | disabled adapter 호출은 fail-fast 처리한다 | local |
raw/project-notes/ca-skeleton-operational-contract | proposed |
| D5 | Java SPI 대안을 채택하지 않는다 | local |
raw/project-notes/ca-skeleton-operational-contract | proposed |
| D6 | Profile 기반 adapter toggle을 채택하지 않는다 | local |
raw/project-notes/ca-skeleton-operational-contract | proposed |
| D7 | runtime feature flag와 startup adapter toggle을 분리한다 | local |
raw/project-notes/ca-skeleton-operational-contract | proposed |
| D8 | plugin architecture는 template 범위에서 제외한다 | local |
raw/project-notes/ca-skeleton-operational-contract | proposed |
| D9 | required/optional 분류 owner와 fail-open/closed 정책 owner를 분리한다 | local |
raw/project-notes/ca-skeleton-operational-contract | proposed |
선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|
없음.
목표
선택형 adapter를 모두 기본 dependency로 탑재하면 skeleton이 무거워집니다. 대신 adapter별 실패 계약과 optional template를 제공하여 붙였을 때 같은 방식으로 실패하고 관측되게 합니다.
- 이슈:
- PR:
범위
포함 범위
- Kafka adapter contract 문서.
- Redis adapter contract 문서.
- Slack notification adapter contract 문서.
- Google Email adapter contract 문서.
- common adapter logging/error contract.
- optional module 또는 sample 분리 기준.
제외 범위
- 실제 Kafka/Redis/Slack/Google Email 운영 인프라 구성.
- provider-specific business workflow.
- 모든 adapter 기본 활성화.
근거 (필수, 최소 1개+)
본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.
| Source | 정당화하는 결정 |
|---|---|
| raw/official-docs/adapter-spring-boot-autoconfig-custom-starter | Spring Boot AutoConfiguration + @ConditionalOnProperty / @ConditionalOnBooleanProperty (3 |
| raw/official-docs/adapter-java-spi-serviceloader | META-INF/services; on/off 표현 불가 + DI 미통합 + default constructor 강제 |
| raw/company-tech-blogs/adapter-togglz-ff4j-feature-toggle-library | runtime toggle; adapter on/off가 아닌 runtime branching 도구라 시맨틱 차이 |
| raw/official-docs/archunit-conditional-on-property-3-layer-pattern | 참조 |
외부 근거 / 대안 조사 (2026-05-22 — Group G-I: Integration Adapter Templates)
본 branch의 optional module + Spring @ConditionalOnProperty + ArchUnit 3-layer detection + AdapterDisabledException fail-fast 결정에 대한 외부 source.
- 채택 결정 (Spring Boot AutoConfiguration +
@ConditionalOnProperty):- raw/official-docs/adapter-spring-boot-autoconfig-custom-starter — Spring Boot AutoConfiguration +
@ConditionalOnProperty/@ConditionalOnBooleanProperty(3.5.0+) +AutoConfiguration.imports
- raw/official-docs/adapter-spring-boot-autoconfig-custom-starter — Spring Boot AutoConfiguration +
- 검토한 대안:
- 대안 1: Java SPI / ServiceLoader — raw/official-docs/adapter-java-spi-serviceloader (
META-INF/services; on/off 표현 불가 + DI 미통합 + default constructor 강제) - 대안 2: Spring
@Profilebased — boolean 시맨틱 부재, profile 조합 복잡도 증가 - 대안 3: Feature flag library (FF4J / Togglz) — raw/company-tech-blogs/adapter-togglz-ff4j-feature-toggle-library (runtime toggle; adapter on/off가 아닌 runtime branching 도구라 시맨틱 차이)
- 대안 4: Plugin architecture (OSGi-style) — Java 진영 deprecated, ca-tmpl scope 외
- 대안 1: Java SPI / ServiceLoader — raw/official-docs/adapter-java-spi-serviceloader (
- 비교 핵심: Spring
@ConditionalOnProperty는 Layer 1만 공식 cover. Layer 2(ArchUnitnoClasses().that().resideInAPackage("..application..").should().dependOnClassesThat().resideInAPackage("..adapters.{disabled}.."))와 Layer 3(AdapterDisabledException)는 ca-tmpl 자체 contract. 보강 후보: ArchUnit source 별도 필요. SPI는 on/off 표현 불가 + DI 미통합으로 ca-tmpl 결정과 정면 충돌. Togglz/FF4J는 startup-time toggle이 아닌 runtime branching이라 시맨틱 다름 — feature flag service와 adapter on/off는 분리 영역.
후속 보강 (2026-05-22): ArchUnit Layer 2의 정적 검사 가능 범위 평가. raw/official-docs/archunit-conditional-on-property-3-layer-pattern 참조.
TODO
TODO drained 2026-05-22 — Kafka/Redis/Slack/Google Email adapter 별 정책, common logging/error contract, optional module vs sample 분리는 "결정 사항" / "Adapter Template Defaults" / "테스트 계약" 표에 반영됨. 잔존 TODO 없음.
Work Item Contract
각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 기준 작성으로 남아 있으면 이 branch는 완료로 보지 않습니다.
| field | required | rule |
|---|---|---|
| Decision | yes | 구현자가 선택해야 하는 기본값 |
| Allowed | yes | 허용되는 예외와 조건 |
| Forbidden | yes | 절대 금지되는 구현/문서 상태 |
| Required registry update | conditional | error/env/header/log/metric/capability 변경 시 필수 |
| Required contract test | yes | 계약 위반 시 실패해야 하는 테스트 |
| Failure condition | yes | review/build에서 실패로 판정할 상태 |
| Canonical extraction target | yes | wiki/projects 승급 위치 |
진행 중 메모
- cache miss는 장애가 아닙니다.
- notification failure는 core use case 실패 여부를 adapter별로 명시해야 합니다.
결정 사항 (decisions)
- 2026-05-21: 선택형 adapter는 기본 탑재가 아니라 optional template 기준.
- 2026-05-22: Kafka/Redis/Slack/Google Email은 기본 dependency가 아니며 disabled env가 기본.
- 2026-05-22: Kafka retry/DLQ는 background-job branch vocabulary를 소비하고, outbox core는 Kafka를 강제하지 않음.
- 2026-05-22: adapter 배포 형태는 optional module 기본, sample source set은 문서/fixture 전용일 때만 허용.
- 2026-05-22: required vs optional dependency 분류 SSOT는 runtime-health-lifecycle-contract. 본 branch는 각 adapter의 fail-open/closed 정책과 enable/disable 메커니즘 owns. 두 branch는 양방향 cross-link.
- 2026-05-22: disabled adapter detection 메커니즘 = 2-layer 검출.
- Layer 1 (startup, runtime): Spring
@ConditionalOnProperty(name="app.adapter.{adapterName}.enabled", havingValue="true")적용. flag false 시 adapter bean 등록 X. ApplicationContext에 해당 bean 0개 verify. - Layer 2 (build, static): archetype smoke test
DisabledAdapterArchitectureTest가 application 시작 시APP_ADAPTER_{ADAPTER}_ENABLED=false인 상태에서 해당 adapter package의 class import가 use case path에 등장하면 fail. 측정 방법: ArchUnitnoClasses().that().resideInAPackage("..application..").should().dependOnClassesThat().resideInAPackage("..adapters.{disabled-adapter}..")(when disabled). - Layer 3 (runtime, fail-fast): disabled adapter의 use case path가 invoke되면
AdapterDisabledExceptionthrow + logerror.code=REQUIRED_ADAPTER_DISABLED(migration-startup의 startup validation과 동일). consumer branches는 본 결정을 consume only. adapter 추가 시env-keys.yaml에APP_ADAPTER_{NAME}_ENABLEDrow 추가 필수.
- Layer 1 (startup, runtime): Spring
- 2026-05-22: ArchUnit Layer 2의 정적 검사는 'adapter 후보 클래스가
@ConditionalOnPropertyannotation을 가짐'까지만 보장. runtime active 여부는 Layer 3 (AdapterDisabledException)에 위임. (status: needs-confirmation, fitness function 도입 결정 코드 단계 보류)
Adapter Template Defaults
| adapter | default state | owner contract |
|---|---|---|
| Kafka | disabled optional module | outbox + background retry/DLQ |
| Redis | disabled optional module | cache consistency |
| Slack | disabled optional module | notification failure policy |
| Google Email | disabled optional module | notification failure policy |
| common | dependency log/error mapper required | foundation registry |
결정-근거 매핑
각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. company-tech-blog 증거는
company-case-study로 표기하며 공식 best practice 로 승격하지 않음.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | optional adapter (Kafka/Redis/Slack/Google Email) 는 기본 dependency 아님, disabled env 기본, optional module 형태로 배포 | adapter 가 선택형 (core use case 가 강제하지 않음) 일 때 이 결정. core 가 강제하는 required adapter (예: DB) 면 disabled-default 적용 안 함 → required 분류는 runtime-health-lifecycle-contract 가 owns (D9) |
raw/official-docs/adapter-spring-boot-autoconfig-custom-starter.md#SBAC-C1, raw/official-docs/adapter-spring-boot-autoconfig-custom-starter.md#SBAC-C2, raw/official-docs/adapter-spring-boot-autoconfig-custom-starter.md#SBAC-C5 |
official-vendor-doc (Spring Boot AutoConfiguration + namespace 분리 공식 권고) |
optional module vs sample source set 의 운영 구분 (배포 artifact 관리 부담) |
| D2 | Layer 1 — Spring @ConditionalOnProperty(name="app.{domain}.{adapter}.enabled", havingValue="true", matchIfMissing=false) adapter bean 등록 제어, ApplicationContext bean count = 0 검증 |
startup-time 활성/비활성을 boolean property 로 표현할 때 이 결정. runtime 중 동적 toggle (gradual rollout) 이 필요하면 feature flag 영역 (D7 배제 근거 참조) — 다른 메커니즘 | raw/official-docs/adapter-spring-boot-autoconfig-custom-starter.md#SBAC-C1, raw/official-docs/adapter-spring-boot-autoconfig-custom-starter.md#SBAC-C3, raw/official-docs/adapter-spring-boot-autoconfig-custom-starter.md#SBAC-C4 |
official-vendor-doc + official-reference (Spring 공식 — Boolean 시맨틱은 3.5.0+ @ConditionalOnBooleanProperty 권장) |
3.5.0 미만 baseline 이면 havingValue="true" 명시 + matchIfMissing=false 정확 표현 필요. ApplicationContext bean count 검증 패턴 자체는 Spring 공식 verification 패턴 아님 (SBAC-C1~C5 Usage Boundaries 참조). ENV_KEY_DRIFT: property 는 app.adapter.{name} 이 아니라 도메인 namespace (app.cache.redis/app.messaging.kafka/app.notification.{slack,google-email}) — §Audit & Findings A1 |
| D3 | Layer 2 — ArchUnit noClasses().that().resideInAPackage("..application..").should().dependOnClassesThat().resideInAPackage("..adapters.{disabled}..") 정적 검사 |
빌드 시점에 disabled adapter package 가 application layer import 경로에 등장하면 fail 시키고 싶을 때 이 결정. 단 'disabled' 는 runtime config 평가라 정적 검사로 완전 보장 불가 → runtime 보장은 Layer 3 (D4) | raw/official-docs/archunit-conditional-on-property-3-layer-pattern.md#AUCP-C1, raw/official-docs/archunit-conditional-on-property-3-layer-pattern.md#AUCP-C4, raw/official-docs/archunit-conditional-on-property-3-layer-pattern.md#AUCP-C5 |
official-vendor-doc + engineering-blog (AUCP-C5 는 Building Evolutionary Architectures 서적 — engineering-blog 강도) |
ArchUnit Layer 2 의 정적 검사는 'adapter 후보 클래스가 @ConditionalOnProperty annotation 을 가짐' 까지만 보장 — runtime active 여부는 Layer 3 위임 (branch 자체 명시) |
| D4 | Layer 3 — disabled adapter 의 use case path 가 invoke 되면 AdapterDisabledException throw + fail-fast (silent no-op / timeout 대기 금지) |
Layer 1(bean 미등록)·Layer 2(정적) 를 우회해 disabled adapter 가 runtime 에 실제 호출되는 경우의 최후 방어선. 정상 경로는 Layer 1 에서 bean 자체가 없어 호출 불가 | UNSUPPORTED_DECISION (cited official-doc 중 fail-fast adapter exception 패턴 직접 인용 없음 — ca-tmpl 자체 contract). error code 재사용은 미정 — REQUIRED_ADAPTER_DISABLED 는 feature-migration-startup-contract owns + startup-exit(72) 시맨틱 → runtime 재사용 적정성 검토 필요 (§Audit & Findings A2) |
n/a | raw/branch-notes/feature-migration-startup-contract 와 cross-link 필요 (startup validation 의 동등 패턴). runtime 전용 error code 신규 제안 여부 미결 |
| D5 | (대안 비교) Java SPI / ServiceLoader 배제 — on/off 표현 불가 + DI 미통합 + default constructor 강제 | N/A (배제된 대안 — 채택된 D2 의 반례) | raw/official-docs/adapter-java-spi-serviceloader.md#SPI-C1, raw/official-docs/adapter-java-spi-serviceloader.md#SPI-C2, raw/official-docs/adapter-java-spi-serviceloader.md#SPI-C3, raw/official-docs/adapter-java-spi-serviceloader.md#SPI-C5 |
official-vendor-doc (Oracle Java Tutorial — classpath 존재 = 활성, property 게이팅 부재) |
JPMS (Java 9+) provides...with... + Java 9+ provider() static method 통합 시맨틱은 본 SPI source 범위 밖 |
| D6 | (대안 비교) Spring @Profile 배제 — boolean 시맨틱 부재, 다중 활성/비활성 표현 복잡 |
N/A (배제된 대안 — 채택된 D2 의 반례) | UNSUPPORTED_DECISION (cited raw 중 @Profile vs @ConditionalOnProperty 정확 비교 source 부재 — raw/official-docs/adapter-spring-boot-autoconfig-custom-starter 의 Usage Boundaries 가 "정확한 우선순위·결합 시맨틱 미증명" 명시). trade-off: 배제 사유는 'profile 은 환경 묶음용, adapter on/off 는 직교 축' 이라는 설계 판단 — 강한 외부 인용 없이 채택 가능 |
n/a | Spring @Profile Javadoc 별도 fetch 필요 (rejected alt — depth-blocking 아님) |
| D7 | (대안 비교) Feature flag library (FF4J / Togglz) 배제 — runtime branching 도구, adapter on/off 와 시맨틱 차이 | startup-time on/off 면 D2. runtime gradual rollout / A-B 가 필요하면 feature flag 가 더 적합 — 두 영역 분리 (이 branch scope 밖) | raw/company-tech-blogs/adapter-togglz-ff4j-feature-toggle-library.md#TOGGLZ-FF4J-C1, raw/company-tech-blogs/adapter-togglz-ff4j-feature-toggle-library.md#TOGGLZ-FF4J-C2, raw/company-tech-blogs/adapter-togglz-ff4j-feature-toggle-library.md#TOGGLZ-FF4J-C3, raw/company-tech-blogs/adapter-togglz-ff4j-feature-toggle-library.md#TOGGLZ-FF4J-C5 |
company-case-study (vendor 공식 페이지 — best practice 승격 금지) |
runtime toggle 자체가 adapter 비활성보다 더 적합한 시나리오 (예: gradual rollout) 가 ca-tmpl 에 등장할 가능성 — feature flag 와 adapter on/off 의 분리 영역 명시 필요 |
| D8 | (대안 비교) Plugin architecture (OSGi-style) 배제 — Java 진영 deprecated, ca-tmpl scope 외 | N/A (배제된 대안 — 채택된 D2 의 반례) | UNSUPPORTED_DECISION (OSGi deprecation 의 1차 official source 미인용 — cited raw 에 OSGi 직접 source 없음). trade-off: 배제 사유는 'classpath modular plugin 은 ca-tmpl 단일 배포 모델과 불일치' 라는 scope 판단 — 외부 인용 없이 채택 가능 | n/a | Eclipse Foundation OSGi 또는 JBoss Modules official status source 보강 필요 (rejected alt — depth-blocking 아님) |
| D9 | required vs optional dependency 분류 SSOT 는 runtime-health-lifecycle-contract, 본 branch 는 fail-open/closed 정책 owner |
adapter 가 required (없으면 app 못 뜸) 인지 optional 인지 분류는 D9 가 위임받은 SSOT 가 결정. 본 branch 는 각 optional adapter 가 없을 때 어떻게 실패/degrade 하는지(fail-open vs fail-closed) 만 owns | UNSUPPORTED_DECISION (분리 자체는 ca-tmpl 자체 contract — 두 branch 간 cross-link 가정) | n/a | 양방향 cross-link 확인 + runtime-health branch 의 Decision Evidence Map 와 정합성 검증 |
구현 가이드
결정 (Decisions) 이 "무엇" 이라면, 본 §는 "어디에 어떻게" 의 사전 명세. 다음 구현자가 되묻지 않고 코드를 작성할 수 있는 수준.
구현 현황 (2026-06-09 ca-tmpl
src/grep 결과): kafka/redis/slack/email adapter 모듈은src/에 존재하지 않음 (현존 adapter 모듈 =adapter-identifier·adapter-outbound·adapter-persistence·adapter-web).AdapterDisabledException·@ConditionalOnPropertyadapter wiring 도 코드 부재 → 본 § 의 Java 측 명세는 전량planned. 유일하게 landed 된 것은docs/registries/env-keys.yaml의 enable 키 5개 (documented-only — registry row 만 존재).구현 완료 (2026-06-09, branch
feature/integration-adapter-templates): 위planned항목 전량 구현 + locally-verified. 패키징 결정: 신규 Gradle 모듈이 아니라 기존adapter-outbound모듈의cache/messaging/notification패키지에 template 으로 landing (빈 package +.gitkeep가 이미 그 용도로 존재했고, Gradle matrix·ArchUnit 가..adapter.outbound..를 이미 커버하므로 신규 모듈 오버헤드 회피). heavy SDK(spring-kafka/lettuce/slack/mail) 미추가 — 각 adapter 는KafkaSender/RedisClient/SlackClient/GoogleEmailClientintegration seam(interface) 만 제공하고 실제 client 는 fork 한 프로젝트가 구현 (§목표/WHY "skeleton 경량 유지"). landed:
- shared-contract:
OperationalError.ADAPTER_DISABLED(INTERNAL/500/retryable=false) +AdapterDisabledException(A2 해소 — startupREQUIRED_ADAPTER_DISABLED재사용 안 함, runtime 전용 신규 코드 owner=본 branch).- adapter-outbound:
support/(OutboundCorrelation, OutboundDependencyLogger, OutboundSupportConfig) + adapter 4종 = port + seam + fail-open 구현 + disabled sentinel +@ConditionalOnPropertyconfig (+ Kafka 는KafkaAdapterSettingsbrokers 검증). build.gradle 에spring-boot-autoconfigure+slf4j-api추가.- adapter-web:
GlobalExceptionHandler가AdapterDisabledException→ADAPTER_DISABLED매핑.- app-bootstrap:
DisabledAdapterArchitectureTest(Layer 2: 격리 +@Beangating) 신규,CleanArchitectureTestB7 rule 을@Configurationfactory 제외로 scoping,application.ymlapp.*block.- registries/env:
error-codes.yamlADAPTER_DISABLED row,src/.env5개 키.- 검증:
:shared-contract:test·:adapter-outbound:test·:adapter-web:test·:app-bootstrap:test·verifyCleanArchitectureDependencies·verifyEnvKeys·verifyPublicPathSnapshot모두 PASS. ca-architect-sentinel PASS.
1. Adapter enable/disable env 키 계약 (FACT — env-keys.yaml landed)
Trace: D1 (disabled-default optional) + D2 (Layer 1 boolean property). Supporting:
SBAC-C1/SBAC-C2/SBAC-C5. 증거 등급:documented-only(env-keys.yaml row 존재, Java adapter 코드 부재).
- UNSUPPORTED_IMPL_DECISION: 없음 — 아래 키·기본값·validation·required_test 는 모두
ca-tmpl/docs/registries/env-keys.yaml의 기존 값 재사용 (invent 아님).
| adapter | env key (registry SSOT) | Spring property | default | validation | required_test (registry) | owner_branch |
|---|---|---|---|---|---|---|
| Redis | APP_CACHE_REDIS_ENABLED |
app.cache.redis.enabled |
false |
boolean_strict |
adapter-contract:redis-disabled-default |
feature-integration-adapter-templates |
| Kafka | APP_MESSAGING_KAFKA_ENABLED |
app.messaging.kafka.enabled |
false |
boolean_strict |
adapter-contract:kafka-disabled-default |
feature-integration-adapter-templates |
| Kafka brokers | APP_MESSAGING_KAFKA_BROKERS |
app.messaging.kafka.brokers |
null |
csv_of_host_port_when_kafka_enabled |
adapter-contract:kafka-brokers-when-enabled |
feature-integration-adapter-templates |
| Slack | APP_NOTIFICATION_SLACK_ENABLED |
app.notification.slack.enabled |
false |
boolean_strict |
adapter-contract:slack-disabled-default |
feature-integration-adapter-templates |
| Google Email | APP_NOTIFICATION_GOOGLE_EMAIL_ENABLED |
app.notification.google-email.enabled |
false |
boolean_strict |
adapter-contract:google-email-disabled-default |
feature-integration-adapter-templates |
⚠️ 본 branch 의 prose/결정에 등장하는 일반화 패턴
APP_ADAPTER_{NAME}_ENABLED/app.adapter.{name}.enabled는 registry 에 landed 된 실제 키와 불일치 (도메인 namespace 사용). 신규 adapter 추가 시에도APP_ADAPTER_*가 아니라 도메인 prefix (APP_CACHE_*/APP_MESSAGING_*/APP_NOTIFICATION_*) 를 따른다. → §Audit & Findings A1.
2. Layer 1 — Spring @ConditionalOnProperty bean 게이팅 (planned)
Trace: D2. Supporting:
SBAC-C1(ConditionalOnProperty 존재)·SBAC-C3(default 누락 시 미매칭)·SBAC-C4(3.5.0+ Boolean 변형). 증거 등급:planned(코드 부재).
- UNSUPPORTED_IMPL_DECISION:
- adapter bean package 명 (
dev.caskeleton.adapter.{messaging.kafka|cache.redis|notification.slack|notification.googleemail}) — 코드 미존재, 현존 adapter 모듈 명명 관행(dev.caskeleton.adapter.*) 에서 추정. trade-off: 모듈 경계가 코드로 확정되면 정합 필요.- "ApplicationContext bean count = 0 검증" 패턴 — Spring 공식 verification 패턴 아님 (
SBACUsage Boundaries). trade-off: disabled 상태 정합성을 startup 테스트로 직접 assert 하려는 ca-tmpl 자체 선택.
각 adapter auto-config 클래스에 @ConditionalOnProperty(name="app.{domain}.{adapter}.enabled", havingValue="true", matchIfMissing=false) 부착. matchIfMissing=false 명시 의무 — env 누락 시 disabled 가 기본 (D2 Open Risk). Boolean baseline 이 Spring Boot 3.5.0+ 면 @ConditionalOnBooleanProperty 로 치환 가능 (SBAC-C4; baseline 버전은 §Claims To Verify 미확정 항목).
3. Layer 2 — ArchUnit 정적 격리 규칙 (planned)
Trace: D3. Supporting:
AUCP-C1·AUCP-C4·AUCP-C5. 증거 등급:planned(rule 코드 부재).
- UNSUPPORTED_IMPL_DECISION: rule 클래스 명 / 배치 모듈 —
app-bootstrap의 기존CleanArchitectureTest패키지 관행에서 추정 (src/app-bootstrap/.../architecture/). trade-off: 실제 ArchUnit suite 배치는 코드 확정 시 정합.
noClasses().that().resideInAPackage("..application..")
.should().dependOnClassesThat().resideInAPackage("..adapter.{disabled-adapter}..")
정적 검사가 보장하는 범위는 'application layer 가 특정 adapter package 를 import 하지 않음' 까지. 'disabled' 라는 runtime config 조건은 정적으로 완전 평가 불가 (AUCP-C5 Usage Boundaries) → runtime 보장은 §4 (Layer 3).
4. Layer 3 — runtime fail-fast AdapterDisabledException (planned, error code 미정)
Trace: D4 (UNSUPPORTED_DECISION — ca-tmpl 자체 contract). 증거 등급:
planned—src/grep 결과AdapterDisabledException부재.
- UNSUPPORTED_IMPL_DECISION:
- 예외 클래스 명
AdapterDisabledException— 코드 부재, 명명은 임의. trade-off: shared-contract 예외 계층과 정합 필요.- error code 재사용
REQUIRED_ADAPTER_DISABLED— 이 코드는feature-migration-startup-contractowns + startup-exit(72) / INTERNAL 500 시맨틱 (error-codes.yaml L830). runtime invoke-path 예외에 재사용하는 것이 적정한지 미결 → §Audit & Findings A2. trade-off: 재사용 시 코드 1개로 startup·runtime 두 lifecycle 을 표현(혼란) vs 신규 runtime 코드 추가(registry 증식).
정상 경로에서는 Layer 1 이 bean 자체를 등록하지 않으므로 disabled adapter 는 호출 불가. 본 Layer 는 Layer 1·2 를 우회한 호출의 최후 방어선 — silent no-op / timeout 대기 금지, 즉시 throw.
5. Per-adapter 실패 계약 (planned — owner branch 와 분담)
Trace: D9 (본 branch 는 fail-open/closed 정책 owner). 진행 중 메모("cache miss 는 장애 아님", "notification failure 는 adapter별 core 실패 여부 명시") 의 구체화. 증거 등급:
planned.
- 결정 (D9 도출, 본 branch owns): notification adapter (Slack/Google Email) 는 fail-open 기본. notification 은 skeleton 에서 use case 의 부수 효과(side-effect) 로 모델링되므로, 전송 실패가 core use case 의 HTTP 응답을 실패(5xx)로 만들지 않는다 — 실패는 correlationId + 실패 metric 으로 관측되고 응답은 core 결과를 따른다.
- UNSUPPORTED_IMPL_DECISION:
- notification fail-open 기본값 자체 — 외부 source 가 prescribe 한 값 아님(설계 판단). trade-off: fail-open 이면 알림 유실이 무음(관측에만 의존) vs fail-closed 면 알림 실패가 핵심 API 에러로 표출되어 사용자 경험 저하. skeleton 은 "알림은 부수효과" 가정을 택함.
- OUT_OF_BRANCH_SCOPE: notification 이 primary outcome 인 use case(예: "비밀번호 재설정 메일 발송" 자체가 목적) 는 도메인 특화 — 해당 use case 가 전송을 동기 + fail-closed 로 호출하는 결정은 도메인 branch 몫(skeleton 범위 밖). 본 contract 는 default(fail-open)만 owns.
- correlationId 부착 메커니즘 / PII redaction glob 패턴 — 코드·정책 source 부재. trade-off: 아래는 정책 의도 이며 메커니즘은 구현 시 확정.
| adapter | enabled 시 실패 정책 | fail-open/closed | 분담 owner |
|---|---|---|---|
| Kafka | publish 실패 시 correlationId 부착 + outbox/retry 로 위임 | core use case 는 outbox commit 으로 성공 (fail-open) | retry/DLQ vocab → raw/branch-notes/feature-background-job-async-contract, outbox → raw/branch-notes/feature-domain-event-outbox-contract |
| Redis | unavailable 시 cache-miss 로 graceful degrade (INTERNAL 로 뭉개지 금지) |
fail-open (cache miss = 정상) | cache 일관성 → raw/branch-notes/feature-cache-consistency-contract |
| Slack / Google Email | 전송 실패 시 correlationId + 실패 metric 으로 관측, provider body/PII 는 log 미등장 | fail-open (기본) — notification 실패 ≠ core use case 실패 (5xx 미승격). primary-outcome use case 의 fail-closed 는 OUT_OF_BRANCH_SCOPE | 본 branch owns (default), 도메인별 override 는 도메인 branch |
엣지·실패·의존
R4(깊이 게이트) 캡처용. 정상 경로 외 구현 중 부딪힐 실패/엣지/다른 계약 의존.
- 실패·엣지 경로:
- env 누락 vs
falsevstrue:@ConditionalOnProperty(matchIfMissing=false)로 누락=disabled 가 기본.boolean_strictvalidation 이true/false외 값 거부 (registry). 3-case bean count 검증 필요 (§Claims). - disabled adapter runtime 호출: Layer 1 우회 시
AdapterDisabledExceptionfail-fast — timeout 대기 금지 (D4). - Kafka enabled + brokers 누락:
csv_of_host_port_when_kafka_enabledvalidation 이 startup 에서 차단해야 함 (APP_MESSAGING_KAFKA_BROKERS). - Redis unavailable (enabled): cache-miss degrade, 응답 200 유지, INTERNAL 승격 금지.
- notification provider 실패: PII log 누출 0, core use case 실패 전파 여부 adapter별 명시.
- env 누락 vs
- 다른 계약 의존:
- raw/branch-notes/feature-runtime-health-lifecycle-contract — required vs optional dependency 분류 SSOT (D9). 그 분류가 바뀌면 본 branch 의 disabled-default 적용 대상이 바뀜.
- raw/branch-notes/feature-migration-startup-contract —
REQUIRED_ADAPTER_DISABLEDerror code + startup-exit(72) owner. D4 의 error code 재사용 결정은 이 계약에 의존 (§Audit A2). - raw/branch-notes/feature-cache-consistency-contract — Redis endpoint 키 (
APP_CACHE_REDIS_HOST/PORT, owner) + cache 일관성 정책. 본 branch 는 enable 토글만 owns. - raw/branch-notes/feature-domain-event-outbox-contract + raw/branch-notes/feature-background-job-async-contract — Kafka retry/DLQ vocabulary. outbox core 는 Kafka 를 강제하지 않음 (D 결정 2026-05-22).
Audit & Findings
ca-tmpl ground truth (registry/code) 대조에서 발견한 drift. 사용자 작성 결정 영역이므로 자동 rewrite 하지 않고 정합 권고만 기록.
- A1 — ENV_KEY_DRIFT (
APP_ADAPTER_{NAME}_ENABLED→ 도메인 namespace):- 발견: 본 branch 결정/prose (§결정 사항 disabled adapter detection, D2) 는 일반화 키
APP_ADAPTER_{NAME}_ENABLED/app.adapter.{name}.enabled를 사용. 그러나env-keys.yaml에 실제 landed 된 키는 도메인 namespace —APP_CACHE_REDIS_ENABLED,APP_MESSAGING_KAFKA_ENABLED,APP_NOTIFICATION_SLACK_ENABLED,APP_NOTIFICATION_GOOGLE_EMAIL_ENABLED(모두owner_branch: feature-integration-adapter-templates). - 권고: 구현·
@ConditionalOnProperty는 §구현 가이드 §1 표의 도메인 namespace 키를 SSOT 로 사용. prose 의APP_ADAPTER_*일반화는 abstract placeholder 로만 취급하고, 신규 adapter 도 도메인 prefix 를 따른다.
- 발견: 본 branch 결정/prose (§결정 사항 disabled adapter detection, D2) 는 일반화 키
- A2 — CODE_OWNERSHIP/SEMANTIC drift (
REQUIRED_ADAPTER_DISABLED재사용):- 발견: D4/Layer 3 는 runtime invoke-path 예외 로그에
error.code=REQUIRED_ADAPTER_DISABLED를 적었으나, 이 코드는error-codes.yamlL830 에서owner_branch: feature-migration-startup-contract+ categoryINTERNAL/500 +runbook://startup/required-adapter-disabled— startup-time (exit 72, "disabled required adapter 로 app 이 뜨면 실패") 시맨틱. - 권고: (1) runtime fail-fast 는 startup validation 과 lifecycle 이 다르므로 startup 코드 재사용은 의미 충돌 가능. (2) 선택지 — startup-only 로 유지하고 runtime 은 별도 코드 신규 제안(owner=본 branch) 하거나, migration-startup branch 와 합의해 코드 의미를 명시적으로 두 lifecycle 로 확장. 결정 전까지 D4 의 error code 는
미정.
- 발견: D4/Layer 3 는 runtime invoke-path 예외 로그에
검증해야 할 주장
공식 문서나 사례는 근거지만, 내 프로젝트에서의 동작을 자동으로 보장하지 않는다.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
@ConditionalOnProperty(havingValue="true", matchIfMissing=false) 가 ca-tmpl 의 "기본 disabled" 의도를 정확히 표현 |
SBAC-C3 가 "by default property must be present AND not equal to false" — env 가 누락된 경우 matchIfMissing=false 명시 의무 |
local 통합 테스트로 (1) env 누락 (2) false (3) true 3-case 에서 bean count 검증 |
planned |
3.5.0+ 에서 @ConditionalOnBooleanProperty 가 동등 시맨틱을 더 명시적으로 표현 |
SBAC-C4 가 since 3.5.0 — ca-tmpl baseline 의 Spring Boot 버전 확인 필요 |
gradle/libs.versions.toml 또는 build.gradle.kts 의 Spring Boot 버전 확인 후 적용 |
needs-confirmation |
| ArchUnit Layer 2 rule 이 disabled adapter 의 use case path import 를 실제로 catch | AUCP-C1 PREDICATE/CONDITION 모델로 가능하지만 - "when disabled" 조건은 runtime config 평가 — ArchUnit 의 정적 검사 한계 (AUCP-C5 Usage Boundaries) |
APP_ADAPTER_KAFKA_ENABLED=false 상태에서 violating PR 만들어 ArchUnit rule fail 확인 |
needs-confirmation |
Layer 3 AdapterDisabledException + log code REQUIRED_ADAPTER_DISABLED 가 actual runtime 에서 trigger |
D4 UNSUPPORTED_DECISION — ca-tmpl 자체 contract | adapter aspect + exception throw + log assertion 통합 테스트 | planned |
| disabled adapter 가 runtime path 에서 호출 시 fail-fast (timeout 대기 금지) | shutdown 정책 (raw/branch-notes/feature-outbound-http-client-baseline) 과 정합 — 적용 시점 확인 필요 | shutdown phase 통합 테스트 + thread state assertion | needs-confirmation |
| Kafka publish failure 에 correlationId 가 항상 부착 | Kafka adapter contract — correlationId propagation 메커니즘 자체 검증 필요 | Kafka producer interceptor + log assertion 통합 테스트 | planned |
Redis unavailable 시 cache-miss 로 graceful degrade (INTERNAL 으로 뭉개지지 않음) |
Redis adapter contract — fail-open/closed 정책 명시 필요 | Redis container down + cache read 통합 테스트 + 응답 200 OK + cache miss metric 확인 | planned |
| notification provider body/PII 가 log 에 등장하지 않음 | Slack/Google Email adapter — payload redaction policy 검증 필요 | grep 으로 payload pattern (@gmail.com 등) log 검출 contract test |
planned |
| feature flag (FF4J/Togglz) 와 adapter on/off 의 분리 영역 시각화 | D7 — runtime toggle vs startup toggle 의 운영 혼동 가능 | architecture decision record 작성 + 면접 시 답변 가능한 경계 명시 | planned |
- disabled adapter가 runtime path에서 호출되면 실패.
- Kafka publish failure에 correlationId가 없으면 실패.
- Redis unavailable이 degrade 가능 여부 없이 INTERNAL로 뭉개지면 실패.
- notification provider body/PII가 log에 남으면 실패.
- optional adapter가 core startup에 필수 dependency가 되면 실패.
관심사 커버리지 (coverage-auditor 2026-06-09)
governing doc: raw/project-notes/ca-skeleton-operational-contract (§11 Optional Adapters + §9 Env-driven + §25 SSOT Owner Map + Group G-I). 기준:
rules/coverage-gate.md. 판정: Covered (missing 0). 상태:covered-here(이 브랜치 결정) /delegated(다른 owner 브랜치) /missing(아무도 안 맡음 → Blocking).
| 관심사 | 상태 | owner | 심각도 | 근거 |
|---|---|---|---|---|
| 선택형 adapter disabled-default 정책 | covered-here | — | — | D1 (SBAC-C1/C2/C5) |
| Adapter enable/disable env 키 계약 (5개) | covered-here | — | — | D2 + §구현 가이드 §1 — env-keys.yaml landed (owner=본 branch) |
Layer 1 @ConditionalOnProperty bean 게이팅 |
covered-here | — | — | D2 + §구현 §2 (planned) |
| Layer 2 ArchUnit 정적 격리 | covered-here | — | — | D3 + §구현 §3 (AUCP-C1/C4/C5, planned) |
| Layer 3 runtime fail-fast | covered-here | — | — | D4 + §구현 §4 (planned, error code A2 미결) |
| fail-open/closed per adapter (Kafka/Redis) | covered-here | — | — | D9 + §구현 §5 (둘 다 fail-open) |
| fail-open/closed per adapter (Slack/Google Email) | covered-here | — | — | D9 + §구현 §5 — fail-open 기본 결정 완료 |
| common adapter logging/error contract | covered-here | — | — | §범위 In-scope + Adapter Template Defaults common row (MDC dependency key SSOT 는 log-management consume) |
| optional module vs sample 패키징 기준 | covered-here | — | — | D1 (optional module 기본, sample = 문서/fixture 전용) |
| required vs optional dependency 분류 SSOT | delegated | raw/branch-notes/feature-runtime-health-lifecycle-contract | OK | D9 — 분류 SSOT 위임. cross-link: raw/branch-notes/feature-runtime-health-lifecycle-contract |
REQUIRED_ADAPTER_DISABLED error code (startup lifecycle) |
delegated | raw/branch-notes/feature-migration-startup-contract | Should-fix | error-codes.yaml L830 owner. runtime 재사용 적정성은 §Audit A2 에서 미결 — raw/branch-notes/feature-migration-startup-contract 와 합의 필요 |
| Kafka retry/DLQ vocabulary | delegated | raw/branch-notes/feature-background-job-async-contract | OK | §구현 §5 위임 링크 존재 |
| Redis cache endpoint 키 (HOST/PORT) | delegated | raw/branch-notes/feature-cache-consistency-contract | OK | env-keys.yaml owner + §엣지 의존 링크 존재 |
마주친 문제
-
2026-06-09 B7 ArchUnit 충돌: optional adapter 의
@ConditionalOnProperty@Beanfactory method 가 port 타입(MessagePublisher/CacheStore/… —..adapter.outbound..거주)을 반환하자outbound_adapter_method_returns_only_domain_or_primitives(B7) 가 9건 위반. B7 은 adapter 응답 method 의 external type 누출을 막는 rule 이지 DI factory 가 자기 port 타입을 반환하는 것을 막는 rule 이 아님 → B7 을@Configuration클래스 제외로 scoping. 상세: raw/errors/archunit-b7-configuration-bean-factory-return-type-2026-06-09. -
2026-06-16 messaging broker-SPI 전환 후 주석 drift 정리: 메시징이
app.messaging.kafka.enabled+ 단일KafkaMessagePublisher구조에서app.messaging.broker=<brokerId>+MessageBrokerSPI(KafkaMessageBroker) + broker-agnostic 바인딩 데코레이터(OutboundMessagePublisherfail-open /OutboxMessagePublishAdapterfail-closed) + disabled sentinel 쌍(DisabledMessagePublisher/DisabledOutboxMessagePublisher)으로 리팩터된 뒤, JavaDoc/주석이 옛 구조를 가리키는 drift 6건을 정리(코드 동작 무변경, 주석 only). 수정:MessagePublisher(JavaDoc 를 adapter-local fail-open 으로 재서술 — "a use case holds this port" 삭제, use-case-facing durable 경로는 application-coreOutboxMessagePublishPort임을 명시),MessagingConfig·MessagingSettings(깨진{@link DisabledMessaging}→ 실제Disabled*쌍),kafka/KafkaSender(KafkaMessagePublisher→KafkaMessageBroker+ 바인딩 데코레이터), application-coreOutboxMessagePublishPort(adapter 클래스명 제거 → "general fail-open messaging publisher" 로 일반화),CleanArchitectureTest주석 예시(KafkaAdapterConfig#kafkaMessagePublisher→MessagingConfig#messagePublisher). 검증::adapter-outbound:compileJava :application-core:compileJava :app-bootstrap:compileTestJavaBUILD SUCCESSFUL.- NOTE_DRIFT: 본 노트의 env-key 표(
app.messaging.kafka.enabled/APP_MESSAGING_KAFKA_ENABLED, L163-164)와 D2 예시 ENV_KEY_DRIFT 항목(L130)은 broker-SPI 전환 전 네이밍이라 현재 코드(app.messaging.broker)와 어긋남 — broker-SPI 리팩터(사용자 작업, 본 세션에서 미캡처)가 정합시켜야 할 영역. 본 작업 범위는 코드 주석 only 이므로 노트 표는 자동 rewrite 하지 않음(§Audit & Findings 의 "자동 rewrite 하지 않고 정합 권고만" 정책과 동일).
- NOTE_DRIFT: 본 노트의 env-key 표(
-
2026-06-16
OutboundDependencyLogger→FailOpenDependencyLogger리네임 (책임-명확화 리팩터): 공통 의존성 로거의 이름이 "Outbound*" 라 HTTP 까지 포괄하는 공통 로거로 오독될 소지가 있었음. 실제로는 cache/messaging/notification fail-open optional adapter 전용(WARN, 관측-only)이고 HTTP 경로는 hard failure 를 ERROR 로 올리는 별도httpclient/OutboundHttpDependencyLogger임. 두 로거를 합치지 않는다는 판단은 유지(레벨·필드·error-code 정책 상이)하고 이름만 기존FailOpen*컨벤션(FailOpenCacheStore/FailOpenNotificationProvider)에 맞춰 변경. 범위: 타입 토큰 17개 .java +@Bean메서드outboundDependencyLogger()→failOpenDependencyLogger()(타입 주입이라 안전 — resource/Qualifier by-name 참조 0 확인) + 클래스 JavaDoc 도입부 fail-open 강조 +adapter-outbound/CLAUDE.mdL38 +LogMaskingPatternsJavaDoc 참조. 동작 무변경. 검증::adapter-outbound:test175/175 PASS,:app-bootstrap:compileJavaBUILD SUCCESSFUL.- 보류 (리뷰 권고/판단대로): ①
DependencyLogFields공통 상수/포매터 추출 — 두 로거의 필드셋·레벨 정책이 달라 효익 적고 리뷰도 "중복 조금이 정책 섞임보다 낫다"며 helper "정도만 고려" 권고 → 보류. ②OutboundHttpClient의 classify+outcome+log 흐름을OutboundHttpCallObserver/FailureHandler로 추출 — 리뷰가 "필수 아님, 과하게 쪼개면 처음 보는 사람이 더 힘듦" 명시 → 보류(스켈레톤 가독성·회귀 위험). ③TraceContextPropagationInterceptorFORK LANDMINE 주석 docs/runbook 이관 — 해당 경고는 "이 파일을 고쳐 실 tracer 를 붙이는 사람"이 직접 봐야 하는 load-bearing 안전 정보(sampled=00 강제 + 인터셉터가 OTel 계측보다 먼저 등록되어 race 를 이김)라 in-file 유지 권고, 이관 시 누락 위험 → 보류(사용자 확인 시 in-place 압축만 검토).
- 보류 (리뷰 권고/판단대로): ①
-
2026-06-16 cache 패키지
core/분리 (하이브리드) + 문서 drift 정리: cache 가 한 폴더에 SPI/router/settings/config/fail-open/exception 다 모여 있어, messaging/notificationcore/컨벤션과 맞춰 공통 계약·정책만 분리. 이동(전부 public → 가시성 변경 0, encapsulation-neutral, httpclient resilience/diagnostics 와 동일 패턴):CacheStore·CacheBackend·CacheBackendException·CacheStoreRouter·FailOpenCacheStore→cache/core/;CacheRouterConfig·CacheBindingSettings는 root 유지;cache/redis/불변. import: redis 파일들의 기존cache.*import 를cache.core.*로 path 정정, rootCacheRouterConfig엔 신규 추가, 외부 테스트 3개(OptionalAdapterBeanGatingTest/DisabledAdapterSentinelTest/RedisCacheStoreTest)도 path 정정.adapter-outbound/CLAUDE.mdcache 경로 갱신(CacheRouterConfig 만 root 유지).- 문서 drift 2건 동시 정리:
redis/RedisClient주석("RedisCacheStore 가 fail-open 적용" → 실제는 중앙FailOpenCacheStore데코레이터가CacheBackendException을 cache-miss 로 downgrade);core/CacheStore메서드 javadoc("for the Redis binding" → 모든 backend, 중앙 데코레이터);application.ymloptional-adapter 주석("disabled → fail-fast sentinel" 일반화가 cache/notification 엔 부정확 → messaging=Disabled* sentinel bean, cache/notification=router(CacheStoreRouter/RoutingNotifier) unbound fail-fast 로 구분 명시). - 검증: cache 스코프 테스트 32/32,
CleanArchitectureTest49/49 PASS, 모듈 컴파일 0 에러. 가드레일 무영향(..adapter.outbound..재귀 패턴이cache.core자동 커버).
- 문서 drift 2건 동시 정리:
묶음
- raw/company-tech-blogs/adapter-togglz-ff4j-feature-toggle-library
- raw/official-docs/adapter-java-spi-serviceloader
- raw/official-docs/adapter-spring-boot-autoconfig-custom-starter
- raw/official-docs/archunit-conditional-on-property-3-layer-pattern
- raw/official-docs/outbound-openfeign-declarative-client
- raw/official-docs/outbound-spring-restclient-baseline
본 feature branch 는 더 이상 leaf 아님 — 2026-06-09 실 구현으로 errors / interview / blog-topic 파생 자료 누적.
오류 기록 (본 feature 작업 중 발생)
- raw/errors/archunit-b7-configuration-bean-factory-return-type-2026-06-09 — B7 이
@Configuration@Beanfactory 의 port-타입 반환을 오탐, rule scoping 으로 해소.
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- raw/interviews/optional-adapter-3-layer-disabled-detection-2026-06-09 — startup(
@ConditionalOnPropertybean-gating) / build(ArchUnit 정적 격리 +@Beangating) / runtime(AdapterDisabledExceptionfail-fast) 3계층 disabled-adapter 검출과 각 계층의 보장·한계, fail-open vs fail-closed, runtime 전용 error code 신설(A2) 근거.
Blog topics (이 작업에서 나올 수 있는 글감)
- raw/blog-topics/spring-conditional-on-property-optional-adapter-template-2026-06-09 — heavy SDK 없이
@ConditionalOnProperty+ integration seam + disabled sentinel 로 "선택형 어댑터 템플릿" 을 경량 skeleton 에 싣는 패턴.
관련 일일 노트
이 브랜치를 작업한 날짜들. 양방향 nav 유지.
- 2026-06-09 — Layer 1/2/3 + per-adapter fail-open 계약 실 구현 및 locally-verified.
- 2026-06-16 — messaging broker-SPI 전환 후속 코드 주석 drift 6건 정리(동작 무변경). 위 §마주친 문제 2026-06-16 참조.
완료 후 정리
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경:
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목: 3-layer disabled-adapter 검출(Layer1@ConditionalOnPropertybean-gating / Layer2DisabledAdapterArchitectureTest격리+gating / Layer3AdapterDisabledException); per-adapter fail-open 계약(Kafka publish→correlationId+outbox 위임, Redis unavailable→cache-miss, Slack/Email→관측+무PII); commonOutboundDependencyLogger; A2 runtime 전용ADAPTER_DISABLEDerror code.locally-verified항목: 위 전부 —:shared-contract:test/:adapter-outbound:test/:adapter-web:test/:app-bootstrap:test+verifyCleanArchitectureDependencies/verifyEnvKeys/verifyPublicPathSnapshotPASS, ca-architect-sentinel PASS.prod-verified항목: (없음 — 미배포)
- 추출하지 않을 항목 (planned / documented-only / abandoned): 실제 broker/cache/provider 운영 연동(integration seam 구현은 fork 프로젝트 몫 — OUT_OF_BRANCH_SCOPE); primary-outcome notification 의 fail-closed override(도메인 branch 몫).