31 KiB
title, source_type, status, branch, parent_branch, related_projects, tags, created, last_reviewed, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256
| title | source_type | status | branch | parent_branch | related_projects | tags | created | last_reviewed | target_merge | status_label | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | contract_packet_sha256 | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-streaming-response-contract | branch-note | verified | feature-streaming-response-contract |
|
|
2026-05-31 | 2026-06-04 | in-progress | BR-CA-SKELETON-OPERATIONAL-CONTRACT-045 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-045 |
|
1 | 3a771345ceaa06edbf916bcf7a52f6ade375f5415fe2ed9b6e0b914fab6c41a4 |
branch: feature-streaming-response-contract
Layer:
raw/branch-notes/— Server-Sent Events (SSE) / WebSocket / long-polling / chunked streaming 응답의 지원 여부 + 도입 시 계약을 정의합니다. 완료 후/ingest로wiki/projects/에만 추출합니다.status_label:in-progress|review|merged|abandoned
🟢🟡 D3 구현 완료 — 2026-06-02. 본 branch 는 두 갈래로 분리됩니다 (결정 §결정 사항):
- 🟢 IN-SCOPE (구현 완료): ca-skeleton 은 이벤트/server-push 스트리밍(SSE·WebSocket)을 미지원으로 확정 (D1) 하고, 이를 ArchUnit rule 로 정적 차단 (D3).
SseEmitter/ResponseBodyEmitter/ WebSocket 계열 import 차단. —locally-verified(2026-06-02).- 🟡 DEFERRED (보류): 이벤트 스트리밍을 지원하기로 했을 때 의 매커니즘·envelope·per-event span 계약 (D2). 재개 트리거 = 실제 server→client push use case (실시간 알림 / LLM token streaming) 또는 통신/전송 프로토콜 계약 branch 착수. 6개 근거 raw 는 §Sources 에 보존.
용어 주의 (핵심): 본 branch 의 "streaming response" = 이벤트/server-push 스트리밍 (통신 모델이 request-response → server-push 로 바뀌는 것).
StreamingResponseBody(대용량 파일 다운로드용 응답 body 청크 전송 — 통신 모델은 여전히 request-response) 는 별개 관심사이며 본 branch 범위 밖 — raw/branch-notes/feature-file-resource-handling-contract D8 이 소유. 차단 대상 아님 (R3 OUT_OF_BRANCH_SCOPE).
부모 (필수)
이 branch 가 어느 작업 묶음에 속하는지. 본 branch 는 project-note 의 직접 자식 (
parent_branch:비어 있음).
- Parent project (canonical SSOT): raw/project-notes/ca-skeleton-operational-contract
ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 §3 Structured API Response Contract (envelope 정책) + §13 API Contract Surface + §8 Distributed Tracing Contract 의 운영 계약 중 streaming response 영역을 정제한다.
형제 branch (cross-cite 후보)
- raw/branch-notes/feature-api-contract-baseline — 동기 request-response 표면 SSOT. streaming 은 그 예외 표면 — envelope 정책 우회 여부 결정 필요.
- raw/branch-notes/feature-webhook-outbound-contract — async server-push 의 대안 매커니즘. 결정 시점에 webhook vs streaming trade-off 비교.
- raw/branch-notes/feature-operational-error-observability-foundation — envelope
success/error정책. streaming 은 한 connection 안에 multiple event 가 흐르므로 envelope shape 적용 모호. - raw/branch-notes/feature-distributed-tracing-contract — streaming connection 의 trace context 전파 (HTTP request 단위 traceId 가 multiple event 에 어떻게 적용?).
- raw/branch-notes/feature-rate-limit-idempotency-contract — streaming connection 의 rate limit 정책 (connection-per-user limit?).
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: 지원 protocol과 timeout·failure contract test가 고정된다
상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-API-VERSIONING-001@1 |
URI prefix /v1이 default이며 X-Api-Version은 compatibility 실험용 보조 header다 | Work Item 완료 조건에 적용 | raw/project-notes/ca-skeleton-operational-contract |
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-RESILIENCE-001@1 |
Resilience4j가 default이며 Spring Retry는 simple blocking retry에만 허용한다 | Work Item 완료 조건에 적용 | raw/project-notes/ca-skeleton-operational-contract |
브랜치 지역 결정
기존 branch-local 결정은 아래
## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|
선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|
목표
ca-skeleton 의 현재 default 는 request-response 동기 응답 만 지원합니다. SSE / WebSocket / long-polling / chunked streaming 은 envelope 정책 적용 모호 + 운영 부담 (connection 수, timeout, load balancer 설정 차이) 이 큰 영역.
본 branch 의 1차 결정: ca-skeleton 이 streaming 을 지원 할지 명시적으로 미지원 할지.
만약 지원 결정이면:
- 어떤 streaming 매커니즘 (SSE / WebSocket / long-poll / chunked-transfer-encoding)
- event envelope shape (envelope.success/error 적용 여부, event header)
- trace context 전파 (한 connection 에 multiple event 의 traceId 정책)
- timeout / heartbeat / reconnect 정책
- observability (connection metric / event throughput / error rate)
- load balancer / reverse proxy 설정 의무 (sticky session? keep-alive 시간?)
만약 미지원 결정이면:
- 정확한 out of scope 라벨링
- 대안 매커니즘 안내 (webhook outbound, polling endpoint)
- controller 에서 streaming API 사용 금지 ArchUnit rule (
StreamingResponseBody,SseEmitter,@WebSocket등 import 차단)
- 이슈:
- PR:
범위
포함 범위 (🟢 결정 완료 — 착수 가능)
- 이벤트/server-push 스트리밍 지원 여부 결정 → D1: 미지원 확정
- 미지원의 ArchUnit 정적 강제 → D3:
no_sse_emitter/no_response_body_emitter/no_websocket_handler(§구현 가이드)
Deferred (🟡 D2 — 지원 시 계약, 보류)
- 지원 결정 시 매커니즘 선택 (SSE / WebSocket / long-poll / chunked) — 재개 시 SSE 우선 (D2)
- 지원 결정 시 event envelope shape + per-event trace context 전파 (tracing branch 와 OPEN)
- 지원 결정 시 timeout / heartbeat / reconnect / connection cap
- 지원 결정 시 observability metric + reverse proxy 설정 가이드
제외 범위
- GraphQL subscription — 별도 query layer (ca-skeleton 은 REST default)
- gRPC streaming — ca-skeleton 은 HTTP/REST default, gRPC 도입은 완전 별도 branch
- WebRTC — 미디어 streaming 은 ca-skeleton 범위 밖
- async webhook 발송 — raw/branch-notes/feature-webhook-outbound-contract SSOT
- async polling endpoint (LRO) — raw/branch-notes/feature-api-contract-baseline D17 SSOT
근거 (필수, 최소 1개+)
본 branch 의 결정 근거 (D1/D3 in-scope + D2 보류분).
2026-06-02:
wiki-decision-researcher가 streaming 매커니즘 비교를 위해 아래 6개 raw 를 아카이빙 완료 (각 verbatim quote + self-grep 검증 + 본 branch 로 Parent upward link). D2(지원 계약) 재개 시 재조사 없이 재사용.
| Source | 정당화할 결정 영역 | Claim ID | 상태 |
|---|---|---|---|
| raw/official-docs/whatwg-html-server-sent-events | WHATWG HTML SSE spec (EventSource, retry, last-event-id, text/event-stream wire format) | WHATWG-SSE-C1~C6 |
✅ 아카이빙 (official-standard) |
| raw/official-docs/rfc6455-websocket | IETF RFC 6455 WebSocket protocol (full-duplex, HTTP Upgrade handshake, masking) | RFC6455-C1~C6 |
✅ 아카이빙 (official-standard) |
| raw/official-docs/spring-mvc-async-streaming | Spring MVC SseEmitter / ResponseBodyEmitter / StreamingResponseBody vendor doc |
SPRING-ASYNC-C1~C7 |
✅ 아카이빙 (official-vendor-doc) |
| raw/official-docs/rfc9112-http-1-1-chunked-transfer | HTTP/1.1 chunked transfer encoding (§7.1 framing, HTTP/2 금지 경계) | RFC9112-CHUNK-C1~C5 |
✅ 아카이빙 (official-standard) |
| raw/official-docs/rfc9110-http-semantics (기존) | HTTP semantics — 단 C1~C22 는 모두 다른 branch 귀속, streaming connection 의미론 claim 없음 (재개 시 §7.1 chunked 로 대체) | — | 기존 raw (streaming traceability 단절) |
| raw/company-tech-blogs/sse-realtime-notification-woowahan | 한국 사례 — SSE 운영 부담 (thundering herd, Pub/Sub fan-out, 4천만/일) | WOOWA-SSE-C1~C6 |
✅ 아카이빙 (company-case-study — 공식 best practice 아님) |
| raw/company-tech-blogs/realtime-service-experience-woowahan-websocket | 한국 사례 — WebSocket 운영 문제 (이벤트 유실, 모바일 네트워크, 클러스터링) | WOOWA-WS-C1~C5 |
✅ 아카이빙 (company-case-study — 공식 best practice 아님) |
미발견: kakao 공식 기술블로그 SSE/WebSocket 실운영 글 (JS-rendered 페이지 본문 추출 실패). 재개 시 접근 가능하면 보강.
TODO
각 항목 옆에 증거 등급 표기: actually-implemented | locally-verified | prod-verified | documented-only | planned | needs-confirmation
- (P0) ca-skeleton 의 이벤트/server-push 스트리밍 지원 여부 결정 → D1: 미지원 확정 (2026-06-02)
- (D3) ArchUnit rule 구현:
no_sse_emitter,no_response_body_emitter,no_websocket_handler—CleanArchitectureTest에 등록. violations-as-data fixtures (streaming/), over-block guard (allowed/streaming/), testCompileOnly 3종 추가. ArchUnit 33 rules, FixtureTest 24 tests — 모두 GREEN. — 등급:locally-verified(2026-06-02, 커밋 미확정 — 사용자가 직접 커밋 예정)- 품질 리뷰 후속 (2026-06-02): WebSocket fixture 테스트가 공유
VIOLATION_CLASSES풀에서 평가되어 spring/jakarta 두 glob 중 하나만 동작해도 vacuous-pass 가능하던 갭 → spring·jakarta fixture 를 각각ClassFileImporter.importClasses(...)격리 corpus(SPRING_WEBSOCKET_FIXTURE_ONLY/JAKARTA_WEBSOCKET_FIXTURE_ONLY)로 평가하도록 수정. 각 glob 독립 검증. annotation-only fixture 라 격리 import 시 link-time 클래스 로딩 안전(NoClassDefFoundError 없음, 24 tests GREEN 재확인).
- 품질 리뷰 후속 (2026-06-02): WebSocket fixture 테스트가 공유
지원 결정 시 매커니즘 trade-off→ D2 보류. 재개 시 §Sources 6개 raw 로 비교 (SSE 우선). — 등급:planned- 지원 결정 시 event envelope shape (envelope
{success, data, meta}적용? 별도 SSE event formatevent:...\ndata:...\n\n?) — 등급:planned - 지원 결정 시 trace context 전파 (W3C
traceparent가 한 connection 의 multiple event 에 어떻게 적용? per-event 새 span?) — 등급:planned - 지원 결정 시 timeout / heartbeat / reconnect (Last-Event-Id 활용 / connection idle timeout / heartbeat ping 간격) — 등급:
planned - 지원 결정 시 reverse proxy 설정 의무 (Nginx
proxy_buffering off,proxy_read_timeout, keep-alive) — 등급:planned - 지원 결정 시 connection 수 cap (per-user / per-IP / per-tenant) — DoS 방어 — 등급:
planned
진행 중 메모
- 본 branch 의 1차 결정 은 사실상 "ca-skeleton minimalist 정신" vs "도입 필요성" 의 trade-off. 현재 sample-portfolio fixture 가 streaming 시나리오 없으므로 미지원 default + ArchUnit 차단 이 가장 자연스러운 default 일 가능성 — 단 실제 사용자 도메인이 추가될 때 도입 가능성 열어둠.
- 미지원 결정의 핵심 cost: streaming 이 필요한 use case (real-time notification, large file streaming, server-push) 가 등장하면 webhook outbound 또는 polling 으로 우회 — raw/branch-notes/feature-webhook-outbound-contract 가 webhook 대안 SSOT.
결정 사항
-
D1 (2026-06-02): 이벤트/server-push 스트리밍 미지원 확정. ca-skeleton 은 SSE / WebSocket 등 server-push 이벤트 스트리밍을 default 로 지원하지 않는다. (통신 모델을 request-response → server-push 로 바꾸는 영역)
- 사유 ① streaming-response 는 독립 결정이 아니라 통신/전송 프로토콜 계약(HTTP vs gRPC vs streaming)의 한 facet. 전송 프로토콜은 skeleton 에서 가장 마지막에 고정 해야 할 영역 (가장 덜 보편적, 모든 파생 프로젝트의 기본기로 박을 근거 약함).
- 사유 ② 현 sample-portfolio fixture 에 server-push use case 부재 (YAGNI / speculative generality 회피).
- 사유 ③ sibling raw/branch-notes/feature-api-contract-baseline 가 이미 verified scope 에 "ca-skeleton 은 request-response 만 지원, SSE/WS 도입은 별도 branch" 선언 — 일관성.
- 범위 명확화 (R3): 미지원 대상은 이벤트 스트리밍(server-push) 이지,
StreamingResponseBody기반 대용량 다운로드(응답 body 청크 전송) 가 아니다. 후자는 request-response 모델 내 다운로드 최적화이며 raw/branch-notes/feature-file-resource-handling-contract D8 이 소유. 본 branch 차단 대상에서 제외.
-
D3 (2026-06-02): 이벤트 스트리밍 미지원을 ArchUnit rule 로 정적 강제 (IN-SCOPE, 착수 가능). D1 을 코드 단계에서 강제 — controller/adapter 가 이벤트 스트리밍 API 를 import 하면 build 실패.
- rule:
no_sse_emitter,no_response_body_emitter,no_websocket_handler(상세 명세는 §구현 가이드). - 메커니즘 선례: raw/branch-notes/feature-boundary-validation-mapping-contract D5 (
no_problem_detail_usage= import-level 차단), 동 branch 가 ArchUnit suite host, archunit-junit5 1.3.0 (project §34 Stack Commitment). - 근거:
SPRING-ASYNC-C4(SseEmitter=ResponseBodyEmittersubclass, W3C SSE 포맷),SPRING-ASYNC-C3(ResponseBodyEmitter= 객체 stream emit) — 차단 대상 클래스의 vendor 정의.
- rule:
-
D2 (2026-06-02): 이벤트 스트리밍 지원 계약 = 보류 (Deferred). 만약 지원하기로 하면 필요한 매커니즘 선택(SSE vs WebSocket) / event envelope shape / per-event trace span / timeout·heartbeat·reconnect / connection cap / reverse proxy 의무 — 모두 보류.
- 재개 트리거: (a) 실제 server→client push use case 등장 (실시간 알림 / LLM token streaming / 대용량 export 진행률), 또는 (b) 통신/전송 프로토콜 계약 branch 착수 (예정 — 생성 시 본 branch D2 를 forward-ref). 재개 시 §Sources 6개 raw 로 되묻지 않고 진행 가능.
- 재개 시 우선 매커니즘: SSE (
SseEmitter) — 단방향 push 에 적합, 기존 HTTP 인프라 재사용, WebSocket 대비 proxy 부담 낮음. 근거:WHATWG-SSE-C1~C3(official-standard) +SPRING-ASYNC-C4(official-vendor-doc). 단 재개 시 D3 ArchUnit rule 의 SSE 차단을 명시적으로 해제해야 함.
결정-근거 매핑
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|
| D1 | 이벤트/server-push 스트리밍(SSE·WebSocket) 미지원 확정 | SPRING-ASYNC-C4 (SseEmitter=W3C SSE), WHATWG-SSE-C5 (단방향 push), RFC6455-C1 (full-duplex) — 무엇을 미지원하는지 의 클래스 정의 + raw/branch-notes/feature-api-contract-baseline scope 선언 (request-response only) = UNSUPPORTED_DECISION (project-internal consistency 논거 — api-baseline Out of scope 선언과의 정합, 보조 근거) |
official-standard + official-vendor-doc + cross-branch-consistency |
실제 push use case 등장 시 D2 로 재개. StreamingResponseBody(다운로드)와 혼동 금지 — 별 관심사 |
| D3 | D1 을 ArchUnit import-ban 으로 정적 강제 (IN-SCOPE): no_sse_emitter / no_response_body_emitter / no_websocket_handler |
SPRING-ASYNC-C4 (SseEmitter FQN + 역할), SPRING-ASYNC-C3 (ResponseBodyEmitter = 객체 stream emit) + 선례 raw/branch-notes/feature-boundary-validation-mapping-contract D5 (import-level 차단 메커니즘) + project-ssot (raw/project-notes/ca-skeleton-operational-contract §34 Stack Commitment — archunit-junit5 1.3.0 lock) |
official-vendor-doc (차단 대상 클래스) + cross-branch-SSOT (ArchUnit suite host = boundary branch) + project-ssot |
rule 명명 + 차단 메커니즘(import vs reference) = UNSUPPORTED_IMPL_DECISION (§구현 가이드). WebFlux 타입은 stack(MVC) 밖이라 미포함 |
| D2 | 이벤트 스트리밍 지원 계약(매커니즘/envelope/per-event span/timeout/cap/proxy) = 보류 (Deferred) | WHATWG-SSE-C1~C6, RFC6455-C1~C6, RFC9112-CHUNK-C1~C5, SPRING-ASYNC-C1~C7, WOOWA-SSE-C1~C6, WOOWA-WS-C1~C5 (재개 시 재조사 불필요하게 보존) |
official-standard + official-vendor-doc + company-case-study |
재개 트리거 전까지 dormant. 아래 OPEN 갭(per-event span) 동반 |
교차 계약 의존 정리:
- (D3 의존, 활성) ArchUnit suite host = raw/branch-notes/feature-boundary-validation-mapping-contract (D5 import-ban 선례). 본 branch 의 3개 rule 은 그 suite 에 등록. archunit-junit5 1.3.0 (project §34). suite 구조가 바뀌면 본 branch rule 영향.
- (폴링 대안, 닫힘) server-push 미지원 시 비동기 응답 우회 = raw/branch-notes/feature-api-contract-baseline D17 (verified LRO: 202 +
Location+ polling endpoint +Retry-After). webhook 우회는 raw/branch-notes/feature-webhook-outbound-contract 가 미결정(forward-ref) + 동 branch 가 SSE 대체를 본 branch 로 역참조 → 상호 보류.- (D3 경계, OUT_OF_BRANCH_SCOPE)
StreamingResponseBody는 raw/branch-notes/feature-file-resource-handling-contract D8 (verified, 다운로드 메커니즘) 소유 → 차단 안 함. blanket ban 시 파일 다운로드 build 실패하므로 명시적 제외.- (D2 동반 OPEN, 미해결) per-event trace span: raw/branch-notes/feature-distributed-tracing-contract D5/D7 은 traceparent 를 request 단위 로 전파하나 propagation surface(outbound HTTP/Kafka/Rabbit/scheduler)에 long-lived streaming connection 없음. "한 connection 의 N개 event 에 traceId 를 어떻게" 는 기존 Decision ID 로 닫히지 않는 진짜 OPEN 갭 — D2 재개 시 tracing branch 와 동시 결정 필요.
구현 가이드
본 §는 D3 (이벤트 스트리밍 미지원의 ArchUnit 정적 강제) 만 명세한다. D2 (지원 계약) 는 보류이므로 구현 명세 없음 — 재개 시 작성.
1. 이벤트 스트리밍 차단 ArchUnit rules (D3)
Trace: D1 (이벤트/server-push 스트리밍 미지원) → D3 (ArchUnit 정적 강제). 차단 대상 클래스 정의 =
SPRING-ASYNC-C4(SseEmitter=ResponseBodyEmittersubclass, W3C SSE 포맷),SPRING-ASYNC-C3(ResponseBodyEmitter= 객체 stream emit). 메커니즘 선례 = raw/branch-notes/feature-boundary-validation-mapping-contract D5 (no_problem_detail_usageimport-level 차단). suite host = boundary branch ArchUnit suite, archunit-junit5 1.3.0 (project §34).
- UNSUPPORTED_IMPL_DECISION:
- ① rule 이름
no_sse_emitter/no_response_body_emitter/no_websocket_handler— 임의 명명 (boundary D5 명명 컨벤션 차용, 근거 raw 가 이름 권고 안 함).- ② import-level
dependOnClassesThat()vs reference-level 차단 의 메커니즘 선택 — boundary D5 와 동일 trade-off (false positive 회피 vs 회귀 차단). import-level 채택은 사용자 결정.- ③ WebSocket 차단 FQN 범위 — RFC 6455 (
RFC6455-C*) 는 프로토콜 만 다루고 Spring/Jakarta API 클래스를 열거하지 않음. 아래 FQN 목록(spring-websocket 패키지 + STOMP + Jakarta) 은 사용자가 선정한 차단 표면.- ④
ResponseBodyEmitter포함 여부 — D1 은 "이벤트 스트리밍" 미지원.ResponseBodyEmitter는 SSE 의 base 이자 incremental 객체-emit 메커니즘(SPRING-ASYNC-C3)이므로 차단에 포함. 단 비-SSE JSON object-stream 용도까지 막는 것은 사용자 판단 (server-push 성격으로 간주).- ⑤ 차단 scope = production code only (
ImportOption.DoNotIncludeTests) — 테스트에서 차단 위반 재현용 fixture 작성 가능하도록.
| rule 이름 | 차단 대상 FQN | 메커니즘 | 비고 |
|---|---|---|---|
no_sse_emitter |
org.springframework.web.servlet.mvc.method.annotation.SseEmitter |
noClasses().should().dependOnClassesThat().haveFullyQualifiedName(...) |
SSE server-push 의 직접 표면 (SPRING-ASYNC-C4) |
no_response_body_emitter |
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter |
동일 | SSE 의 base + incremental 객체 emit (SPRING-ASYNC-C3). SseEmitter 가 이를 상속하므로 함께 차단. 비-SSE JSON object-stream 용도까지 server-push 모델로 간주해 차단 (UNSUPPORTED_IMPL_DECISION④) |
no_websocket_handler |
org.springframework.web.socket.. (패키지 전체) + jakarta.websocket.. (패키지 전체) |
dependOnClassesThat().resideInAnyPackage(...) |
spring-websocket(WebSocketHandler, @EnableWebSocket, @EnableWebSocketMessageBroker/STOMP) + Jakarta @ServerEndpoint 계열 일괄 차단 (RFC6455-C1 full-duplex 모델). WebSocket 표면은 단일 FQN 이 없어 haveFullyQualifiedName 대신 resideInAnyPackage glob 불가피 (UNSUPPORTED_IMPL_DECISION③) |
명시적 비-차단 (R3 OUT_OF_BRANCH_SCOPE):
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody— 차단하지 않음. 대용량 다운로드용 응답 body 청크 전송 (SPRING-ASYNC-C2: "for example, for a file download"), 통신 모델은 request-response 유지. raw/branch-notes/feature-file-resource-handling-contract D8 (verified) 소유. blanket ban 시 다운로드 build 실패.- WebFlux reactive 타입(
Flux<ServerSentEvent>등) — ca-skeleton stack 은 spring-webmvc(SPRING-ASYNC-C1/C5의 servlet 전제)이므로 해당 클래스가 classpath 에 없음 → rule 불필요 (재개 시 WebFlux 전환하면 별도 검토).
2. 미지원 시 대안 경로 (문서화만, 코드 없음)
Trace: D1 미지원 결정의 운영 cost 흡수 경로. 코드는 본 branch 가 작성하지 않음 — 기존 형제 branch 결정 재사용.
- 비동기 server→client 결과 전달이 필요하면: raw/branch-notes/feature-api-contract-baseline D17 LRO 패턴 (202 +
Location+GET /operations/{id}polling +Retry-After) 사용. - 외부 시스템 push 가 필요하면: raw/branch-notes/feature-webhook-outbound-contract (미결정, forward-ref).
엣지·실패·의존
R4 캡처. D3 (ArchUnit 차단) 구현 중 부딪힐 실패/엣지/계약 의존. D2 (지원 계약) 는 보류이므로 그쪽 엣지(connection 끊김/reconnect/cap 초과)는 재개 시 작성 — 여기서는 "OPEN" 으로만 표시.
- 실패·엣지 경로 (D3 차단 rule):
- false positive —
StreamingResponseBody오차단: rule glob 이 너무 넓으면 file-resource D8 다운로드가 깨짐. 기대 동작 =StreamingResponseBody는 rule 대상 FQN 목록에 포함하지 않음 (§구현 가이드 명시적 비-차단). 차단되면 안 됨. - transitive dependency 차단:
dependOnClassesThat()는 transitive import 도 잡으므로, 제3 라이브러리가 내부적으로SseEmitter를 참조하면 false positive 가능. 기대 동작 = production source 의 직접 의존만 위반으로 본다 (필요 시ImportOption으로 vendor 패키지 제외 — UNSUPPORTED_IMPL_DECISION). 해소 선례: raw/branch-notes/feature-boundary-validation-mapping-contract §구현 가이드 §8no_problem_detail_usage의 import-level 차단 trade-off 참조. - WebSocket 패키지 glob 과다:
org.springframework.web.socket..전체 차단이 의도치 않은 하위 유틸까지 막을 수 있음. 기대 동작 = WebSocket 미사용이 default 이므로 전체 차단이 안전, 도입 시 rule 해제. - 테스트 코드: 차단 위반 재현 fixture 는 test 에 있어야 하므로 rule scope =
DoNotIncludeTests(production만).
- false positive —
- 다른 계약 의존:
- raw/branch-notes/feature-boundary-validation-mapping-contract D5 / ArchUnit suite host 에 의존 — 본 branch 3개 rule 을 그 suite 에 등록. suite 구조·archunit-junit5 버전(project §34, 1.3.0)이 바뀌면 본 branch 영향.
- raw/branch-notes/feature-api-contract-baseline D17 consume (미지원 시 비동기 우회 = LRO polling). D17 계약이 바뀌면 본 branch §구현 가이드 §2 대안 경로 갱신 필요.
- raw/branch-notes/feature-file-resource-handling-contract D8 과 경계 공유 —
StreamingResponseBody소유권. D8 이 다른 streaming 클래스를 쓰기 시작하면 본 branch 비-차단 목록 재검토.
- D2 보류분 OPEN 의존 (재개 시): raw/branch-notes/feature-distributed-tracing-contract D5/D7 — long-lived connection 의 per-event trace span 정책 미존재. 재개 시 동시 결정 필요.
검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
no_sse_emitter / no_response_body_emitter 가 production 코드의 SseEmitter·ResponseBodyEmitter import 를 build 단계에서 실제 차단 |
ArchUnit rule 작성 후 실측 전까지 동작 미보장 | violations-as-data fixtures (SseEmitterUsingFixture, ResponseBodyEmitterUsingFixture) → FixtureTest GREEN |
locally-verified (2026-06-02) |
no_websocket_handler 의 org.springframework.web.socket.. + jakarta.websocket.. glob 이 정확히 WebSocket 표면만 차단 (over-block 없음) |
패키지 glob 범위가 vendor 패키지 구조에 의존 | SpringWebSocketHandlerFixture (@EnableWebSocket), JakartaWebSocketEndpointFixture (@ServerEndpoint) fixtures → FixtureTest GREEN |
locally-verified (2026-06-02) |
StreamingResponseBody 가 rule 에 걸리지 않아 file-resource D8 다운로드가 정상 build |
blanket ban 시 false positive 위험 (핵심 회귀) | StreamingResponseBodyAllowedFixture over-block guard → 3개 D3 rule 모두 hasViolation() == false — GREEN |
locally-verified (2026-06-02) |
| ArchUnit suite host(boundary branch) 에 3개 rule 등록이 기존 suite 와 충돌 없음 | suite 구조·rule 명명 충돌 가능 | CleanArchitectureTest 33 tests (기존 30 + D3 3) — 0 failures |
locally-verified (2026-06-02) |
마주친 문제
- testCompileOnly + class loading:
SpringWebSocketHandlerFixture최초 버전은TextWebSocketHandler를 extends — JUnit 이 fixture 클래스를 로드할 때NoClassDefFoundError발생 (testCompileOnlyjar 는 runtime classpath 에 없으므로). 해결:@EnableWebSocketannotation 참조만으로 교체. Annotation 은 JVM 에서 lazy access (class load 시 필요 없음) → ArchUnit bytecode 분석은 정상 동작. 패턴: annotation-only reference 는testCompileOnlyfixture 에서 runtime-safe. - jakarta.websocket-api 2.1.1 는 server-only:
jakarta.websocket-api2.1.1 jar 는jakarta.websocket.server.*만 포함 (Session,OnMessage등 base 패키지 없음).jakarta.websocket-all또는 client jar 가 필요.@ServerEndpoint(server 패키지) 만으로 fixture 재작성.
묶음 (이 branch에서 파생된 자료)
- raw/company-tech-blogs/realtime-service-experience-woowahan-websocket
- raw/company-tech-blogs/sse-realtime-notification-woowahan
- raw/official-docs/rfc6455-websocket
- raw/official-docs/rfc9112-http-1-1-chunked-transfer
- raw/official-docs/spring-mvc-async-streaming
- raw/official-docs/spring-streaming-response-body
- raw/official-docs/whatwg-html-server-sent-events
- raw/blog-topics/archunit-testcompileonly-fixture-annotation-pattern-2026-06-02
- raw/blog-topics/streaming-response-not-supported-archunit-ban-2026-07-02
Sub-branches (세부 작업)
- (없음)
오류 기록 (이 branch 작업 중 발생)
- raw/errors/archunit-testcompileonly-class-loading-2026-06-02 —
testCompileOnly로 선언된 타입을 extends 하는 fixture 가 JUnit 실행 시NoClassDefFoundError를 유발하는 문제 + 해결 패턴
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- raw/interviews/archunit-violations-as-data-pattern-2026-06-02 참조 (ArchUnit violations-as-data 패턴, testCompileOnly fixture 설계)
강의 (이 작업을 위해 학습한 강의)
- (없음)
job-posting tie-ins (이 작업에서 파생된 글감)
- raw/blog-topics/archunit-testcompileonly-fixture-annotation-pattern-2026-06-02 — testCompileOnly 타입을 ArchUnit fixture 에서 안전하게 참조하는 annotation-only 패턴
관련 일일 노트
- (없음 — scaffolding 단계)
완료 후 정리
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경:
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목: D3 ArchUnit 차단 rule 3개(no_sse_emitter/no_response_body_emitter/no_websocket_handler) + violations-as-data fixtures + over-block guard +StreamingResponseBody비-차단 경계 → wiki/projects/ca-tmpl/streaming-response-supportlocally-verified항목:CleanArchitectureTest+ArchitectureViolationFixtureTestGREEN (rule catch + over-block guard + spring/jakarta 격리 corpus) → 동 문서 §로컬/dev 검증prod-verified항목: 없음 (스트리밍 미지원이므로 운영 streaming 지표 부재)
- 추출하지 않을 항목 (planned / documented-only / abandoned): D2 스트리밍 지원 계약 전체(매커니즘/envelope/per-event span/timeout/cap/proxy) =
planned보류. 일반 개념(SSE/WebSocket/long-poll/chunked trade-off)은 canonical wiki/concepts/streaming-response-patterns 로 분리.
/ingest 처리 (2026-06-04, ca-tmpl @9693d72 ground-truth 대조): 본 branch 의 D3(미지원 ArchUnit 강제)를
wiki/projects/ca-tmpl/streaming-response-support.md로 추출(CREATE), 일반 개념을wiki/concepts/streaming-response-patterns.md로 추출(CREATE). production 코드에 streaming import 0건 확인 — 미지원(ban)이 실제. honest framing: 구현된 것은 차단 가드레일 이지 스트리밍 지원 아님. status → verified.