refactor(topics): 멀티 에이전트용 지식 재편 — _Common(공통 기본기) + Domain_* 구조
에이전트 8종(대화형/프로그래머 C·S/디자이너/설계자/기획자/QA/PD/PM)에게 [공통 기본 능력 + 롤별 Specialty] 2층으로 지식을 주입하기 위한 재분류. 문서 내용·포맷은 무수정, 폴더 이동만 (6,372개 문서 수 보존 확인). - Topic_Programming → Domain_Programming (내부 구조 보존) - Topic_Graphic → Domain_Design - Topic_Business → Domain_Product - Topic_General → Domain_General - _Common 신설: Math(구 Topic_Math_Specialty), Reasoning(구 General/From_Thinking & Reasoning), Reasoning_Creativity(구 General/From_창의성), Communication(Poetic_Blog_Writing + From_writing) - 타 도메인의 From_* 폴더는 유지 (출처 표기일 뿐, 이미 도메인에 맞게 분류된 문서) - 빈 폴더 정리 (memory/procedures) - 에이전트→폴더 매핑은 workspace의 .astra/agent-knowledge-map.json (9개 에이전트) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+136
@@ -0,0 +1,136 @@
|
||||
---
|
||||
id: wiki-2026-0508-2026년-3월-연구-드롭-march-2026-resear
|
||||
title: 2026년 3월 연구 드롭 (March 2026 Research Drop)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [March 2026 Research Drop, 2026-03 Drop, Spring 2026 Releases]
|
||||
duplicate_of: none
|
||||
source_trust_level: B
|
||||
confidence_score: 0.85
|
||||
verification_status: applied
|
||||
tags: [research, llm, releases, 2026, frontier-models]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: en
|
||||
framework: research
|
||||
---
|
||||
|
||||
# 2026년 3월 연구 드롭 (March 2026 Research Drop)
|
||||
|
||||
## 매 한 줄
|
||||
> **"2026-03 한 달간의 frontier AI / systems research releases 의 묶음 timeline."** 매 Anthropic Claude Opus 4.7 1M context, 매 OpenAI GPT-5 family minor refresh, 매 DeepSeek-V4, 매 Llama 3.5, 매 Mistral Large 3, 매 vLLM 0.10, 매 MLX 0.30, 매 NVIDIA Blackwell-Ultra B300 GA — 매 single month 의 cluster 이벤트. 매 이 문서는 매 architecture / infra perspective 에서의 entry point.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 model releases (March 2026)
|
||||
- **Claude Opus 4.7**: 매 200K → 1M context (beta), 매 extended thinking budget tuning, 매 native interleaved tool calls.
|
||||
- **GPT-5.1**: 매 reasoning model — 매 cheaper $/Mtok, 매 deterministic mode 추가.
|
||||
- **DeepSeek-V4 (671B MoE)**: 매 open-weights, 매 MIT license, 매 vLLM Day-0 지원.
|
||||
- **Llama 3.5 405B / 70B**: 매 Meta 의 incremental, 매 long-context 8M (research preview).
|
||||
- **Mistral Large 3**: 매 EU sovereign deploy 강화, 매 Codestral 25 동반.
|
||||
|
||||
### 매 systems / infra
|
||||
- **vLLM 0.10**: 매 PagedAttention v3, 매 disaggregated prefill/decode, 매 H200/B300 FP8.
|
||||
- **MLX 0.30** (Apple Silicon): 매 unified memory 의 KV cache offload, 매 Llama 3.5 405B Q4 on M5 Ultra.
|
||||
- **CUDA 13 + Blackwell-Ultra (B300)**: 매 FP4 native, 매 1.5×B300 perf vs B200.
|
||||
- **TensorRT-LLM 0.20**: 매 speculative decoding native, 매 EAGLE-3 default.
|
||||
|
||||
### 매 응용
|
||||
1. 매 long-context retrieval (1M+) 의 production 도입.
|
||||
2. 매 on-prem MoE serving (DeepSeek-V4) 의 cost benchmark.
|
||||
3. 매 Apple Silicon edge 의 frontier model.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### 1) Claude Opus 4.7 with 1M context
|
||||
```python
|
||||
from anthropic import Anthropic
|
||||
client = Anthropic()
|
||||
resp = client.messages.create(
|
||||
model="claude-opus-4-7",
|
||||
extra_headers={"anthropic-beta": "context-1m-2025-08-07"},
|
||||
max_tokens=8192,
|
||||
messages=[{"role":"user","content": huge_corpus}]
|
||||
)
|
||||
```
|
||||
|
||||
### 2) Prompt caching (1M era 필수)
|
||||
```python
|
||||
client.messages.create(
|
||||
model="claude-opus-4-7",
|
||||
system=[{
|
||||
"type":"text","text": codebase_dump,
|
||||
"cache_control":{"type":"ephemeral"} # 매 5min TTL
|
||||
}],
|
||||
messages=[{"role":"user","content":"diff X vs Y"}]
|
||||
)
|
||||
```
|
||||
|
||||
### 3) vLLM 0.10 disaggregated serving
|
||||
```bash
|
||||
# 매 prefill node
|
||||
vllm serve deepseek-ai/DeepSeek-V4 --role prefill --port 8001
|
||||
# 매 decode node
|
||||
vllm serve deepseek-ai/DeepSeek-V4 --role decode --port 8002 \
|
||||
--kv-transfer-config '{"connector":"NIXL"}'
|
||||
```
|
||||
|
||||
### 4) MLX 0.30 on M5 Ultra
|
||||
```python
|
||||
import mlx.core as mx
|
||||
from mlx_lm import load, generate
|
||||
model, tok = load("mlx-community/Llama-3.5-405B-4bit")
|
||||
print(generate(model, tok, "What is...", max_tokens=512))
|
||||
```
|
||||
|
||||
### 5) Speculative decoding (TRT-LLM 0.20 EAGLE-3)
|
||||
```python
|
||||
# 매 1.8-2.3× throughput on Blackwell
|
||||
build_config = {
|
||||
"speculative_decoding_mode": "eagle3",
|
||||
"max_draft_len": 5
|
||||
}
|
||||
```
|
||||
|
||||
### 6) GPT-5.1 deterministic mode
|
||||
```python
|
||||
from openai import OpenAI
|
||||
oa = OpenAI()
|
||||
oa.responses.create(model="gpt-5.1", input="...", seed=42, temperature=0)
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| 매 1M+ context 가 핵심 | Claude Opus 4.7 1M |
|
||||
| 매 reasoning + 매 cheap | GPT-5.1 / DeepSeek-V4 |
|
||||
| 매 self-host / EU sovereign | Mistral Large 3 / Llama 3.5 |
|
||||
| 매 open MoE | DeepSeek-V4 + vLLM 0.10 |
|
||||
| 매 Apple edge | MLX 0.30 |
|
||||
|
||||
**기본값**: 매 production agent → Claude Opus 4.7, 매 self-host → DeepSeek-V4 / Llama 3.5, 매 edge → MLX.
|
||||
|
||||
## 🔗 Graph
|
||||
- Adjacent: [[LLM_Optimization_and_Deployment_Strategies|vLLM]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 architecture decision 의 timeline reference, 매 "what changed in 2026-03" 매 quick lookup.
|
||||
**언제 X**: 매 superseded 된 detail (매 매 quarter 마다 stale 가능).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Treat as exhaustive**: 매 일부 release 만 캡처. 매 official changelog 를 매 source of truth 로.
|
||||
- **Stale benchmarks**: 매 vLLM 0.10 의 perf 는 매 0.11 에서 변경 가능.
|
||||
- **Mix research preview vs GA**: 매 Llama 8M context 는 preview, 매 production 가정 X.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Anthropic news 2026-03, OpenAI blog 2026-03, DeepSeek HuggingFace 2026-03, vLLM v0.10 release notes).
|
||||
- 신뢰도 B (매 timeline 의 entry — 매 detail 은 individual page 에서).
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — March 2026 release roundup (models + infra) |
|
||||
@@ -0,0 +1,127 @@
|
||||
---
|
||||
id: wiki-2026-0508-3의-법칙-rule-of-three
|
||||
title: 3의 법칙 (Rule of Three)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Rule of Three, WET, Three Strikes Refactor]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [refactoring, dry, code-smell, abstraction]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: python
|
||||
framework: general
|
||||
---
|
||||
|
||||
# 3의 법칙 (Rule of Three)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 두 번까지는 복사, 세 번째 등장하면 추출."** Don Roberts 가 *Refactoring* (Fowler, 1999) 에 정식화한 휴리스틱. 매 premature abstraction 을 피하면서 매 진짜 duplication 만 제거하는 trade-off rule. 2026 LLM 기반 codegen 시대에도 매 the cheapest signal — 매 abstraction 의 timing 을 결정.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 왜 "3"인가
|
||||
- **2회는 coincidence**: 매 same-shape 의 코드 두 개는 매 future 에 diverge 할 가능성이 높음. 매 abstraction 으로 묶으면 매 wrong abstraction lock-in.
|
||||
- **3회는 pattern**: 매 third occurrence 에서 매 invariant 가 무엇인지 보임. 매 axis of variation 이 명확해짐.
|
||||
- **AHA principle**: "Avoid Hasty Abstractions" (Sandi Metz). 매 duplication is far cheaper than the wrong abstraction.
|
||||
|
||||
### 매 vs DRY
|
||||
- DRY: "Every piece of knowledge must have a single, authoritative representation" — 매 *knowledge* 에 대한 rule.
|
||||
- Rule of 3: 매 *code shape* 에 대한 rule.
|
||||
- 매 두 코드가 same-shape 이지만 different-knowledge 일 수 있음 → 매 DRY 가 적용되지 않음.
|
||||
|
||||
### 매 응용
|
||||
1. Helper function 추출 timing 결정.
|
||||
2. Component / Module 추출 timing 결정.
|
||||
3. Configuration parameter 도입 timing 결정.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### 1) 2회까지는 inline (resist abstraction)
|
||||
```python
|
||||
# Occurrence 1
|
||||
user_email = data["user"]["email"].strip().lower()
|
||||
|
||||
# Occurrence 2 — DO NOT extract yet
|
||||
admin_email = data["admin"]["email"].strip().lower()
|
||||
```
|
||||
|
||||
### 2) 3회째에 추출 — invariant 가 분명해진 시점
|
||||
```python
|
||||
def normalize_email(raw: str) -> str:
|
||||
return raw.strip().lower()
|
||||
|
||||
user_email = normalize_email(data["user"]["email"])
|
||||
admin_email = normalize_email(data["admin"]["email"])
|
||||
support_email = normalize_email(data["support"]["email"]) # ← trigger
|
||||
```
|
||||
|
||||
### 3) 매 wrong-abstraction 의 anti-pattern
|
||||
```python
|
||||
# 2회만에 추출했다가 3회째가 다른 shape 으로 등장
|
||||
def process_record(r, mode): # mode 가 점점 늘어남 — leaky abstraction
|
||||
if mode == "user": ...
|
||||
elif mode == "admin": ...
|
||||
elif mode == "audit": ... # 완전히 다른 logic
|
||||
```
|
||||
|
||||
### 4) 매 React component 의 Rule of 3
|
||||
```tsx
|
||||
// 2개 까지는 copy-paste
|
||||
<Card title="A"><p>...</p></Card>
|
||||
<Card title="B"><p>...</p></Card>
|
||||
// 3번째 → extract <UserCard> abstraction
|
||||
```
|
||||
|
||||
### 5) 매 LLM-assisted refactoring 의 trigger
|
||||
```python
|
||||
# Claude / Cursor 에게 "extract this duplication" 을 매 3회 occurrence 부터 요청.
|
||||
# 2회 occurrence 에서는 "leave inline" 을 명시.
|
||||
```
|
||||
|
||||
### 6) 매 test 에서의 예외
|
||||
```python
|
||||
# Test 코드는 Rule of 3 보다 readability 우선.
|
||||
# 매 fixture 추출은 매 2회에도 OK (DAMP > DRY in tests).
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| 매 same-shape 코드 2회 | Inline 유지 |
|
||||
| 매 same-shape + same-knowledge 3회 | Extract |
|
||||
| 매 same-shape + different-knowledge 3회 | Inline 유지 (DRY 위반 아님) |
|
||||
| Test fixture | 2회에도 추출 OK |
|
||||
| Cross-module duplication | 3회 + 매 stable interface 확인 후 |
|
||||
|
||||
**기본값**: 매 3회까지 wait, 매 4번째에 후회하지 않을 abstraction 만 추출.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Refactoring_Best_Practices|Refactoring]] · [[DRY]]
|
||||
- 변형: [[YAGNI]]
|
||||
- 응용: [[Code Smell]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 codegen 의 review 시 — "이거 2회만에 추출됐는데 3회 기다려야 하나?" 매 sanity check.
|
||||
**언제 X**: 매 test 코드 / 매 boilerplate / 매 framework-required pattern 에는 적용 X.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Premature abstraction**: 매 1-2회 occurrence 에서 추출 → 매 wrong axis 로 lock-in.
|
||||
- **Eternal copy-paste**: 매 5+ occurrence 인데도 추출 안 함 → 매 maintenance burden.
|
||||
- **Mode-flag explosion**: 매 추출한 함수에 매 boolean / enum flag 가 계속 늘어남 → 매 wrong abstraction signal.
|
||||
- **DRY-religious**: 매 "any duplication is sin" → 매 shape 만 같은 code 까지 강제 통합.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Fowler *Refactoring* 2nd ed. 2018, Sandi Metz "The Wrong Abstraction" 2016).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Rule of 3 trade-off + AHA + LLM refactor trigger 정리 |
|
||||
@@ -0,0 +1,172 @@
|
||||
---
|
||||
id: wiki-2026-0508-a2a
|
||||
title: A2A (Agent-to-Agent Protocol)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Agent-to-Agent, A2A Protocol, Agent2Agent]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [agents, protocol, interop, anthropic, mcp]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: python
|
||||
framework: anthropic-sdk
|
||||
---
|
||||
|
||||
# A2A (Agent-to-Agent Protocol)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 agent 가 다른 agent 와 모델/벤더 차이 없이 task 를 위임/협상/결과 교환 하기 위한 open protocol."** Google 이 2025 봄 announce, 2025-06 Linux Foundation 으로 stewardship 이전, 2026 현재 Anthropic / Microsoft / Salesforce 등 50+ 기업 adoption. 매 MCP (tool/data) 와 directly complementary — 매 A2A 는 *agent ↔ agent*, MCP 는 *agent ↔ tool*.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 5 design principles
|
||||
- **Agentic by default**: 매 agent autonomy 를 가정 (매 mere RPC 가 아님).
|
||||
- **Modality-agnostic**: 매 text / audio / video / structured data 모두 transport.
|
||||
- **Built on web standards**: HTTP + JSON-RPC 2.0 + SSE / WebSocket — 매 separate runtime 불필요.
|
||||
- **Secure by design**: OAuth 2.1, 매 mTLS, 매 capability scoping.
|
||||
- **Long-running task aware**: 매 minutes ~ days 의 async task — 매 polling + push-notification 모두 지원.
|
||||
|
||||
### 매 핵심 primitives
|
||||
- **AgentCard** (`/.well-known/agent.json`): 매 agent 의 capability advertisement.
|
||||
- **Task**: 매 unit of work — `submitted → working → input-required → completed/failed/canceled`.
|
||||
- **Message + Artifact**: 매 conversation chunk + 매 final output.
|
||||
- **Streaming**: SSE 로 매 partial token / 매 status update 전송.
|
||||
|
||||
### 매 응용
|
||||
1. Cross-vendor agent orchestration (Claude → Gemini → in-house).
|
||||
2. Specialist agent dispatch (legal, finance, code-review).
|
||||
3. Marketplace 의 agent invocation.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### 1) AgentCard 발행
|
||||
```json
|
||||
{
|
||||
"name": "claude-research-agent",
|
||||
"version": "1.2.0",
|
||||
"url": "https://api.example.com/a2a",
|
||||
"capabilities": {
|
||||
"streaming": true,
|
||||
"pushNotifications": true,
|
||||
"stateTransitionHistory": true
|
||||
},
|
||||
"skills": [
|
||||
{"id": "deep-research", "description": "Multi-source web research", "inputModes": ["text"], "outputModes": ["text", "file"]}
|
||||
],
|
||||
"auth": {"type": "oauth2.1", "scopes": ["task:submit"]}
|
||||
}
|
||||
```
|
||||
|
||||
### 2) Task 제출 (client agent)
|
||||
```python
|
||||
import httpx, uuid
|
||||
|
||||
resp = httpx.post(
|
||||
"https://api.example.com/a2a/tasks/send",
|
||||
json={
|
||||
"jsonrpc": "2.0", "id": "1", "method": "tasks/send",
|
||||
"params": {
|
||||
"id": str(uuid.uuid4()),
|
||||
"message": {
|
||||
"role": "user",
|
||||
"parts": [{"type": "text", "text": "Summarize Q4 earnings of NVDA."}]
|
||||
}
|
||||
}
|
||||
},
|
||||
headers={"Authorization": f"Bearer {token}"}
|
||||
)
|
||||
task = resp.json()["result"]
|
||||
```
|
||||
|
||||
### 3) SSE streaming 으로 partial result 수신
|
||||
```python
|
||||
with httpx.stream("POST", url + "/tasks/sendSubscribe", json=req) as s:
|
||||
for line in s.iter_lines():
|
||||
if line.startswith("data:"):
|
||||
event = json.loads(line[5:])
|
||||
if event["type"] == "status": ...
|
||||
elif event["type"] == "artifact": print(event["artifact"]["parts"])
|
||||
```
|
||||
|
||||
### 4) Push notification 등록 (long task)
|
||||
```python
|
||||
httpx.post(url + "/tasks/pushNotification/set", json={
|
||||
"taskId": task["id"],
|
||||
"pushNotificationConfig": {
|
||||
"url": "https://my-app.com/a2a/webhook",
|
||||
"token": webhook_secret
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 5) Server-side handler (FastAPI 예)
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
app = FastAPI()
|
||||
|
||||
@app.post("/a2a/tasks/send")
|
||||
async def send(req: dict):
|
||||
task_id = req["params"]["id"]
|
||||
# 매 background worker 에게 dispatch
|
||||
await queue.put((task_id, req["params"]["message"]))
|
||||
return {"jsonrpc":"2.0","id":req["id"],"result":{"id":task_id,"status":{"state":"submitted"}}}
|
||||
```
|
||||
|
||||
### 6) Multi-agent orchestration (A2A + MCP combo)
|
||||
```python
|
||||
# Orchestrator agent: A2A 로 specialist 호출, MCP 로 tool 사용
|
||||
research = await a2a_call("research-agent", query)
|
||||
draft = await claude.messages.create( # MCP tools attached
|
||||
model="claude-opus-4-7",
|
||||
tools=mcp_tools,
|
||||
messages=[{"role":"user","content": f"Draft based on: {research}"}]
|
||||
)
|
||||
```
|
||||
|
||||
### 7) Capability negotiation
|
||||
```python
|
||||
card = httpx.get(agent_url + "/.well-known/agent.json").json()
|
||||
if not card["capabilities"]["streaming"]:
|
||||
# 매 polling fallback
|
||||
use_polling = True
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| 매 agent ↔ tool / data | MCP |
|
||||
| 매 agent ↔ agent (cross-vendor) | A2A |
|
||||
| 매 same-process agent | Direct call (no protocol) |
|
||||
| 매 long-running (>30s) | A2A + push notification |
|
||||
| 매 strict typing 필요 | A2A + JSON Schema in skill spec |
|
||||
|
||||
**기본값**: 매 cross-org / cross-vendor agent collaboration 에 매 A2A, 매 internal tool wiring 은 매 MCP.
|
||||
|
||||
## 🔗 Graph
|
||||
- Adjacent: [[OAuth 2.1]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 multi-vendor agent stack 의 interop, 매 long-running specialist agent 의 호출.
|
||||
**언제 X**: 매 single-process tool 호출 (매 MCP 를 사용), 매 latency-critical (<50ms) 의 inner loop.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **A2A 로 tool 호출**: 매 MCP scope. 매 protocol 혼동.
|
||||
- **No AgentCard**: 매 capability 알려주지 않으면 매 client 가 fallback 못함.
|
||||
- **Sync polling on long task**: 매 push notification 또는 SSE 필수.
|
||||
- **Token leakage**: 매 webhook URL 의 token 검증 누락.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (a2aproject.org spec v0.3, Linux Foundation A2A Project 2025-06-23, Anthropic A2A blog 2025).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — A2A protocol primitives + MCP comparison + 7 working patterns |
|
||||
@@ -0,0 +1,144 @@
|
||||
---
|
||||
id: wiki-2026-0508-acid-transactions
|
||||
title: ACID Transactions
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [ACID, Database Transactions, Atomicity Consistency Isolation Durability]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.95
|
||||
verification_status: applied
|
||||
tags: [database, transactions, consistency, postgres]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: sql
|
||||
framework: postgres-17
|
||||
---
|
||||
|
||||
# ACID Transactions
|
||||
|
||||
## 매 한 줄
|
||||
> **"Atomicity / Consistency / Isolation / Durability — 매 transaction 의 four guarantees."** Härder & Reuter (1983) 가 정식화한 properties. 매 RDBMS (Postgres, Oracle, MySQL InnoDB) 의 default 보장이며, 2026 distributed era 에도 매 NewSQL (CockroachDB, Spanner, TiDB, Neon) 가 매 ACID across nodes 를 표방. 매 단일 transaction 이 매 multi-statement 의 all-or-nothing + isolated + persisted 를 보장.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 4 properties
|
||||
- **Atomicity**: 매 all statements commit or all rollback. 매 partial state 없음.
|
||||
- **Consistency**: 매 transaction 종료 시 매 모든 invariant (FK, check, unique) 가 valid.
|
||||
- **Isolation**: 매 concurrent transactions 가 매 serially executed 처럼 보임. 매 isolation level 로 trade-off.
|
||||
- **Durability**: 매 commit 후 매 crash / power loss 에도 매 data 가 살아남음 (WAL → fsync).
|
||||
|
||||
### 매 Isolation levels (SQL standard + Postgres)
|
||||
- **Read Uncommitted**: 매 dirty read 허용 (Postgres 는 사실상 RC 로 mapping).
|
||||
- **Read Committed** (Postgres default): 매 dirty read X, 매 non-repeatable read O.
|
||||
- **Repeatable Read** (Postgres = snapshot isolation): 매 phantom read 거의 없음, 매 serialization anomaly O.
|
||||
- **Serializable** (Postgres = SSI): 매 strictest, 매 overhead 큼.
|
||||
|
||||
### 매 응용
|
||||
1. 매 financial / inventory 의 multi-row update.
|
||||
2. 매 idempotent worker — 매 transaction + unique key.
|
||||
3. 매 saga compensation 의 단위.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### 1) Postgres BEGIN/COMMIT
|
||||
```sql
|
||||
BEGIN;
|
||||
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
|
||||
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
|
||||
COMMIT; -- 매 atomic + durable
|
||||
```
|
||||
|
||||
### 2) SAVEPOINT 로 partial rollback
|
||||
```sql
|
||||
BEGIN;
|
||||
INSERT INTO orders(id, total) VALUES (1, 50);
|
||||
SAVEPOINT before_items;
|
||||
INSERT INTO items(order_id, sku) VALUES (1, 'BAD-SKU'); -- FK 위반
|
||||
ROLLBACK TO SAVEPOINT before_items;
|
||||
INSERT INTO items(order_id, sku) VALUES (1, 'OK');
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
### 3) Isolation level 명시 (Python + asyncpg)
|
||||
```python
|
||||
async with conn.transaction(isolation="serializable"):
|
||||
row = await conn.fetchrow("SELECT balance FROM accounts WHERE id=$1", 1)
|
||||
await conn.execute("UPDATE accounts SET balance=$1 WHERE id=$2", row["balance"]-100, 1)
|
||||
# 매 SSI — 매 serialization_failure 시 retry 필요
|
||||
```
|
||||
|
||||
### 4) Optimistic concurrency (version column)
|
||||
```sql
|
||||
UPDATE doc SET body = $1, version = version + 1
|
||||
WHERE id = $2 AND version = $3;
|
||||
-- 매 affected_rows = 0 → conflict, 매 retry / 사용자 alert
|
||||
```
|
||||
|
||||
### 5) SELECT FOR UPDATE (pessimistic lock)
|
||||
```sql
|
||||
BEGIN;
|
||||
SELECT * FROM accounts WHERE id = 1 FOR UPDATE; -- 매 row lock
|
||||
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
### 6) Outbox pattern (transaction + async publish)
|
||||
```sql
|
||||
BEGIN;
|
||||
INSERT INTO orders(id, ...) VALUES (...);
|
||||
INSERT INTO outbox(event_type, payload) VALUES ('OrderCreated', $1);
|
||||
COMMIT;
|
||||
-- 매 separate worker 가 outbox 를 읽고 매 broker 로 publish
|
||||
```
|
||||
|
||||
### 7) Retry on serialization failure (Python)
|
||||
```python
|
||||
from psycopg.errors import SerializationFailure
|
||||
for _ in range(5):
|
||||
try:
|
||||
with conn.transaction(): do_work()
|
||||
break
|
||||
except SerializationFailure:
|
||||
time.sleep(random.uniform(0, 0.05))
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Isolation level |
|
||||
|---|---|
|
||||
| 매 read-heavy dashboard | Read Committed |
|
||||
| 매 report 의 consistent snapshot | Repeatable Read |
|
||||
| 매 money / counter / unique reservation | Serializable |
|
||||
| 매 high-conflict hot row | Pessimistic FOR UPDATE |
|
||||
| 매 low-conflict OLTP | Optimistic version |
|
||||
|
||||
**기본값**: Postgres Read Committed + 매 critical path 만 매 Serializable / FOR UPDATE.
|
||||
|
||||
## 🔗 Graph
|
||||
- 변형: [[BASE]] · [[Snapshot Isolation]]
|
||||
- 응용: [[Idempotency]]
|
||||
- Adjacent: [[CAP Theorem & PACELC]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 codegen 의 SQL transaction boundary 검증, 매 isolation level mismatch 점검.
|
||||
**언제 X**: 매 eventually-consistent NoSQL (DynamoDB single-region) — 매 BASE 모델로 reasoning.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **App-side "transaction"**: 매 read-modify-write 가 매 BEGIN/COMMIT 밖. 매 race condition.
|
||||
- **Long transaction**: 매 minutes 단위 hold → 매 lock contention + bloat.
|
||||
- **Wrong isolation**: 매 default RC 에서 매 lost update 발생, 매 detect 못 함.
|
||||
- **Ignoring serialization failure**: 매 SSI 에서 매 retry 안 함 → 매 user-facing error.
|
||||
- **Transaction across services**: 매 distributed XA 시도 → 매 saga 로 대체.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Postgres 17 docs, Härder & Reuter 1983, Bailis "Highly Available Transactions" 2014).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — ACID + Postgres isolation levels + 7 patterns |
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
---
|
||||
id: wiki-2026-0508-adr-architecture-decision-record
|
||||
title: ADR (Architecture Decision Records)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [ADR, Architecture Decision Record, Decision Log, Lightweight ADR]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.95
|
||||
verification_status: applied
|
||||
tags: [architecture, documentation, decisions, governance]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: markdown
|
||||
framework: madr
|
||||
---
|
||||
|
||||
# ADR (Architecture Decision Records)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 architecturally significant decision 을 매 immutable, dated, numbered markdown 파일로 기록하는 lightweight practice."** Michael Nygard 의 2011 blog post 가 origin, 매 이후 MADR / Y-statement 등 variants 가 표준화. 2026 현재 매 GitOps + LLM agent 시대에 매 ADR 은 매 single source of truth — 매 *왜 이 design 인가* 의 history 를 매 codebase 안에 매 living document 로 유지.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 5 essential fields (Nygard original)
|
||||
- **Title**: 매 short, declarative — `0007: Use Postgres for OLTP`.
|
||||
- **Status**: `proposed | accepted | deprecated | superseded by NNNN`.
|
||||
- **Context**: 매 forces — 매 problem, 매 constraint, 매 stakeholder.
|
||||
- **Decision**: 매 actually chosen approach (one paragraph).
|
||||
- **Consequences**: 매 positive + negative + neutral. 매 honest trade-offs.
|
||||
|
||||
### 매 MADR (Markdown ADR) extras
|
||||
- Decision drivers, considered options, pros/cons of each.
|
||||
- 매 numbered (`adr/0001-record-architecture-decisions.md`).
|
||||
- 매 PR 에 포함 — 매 code change 와 매 same review.
|
||||
|
||||
### 매 응용
|
||||
1. 매 framework / DB / cloud provider 선택.
|
||||
2. 매 API style (REST vs gRPC vs GraphQL).
|
||||
3. 매 auth, observability, deploy strategy.
|
||||
4. 매 LLM model / provider 선택.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### 1) Nygard ADR template
|
||||
```markdown
|
||||
# 0007. Use Postgres for OLTP
|
||||
|
||||
Date: 2026-05-10
|
||||
Status: Accepted
|
||||
|
||||
## Context
|
||||
We need an OLTP store with strong ACID, JSON support, and managed offerings
|
||||
across AWS/GCP/Azure. Team size 12. Expected QPS 5k.
|
||||
|
||||
## Decision
|
||||
Adopt Postgres 17 (RDS / Aurora / Cloud SQL / Neon).
|
||||
|
||||
## Consequences
|
||||
+ Mature ecosystem, ACID, JSONB, pgvector.
|
||||
- Vertical scaling ceiling — needs Citus / sharding past ~30TB.
|
||||
~ Team will invest in `psql` / pgbouncer expertise.
|
||||
```
|
||||
|
||||
### 2) MADR full template
|
||||
```markdown
|
||||
# Use Postgres for OLTP
|
||||
|
||||
* Status: Accepted
|
||||
* Deciders: @alice, @bob, @carol
|
||||
* Date: 2026-05-10
|
||||
|
||||
## Context and Problem Statement
|
||||
...
|
||||
|
||||
## Decision Drivers
|
||||
* ACID, managed offering, ecosystem
|
||||
* Cost ceiling at 5k QPS
|
||||
* Vector search (pgvector)
|
||||
|
||||
## Considered Options
|
||||
* Postgres
|
||||
* MySQL
|
||||
* CockroachDB
|
||||
* DynamoDB
|
||||
|
||||
## Decision Outcome
|
||||
Chosen option: "Postgres", because it dominates on drivers (1) and (3),
|
||||
and matches (2) within budget.
|
||||
|
||||
### Positive Consequences
|
||||
* JSONB, pgvector, mature tooling
|
||||
### Negative Consequences
|
||||
* Sharding burden later
|
||||
```
|
||||
|
||||
### 3) Y-statement (one-liner ADR)
|
||||
```text
|
||||
In the context of <use case>,
|
||||
facing <concern>,
|
||||
we decided for <option>
|
||||
to achieve <quality>,
|
||||
accepting <downside>.
|
||||
```
|
||||
|
||||
### 4) adr-tools (CLI)
|
||||
```bash
|
||||
brew install adr-tools
|
||||
adr init doc/adr
|
||||
adr new "Use Postgres for OLTP"
|
||||
adr new -s 7 "Use CockroachDB instead of Postgres" # 매 supersede
|
||||
adr generate toc > doc/adr/README.md
|
||||
```
|
||||
|
||||
### 5) GitHub PR-based workflow
|
||||
```bash
|
||||
git checkout -b adr/0007-postgres
|
||||
$EDITOR doc/adr/0007-use-postgres-for-oltp.md
|
||||
git commit -m "ADR-0007: Use Postgres for OLTP"
|
||||
gh pr create --label adr --reviewer @platform-team
|
||||
```
|
||||
|
||||
### 6) Status transitions
|
||||
```markdown
|
||||
# 0007. Use Postgres
|
||||
Status: Superseded by [0019](0019-use-cockroachdb.md)
|
||||
```
|
||||
|
||||
### 7) LLM-assisted drafting (2026)
|
||||
```text
|
||||
You are an ADR scribe. Given a Slack discussion transcript,
|
||||
output a MADR-format ADR with:
|
||||
- Decision drivers extracted from the discussion
|
||||
- All options mentioned with pros/cons
|
||||
- Status: Proposed
|
||||
Do not invent options not discussed.
|
||||
```
|
||||
|
||||
### 8) Linking from code
|
||||
```ts
|
||||
// See ADR-0007 for why Postgres + pgvector instead of pinecone
|
||||
import { Pool } from 'pg';
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | ADR 작성 여부 |
|
||||
|---|---|
|
||||
| 매 reversible (1 file 내 결정) | 작성 X |
|
||||
| 매 cross-team / cross-service impact | 작성 |
|
||||
| 매 vendor / framework 선택 | 작성 |
|
||||
| 매 reverse 시 매 1주 이상 cost | 작성 |
|
||||
| 매 trivial styling | 작성 X (lint config 로) |
|
||||
|
||||
**기본값**: 매 "되돌리는 데 1주 이상 걸리면 ADR". 매 Nygard 5-field minimum, 매 큰 결정만 MADR.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Architecture Documentation]]
|
||||
- 응용: [[ATAM]] · [[RFC Process]] · [[C4 Model (Architecture Documentation)]]
|
||||
- Adjacent: [[GitOps]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 design discussion 의 transcript → ADR draft 자동 생성, 매 ADR audit (status 누락 등).
|
||||
**언제 X**: 매 LLM 이 매 invent 한 driver/option 을 매 사실로 commit X. 매 human verifier 필요.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Wiki-only ADR**: 매 codebase 밖 → 매 stale. 매 repo 안에 두고 매 PR 로 review.
|
||||
- **Mutable ADR**: 매 기존 ADR 을 edit. 매 새 ADR 로 supersede.
|
||||
- **Decision-only**: 매 Context / Consequences 누락 → 매 6개월 후 매 누구도 이유 모름.
|
||||
- **Too coarse**: 매 "Use Microservices" — 매 specific 으로 (어떤 service, 매 boundary 어떻게).
|
||||
- **Too fine**: 매 매 PR 마다 ADR — 매 noise.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Nygard 2011 blog, MADR spec adr.github.io, Thoughtworks Tech Radar).
|
||||
- 신뢰도 A.
|
||||
- 매 [[ADR_(Architecture_Decision_Records)|ADR_(Architecture_Decision_Record)]] 가 매 redirect 로 본 문서를 가리킴.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Nygard + MADR templates + adr-tools workflow |
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
---
|
||||
id: wiki-2026-0508-ai-assisted-refactoring-ai-기반-리팩
|
||||
title: AI-Assisted Refactoring (AI 기반 리팩토링)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [AI Refactoring, LLM Refactoring, Agent Refactoring]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [refactoring, ai, llm, claude-code, cursor, codemod]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: claude-code
|
||||
---
|
||||
|
||||
# AI-Assisted Refactoring (AI 기반 리팩토링)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 LLM agent 가 codebase-wide refactoring 을 매 propose / apply / verify 하는 workflow."** 2026 현재 매 Claude Code (Opus 4.7), Cursor (Composer), Aider, Continue, Codex CLI 등이 매 multi-file edit + test loop 를 자동화. 매 핵심은 매 *deterministic checks* (test, type, lint) 로 매 LLM 의 stochasticity 를 매 제어. 매 "AI 가 짜준다"가 아닌 매 "AI 가 propose, CI 가 verify".
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 effective workflow
|
||||
- **Plan → Apply → Verify → Commit**: 매 매 step 마다 매 human 또는 매 automated gate.
|
||||
- **Small diffs**: 매 100-300 lines / commit. 매 1000+ line PR 은 매 review 불가능.
|
||||
- **Test as oracle**: 매 LLM 의 output 을 매 existing test suite 로 검증. 매 test 없으면 매 refactoring 시도 X.
|
||||
- **Type-driven**: 매 TS / Python (mypy/pyright) / Rust 등 매 strict typing 이 매 hallucination 을 매 catch.
|
||||
|
||||
### 매 적합 task
|
||||
- Rename / move (매 IDE refactor 와 비슷하나 매 cross-language).
|
||||
- Library migration (매 Express → Fastify, 매 enzyme → RTL, 매 moment → date-fns).
|
||||
- API surface 변경의 매 callsite 일괄 업데이트.
|
||||
- Style 통일 (매 callback → async/await, 매 class → hooks).
|
||||
- 매 dead code 제거.
|
||||
|
||||
### 매 부적합 task
|
||||
- 매 architectural redesign — 매 human 의 design 결정 필요.
|
||||
- 매 perf-critical hot path — 매 measurement 없이 매 LLM 의 "looks faster" 신뢰 X.
|
||||
- 매 security-sensitive (auth, crypto) — 매 human review 필수.
|
||||
|
||||
### 매 응용
|
||||
1. Legacy codebase 의 점진적 modernization.
|
||||
2. Mass dependency upgrade.
|
||||
3. Test coverage 가속.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### 1) Claude Code 의 대규모 rename
|
||||
```bash
|
||||
claude
|
||||
> Rename `getUserData` to `fetchUserProfile` across the repo.
|
||||
> Update all callsites, tests, and JSDoc.
|
||||
> Run `npm test` after.
|
||||
```
|
||||
|
||||
### 2) Plan-first prompt (Opus 4.7)
|
||||
```text
|
||||
Before editing, output a numbered PLAN of files & changes.
|
||||
Wait for "approved" before applying any edit.
|
||||
After each file, run `pnpm typecheck` and stop on first error.
|
||||
```
|
||||
|
||||
### 3) Codemod fallback (deterministic)
|
||||
```ts
|
||||
// jscodeshift transform — 매 LLM 보다 매 reliable for syntactic
|
||||
import { API, FileInfo } from 'jscodeshift';
|
||||
export default function (file: FileInfo, api: API) {
|
||||
return api.jscodeshift(file.source)
|
||||
.find('CallExpression', { callee: { name: 'getUserData' } })
|
||||
.replaceWith(p => api.jscodeshift.callExpression(
|
||||
api.jscodeshift.identifier('fetchUserProfile'),
|
||||
p.value.arguments
|
||||
))
|
||||
.toSource();
|
||||
}
|
||||
```
|
||||
|
||||
### 4) Hybrid: codemod + LLM cleanup
|
||||
```bash
|
||||
# 매 1) codemod 로 매 syntactic 변경
|
||||
jscodeshift -t rename.ts src/
|
||||
# 매 2) LLM 으로 매 semantic 보정 (매 import path, comment, type narrowing)
|
||||
claude "Fix any remaining type errors after the rename."
|
||||
```
|
||||
|
||||
### 5) Test-driven refactor loop
|
||||
```bash
|
||||
# 매 Aider / Claude Code 의 매 auto-test loop
|
||||
claude --auto-test "Extract logger to a separate module. Run pytest after."
|
||||
# 매 pytest fail → 매 LLM 이 매 fix 시도 → 매 max 3 retries → 매 stop
|
||||
```
|
||||
|
||||
### 6) Diff size guard (pre-commit)
|
||||
```bash
|
||||
# .git/hooks/pre-commit
|
||||
LINES=$(git diff --cached --shortstat | grep -oE '[0-9]+ insertions' | head -1)
|
||||
if [ "${LINES%% *}" -gt 500 ]; then
|
||||
echo "Refusing >500 line AI commit. Split it."; exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### 7) Chain of LLMs — propose / critique
|
||||
```python
|
||||
draft = claude.refactor(code)
|
||||
critique = claude.review(draft, focus="correctness, edge cases")
|
||||
final = claude.apply(critique)
|
||||
```
|
||||
|
||||
### 8) Verification matrix
|
||||
```yaml
|
||||
# 매 매 refactor PR 의 mandatory gates
|
||||
gates:
|
||||
- typecheck: pnpm tsc --noEmit
|
||||
- test: pnpm vitest run
|
||||
- lint: pnpm biome check
|
||||
- perf: bench compare main vs HEAD (regression < 5%)
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| 매 syntactic mass change | Codemod (jscodeshift / ast-grep) |
|
||||
| 매 semantic + 매 cross-cutting | LLM (Claude Code / Cursor) |
|
||||
| 매 1-file refactor | IDE refactor menu |
|
||||
| 매 risk-heavy (auth/payment) | Manual + LLM 보조 review |
|
||||
| 매 large repo (>1M LoC) | Hybrid: codemod 먼저, LLM 보정 |
|
||||
|
||||
**기본값**: 매 plan-first + 매 small diff + 매 test gate. 매 syntactic 부분은 매 codemod, 매 semantic 부분만 매 LLM.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Refactoring_Best_Practices|Refactoring]]
|
||||
- 응용: [[3의 법칙 (Rule of Three)]]
|
||||
- Adjacent: [[Claude Code]] · [[Cursor]] · [[Aider]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 cross-file rename/migrate, 매 boilerplate 변환, 매 test scaffold 생성.
|
||||
**언제 X**: 매 architectural decision, 매 perf hot path, 매 security crypto.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Massive PR**: 매 5000-line "AI did it all" PR. 매 review 불가능 → 매 bug 잠복.
|
||||
- **No tests**: 매 LLM refactor 의 검증 없음. 매 silent regression.
|
||||
- **Trust without verify**: 매 LLM 의 매 "I updated all callsites" 를 매 grep 으로 매 cross-check 안 함.
|
||||
- **Architectural delegation**: 매 design 까지 LLM 에 위임 → 매 incoherent system.
|
||||
- **Skipping codemod**: 매 syntactic mass-rename 까지 매 LLM 으로 → 매 token waste + 매 inconsistency.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Anthropic Claude Code docs 2026, Cursor Composer guide 2026, jscodeshift Meta OSS).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — workflow + codemod hybrid + 8 patterns |
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
category: Computer_Science_and_Theory
|
||||
tags: [auto-wikified, technical-documentation, computer_science_and_theory]
|
||||
title: AOP (Aspect-Oriented Programming)
|
||||
description: "AOP(Aspect-Oriented Programming)는 로깅, 보안, 트랜잭션 관리와 같은 횡단 관심사(Cross-Cutting Concerns)를 소프트웨어 시스템의 여러 모듈에서 분리하여 관리하는 방법론이자 프로그래밍 기법입니다 [1, 2]."
|
||||
last_updated: 2026-05-04
|
||||
---
|
||||
|
||||
# AOP (Aspect-Oriented Programming)
|
||||
|
||||
## 📌 Brief Summary
|
||||
AOP(Aspect-Oriented Programming)는 로깅, 보안, 트랜잭션 관리와 같은 횡단 관심사(Cross-Cutting Concerns)를 소프트웨어 시스템의 여러 모듈에서 분리하여 관리하는 방법론이자 프로그래밍 기법입니다 [1, 2]. 핵심 비즈니스 로직과 인프라 코드를 분리함으로써 소스 코드의 오염을 방지하고 코드의 가독성과 유지보수성을 크게 높여줍니다 [3, 4]. 주로 런타임에 메서드 호출을 가로채어 실행 전, 후, 또는 주변(around)에 원하는 동작을 적용하는 방식으로 작동합니다 [1, 2].
|
||||
|
||||
## 📖 Core Content
|
||||
- **관심사의 분리 (Separation of Concerns)**: 소프트웨어는 주요 기능을 담당하는 핵심 관심사(Core Concerns, 비즈니스 로직)와 시스템 전반에 걸쳐 필요한 보조 기능인 횡단 관심사(Crosscutting Concerns)로 나뉩니다 [5]. AOP는 로깅, 데이터 전송, 보안, 성능 모니터링, 캐싱 등 애플리케이션의 여러 계층을 가로지르는 횡단 관심사를 별도의 '애스펙트(Aspect)'로 분리하여 관리합니다 [1, 5].
|
||||
- **Spring Boot에서의 실전 구현**: Spring 생태계에서 AOP는 컨트롤러, 서비스, 리포지토리 등 모든 Spring Bean의 메서드를 인터셉트할 수 있는 애플리케이션 및 서비스 계층의 기술로 활용됩니다 [1, 6]. 개발자는 `@Aspect`를 사용하여 `@Before`, `@After`, `@Around` 등의 시점(Pointcut)을 정의해 비즈니스 로직의 수정 없이 공통 기능을 주입할 수 있습니다 [1, 6].
|
||||
- **주요 활용 사례**:
|
||||
- **트랜잭션 동작(Transactional Behavior)**: 모든 서비스 메서드에 `try-catch` 블록과 `commit`/`rollback` 호출을 반복 작성하는 대신, 어노테이션 마커를 통해 트랜잭션 처리 책임을 AOP에 위임합니다 [7].
|
||||
- **인가(Authorization)**: 서비스 메서드를 호출할 수 있는 권한을 마커로 표시하고, AOP 어드바이스(Advice)가 해당 메서드의 실행 허용 여부를 동적으로 결정하게 합니다 [8].
|
||||
- **원격 호출(Remoting)**: 분산 시스템 환경에서 대상 식별, 데이터 직렬화, 원격 네트워크 호출 등의 복잡한 처리를 애스펙트가 대신 수행하여, 개발자는 로컬 컴포넌트를 호출하는 것처럼 코드를 작성할 수 있습니다 [9].
|
||||
- **코드 품질 및 설계 향상**: AOP를 통해 공통 인프라 로직을 캡슐화하면 코드의 산재(Scattering)와 얽힘(Tangling)을 효과적으로 방지할 수 있습니다 [3, 10]. 이는 DRY(Don't Repeat Yourself) 원칙과 단일 책임 원칙(SRP) 위반을 막아주어, 대규모 코드베이스를 깨끗하고 리팩토링하기 쉬운 상태로 유지하도록 돕습니다 [3, 11].
|
||||
|
||||
## ⚖️ Trade-offs & Caveats
|
||||
- **디버깅 난이도 상승 (마법 같은 동작)**: AOP는 비즈니스 코드와 인프라 코드를 완벽하게 분리하는 강력한 장점이 있지만, 실행 시점에 동적으로 로직을 삽입하므로 코드의 실제 실행 흐름을 직관적으로 파악하기 어렵게 만듭니다 [4]. 이러한 지나치게 '마법 같은(magical)' 동작 방식은 예기치 않은 오류 발생 시 추적과 디버깅을 매우 까다롭게 만드는 부작용이 있습니다 [4].
|
||||
- **런타임 성능 오버헤드**: Castle.DynamicProxy와 같은 런타임 인터셉터 프레임워크를 사용할 경우, 각 메서드나 클래스에 대한 프록시 래퍼(Proxy wrapper)를 동적으로 생성해야 하므로 추가적인 런타임 비용이 발생합니다 [12]. 로깅과 같이 매우 빈번하게 호출되는 연산에 전면적으로 적용할 경우 애플리케이션 전반의 성능을 저하시킬 수 있습니다 [12].
|
||||
- **빌드 파이프라인의 복잡성 증가**: 성능 오버헤드라는 제약을 피하기 위해 런타임 프록시 대신 컴파일 타임(Compile-time)이나 링크 타임(Link-time) 위빙(Weaving) 방식으로 AOP를 구현할 수 있습니다 [12]. 하지만 이 최적화 방법은 빌드 과정 자체를 훨씬 복잡하게 만드는 반대급부를 수반합니다 [12].
|
||||
|
||||
---
|
||||
*Last updated: 2026-05-03*
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
category: Computer_Science_and_Theory
|
||||
tags: [auto-wikified, technical-documentation, computer_science_and_theory]
|
||||
title: AOT Compilation (Ahead-of-Time)
|
||||
description: "AOT(Ahead-of-Time) 컴파일은 애플리케이션이 실행되기 전에 소스 코드를 기기의 네이티브 기계어(ARM 코드 등)로 미리 컴파일해 두는 기술입니다 [1, 2]."
|
||||
last_updated: 2026-05-04
|
||||
---
|
||||
|
||||
# AOT Compilation (Ahead-of-Time)
|
||||
|
||||
## 📌 Brief Summary
|
||||
AOT(Ahead-of-Time) 컴파일은 애플리케이션이 실행되기 전에 소스 코드를 기기의 네이티브 기계어(ARM 코드 등)로 미리 컴파일해 두는 기술입니다 [1, 2]. 주로 Flutter 프레임워크의 Dart 언어나 Spring Boot의 GraalVM 네이티브 컴파일 환경 등에서 활용됩니다 [3, 4]. 실행 시점에 코드를 동적으로 해석하거나 파싱할 필요가 없으므로 애플리케이션의 콜드 스타트(Cold start) 시간을 크게 단축시키며, 네이티브 수준의 높은 실행 효율과 성능을 보장할 수 있습니다 [1, 5].
|
||||
|
||||
## 📖 Core Content
|
||||
* **Flutter와 Dart의 AOT 컴파일:** Flutter를 구동하는 Dart 가상 머신(VM)은 빠른 컴파일 환경을 제공하기 위해 JIT(Just-In-Time) 컴파일과 AOT 컴파일을 모두 지원합니다 [6]. 프로덕션 배포 시 Flutter 앱은 AOT 컴파일을 통해 Android 및 iOS용 네이티브 기계어(ARM 코드 등)로 사전에 직접 컴파일됩니다 [1, 2]. 엔진 초기화 및 자바스크립트 번들 파싱 과정을 거쳐야 하는 다른 크로스 플랫폼 기술과 비교했을 때, 런타임 오버헤드가 대폭 제거되어 콜드 스타트 시간이 눈에 띄게 단축되며 실행 효율이 극대화됩니다 [1, 5]. 결과적으로 네이티브 애플리케이션 본연의 성능(Original performance)을 달성할 수 있습니다 [3].
|
||||
* **Spring Boot의 네이티브 컴파일 (GraalVM 활용):** 자바(Java) 기반 백엔드 프레임워크인 Spring Boot 환경에서도 AOT 최적화 맥락과 맞닿아 있는 GraalVM 네이티브 컴파일을 채택하고 있습니다 [4]. 일반적인 JVM 애플리케이션은 시작하는 데 5~30초의 시간이 소요되지만, 이 네이티브 컴파일을 적용하면 코드가 사전 빌드되어 밀리초 단위로 애플리케이션을 시작할 수 있고 기존보다 훨씬 적은 힙 메모리(a fraction of the heap)만으로도 가동할 수 있습니다 [4].
|
||||
|
||||
## ⚖️ Trade-offs & Caveats
|
||||
AOT 및 사전 네이티브 컴파일 방식을 애플리케이션 아키텍처에 도입할 경우, 최적화 이면에서 다음과 같은 반대 급부와 제약 사항을 반드시 고려해야 합니다.
|
||||
|
||||
* **빌드 복잡성 증가 및 런타임 유연성 제한:** Spring Boot와 같은 환경에서 GraalVM을 활용한 네이티브 빌드를 적용할 경우, 사전에 정적 분석 및 컴파일을 수행해야 하므로 빌드 환경 구축과 과정의 복잡성(build complexity)이 크게 증가합니다 [4]. 더불어 코드가 네이티브 이미지로 완전히 고정되기 때문에, 기존 JVM이 지니고 있던 동적 로딩 등의 런타임 유연성(runtime flexibility)이 일부 상실되는 제약을 감수해야 합니다 [4].
|
||||
* **애플리케이션 파일 크기(앱 번들) 상승 부담:** 네이티브 기계어로 직접 컴파일되는 Flutter 애플리케이션의 경우, 컴파일된 결과물과 함께 Dart 런타임 및 자체 렌더링 엔진(예: Impeller)이 하나로 패키징되어 포함되어야 합니다 [1]. 이로 인해 아주 단순한 앱이라도 최소 기본 APK 크기가 8~12MB 수준에 달하게 되며, 복잡한 네이티브 코드가 포함됨에 따라 전체 앱 크기(App Size)가 다소 커지는 단점이 발생할 수 있습니다 [1, 7].
|
||||
|
||||
---
|
||||
*Last updated: 2026-05-03*
|
||||
@@ -0,0 +1,197 @@
|
||||
---
|
||||
id: wiki-2026-0508-api-gateway
|
||||
title: API Gateway
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [API GW, Gateway pattern, Edge service]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [architecture, microservices, api, gateway, edge]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: yaml
|
||||
framework: Kong, AWS API Gateway, Envoy
|
||||
---
|
||||
|
||||
# API Gateway
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 single entry point — fan-out, auth, rate limit"**. 매 microservices 의 클라이언트 facing facade. 매 Netflix Zuul (2013) 시작 → Kong (2015) → Envoy/Istio (2017) → AWS API Gateway HTTP API (2019). 매 2026 modern stack 은 Envoy + xDS control plane, edge AI inference gateway (LiteLLM, Portkey) 의 추가.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 책임
|
||||
- **Routing**: path/host/header → upstream service.
|
||||
- **Auth/AuthZ**: JWT validation, OAuth2 introspection, mTLS termination.
|
||||
- **Rate limiting**: per-key, per-IP, sliding window.
|
||||
- **Observability**: trace propagation (W3C Trace Context), metrics, access log.
|
||||
- **Transformation**: request/response shaping, protocol translation (REST↔gRPC).
|
||||
|
||||
### 매 NOT 책임
|
||||
- Business logic — 매 service 의 책임.
|
||||
- Data persistence — 매 stateless edge.
|
||||
- Heavy aggregation — 매 BFF (Backend-for-Frontend) layer 의 책임.
|
||||
|
||||
### 매 응용
|
||||
1. **Public API edge** — Stripe, Twilio 형 SaaS API.
|
||||
2. **BFF per client** — mobile/web/CLI 매 다른 shape.
|
||||
3. **LLM gateway** — multi-provider routing (Claude, GPT, local), fallback, cost cap.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Kong declarative config
|
||||
```yaml
|
||||
_format_version: "3.0"
|
||||
services:
|
||||
- name: orders-api
|
||||
url: http://orders.svc.cluster.local:8080
|
||||
routes:
|
||||
- name: orders-route
|
||||
paths: ["/api/orders"]
|
||||
strip_path: false
|
||||
plugins:
|
||||
- name: rate-limiting
|
||||
config: { minute: 600, policy: redis }
|
||||
- name: jwt
|
||||
config: { key_claim_name: kid }
|
||||
- name: prometheus
|
||||
```
|
||||
|
||||
### Envoy route config
|
||||
```yaml
|
||||
route_config:
|
||||
virtual_hosts:
|
||||
- name: api
|
||||
domains: ["api.example.com"]
|
||||
routes:
|
||||
- match: { prefix: "/v1/orders" }
|
||||
route:
|
||||
cluster: orders_cluster
|
||||
timeout: 5s
|
||||
retry_policy:
|
||||
retry_on: 5xx,reset,connect-failure
|
||||
num_retries: 2
|
||||
per_try_timeout: 1s
|
||||
```
|
||||
|
||||
### AWS API Gateway HTTP API + Lambda authorizer
|
||||
```yaml
|
||||
# SAM template
|
||||
HttpApi:
|
||||
Type: AWS::Serverless::HttpApi
|
||||
Properties:
|
||||
Auth:
|
||||
Authorizers:
|
||||
JwtAuth:
|
||||
IdentitySource: $request.header.Authorization
|
||||
JwtConfiguration:
|
||||
issuer: https://auth.example.com
|
||||
audience: [api.example.com]
|
||||
DefaultAuthorizer: JwtAuth
|
||||
RouteSettings:
|
||||
"POST /orders":
|
||||
ThrottlingBurstLimit: 100
|
||||
ThrottlingRateLimit: 50
|
||||
```
|
||||
|
||||
### LLM gateway (Portkey-style fallback)
|
||||
```python
|
||||
from portkey_ai import Portkey
|
||||
|
||||
client = Portkey(
|
||||
api_key="...",
|
||||
config={
|
||||
"strategy": {"mode": "fallback"},
|
||||
"targets": [
|
||||
{"provider": "anthropic", "override_params": {"model": "claude-opus-4-7"}},
|
||||
{"provider": "openai", "override_params": {"model": "gpt-5"}},
|
||||
],
|
||||
"cache": {"mode": "semantic", "max_age": 3600},
|
||||
},
|
||||
)
|
||||
resp = client.chat.completions.create(messages=[{"role":"user","content":"hi"}])
|
||||
```
|
||||
|
||||
### Rate limit (token bucket, Redis)
|
||||
```lua
|
||||
-- Kong-style Redis Lua
|
||||
local key = "rl:" .. consumer_id
|
||||
local tokens = tonumber(redis.call("GET", key) or "100")
|
||||
if tokens <= 0 then return 429 end
|
||||
redis.call("DECR", key)
|
||||
redis.call("EXPIRE", key, 60)
|
||||
return 200
|
||||
```
|
||||
|
||||
### Header-based canary
|
||||
```yaml
|
||||
routes:
|
||||
- match:
|
||||
prefix: "/v1/checkout"
|
||||
headers: [{name: "x-canary", exact_match: "true"}]
|
||||
route: { cluster: checkout_v2 }
|
||||
- match: { prefix: "/v1/checkout" }
|
||||
route: { cluster: checkout_v1 }
|
||||
```
|
||||
|
||||
### gRPC-Web transcoding
|
||||
```yaml
|
||||
http_filters:
|
||||
- name: envoy.filters.http.grpc_web
|
||||
- name: envoy.filters.http.grpc_json_transcoder
|
||||
typed_config:
|
||||
proto_descriptor: /etc/proto/api.pb
|
||||
services: ["api.OrderService"]
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Public SaaS API, multi-tenant | Kong / AWS API Gateway |
|
||||
| Service mesh edge ingress | Envoy + Istio Gateway |
|
||||
| Single-team internal API | Skip gateway → direct service + library SDK |
|
||||
| Multi-LLM provider | Portkey / LiteLLM gateway |
|
||||
| Heterogeneous protocols (REST+gRPC+WS) | Envoy with transcoding filters |
|
||||
|
||||
**기본값**: 매 Envoy-based (Istio Gateway / Contour) 의 in-cluster, AWS API Gateway 의 fully managed edge.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Microservices]] · [[Edge Computing]]
|
||||
- 변형: [[Service Mesh]] · [[Reverse Proxy]]
|
||||
- 응용: [[Rate Limiting]] · [[mTLS]]
|
||||
- Adjacent: [[Load Balancer]] · [[CDN]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 multi-service public API, 매 cross-cutting concerns (auth/rate-limit/observability) 의 centralization, 매 multi-provider LLM routing.
|
||||
**언제 X**: 매 single monolith, 매 internal service-to-service only (use mesh sidecar), 매 hot path 의 < 100us latency 요구.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Smart gateway**: 매 business logic 의 gateway 에 stuff — 매 deployment coupling 의 발생.
|
||||
- **Single gateway for all clients**: 매 mobile/web/partner 매 BFF 의 분리 안 함 → over-fetching.
|
||||
- **No timeout/retry budget**: 매 cascading failure 의 발생.
|
||||
- **Auth-only gateway, no rate limit**: 매 abuse vector.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Kong docs, Envoy docs, AWS API Gateway docs, Microsoft Azure Architecture Center "Gateway Aggregation" pattern).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — full content (Kong/Envoy/AWS/LLM gateway patterns) |
|
||||
|
||||
## 🛠️ 적용 사례 (Applied in summary)
|
||||
|
||||
<!-- CODE-GROUNDING:START -->
|
||||
### 🔎 코드베이스 근거 (자동 추출 — E:\Wiki 레포)
|
||||
**실제 구현/사용 위치:**
|
||||
- `connectai/src/features/secondBrainTrace.ts:256` — [Omitted long matching line]
|
||||
|
||||
_자동 생성: code_grounding.mjs · 재실행 시 갱신됨_
|
||||
<!-- CODE-GROUNDING:END -->
|
||||
@@ -0,0 +1,195 @@
|
||||
---
|
||||
id: wiki-2026-0508-api-contract-definition
|
||||
title: API Contract Definition
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [API Contract, OpenAPI Contract, Schema-First API, Contract-First Design]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.95
|
||||
verification_status: applied
|
||||
tags: [api, openapi, contract, schema, design]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: yaml
|
||||
framework: openapi-3.1
|
||||
---
|
||||
|
||||
# API Contract Definition
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 API 의 shape (path, method, schema, error, auth) 을 매 machine-readable spec 으로 매 먼저 정의하고 매 그 후 server/client 가 매 그것을 따른다."** OpenAPI 3.1 (REST), gRPC `.proto`, GraphQL SDL, AsyncAPI (event), JSON Schema 가 매 dominant. 2026 현재 매 LLM 이 매 spec 을 읽어 매 client / mock / test 를 매 generate — 매 contract-first 가 매 AI 시대의 매 default.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 contract-first vs code-first
|
||||
- **Contract-first**: 매 spec → 매 server stub + 매 client SDK. 매 multi-team / multi-language 의 default.
|
||||
- **Code-first**: 매 code → 매 spec generated (FastAPI, NestJS Swagger). 매 single team 에서 매 빠름.
|
||||
- 2026 추세: 매 contract-first 가 매 LLM-friendly + 매 mock-driven dev 에 유리.
|
||||
|
||||
### 매 standard 별 사용처
|
||||
- **OpenAPI 3.1**: 매 REST/HTTP. 매 JSON Schema 2020-12 align.
|
||||
- **gRPC + protobuf**: 매 internal high-perf, 매 strong typing.
|
||||
- **GraphQL SDL**: 매 client-driven query, 매 BFF.
|
||||
- **AsyncAPI 3**: 매 Kafka / NATS / WebSocket event.
|
||||
- **JSON Schema**: 매 payload validation, 매 LLM structured output.
|
||||
|
||||
### 매 contract testing
|
||||
- **Provider tests**: 매 server 가 매 spec 을 만족하는지 검증 (매 schemathesis, dredd, prism).
|
||||
- **Consumer-driven contracts**: 매 Pact — 매 client 가 매 expectation 을 매 publish, 매 server 가 매 verify.
|
||||
|
||||
### 매 응용
|
||||
1. SDK auto-generation (Stainless, Speakeasy, OpenAPI Generator).
|
||||
2. Mock server (Prism, Mockoon).
|
||||
3. Fuzz / property test (Schemathesis).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### 1) OpenAPI 3.1 minimal
|
||||
```yaml
|
||||
openapi: 3.1.0
|
||||
info: { title: Orders API, version: 1.2.0 }
|
||||
servers: [{ url: https://api.example.com/v1 }]
|
||||
paths:
|
||||
/orders/{id}:
|
||||
get:
|
||||
operationId: getOrder
|
||||
parameters:
|
||||
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
|
||||
responses:
|
||||
'200':
|
||||
content: { application/json: { schema: { $ref: '#/components/schemas/Order' } } }
|
||||
'404': { description: Not found }
|
||||
components:
|
||||
schemas:
|
||||
Order:
|
||||
type: object
|
||||
required: [id, total]
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
total: { type: number, minimum: 0 }
|
||||
status: { type: string, enum: [pending, paid, shipped, canceled] }
|
||||
```
|
||||
|
||||
### 2) gRPC proto
|
||||
```proto
|
||||
syntax = "proto3";
|
||||
package orders.v1;
|
||||
|
||||
service Orders {
|
||||
rpc Get (GetReq) returns (Order);
|
||||
rpc Stream (StreamReq) returns (stream Event);
|
||||
}
|
||||
message GetReq { string id = 1; }
|
||||
message Order { string id = 1; double total = 2; Status status = 3; }
|
||||
enum Status { PENDING = 0; PAID = 1; SHIPPED = 2; CANCELED = 3; }
|
||||
```
|
||||
|
||||
### 3) GraphQL SDL
|
||||
```graphql
|
||||
type Order {
|
||||
id: ID!
|
||||
total: Float!
|
||||
status: Status!
|
||||
}
|
||||
enum Status { PENDING PAID SHIPPED CANCELED }
|
||||
type Query { order(id: ID!): Order }
|
||||
```
|
||||
|
||||
### 4) AsyncAPI 3 (Kafka event)
|
||||
```yaml
|
||||
asyncapi: 3.0.0
|
||||
info: { title: Orders Events, version: 1.0.0 }
|
||||
channels:
|
||||
orders.created:
|
||||
address: orders.created
|
||||
messages:
|
||||
OrderCreated:
|
||||
payload: { $ref: '#/components/schemas/Order' }
|
||||
operations:
|
||||
publishOrderCreated:
|
||||
action: send
|
||||
channel: { $ref: '#/channels/orders.created' }
|
||||
```
|
||||
|
||||
### 5) Server stub generation
|
||||
```bash
|
||||
# 매 OpenAPI → TypeScript Express
|
||||
npx openapi-generator-cli generate -i openapi.yaml -g typescript-node -o ./gen
|
||||
# 매 OpenAPI → Python (FastAPI server)
|
||||
npx openapi-typescript openapi.yaml -o gen/types.ts
|
||||
```
|
||||
|
||||
### 6) Schemathesis property-based test
|
||||
```bash
|
||||
schemathesis run openapi.yaml --base-url=http://localhost:8000 \
|
||||
--checks=all --hypothesis-deadline=2000
|
||||
```
|
||||
|
||||
### 7) Pact consumer test (TS)
|
||||
```ts
|
||||
import { Pact } from '@pact-foundation/pact';
|
||||
const provider = new Pact({ consumer: 'web', provider: 'orders' });
|
||||
await provider.addInteraction({
|
||||
state: 'order 1 exists',
|
||||
uponReceiving: 'a get for order 1',
|
||||
withRequest: { method: 'GET', path: '/v1/orders/1' },
|
||||
willRespondWith: { status: 200, body: { id: '1', total: 99 } }
|
||||
});
|
||||
```
|
||||
|
||||
### 8) JSON Schema for LLM structured output
|
||||
```python
|
||||
order_schema = {
|
||||
"type": "object",
|
||||
"required": ["id", "total"],
|
||||
"properties": {"id":{"type":"string"},"total":{"type":"number"}}
|
||||
}
|
||||
client.messages.create(
|
||||
model="claude-opus-4-7",
|
||||
tools=[{"name":"return_order","input_schema":order_schema}],
|
||||
tool_choice={"type":"tool","name":"return_order"},
|
||||
messages=[...]
|
||||
)
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Standard |
|
||||
|---|---|
|
||||
| 매 public REST | OpenAPI 3.1 |
|
||||
| 매 internal microservices | gRPC + protobuf |
|
||||
| 매 client-shaped query | GraphQL |
|
||||
| 매 event-driven | AsyncAPI 3 |
|
||||
| 매 LLM structured output | JSON Schema |
|
||||
|
||||
**기본값**: 매 contract-first + 매 OpenAPI 3.1 + 매 SDK auto-gen + 매 schemathesis CI. 매 internal 은 gRPC.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[API Design]]
|
||||
- 변형: [[OpenAPI]] · [[gRPC]]
|
||||
- 응용: [[Contract Testing]]
|
||||
- Adjacent: [[JSON Schema]] · [[Pact]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 spec → SDK / mock / test scaffold 자동 생성, 매 spec lint, 매 changelog diff.
|
||||
**언제 X**: 매 LLM 이 매 spec 을 매 invent — 매 source-of-truth 는 매 git-tracked spec file.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Spec drift**: 매 server 가 매 spec 과 매 다름. 매 schemathesis CI 로 차단.
|
||||
- **No versioning**: 매 breaking change 를 매 same path 에 push. 매 `/v1` → `/v2` 또는 매 deprecated header.
|
||||
- **Anyof everywhere**: 매 spec 이 매 너무 permissive → 매 client 의 매 type narrowing 불가.
|
||||
- **Code-first without lint**: 매 generated spec 이 매 inconsistent (매 nullable, 매 enum).
|
||||
- **Mock-only test**: 매 contract test 없이 매 mock 만 사용 → 매 prod fail.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (OpenAPI 3.1 spec, AsyncAPI 3.0 spec, Pact docs, Schemathesis docs).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — OpenAPI/gRPC/GraphQL/AsyncAPI 4-standard + contract testing |
|
||||
@@ -0,0 +1,197 @@
|
||||
---
|
||||
id: wiki-2026-0508-api-first-architecture
|
||||
title: API-First Architecture
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [API-first design, Contract-first, Schema-first]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [architecture, api, openapi, contract-first, design]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: yaml
|
||||
framework: OpenAPI 3.1, Stoplight, Spectral, buf
|
||||
---
|
||||
|
||||
# API-First Architecture
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 API spec 의 first artifact — code follows contract"**. 매 design-first → spec → mock → impl 매 separate workflow. 매 Swagger (2010) → OpenAPI 3.0 (2017) → 3.1 (2021, JSON Schema 2020-12 align) → AsyncAPI 2.6/3.0 (events) → buf (gRPC). 매 2026 modern stack 은 spec-driven codegen + lint (Spectral) + breaking change detection (buf, oasdiff) + AI-assisted spec (Claude Opus 4.7).
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 workflow
|
||||
1. **Design**: write OpenAPI/proto spec 매 먼저.
|
||||
2. **Review**: stakeholders (FE/BE/partner) review spec, not code.
|
||||
3. **Mock**: Prism/Stoplight serve mock from spec.
|
||||
4. **Generate**: SDK (oapi-codegen, openapi-typescript), server stubs.
|
||||
5. **Implement**: fill stubs, validate at runtime.
|
||||
6. **Test**: contract tests against spec.
|
||||
7. **Govern**: lint (Spectral), diff (oasdiff), versioning.
|
||||
|
||||
### 매 vs code-first
|
||||
- **Code-first**: write handler → annotate → generate spec. Drift risk, late stakeholder feedback.
|
||||
- **API-first**: write spec → generate handler → fill. Single source of truth.
|
||||
|
||||
### 매 응용
|
||||
1. **Public SaaS API** — Stripe-style spec-driven.
|
||||
2. **Multi-platform SDK distribution** — auto-generated TS/Python/Go/Java clients.
|
||||
3. **Frontend/backend parallel dev** — FE works against mock from day 1.
|
||||
4. **B2B integration contracts** — partners review spec before impl.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### OpenAPI 3.1 spec
|
||||
```yaml
|
||||
openapi: 3.1.0
|
||||
info: { title: Orders API, version: 1.0.0 }
|
||||
paths:
|
||||
/orders/{id}:
|
||||
get:
|
||||
operationId: getOrder
|
||||
parameters:
|
||||
- { name: id, in: path, required: true, schema: {type: string} }
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Order" }
|
||||
"404": { $ref: "#/components/responses/NotFound" }
|
||||
components:
|
||||
schemas:
|
||||
Order:
|
||||
type: object
|
||||
required: [id, status]
|
||||
properties:
|
||||
id: { type: string, format: uuid }
|
||||
status: { type: string, enum: [pending, paid, shipped] }
|
||||
total: { type: number, minimum: 0 }
|
||||
```
|
||||
|
||||
### Mock server (Prism)
|
||||
```bash
|
||||
npx @stoplight/prism-cli mock spec.yaml --port 4010
|
||||
# Now FE devs hit http://localhost:4010/orders/123 with realistic responses
|
||||
```
|
||||
|
||||
### Spectral lint config
|
||||
```yaml
|
||||
# .spectral.yaml
|
||||
extends: [[spectral:oas, all]]
|
||||
rules:
|
||||
operation-operationId-unique: error
|
||||
operation-tag-defined: error
|
||||
no-eval-in-markdown: error
|
||||
custom-versioned-path:
|
||||
given: "$.paths"
|
||||
severity: error
|
||||
then:
|
||||
function: pattern
|
||||
functionOptions: { match: "^/v\\d+/" }
|
||||
```
|
||||
|
||||
### Type-safe client (openapi-typescript)
|
||||
```bash
|
||||
npx openapi-typescript spec.yaml -o src/api-types.ts
|
||||
```
|
||||
```typescript
|
||||
import createClient from "openapi-fetch";
|
||||
import type { paths } from "./api-types";
|
||||
|
||||
const client = createClient<paths>({ baseUrl: "https://api.example.com" });
|
||||
const { data, error } = await client.GET("/orders/{id}", {
|
||||
params: { path: { id: "abc" } },
|
||||
});
|
||||
// data: Order | undefined, fully typed
|
||||
```
|
||||
|
||||
### Server stub (oapi-codegen, Go)
|
||||
```bash
|
||||
oapi-codegen -package api -generate types,server spec.yaml > api/api.gen.go
|
||||
```
|
||||
```go
|
||||
type ServerImpl struct{ db *sql.DB }
|
||||
func (s *ServerImpl) GetOrder(c echo.Context, id string) error {
|
||||
var o Order
|
||||
if err := s.db.QueryRow("SELECT ...").Scan(...); err != nil { ... }
|
||||
return c.JSON(200, o)
|
||||
}
|
||||
```
|
||||
|
||||
### Breaking change detection (oasdiff)
|
||||
```bash
|
||||
oasdiff breaking spec-v1.yaml spec-v2.yaml --fail-on ERR
|
||||
# CI gate: blocks PR if breaking change without major version bump
|
||||
```
|
||||
|
||||
### AsyncAPI for events
|
||||
```yaml
|
||||
asyncapi: 3.0.0
|
||||
info: { title: Orders Events, version: 1.0.0 }
|
||||
channels:
|
||||
orderCreated:
|
||||
address: orders.created
|
||||
messages:
|
||||
OrderCreatedMessage:
|
||||
payload:
|
||||
$ref: "#/components/schemas/Order"
|
||||
operations:
|
||||
publishOrderCreated:
|
||||
action: send
|
||||
channel: { $ref: "#/channels/orderCreated" }
|
||||
```
|
||||
|
||||
### buf for gRPC governance
|
||||
```yaml
|
||||
# buf.yaml
|
||||
version: v2
|
||||
modules:
|
||||
- path: proto
|
||||
breaking:
|
||||
use: [FILE]
|
||||
lint:
|
||||
use: [DEFAULT]
|
||||
except: [PACKAGE_VERSION_SUFFIX]
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Spec |
|
||||
|---|---|
|
||||
| HTTP REST, public | OpenAPI 3.1 |
|
||||
| Async events | AsyncAPI 3.0 |
|
||||
| gRPC | proto + buf |
|
||||
| GraphQL | SDL + Apollo Federation |
|
||||
| Internal-only, single team | Code-first (faster iteration) |
|
||||
|
||||
**기본값**: 매 OpenAPI 3.1 + Spectral lint + Prism mock + openapi-typescript codegen + oasdiff CI.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[API Fundamentals]]
|
||||
- 응용: [[OpenAPI]]
|
||||
- Adjacent: [[Consumer-Driven Contracts]] · [[API Gateway]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 multi-team API, 매 public SDK distribution, 매 partner integration, 매 FE/BE parallel dev.
|
||||
**언제 X**: 매 prototype, 매 throwaway script, 매 single-developer monolith.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Spec-as-documentation only**: 매 not source of truth, 매 drift. 매 codegen-driven 의 enforce.
|
||||
- **No CI lint**: 매 spec rot. 매 Spectral / vacuum 의 사용.
|
||||
- **Big-bang spec**: 매 review fatigue. 매 incremental + path-scoped reviews.
|
||||
- **No mock**: 매 FE blocked on BE. 매 Prism mock 의 day-1 deploy.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (OpenAPI 3.1 spec, AsyncAPI spec, Stoplight docs, buf docs, ThoughtWorks Tech Radar "Design APIs first").
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — full content (API-first workflow + tooling) |
|
||||
@@ -0,0 +1,189 @@
|
||||
---
|
||||
id: wiki-2026-0508-api-fundamentals
|
||||
title: API Fundamentals
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [API basics, REST GraphQL gRPC, Web API]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [api, rest, graphql, grpc, fundamentals]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: REST/GraphQL/gRPC/tRPC
|
||||
---
|
||||
|
||||
# API Fundamentals
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 contract between systems — verbs, nouns, errors, evolution"**. 매 RPC (1980s, Sun RPC) → CORBA (1991) → SOAP (1998) → REST (Fielding 2000) → gRPC (2015) → GraphQL (2015) → tRPC (2020). 매 2026 modern stack 은 typed end-to-end (tRPC, GraphQL Codegen, gRPC + buf) + AsyncAPI 2.x for events.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 four styles
|
||||
- **REST**: resource-oriented, HTTP verbs, stateless, cacheable. 매 public API standard.
|
||||
- **GraphQL**: client-specified queries, single endpoint, strong types. 매 BFF / mobile.
|
||||
- **gRPC**: binary (protobuf), HTTP/2, streaming, codegen. 매 internal, low-latency.
|
||||
- **tRPC**: TypeScript-first, no codegen, type inference. 매 Next.js / monorepo.
|
||||
|
||||
### 매 cross-cutting concerns
|
||||
- **Versioning**: URI (`/v1`), header (`Accept-Version`), or evolution (additive only).
|
||||
- **Idempotency**: idempotency key for unsafe verbs (POST). At-least-once → exactly-once at API.
|
||||
- **Pagination**: cursor (preferred) vs offset. 매 cursor 의 stable ordering.
|
||||
- **Errors**: RFC 7807 problem+json, gRPC status codes, GraphQL `errors[]`.
|
||||
- **Auth**: OAuth2 / OIDC / mTLS / API key.
|
||||
|
||||
### 매 응용
|
||||
1. **Public REST + GraphQL** — Stripe (REST), GitHub (both), Shopify (GraphQL).
|
||||
2. **Internal gRPC mesh** — Google, Uber, Square.
|
||||
3. **Full-stack tRPC** — Vercel, Cal.com, T3 stack.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### REST resource design
|
||||
```http
|
||||
GET /orders?status=pending&cursor=abc123 → 200 [{...}], next_cursor
|
||||
POST /orders → 201 {id, ...}, Location
|
||||
GET /orders/{id} → 200 {...} | 404
|
||||
PATCH /orders/{id} (JSON Merge Patch) → 200 {...}
|
||||
DELETE /orders/{id} → 204
|
||||
```
|
||||
|
||||
### REST idempotency
|
||||
```typescript
|
||||
app.post("/orders", async (req, res) => {
|
||||
const idemKey = req.header("Idempotency-Key");
|
||||
const cached = await redis.get(`idem:${idemKey}`);
|
||||
if (cached) return res.status(200).json(JSON.parse(cached));
|
||||
|
||||
const order = await createOrder(req.body);
|
||||
await redis.setex(`idem:${idemKey}`, 86400, JSON.stringify(order));
|
||||
res.status(201).json(order);
|
||||
});
|
||||
```
|
||||
|
||||
### Cursor pagination
|
||||
```typescript
|
||||
const limit = 50;
|
||||
const rows = await db.query(
|
||||
`SELECT * FROM orders WHERE (created_at, id) < ($1, $2)
|
||||
ORDER BY created_at DESC, id DESC LIMIT $3`,
|
||||
[cursor.ts, cursor.id, limit + 1]
|
||||
);
|
||||
const hasMore = rows.length > limit;
|
||||
const items = rows.slice(0, limit);
|
||||
const nextCursor = hasMore ? encode(items.at(-1)) : null;
|
||||
```
|
||||
|
||||
### GraphQL schema + resolver
|
||||
```graphql
|
||||
type Query {
|
||||
order(id: ID!): Order
|
||||
orders(first: Int!, after: String): OrderConnection!
|
||||
}
|
||||
type Order { id: ID!, status: OrderStatus!, items: [Item!]! }
|
||||
type OrderConnection {
|
||||
edges: [OrderEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
```
|
||||
```typescript
|
||||
const resolvers = {
|
||||
Query: {
|
||||
order: (_, {id}, ctx) => ctx.dataloaders.order.load(id),
|
||||
orders: async (_, {first, after}, ctx) => paginateOrders(first, after, ctx),
|
||||
},
|
||||
Order: {
|
||||
items: (order, _, ctx) => ctx.dataloaders.itemsByOrder.load(order.id),
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### gRPC service (proto3)
|
||||
```proto
|
||||
syntax = "proto3";
|
||||
package orders.v1;
|
||||
|
||||
service OrderService {
|
||||
rpc Get(GetRequest) returns (Order);
|
||||
rpc List(ListRequest) returns (stream Order); // server streaming
|
||||
rpc Watch(WatchRequest) returns (stream OrderEvent); // bidi-friendly
|
||||
}
|
||||
message Order {
|
||||
string id = 1;
|
||||
OrderStatus status = 2;
|
||||
google.protobuf.Timestamp created_at = 3;
|
||||
}
|
||||
```
|
||||
|
||||
### tRPC procedure
|
||||
```typescript
|
||||
import { router, publicProcedure } from "./trpc";
|
||||
import { z } from "zod";
|
||||
|
||||
export const orderRouter = router({
|
||||
get: publicProcedure
|
||||
.input(z.object({ id: z.string() }))
|
||||
.query(({ input, ctx }) => ctx.db.order.findUnique({ where: { id: input.id } })),
|
||||
|
||||
create: publicProcedure
|
||||
.input(z.object({ items: z.array(z.string()).min(1) }))
|
||||
.mutation(({ input, ctx }) => ctx.db.order.create({ data: { items: input.items } })),
|
||||
});
|
||||
// Client gets full type inference: trpc.order.get.useQuery({id}) typed
|
||||
```
|
||||
|
||||
### RFC 7807 error
|
||||
```json
|
||||
{
|
||||
"type": "https://api.example.com/errors/insufficient-funds",
|
||||
"title": "Insufficient funds",
|
||||
"status": 402,
|
||||
"detail": "Account 12345 has balance $5, requested $50",
|
||||
"instance": "/orders/abc"
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Style |
|
||||
|---|---|
|
||||
| Public 3rd-party API | REST + OpenAPI 3.1 |
|
||||
| Mobile/web with diverse query needs | GraphQL |
|
||||
| Internal microservices, low latency | gRPC |
|
||||
| TypeScript monorepo (Next.js) | tRPC |
|
||||
| Real-time bidirectional streams | gRPC streaming or WebSocket |
|
||||
| Async events | AsyncAPI + Kafka/NATS |
|
||||
|
||||
**기본값**: 매 REST + OpenAPI 의 public, 매 gRPC 의 internal, 매 tRPC 의 TS monorepo.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Distributed Systems]] · [[Web Architecture]]
|
||||
- 변형: [[REST]] · [[gRPC]]
|
||||
- 응용: [[OpenAPI]] · [[API Gateway]] · [[Service Mesh]]
|
||||
- Adjacent: [[JSON Schema]] · [[Protocol Buffers]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 cross-system contract 의 design, 매 client/server 매 separated, 매 versioned interface 필요.
|
||||
**언제 X**: 매 internal function call (just call it), 매 single process, 매 < 100 lines glue script.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **GET with side effects**: 매 cache poisoning, 매 idempotency violation.
|
||||
- **HTTP 200 with `{error: ...}` body**: 매 status code semantics 의 ignore.
|
||||
- **Versioning by minor change**: 매 v1/v2/v3 explosion. 매 additive evolution 의 prefer.
|
||||
- **Chatty API**: 매 N+1 client roundtrips. 매 batch endpoint or GraphQL.
|
||||
- **No pagination**: 매 unbounded list → OOM at scale.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (RFC 7231 HTTP, RFC 7807 problem+json, gRPC docs, GraphQL spec, tRPC docs).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — full content (REST/GraphQL/gRPC/tRPC fundamentals) |
|
||||
@@ -0,0 +1,223 @@
|
||||
---
|
||||
id: wiki-2026-0508-ast-traversal
|
||||
title: AST Traversal
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [ast-walk, syntax-tree-traversal, visitor-pattern]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [ast, compilers, codemod, static-analysis]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: ts-morph
|
||||
---
|
||||
|
||||
# AST Traversal
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 code 의 tree 의 walk 한다"**. 매 AST (Abstract Syntax Tree) traversal 의 compiler/linter/codemod/IDE 의 foundation. 매 2026 의 tree-sitter (Atom, 2018; 매 GitHub 의 sole semantic search engine) + ts-morph + Babel + RustPython AST 의 dominant tooling.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 traversal strategies
|
||||
- **Pre-order DFS**: 매 visit parent → children. 매 default (most visitors).
|
||||
- **Post-order DFS**: 매 visit children → parent. 매 type inference, dead-code elim.
|
||||
- **BFS**: 매 level-by-level. 매 rare — scope analysis.
|
||||
- **Visitor pattern**: 매 node-type-keyed callbacks. 매 ESLint, Babel, ts-morph standard.
|
||||
|
||||
### 매 mutating vs read-only
|
||||
- **Read-only**: linter, complexity metrics, security scanner.
|
||||
- **Mutating**: codemod, formatter, transpiler. 매 immutability + new tree 의 produce 의 best-practice (Babel: `path.replaceWith`).
|
||||
|
||||
### 매 응용
|
||||
1. ESLint rule — 매 pattern detection.
|
||||
2. Codemod — jscodeshift, ts-morph, ast-grep.
|
||||
3. Tree-sitter query — 매 IDE syntax highlight, code nav.
|
||||
4. AST-based diffing — 매 difftastic, semantic diff.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Babel visitor (JavaScript)
|
||||
```js
|
||||
import { parse } from '@babel/parser';
|
||||
import traverse from '@babel/traverse';
|
||||
import generate from '@babel/generator';
|
||||
import * as t from '@babel/types';
|
||||
|
||||
const code = `console.log("hello"); foo("world");`;
|
||||
const ast = parse(code);
|
||||
|
||||
traverse(ast, {
|
||||
CallExpression(path) {
|
||||
const callee = path.node.callee;
|
||||
if (t.isMemberExpression(callee) &&
|
||||
t.isIdentifier(callee.object, { name: 'console' })) {
|
||||
// strip console.* calls
|
||||
path.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(generate(ast).code); // foo("world");
|
||||
```
|
||||
|
||||
### ts-morph (TypeScript refactor)
|
||||
```ts
|
||||
import { Project, SyntaxKind } from 'ts-morph';
|
||||
|
||||
const project = new Project({ tsConfigFilePath: 'tsconfig.json' });
|
||||
for (const sf of project.getSourceFiles()) {
|
||||
sf.forEachDescendant(node => {
|
||||
if (node.getKind() === SyntaxKind.CallExpression) {
|
||||
const ce = node.asKindOrThrow(SyntaxKind.CallExpression);
|
||||
if (ce.getExpression().getText() === 'fetch') {
|
||||
ce.replaceWithText(`httpClient.${ce.getText().replace('fetch', '')}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
await project.save();
|
||||
```
|
||||
|
||||
### Python — `ast` + NodeTransformer
|
||||
```python
|
||||
import ast
|
||||
|
||||
class StripPrint(ast.NodeTransformer):
|
||||
def visit_Expr(self, node):
|
||||
if (isinstance(node.value, ast.Call)
|
||||
and isinstance(node.value.func, ast.Name)
|
||||
and node.value.func.id == "print"):
|
||||
return None # remove
|
||||
return node
|
||||
|
||||
src = "print('hi')\nx = 1"
|
||||
tree = ast.parse(src)
|
||||
StripPrint().visit(tree)
|
||||
ast.fix_missing_locations(tree)
|
||||
print(ast.unparse(tree)) # x = 1
|
||||
```
|
||||
|
||||
### tree-sitter query (multi-language)
|
||||
```scheme
|
||||
; queries/python/calls.scm — find all decorated functions
|
||||
(decorated_definition
|
||||
(decorator (call function: (identifier) @decorator-name))
|
||||
definition: (function_definition name: (identifier) @func-name))
|
||||
```
|
||||
|
||||
```python
|
||||
import tree_sitter_python as tspython
|
||||
from tree_sitter import Language, Parser, Query
|
||||
|
||||
PY = Language(tspython.language())
|
||||
parser = Parser(PY)
|
||||
tree = parser.parse(b"@app.route('/')\ndef home(): pass")
|
||||
q = Query(PY, open("queries/python/calls.scm").read())
|
||||
for node, name in q.captures(tree.root_node):
|
||||
print(name, node.text)
|
||||
```
|
||||
|
||||
### ast-grep (rule-based, polyglot)
|
||||
```yaml
|
||||
# rule.yml
|
||||
id: no-console-log
|
||||
language: js
|
||||
rule:
|
||||
pattern: console.log($$$)
|
||||
fix: ''
|
||||
```
|
||||
|
||||
```bash
|
||||
ast-grep scan -r rule.yml --update
|
||||
```
|
||||
|
||||
### Rust — syn (proc-macro / codegen)
|
||||
```rust
|
||||
use syn::{visit::Visit, ItemFn, File};
|
||||
|
||||
struct FnCounter(usize);
|
||||
impl<'ast> Visit<'ast> for FnCounter {
|
||||
fn visit_item_fn(&mut self, i: &'ast ItemFn) {
|
||||
self.0 += 1;
|
||||
syn::visit::visit_item_fn(self, i);
|
||||
}
|
||||
}
|
||||
|
||||
let src = std::fs::read_to_string("src/lib.rs").unwrap();
|
||||
let file: File = syn::parse_file(&src).unwrap();
|
||||
let mut c = FnCounter(0);
|
||||
c.visit_file(&file);
|
||||
println!("functions: {}", c.0);
|
||||
```
|
||||
|
||||
### ESLint custom rule
|
||||
```js
|
||||
module.exports = {
|
||||
meta: { type: 'problem', schema: [] },
|
||||
create(ctx) {
|
||||
return {
|
||||
'CallExpression[callee.name="eval"]'(node) {
|
||||
ctx.report({ node, message: 'eval is forbidden' });
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Pre-order vs post-order (manual walk)
|
||||
```ts
|
||||
function walkPre(node: Node, fn: (n: Node) => void) {
|
||||
fn(node);
|
||||
for (const c of node.children) walkPre(c, fn);
|
||||
}
|
||||
function walkPost(node: Node, fn: (n: Node) => void) {
|
||||
for (const c of node.children) walkPost(c, fn);
|
||||
fn(node);
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| Goal | Tool |
|
||||
|---|---|
|
||||
| JS/TS lint rule | ESLint visitor |
|
||||
| TS large refactor | ts-morph |
|
||||
| Python codemod | LibCST (preserves whitespace) > ast |
|
||||
| Polyglot pattern search | ast-grep, Semgrep |
|
||||
| IDE / syntax highlight | tree-sitter |
|
||||
| Rust macro | syn / quote |
|
||||
| Babel plugin | @babel/traverse |
|
||||
|
||||
**기본값**: 매 ts-morph (TS refactor) · ast-grep (polyglot scan) · LibCST (Python codemod).
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Static-Analysis]]
|
||||
- 변형: [[Visitor-Pattern]]
|
||||
- 응용: [[ESLint]] · [[Refactoring_Best_Practices|Refactoring]]
|
||||
- Adjacent: [[Architectural-Constraint-Enforcement]] · [[Architecture_Refactor]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 codebase 의 LLM-driven structural query — 매 ast-grep + Claude 의 hybrid (LLM 의 generate pattern, ast 의 execute).
|
||||
**언제 X**: 매 LLM 의 raw text find/replace — 매 AST-aware tool 의 사용.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Regex on code**: 매 multiline construct 의 break — 매 AST 의 사용.
|
||||
- **Mutating during traversal**: 매 visitor 의 reentrancy bug — 매 collect-then-apply.
|
||||
- **Ignore comments/whitespace**: 매 codemod 의 lose comments — 매 LibCST/Recast 의 사용.
|
||||
- **Single-pass dependence**: 매 transformation order 의 fragile — 매 idempotent 의 design.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Babel docs; ts-morph guide; tree-sitter playground; *Crafting Interpreters* — Nystrom).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Babel/ts-morph/tree-sitter/ast-grep patterns |
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
---
|
||||
id: wiki-2026-0508-atam-architecture-trade-offs-ana
|
||||
title: ATAM (Architecture Trade-offs Analysis Method)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [atam, architecture-evaluation, architecture-review]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [architecture, evaluation, atam, sei, quality-attributes]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: process
|
||||
framework: sei-atam
|
||||
---
|
||||
|
||||
# ATAM (Architecture Trade-offs Analysis Method)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 architecture 의 quality attribute 의 trade-off 의 reveal 한다"**. 매 ATAM 의 SEI (Software Engineering Institute, CMU) 의 1998 의 published — 매 Bass, Clements, Kazman 의 *Software Architecture in Practice* 의 codified. 매 2026 의 lightweight 의 RFC/ADR + LLM-assisted scenario generation 의 evolved 됨 — 매 core 의 still scenario-driven trade-off analysis.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 9 phases (classical ATAM)
|
||||
1. Present ATAM (method intro).
|
||||
2. Present business drivers.
|
||||
3. Present architecture.
|
||||
4. Identify architectural approaches.
|
||||
5. Generate quality attribute utility tree.
|
||||
6. Analyze approaches against scenarios.
|
||||
7. Brainstorm + prioritize scenarios (stakeholders).
|
||||
8. Re-analyze.
|
||||
9. Present results.
|
||||
|
||||
### 매 outputs
|
||||
- **Utility tree**: quality goals → attributes → scenarios (H/M/L priority + difficulty).
|
||||
- **Risks**: 매 architecture decision 의 negative consequence.
|
||||
- **Non-risks**: 매 sound decision 의 confirmation.
|
||||
- **Sensitivity points**: 매 single decision 의 single attribute 의 strongly affect.
|
||||
- **Trade-off points**: 매 single decision 의 multiple attributes 의 differently affect.
|
||||
|
||||
### 매 scenario template
|
||||
```
|
||||
Stimulus (event) — Source (actor) — Environment (state) →
|
||||
Artifact (component) → Response (system action) → Measure (latency, %, etc.)
|
||||
```
|
||||
|
||||
### 매 응용
|
||||
1. Pre-build architecture review.
|
||||
2. Major refactor 의 GO/NO-GO.
|
||||
3. Vendor selection — 매 candidate 의 same scenarios 의 score.
|
||||
4. Tech radar 의 input.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Utility tree (Markdown)
|
||||
```markdown
|
||||
# Utility Tree — Order Service v3
|
||||
|
||||
## Performance
|
||||
- (H, H) p95 checkout latency < 300ms at 10k RPS
|
||||
- (H, M) Cold start < 500ms (serverless)
|
||||
- (M, L) Background job throughput > 1k/s
|
||||
|
||||
## Availability
|
||||
- (H, H) 99.99% monthly SLO for /checkout
|
||||
- (H, M) Graceful DB failover < 30s
|
||||
- (M, M) Region evacuation < 5min
|
||||
|
||||
## Modifiability
|
||||
- (H, M) New payment method added in < 5 dev-days
|
||||
- (M, L) Tenant-specific pricing rules without redeploy
|
||||
|
||||
## Security
|
||||
- (H, H) PCI-DSS compliance for card data path
|
||||
- (M, M) PII encryption at rest
|
||||
|
||||
(H/M/L = Importance, Difficulty)
|
||||
```
|
||||
|
||||
### Scenario (concrete)
|
||||
```yaml
|
||||
id: PERF-01
|
||||
quality_attribute: performance
|
||||
priority: H
|
||||
difficulty: H
|
||||
stimulus: "10,000 concurrent checkout submissions"
|
||||
source: "authenticated mobile clients"
|
||||
environment: "normal operations, peak hour"
|
||||
artifact: "checkout-api + payment-gateway"
|
||||
response: "process to confirmed order"
|
||||
measure: "p95 < 300ms, p99 < 800ms, error rate < 0.1%"
|
||||
analysis:
|
||||
approach: "Read-through Redis cache + async write to Kafka"
|
||||
sensitivity: ["Redis cluster size", "Kafka partition count"]
|
||||
tradeoff: "Cache adds read latency variance vs DB-direct correctness"
|
||||
risk: "Cache stampede on Redis failover"
|
||||
mitigations: ["request coalescing", "stale-while-revalidate"]
|
||||
```
|
||||
|
||||
### Risk / Non-risk / Sensitivity / Trade-off ledger
|
||||
```markdown
|
||||
| ID | Type | Decision | Affects | Status |
|
||||
|----|------|----------|---------|--------|
|
||||
| R-01 | Risk | Sync DB write in checkout | Avail, Perf | Open — needs async branch |
|
||||
| R-02 | Sensitivity | Redis TTL = 60s | Performance | Confirmed — measured |
|
||||
| R-03 | Tradeoff | Multi-region active-active | Avail+ / Cost-, Consist- | Accepted in ADR-014 |
|
||||
| N-01 | Non-risk | JWT for auth | Security | OK — standard practice |
|
||||
```
|
||||
|
||||
### Lightweight ATAM (modern, 2-day workshop)
|
||||
```markdown
|
||||
Day 1 AM: Business drivers + architecture walkthrough (2h)
|
||||
Day 1 PM: Utility tree co-construction (3h)
|
||||
Day 2 AM: Scenario analysis — top 5 (4h)
|
||||
Day 2 PM: Risks/tradeoffs ledger + ADR drafts (3h)
|
||||
Output: 1-page exec summary + per-scenario deep dives
|
||||
```
|
||||
|
||||
### Tooling — Structurizr + ADR + LLM
|
||||
```bash
|
||||
# 1. Architecture in Structurizr DSL
|
||||
# 2. ADRs in /docs/adr/*.md (adr-tools)
|
||||
adr new "Use Redis cache for checkout reads"
|
||||
|
||||
# 3. LLM-assisted scenario brainstorm
|
||||
claude --prompt "Given utility tree and arch, propose 10 stress scenarios"
|
||||
|
||||
# 4. CI gate — utility-tree.yml diff = ADR required
|
||||
```
|
||||
|
||||
### CBAM extension (cost-benefit)
|
||||
```python
|
||||
# Cost-Benefit Analysis Method (Kazman, Asundi, Klein 2002)
|
||||
def score(scenario):
|
||||
benefit = scenario.utility * scenario.weight
|
||||
cost = scenario.implementation_cost
|
||||
return benefit / cost # ROI for prioritization
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Method |
|
||||
|---|---|
|
||||
| Greenfield, high-stakes | Full ATAM (5-day) |
|
||||
| Iteration / sprint review | Lightweight ATAM (1-2 day) |
|
||||
| Single decision | ADR with trade-off section |
|
||||
| Cost-aware prioritization | CBAM |
|
||||
| Solo / startup | RFC + scenario list (informal) |
|
||||
| Continuous | Fitness functions + ADR |
|
||||
|
||||
**기본값**: ADR-per-decision + lightweight ATAM 의 quarterly.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Software Architecture]] · [[Quality Attributes]]
|
||||
- 응용: [[ADR]] · [[RFC]] · [[Architecture_Refactor]]
|
||||
- Adjacent: [[Architecture Evaluation (아키텍처 평가)]] · [[Architecture Review (아키텍처 및 설계 리뷰)]] · [[Architectural-Constraint-Enforcement]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 utility tree 의 first-draft generation, 매 scenario 의 stress-test brainstorm, 매 risk ledger 의 categorization.
|
||||
**언제 X**: 매 final risk acceptance — 매 stakeholder consensus 의 필수.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Theatre review**: 매 architect-only attendance — 매 stakeholder buy-in 의 X.
|
||||
- **Scenario without measure**: 매 "fast" / "scalable" — 매 unfalsifiable.
|
||||
- **No follow-up**: 매 risks 의 logged 의 forgotten — 매 ADR 의 link 의 필수.
|
||||
- **One-time**: 매 architecture 의 evolves — 매 evaluation 의 also.
|
||||
- **Skip business drivers**: 매 quality attributes 의 prioritization 의 wrong.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Bass, Clements, Kazman — *Software Architecture in Practice* 4th ed; SEI tech reports CMU/SEI-2000-TR-004).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — utility tree + scenarios + risk ledger + lightweight ATAM |
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
---
|
||||
category: Other
|
||||
tags: [auto-wikified, technical-documentation, other]
|
||||
title: Active Record Pattern vs Repository Pattern
|
||||
description: "Active Record 패턴은 데이터베이스 모델의 관심사와 비즈니스 로직을 하나의 객체나 계층에 결합하여 처리하는 설계 방식이다 [1]."
|
||||
last_updated: 2026-05-04
|
||||
---
|
||||
|
||||
# Active Record Pattern vs Repository Pattern
|
||||
|
||||
## 📌 Brief Summary
|
||||
Active Record 패턴은 데이터베이스 모델의 관심사와 비즈니스 로직을 하나의 객체나 계층에 결합하여 처리하는 설계 방식이다 [1]. 반면 Repository 패턴은 데이터 접근을 전담하는 저장소(Repository) 계층을 분리하고, 모델은 비즈니스 로직이 없는 단순한 객체(dumb models/DTOs)로 유지하는 상반된 접근 방식을 취한다 [1, 2]. 최근 소프트웨어 산업은 모델 중심이 아닌 기능(Feature)을 축으로 비즈니스 로직을 분리하기 위해 점차 Active Record에서 Repository 패턴으로 이동하는 추세를 보이고 있다 [1].
|
||||
|
||||
## 📖 Core Content
|
||||
* **Active Record 패턴의 특성**
|
||||
Active Record 패턴은 데이터베이스 모델 내에 데이터 처리와 비즈니스 로직을 함께 구현하는 방식이다 [1]. 과거에는 데이터베이스 관련 관심사를 단순화하여 비즈니스 로직을 직관적으로 표현할 수 있다는 장점이 있었으나, 점차 애플리케이션 규모가 커지고 성능 및 확장성 등 다양한 기술적 관심사가 추가되면서 단일 계층에 너무 많은 책임이 혼재되는 문제를 겪게 되었다 [1].
|
||||
|
||||
* **Repository 패턴의 특성 및 구현**
|
||||
Repository 패턴은 데이터 접근은 오직 리포지토리를 통해서만 이루어지도록 강제한다 [2]. 이 패턴에서는 모델을 능동적인 객체가 아닌 '단순한 모델(dumb models)이나 DTO'로 정의하며, 실제 비즈니스 로직은 컨트롤러나 서비스 계층에서 담당하도록 관심사를 분리한다 [1].
|
||||
Spring Boot 환경에서는 Spring Data JPA를 통해 Repository 패턴을 핵심적으로 활용하며, 메서드 이름에 따라 자동으로 쿼리를 생성해 데이터 접근에 필요한 보일러플레이트 코드를 크게 줄여준다 [3]. 이 방식은 관심사의 깔끔한 분리를 유지하면서도 복잡한 데이터베이스 쿼리를 원활히 지원하는 장점이 있다 [4].
|
||||
|
||||
## ⚖️ Trade-offs & Caveats
|
||||
* **Active Record의 관심사 혼재와 부작용 위험**
|
||||
Active Record 패턴을 유지하면서 Django의 시그널(Signals)과 같은 기능을 혼용할 경우, 비즈니스 로직이 예기치 않게 곳곳으로 스며들거나(creeping in) 보이지 않는 부수 효과(side-effects)를 발생시켜 시스템을 통제 불능 상태로 만들 수 있는 심각한 안티 패턴을 초래할 수 있다 [1, 5].
|
||||
* **Repository 패턴의 계층 복잡성**
|
||||
Repository 패턴을 적용하면 Controller → Service → Repository로 이어지는 간접 참조(indirection) 및 계층화가 강제되므로, 단순한 로직을 처리할 때도 불필요한 스캐폴딩이 늘어나 애플리케이션 구조가 복잡해질 수 있다 [6]. 따라서 무조건적인 도메인 모델링보다는 도메인의 복잡성이 이를 요구할 만큼 충분히 높을 때 도입하는 것이 권장된다 [2].
|
||||
|
||||
---
|
||||
*Last updated: 2026-05-03*
|
||||
@@ -0,0 +1,189 @@
|
||||
---
|
||||
id: wiki-2026-0508-alliance-동맹
|
||||
title: Alliance (동맹)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Guild, Clan, Faction, 동맹]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [game-design, social-systems, mmo, architecture]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: Game backend (Colyseus, Nakama, custom)
|
||||
---
|
||||
|
||||
# Alliance (동맹)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 player-formed group — shared goals, shared resources, shared identity"**. 매 MMO/SLG 의 retention 핵심 system. 매 EverQuest guild (1999) → World of Warcraft guild (2004) → Lords Mobile/Rise of Kingdoms 동맹 (2017+). 매 2026 modern SLG (4X/RTS hybrid) 의 core loop driver — solo player retention < 7 days, alliance member retention > 90 days 의 typical metric.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 구조
|
||||
- **Membership tier**: Leader / Officers (R4/R5) / Members / Recruits.
|
||||
- **State**: roster, treasury, buff inventory, war declarations, territory.
|
||||
- **Permissions**: hierarchical RBAC — invite/kick/promote/demote/disband.
|
||||
- **Lifecycle**: create → recruit → grow → war → decline → disband.
|
||||
|
||||
### 매 server-authoritative invariants
|
||||
- 매 single alliance per player 의 enforcement (atomic).
|
||||
- Member cap (typical 50–100) — atomic check-and-insert.
|
||||
- Treasury balance — race-free debit/credit (transactional).
|
||||
- War state machine — pending/active/peace transitions.
|
||||
|
||||
### 매 응용
|
||||
1. **SLG 4X game** (Lords Mobile pattern) — alliance buffs, rallies, KvK.
|
||||
2. **MMO guild** (WoW pattern) — guild bank, calendar, perk levels.
|
||||
3. **Mobile RPG clan** (Clash of Clans pattern) — clan wars, donations.
|
||||
4. **Social fitness app** (Strava clubs) — challenges, leaderboards.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Schema (Postgres)
|
||||
```sql
|
||||
CREATE TABLE alliances (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tag VARCHAR(5) UNIQUE NOT NULL,
|
||||
name VARCHAR(40) NOT NULL,
|
||||
leader_id BIGINT NOT NULL REFERENCES players(id),
|
||||
member_cap SMALLINT NOT NULL DEFAULT 50,
|
||||
treasury_gold BIGINT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE alliance_members (
|
||||
player_id BIGINT PRIMARY KEY REFERENCES players(id),
|
||||
alliance_id BIGINT NOT NULL REFERENCES alliances(id) ON DELETE CASCADE,
|
||||
rank SMALLINT NOT NULL, -- 1=member..5=leader
|
||||
joined_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX ON alliance_members (alliance_id);
|
||||
```
|
||||
|
||||
### Atomic join (server)
|
||||
```typescript
|
||||
async function joinAlliance(playerId: bigint, allianceId: bigint) {
|
||||
return await db.tx(async (t) => {
|
||||
// 1. Player must not be in any alliance
|
||||
const existing = await t.oneOrNone(
|
||||
"SELECT 1 FROM alliance_members WHERE player_id=$1 FOR UPDATE", [playerId]);
|
||||
if (existing) throw new Error("ALREADY_IN_ALLIANCE");
|
||||
|
||||
// 2. Member cap check (lock alliance row)
|
||||
const a = await t.one(
|
||||
"SELECT member_cap, (SELECT COUNT(*) FROM alliance_members WHERE alliance_id=$1)::int AS n " +
|
||||
"FROM alliances WHERE id=$1 FOR UPDATE", [allianceId]);
|
||||
if (a.n >= a.member_cap) throw new Error("ALLIANCE_FULL");
|
||||
|
||||
await t.none(
|
||||
"INSERT INTO alliance_members(player_id, alliance_id, rank) VALUES($1,$2,1)",
|
||||
[playerId, allianceId]);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Permission check
|
||||
```typescript
|
||||
const PERMS = {
|
||||
invite: 2, // R2+
|
||||
kick: 3, // R3+
|
||||
promote:4, // R4+
|
||||
disband:5, // leader only
|
||||
} as const;
|
||||
|
||||
function can(memberRank: number, action: keyof typeof PERMS): boolean {
|
||||
return memberRank >= PERMS[action];
|
||||
}
|
||||
```
|
||||
|
||||
### Alliance chat (Redis pub/sub)
|
||||
```typescript
|
||||
// Publish
|
||||
await redis.publish(`alliance:${allianceId}:chat`,
|
||||
JSON.stringify({ from: playerId, msg, ts: Date.now() }));
|
||||
|
||||
// Subscribe (per connected client)
|
||||
const sub = redis.duplicate();
|
||||
await sub.subscribe(`alliance:${allianceId}:chat`, (raw) => {
|
||||
ws.send(raw);
|
||||
});
|
||||
```
|
||||
|
||||
### War declaration state machine
|
||||
```typescript
|
||||
type WarState = "PEACE" | "PENDING" | "ACTIVE" | "COOLDOWN";
|
||||
|
||||
const transitions: Record<WarState, WarState[]> = {
|
||||
PEACE: ["PENDING"],
|
||||
PENDING: ["ACTIVE", "PEACE"],
|
||||
ACTIVE: ["COOLDOWN"],
|
||||
COOLDOWN: ["PEACE"],
|
||||
};
|
||||
|
||||
function transition(from: WarState, to: WarState) {
|
||||
if (!transitions[from].includes(to))
|
||||
throw new Error(`INVALID_TRANSITION ${from}->${to}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Treasury (idempotent donation)
|
||||
```typescript
|
||||
async function donate(playerId: bigint, amt: bigint, idemKey: string) {
|
||||
await db.tx(async (t) => {
|
||||
const dup = await t.oneOrNone(
|
||||
"SELECT 1 FROM idempotency WHERE key=$1", [idemKey]);
|
||||
if (dup) return;
|
||||
await t.none("INSERT INTO idempotency(key) VALUES($1)", [idemKey]);
|
||||
await t.none("UPDATE players SET gold = gold - $2 WHERE id=$1 AND gold >= $2", [playerId, amt]);
|
||||
await t.none("UPDATE alliances SET treasury_gold = treasury_gold + $2 WHERE id=$1", [allianceId, amt]);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Member roster cache invalidation
|
||||
```typescript
|
||||
async function onMembershipChange(allianceId: bigint) {
|
||||
await redis.del(`alliance:${allianceId}:roster`);
|
||||
await redis.publish(`alliance:${allianceId}:events`, JSON.stringify({type:"ROSTER_CHANGED"}));
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Casual mobile, < 30 members | Single-shard SQL, simple roster |
|
||||
| MMO, 100+ members, real-time chat | Sharded SQL + Redis pub/sub |
|
||||
| Cross-server alliance war (KvK) | Event-sourced log + global service |
|
||||
| Persistent territory control | Server-authoritative grid + alliance ownership |
|
||||
|
||||
**기본값**: 매 Postgres alliance/member tables + Redis pub/sub for chat/presence + idempotent treasury operations.
|
||||
|
||||
## 🔗 Graph
|
||||
- 변형: [[Guild]] · [[Clan]] · [[Faction]]
|
||||
- Adjacent: [[Leaderboard]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 long-session retention 의 game (MMO, SLG, persistent world), 매 social cooperation 의 core mechanic.
|
||||
**언제 X**: 매 short-session arcade, 매 strict-PvP only without cooperation, 매 < 1k DAU 의 single-player feel.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Client-authoritative membership**: 매 cheat 의 trivial (forge join). 매 server-authoritative 만.
|
||||
- **No member cap**: 매 mega-alliance dominance — 매 game balance 의 destruction.
|
||||
- **Synchronous broadcast**: 매 large alliance (500+) 의 chat fan-out blocks. 매 async pub/sub 의 사용.
|
||||
- **Disband without grace period**: 매 leader 의 grief vector. 매 24h cooldown.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Lords Mobile design, EVE Online corporation system, WoW guild system, Clash of Clans clan system).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — full content (game alliance system architecture) |
|
||||
@@ -0,0 +1,203 @@
|
||||
---
|
||||
id: wiki-2026-0508-ambient-declarations
|
||||
title: Ambient Declarations
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [.d.ts, declare keyword, TypeScript ambient, Type definitions]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [typescript, types, declarations, dts, interop]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: TypeScript 5.7
|
||||
---
|
||||
|
||||
# Ambient Declarations
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 type-only declarations — 매 describe shapes that exist elsewhere"**. 매 TypeScript (Anders Hejlsberg, 2012) 매 since v0.8 매 `.d.ts` files. 매 DefinitelyTyped (2014) → @types/* npm scope (2016) → TS 4.x package "exports" + types (2021) → TS 5.7 (2026 stable) 매 `--isolatedDeclarations` 매 fast .d.ts emit.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 ambient 의 의미
|
||||
- **Ambient**: 매 declaration only, 매 no implementation. 매 emit 매 not 매 produce JS.
|
||||
- **`.d.ts` files**: 매 declarations only. 매 imports 매 type-erased.
|
||||
- **`declare` keyword**: 매 inside `.ts` files 매 declare 매 ambient binding.
|
||||
|
||||
### 매 use cases
|
||||
- Type 3rd-party JS library (no built-in types).
|
||||
- Type global runtime (browser, Node, custom).
|
||||
- Augment existing module/global.
|
||||
- Distribute types separate from JS (npm "types" field).
|
||||
- Emit minimal API surface from TS source.
|
||||
|
||||
### 매 응용
|
||||
1. **@types/* packages** — DefinitelyTyped community types.
|
||||
2. **Global augmentation** — extend `Window`, `process.env`.
|
||||
3. **Module augmentation** — add methods to existing module's interface.
|
||||
4. **Asset typing** — `*.svg` `*.css` import as module.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Basic ambient module (3rd-party JS)
|
||||
```typescript
|
||||
// types/legacy-lib.d.ts
|
||||
declare module "legacy-lib" {
|
||||
export function greet(name: string): string;
|
||||
export interface Config { timeout: number }
|
||||
export default class Client {
|
||||
constructor(cfg: Config);
|
||||
send(msg: string): Promise<void>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Ambient global
|
||||
```typescript
|
||||
// types/globals.d.ts
|
||||
declare global {
|
||||
var __APP_VERSION__: string;
|
||||
interface Window {
|
||||
analytics?: { track(event: string, props?: object): void };
|
||||
}
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
DATABASE_URL: string;
|
||||
REDIS_URL: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
export {}; // 매 file 의 module 의 만듦 — augmentation 매 valid
|
||||
```
|
||||
|
||||
### Module augmentation (extend existing)
|
||||
```typescript
|
||||
// add-toJSON.d.ts
|
||||
import "express";
|
||||
declare module "express" {
|
||||
interface Request {
|
||||
user?: { id: string; role: "admin" | "user" };
|
||||
requestId: string;
|
||||
}
|
||||
}
|
||||
// Now in any file: req.user is typed
|
||||
```
|
||||
|
||||
### Asset module typing
|
||||
```typescript
|
||||
// assets.d.ts
|
||||
declare module "*.svg" {
|
||||
const url: string;
|
||||
export default url;
|
||||
}
|
||||
declare module "*.css" {
|
||||
const classes: { readonly [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
declare module "*.json" {
|
||||
const value: unknown;
|
||||
export default value;
|
||||
}
|
||||
```
|
||||
|
||||
### Triple-slash directive (legacy, still used)
|
||||
```typescript
|
||||
/// <reference types="node" />
|
||||
/// <reference path="./vendor.d.ts" />
|
||||
```
|
||||
|
||||
### `declare` inside .ts file
|
||||
```typescript
|
||||
// app.ts
|
||||
declare const FEATURE_FLAGS: { newCheckout: boolean };
|
||||
declare function gtag(cmd: string, ...args: unknown[]): void;
|
||||
|
||||
if (FEATURE_FLAGS.newCheckout) gtag("event", "view_new_checkout");
|
||||
```
|
||||
|
||||
### Conditional types via ambient
|
||||
```typescript
|
||||
// react-augmentation.d.ts
|
||||
import "react";
|
||||
declare module "react" {
|
||||
interface CSSProperties {
|
||||
"--theme-color"?: string;
|
||||
"--theme-radius"?: string;
|
||||
}
|
||||
}
|
||||
// Now: <div style={{ "--theme-color": "blue" }}/> typechecks
|
||||
```
|
||||
|
||||
### tsconfig declarations
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"emitDeclarationOnly": false,
|
||||
"isolatedDeclarations": true,
|
||||
"types": ["node", "vitest/globals"],
|
||||
"typeRoots": ["./node_modules/@types", "./types"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Library author (publish .d.ts)
|
||||
```json
|
||||
// package.json
|
||||
{
|
||||
"name": "my-lib",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.cjs"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| 3rd-party JS lib, no types | Check @types/lib first; else write `.d.ts` |
|
||||
| Add field to req/res in Express/Fastify | Module augmentation |
|
||||
| Type custom Vite/Webpack asset import | Wildcard module decl |
|
||||
| Globals (browser/Node) | `declare global { ... }` + `export {}` |
|
||||
| Library publish | TS source → emit .d.ts (declaration: true) |
|
||||
|
||||
**기본값**: 매 prefer @types/* package, 매 fall back 매 hand-written `.d.ts`, 매 augmentation 매 last resort (subtle to debug).
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[TypeScript]] · [[TypeScript 타입 시스템 (TypeScript Type System)|Type System]]
|
||||
- 변형: [[Global Augmentation]]
|
||||
- 응용: [[DefinitelyTyped]]
|
||||
- Adjacent: [[tsconfig.json]] · [[Conditional Types]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 untyped JS interop, 매 global runtime typing, 매 library distribution, 매 framework extension points.
|
||||
**언제 X**: 매 internal TS code (use regular `interface`/`type`), 매 enforcement need (declarations 매 erased — runtime check 매 separate).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **`any` everywhere in .d.ts**: 매 type loss 의 propagation. 매 `unknown` + narrow.
|
||||
- **Augmenting without `export {}`**: 매 file 매 script 의 treated, 매 augmentation silently fails.
|
||||
- **Triple-slash in modern code**: 매 prefer `tsconfig "types"` array.
|
||||
- **Global pollution**: 매 every helper 매 global 의 declare → namespace clashes.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (TypeScript Handbook "Declaration Files", DefinitelyTyped contribution guide, TS 5.7 release notes).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — full content (.d.ts, declare, augmentation patterns) |
|
||||
@@ -0,0 +1,177 @@
|
||||
---
|
||||
id: wiki-2026-0508-apache-ignite
|
||||
title: Apache Ignite
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Ignite, In-memory data grid, GridGain]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [in-memory, data-grid, distributed-cache, sql, compute-grid]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: java
|
||||
framework: Apache Ignite 2.16
|
||||
---
|
||||
|
||||
# Apache Ignite
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 distributed in-memory data grid + compute grid + ANSI SQL"**. 매 GridGain (2007) → Apache Ignite (2014, donated). 매 2026 modern stack 은 Ignite 2.16 (GA mid-2025) / Ignite 3.x (preview, 매 new architecture: RAFT-based, ANSI SQL-first, 매 GridGain 9 commercial). 매 Hazelcast / Redis 의 alternative — 매 SQL + ACID transactions 의 differentiator.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 features
|
||||
- **In-memory key-value cache** — partitioned (sharded) or replicated.
|
||||
- **Distributed ANSI-99 SQL** — collocated joins, indexes, JDBC/ODBC.
|
||||
- **ACID transactions** — pessimistic / optimistic, distributed two-phase commit.
|
||||
- **Compute grid** — send code to data (Java/.NET/C++).
|
||||
- **Service grid** — deploy stateful services across cluster.
|
||||
- **Native persistence** — durable on-disk (since 2.1, 2017).
|
||||
- **Streaming** — continuous queries, data streamer.
|
||||
|
||||
### 매 architecture
|
||||
- **Topology**: server nodes + client/thin clients.
|
||||
- **Affinity**: rendezvous hashing, partition-to-node assignment.
|
||||
- **Backup**: synchronous/async backups per cache (RF=N).
|
||||
- **Discovery**: TcpDiscoverySpi (multicast / static / Kubernetes / ZooKeeper).
|
||||
- **Communication**: TcpCommunicationSpi.
|
||||
|
||||
### 매 응용
|
||||
1. **Hot cache layer** — in front of Postgres/Oracle, sub-ms reads.
|
||||
2. **Distributed SQL** — operational analytics across shards.
|
||||
3. **Compute grid** — financial risk calc, ML feature scoring at data.
|
||||
4. **Session storage** — JCache (JSR-107) compliant.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Cache config (Java)
|
||||
```java
|
||||
IgniteConfiguration cfg = new IgniteConfiguration();
|
||||
CacheConfiguration<Long, Order> cc = new CacheConfiguration<>("orders");
|
||||
cc.setCacheMode(CacheMode.PARTITIONED);
|
||||
cc.setBackups(1);
|
||||
cc.setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL);
|
||||
cc.setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
|
||||
cc.setIndexedTypes(Long.class, Order.class);
|
||||
cfg.setCacheConfiguration(cc);
|
||||
|
||||
Ignite ignite = Ignition.start(cfg);
|
||||
IgniteCache<Long, Order> orders = ignite.cache("orders");
|
||||
orders.put(1L, new Order(...));
|
||||
```
|
||||
|
||||
### Distributed SQL
|
||||
```java
|
||||
SqlFieldsQuery q = new SqlFieldsQuery(
|
||||
"SELECT o.id, c.name FROM \"orders\".Order o " +
|
||||
"JOIN \"customers\".Customer c ON o.customerId = c.id " +
|
||||
"WHERE o.status = ?")
|
||||
.setArgs("paid");
|
||||
try (var cur = orders.query(q)) {
|
||||
for (List<?> row : cur) System.out.println(row);
|
||||
}
|
||||
```
|
||||
|
||||
### Affinity collocation (cross-cache JOIN performance)
|
||||
```java
|
||||
@QuerySqlField(index = true)
|
||||
private Long customerId;
|
||||
|
||||
// Affinity key — rows with same customerId on same node
|
||||
@AffinityKeyMapped
|
||||
private Long customerId;
|
||||
// Now JOIN orders ↔ customers stays node-local
|
||||
```
|
||||
|
||||
### Transaction (pessimistic, repeatable read)
|
||||
```java
|
||||
try (Transaction tx = ignite.transactions().txStart(
|
||||
TransactionConcurrency.PESSIMISTIC,
|
||||
TransactionIsolation.REPEATABLE_READ)) {
|
||||
Account from = accounts.get(fromId);
|
||||
Account to = accounts.get(toId);
|
||||
if (from.balance < amount) throw new RuntimeException("INSUFFICIENT");
|
||||
from.balance -= amount; to.balance += amount;
|
||||
accounts.put(fromId, from);
|
||||
accounts.put(toId, to);
|
||||
tx.commit();
|
||||
}
|
||||
```
|
||||
|
||||
### Compute grid (broadcast)
|
||||
```java
|
||||
ignite.compute().broadcast(() -> {
|
||||
System.out.println("Hello from " + ignite.cluster().localNode().id());
|
||||
});
|
||||
// Send Lambda — Ignite peer-class-loads to all nodes
|
||||
```
|
||||
|
||||
### Continuous query (CDC-like)
|
||||
```java
|
||||
ContinuousQuery<Long, Order> qry = new ContinuousQuery<>();
|
||||
qry.setLocalListener(events -> {
|
||||
for (CacheEntryEvent<? extends Long, ? extends Order> e : events)
|
||||
System.out.println("Updated: " + e.getKey() + " → " + e.getValue());
|
||||
});
|
||||
qry.setRemoteFilterFactory(() -> e -> e.getValue().getStatus().equals("paid"));
|
||||
orders.query(qry);
|
||||
```
|
||||
|
||||
### Native persistence
|
||||
```java
|
||||
DataStorageConfiguration ds = new DataStorageConfiguration();
|
||||
ds.getDefaultDataRegionConfiguration().setPersistenceEnabled(true);
|
||||
ds.setStoragePath("/var/ignite/persistence");
|
||||
cfg.setDataStorageConfiguration(ds);
|
||||
// Restart-safe; in-memory speed + durability
|
||||
ignite.cluster().state(ClusterState.ACTIVE);
|
||||
```
|
||||
|
||||
### Thin client (lightweight, no peer-class-loading)
|
||||
```java
|
||||
ClientConfiguration cc = new ClientConfiguration().setAddresses("ignite:10800");
|
||||
try (IgniteClient c = Ignition.startClient(cc)) {
|
||||
ClientCache<Long, Order> orders = c.getOrCreateCache("orders");
|
||||
orders.put(1L, new Order(...));
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Tool |
|
||||
|---|---|
|
||||
| Pure key-value cache, simple | Redis |
|
||||
| K-V + distributed events, JVM | Hazelcast |
|
||||
| K-V + ANSI SQL + ACID + compute grid | Ignite |
|
||||
| In-process cache | Caffeine |
|
||||
| Cloud-native managed | ElastiCache / Memorystore / GridGain Cloud |
|
||||
|
||||
**기본값**: 매 Redis 매 simple cache, 매 Ignite 매 SQL+ACID+compute integrated, 매 Hazelcast 매 JVM-native event-driven.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[In-Memory Data Grid]] · [[Distributed Cache]]
|
||||
- 변형: [[GridGain]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 sub-ms latency + SQL + ACID 의 simultaneous requirement, 매 compute-near-data, 매 JVM ecosystem.
|
||||
**언제 X**: 매 simple cache only (Redis cheaper), 매 non-JVM stack (limited tooling), 매 small data (<10GB, single node fine).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **No backups**: 매 node loss → data loss. 매 setBackups(≥1).
|
||||
- **Cross-cache JOIN without affinity**: 매 network shuffle, 매 query 의 slow.
|
||||
- **Synchronous replication everywhere**: 매 latency. 매 PRIMARY_SYNC + async backup balance.
|
||||
- **Mixing partitioned + replicated joins carelessly**: 매 broadcast amplification.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Apache Ignite docs 2.16, GridGain documentation, ASF Ignite 3.x roadmap).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — full content (Ignite cache, SQL, transactions, compute grid) |
|
||||
@@ -0,0 +1,176 @@
|
||||
---
|
||||
id: wiki-2026-0508-append-only-log
|
||||
title: Append-only Log
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Commit log, WAL, Event log, Immutable log]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [log, kafka, event-sourcing, wal, storage]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: java
|
||||
framework: Kafka, Pulsar, Postgres WAL
|
||||
---
|
||||
|
||||
# Append-only Log
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 sequence of immutable events — write once, read many"**. 매 database WAL (1980s) → distributed (LinkedIn Kafka 2011) → event sourcing 의 backbone. 매 2026 modern stack 은 Kafka 3.7 (KRaft, no ZK) / Redpanda (Raft, C++) / Pulsar 3.x (BookKeeper) / Postgres logical replication / WarpStream (S3-backed Kafka).
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 properties
|
||||
- **Append-only**: 매 mutation 의 forbid. 매 corrections via new compensating event.
|
||||
- **Ordered**: monotonic offset/sequence per partition.
|
||||
- **Durable**: fsync, replicated (typical RF=3, ack=all).
|
||||
- **Replayable**: consumers re-read from any offset.
|
||||
- **Retention**: time-based, size-based, or compaction (key-based latest).
|
||||
|
||||
### 매 use cases
|
||||
- **Database WAL** — Postgres pg_wal, MySQL binlog. Crash recovery.
|
||||
- **Event sourcing** — domain events as source of truth, projections rebuild state.
|
||||
- **CDC** — Debezium reads DB log → Kafka → consumers.
|
||||
- **Stream processing** — Flink/Kafka Streams stateful aggregations.
|
||||
- **Audit log** — tamper-evident with hash chain.
|
||||
|
||||
### 매 응용
|
||||
1. **Kafka topic** — 7-day retention, multi-consumer fan-out.
|
||||
2. **Event-sourced aggregate** — order state from order_events.
|
||||
3. **Outbox pattern** — DB transaction + log entry → reliable event publish.
|
||||
4. **Time-travel debugging** — replay from offset N.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Kafka producer (idempotent + transactional)
|
||||
```java
|
||||
Properties p = new Properties();
|
||||
p.put("bootstrap.servers", "broker:9092");
|
||||
p.put("enable.idempotence", "true");
|
||||
p.put("acks", "all");
|
||||
p.put("transactional.id", "orders-producer-1");
|
||||
KafkaProducer<String,String> prod = new KafkaProducer<>(p, new StringSer(), new StringSer());
|
||||
prod.initTransactions();
|
||||
|
||||
prod.beginTransaction();
|
||||
prod.send(new ProducerRecord<>("orders", orderId, json));
|
||||
prod.send(new ProducerRecord<>("audit", orderId, audit));
|
||||
prod.commitTransaction();
|
||||
```
|
||||
|
||||
### Consumer (offset commit after process)
|
||||
```java
|
||||
KafkaConsumer<String,String> c = new KafkaConsumer<>(props);
|
||||
c.subscribe(List.of("orders"));
|
||||
while (true) {
|
||||
ConsumerRecords<String,String> recs = c.poll(Duration.ofSeconds(1));
|
||||
for (var r : recs) processOrder(r.value());
|
||||
c.commitSync(); // at-least-once
|
||||
}
|
||||
```
|
||||
|
||||
### Event sourcing aggregate
|
||||
```typescript
|
||||
type OrderEvent =
|
||||
| { type: "Created", id: string, items: Item[] }
|
||||
| { type: "Paid", amount: number }
|
||||
| { type: "Shipped", trackingId: string };
|
||||
|
||||
function applyEvent(state: Order, e: OrderEvent): Order {
|
||||
switch (e.type) {
|
||||
case "Created": return { ...state, id: e.id, items: e.items, status: "pending" };
|
||||
case "Paid": return { ...state, status: "paid", paidAmount: e.amount };
|
||||
case "Shipped": return { ...state, status: "shipped", tracking: e.trackingId };
|
||||
}
|
||||
}
|
||||
|
||||
const state = events.reduce(applyEvent, {} as Order);
|
||||
```
|
||||
|
||||
### Outbox pattern (Postgres + Debezium)
|
||||
```sql
|
||||
BEGIN;
|
||||
INSERT INTO orders(id, status) VALUES ('abc', 'pending');
|
||||
INSERT INTO outbox(aggregate_id, event_type, payload)
|
||||
VALUES ('abc', 'OrderCreated', '{"id":"abc",...}'::jsonb);
|
||||
COMMIT;
|
||||
-- Debezium tails pg_wal → publishes outbox row → Kafka 'orders' topic
|
||||
```
|
||||
|
||||
### Log compaction (Kafka)
|
||||
```bash
|
||||
# Topic config: cleanup.policy=compact
|
||||
# Same key keeps only latest value → materialize current state
|
||||
kafka-configs.sh --alter --entity-type topics --entity-name user-profiles \
|
||||
--add-config cleanup.policy=compact,min.cleanable.dirty.ratio=0.1
|
||||
```
|
||||
|
||||
### Hash-chained audit log
|
||||
```python
|
||||
import hashlib, json
|
||||
|
||||
def append(prev_hash: str, event: dict) -> tuple[str, dict]:
|
||||
record = {"prev": prev_hash, "event": event, "ts": time.time()}
|
||||
h = hashlib.sha256(json.dumps(record, sort_keys=True).encode()).hexdigest()
|
||||
return h, {**record, "hash": h}
|
||||
|
||||
# Tamper-evident: any modification breaks chain
|
||||
```
|
||||
|
||||
### Postgres logical replication slot
|
||||
```sql
|
||||
SELECT pg_create_logical_replication_slot('app_slot', 'pgoutput');
|
||||
-- Stream WAL changes to consumer (CDC)
|
||||
SELECT * FROM pg_logical_slot_get_changes('app_slot', NULL, NULL);
|
||||
```
|
||||
|
||||
### Snapshot + tail (event sourcing optimization)
|
||||
```typescript
|
||||
async function loadAggregate(id: string): Promise<Order> {
|
||||
const snap = await snapStore.get(id); // periodic snapshot
|
||||
const events = await eventStore.read(id, snap?.version ?? 0);
|
||||
return events.reduce(applyEvent, snap?.state ?? {});
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | System |
|
||||
|---|---|
|
||||
| High-throughput streaming, multi-consumer | Kafka / Redpanda |
|
||||
| Geo-replicated, tiered storage | Pulsar / WarpStream |
|
||||
| Event-sourced single service | EventStoreDB / Postgres + outbox |
|
||||
| Database CDC | Debezium → Kafka |
|
||||
| Tamper-evident audit | Hash-chain + signed |
|
||||
|
||||
**기본값**: 매 Kafka (or Redpanda for ops simplicity) 매 distributed log, 매 Postgres WAL + outbox 매 single-service.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Distributed Systems]]
|
||||
- 변형: [[Kafka]] · [[WAL]] · [[Event Store]]
|
||||
- 응용: [[Event Sourcing]] · [[CDC]] · [[CQRS]]
|
||||
- Adjacent: [[Stream-Processing-Architectures|Stream Processing]] · [[Idempotency]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 audit/replay 요구, 매 multiple consumer/projection, 매 temporal queries, 매 reliable event publishing.
|
||||
**언제 X**: 매 simple CRUD without history, 매 strong consistency snapshot only, 매 storage cost-sensitive (logs grow).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Mutating past events**: 매 invariant violation. 매 compensating event 의 emit.
|
||||
- **Unbounded retention without compaction**: 매 storage explosion.
|
||||
- **Synchronous replay on every read**: 매 latency. 매 snapshot + tail.
|
||||
- **Single-partition Kafka topic**: 매 throughput cap. 매 partition by key.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Jay Kreps "The Log" 2013, Kafka docs, Postgres WAL docs, Greg Young event sourcing).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — full content (Kafka, event sourcing, WAL, outbox) |
|
||||
@@ -0,0 +1,201 @@
|
||||
---
|
||||
id: wiki-2026-0508-architectural-violations
|
||||
title: Architectural Violations
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Architecture Erosion, Architecture Drift, Layer Violations, Dependency Violations]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [architecture, violations, erosion, drift, archunit]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: java/kotlin/typescript
|
||||
framework: ArchUnit / Sonargraph / dependency-cruiser
|
||||
---
|
||||
|
||||
# Architectural Violations
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 architectural violation 의 intended architecture 와 actual code 사이의 deviation"**. Perry & Wolf 의 *erosion* (decay over time) + *drift* (unauthorized addition) 의 distinguishing — 매 erosion 의 explicit decay, drift 의 silent. 매 modern detection 의 fitness function (ArchUnit, dep-cruiser, Sonargraph) + reflexion model — 매 manual review 의 too-late.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 violation 의 종류
|
||||
- **Layer violation**: domain → infrastructure (wrong direction).
|
||||
- **Module violation**: one module imports private internals of another.
|
||||
- **Dependency cycle**: A → B → C → A.
|
||||
- **Convention violation**: `*Service` not in service package.
|
||||
- **Forbidden API**: `java.util.Date`, `console.log` in production code.
|
||||
- **Coupling violation**: too many afferent / efferent dependencies.
|
||||
- **Cohesion violation**: feature smeared across many modules.
|
||||
- **Cross-bounded-context leakage**: shared domain model between contexts.
|
||||
|
||||
### 매 detection mechanism
|
||||
1. **Static structural**: ArchUnit, dep-cruiser, Sonargraph (parse + graph).
|
||||
2. **Reflexion model**: actual graph diff with intended graph.
|
||||
3. **Runtime**: trace analysis (OpenTelemetry) for unexpected service-to-service calls.
|
||||
4. **Reviewer-driven**: ADR conformance check in PR.
|
||||
5. **Visualization**: dependency matrix, sunburst, codecity.
|
||||
|
||||
### 매 lifecycle
|
||||
1. **Detect** (CI fails or alert).
|
||||
2. **Classify** (true violation vs intentional exception).
|
||||
3. **Triage** (debt vs immediate fix).
|
||||
4. **Fix** (refactor or update intended architecture / ADR).
|
||||
5. **Prevent regression** (add fitness function).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Detect layer violation — ArchUnit
|
||||
```kotlin
|
||||
@AnalyzeClasses(packages = ["com.acme"], importOptions = [DoNotIncludeTests::class])
|
||||
class LayerViolationTest {
|
||||
@ArchTest
|
||||
val domain_independent: ArchRule = noClasses()
|
||||
.that().resideInAPackage("..domain..")
|
||||
.should().dependOnClassesThat().resideInAnyPackage(
|
||||
"..application..", "..infrastructure..", "..web..")
|
||||
.because("Domain is the innermost layer (Hexagonal)")
|
||||
|
||||
@ArchTest
|
||||
val no_repo_in_controller: ArchRule = noClasses()
|
||||
.that().resideInAPackage("..web..")
|
||||
.should().dependOnClassesThat().resideInAPackage("..persistence..")
|
||||
}
|
||||
```
|
||||
|
||||
### Detect cycle — dependency-cruiser (TS)
|
||||
```bash
|
||||
npx depcruise --validate .dependency-cruiser.cjs src \
|
||||
--output-type err-html --output-to depgraph.html
|
||||
# Exit 1 if cycles found; html visualizes offending edges
|
||||
```
|
||||
|
||||
### Reflexion model — intended vs actual (jQAssistant + Cypher)
|
||||
```cypher
|
||||
// Intended: domain → no-deps; application → domain; infra → application
|
||||
// Detect any class in :Domain depending on :Infra (forbidden)
|
||||
MATCH (a:Class)-[:DEPENDS_ON]->(b:Class)
|
||||
WHERE a.layer = "Domain" AND b.layer = "Infrastructure"
|
||||
RETURN a.fqn AS violator, b.fqn AS forbidden
|
||||
```
|
||||
|
||||
### Runtime violation — unauthorized service call (OpenTelemetry + Tempo)
|
||||
```promql
|
||||
# Alert if service A calls service C (intended only A→B, B→C)
|
||||
sum(rate(traces_spanmetrics_calls_total{
|
||||
client="service-a", server="service-c"
|
||||
}[5m])) > 0
|
||||
```
|
||||
|
||||
### Cyclomatic + afferent/efferent metric (Sonargraph)
|
||||
```xml
|
||||
<!-- sonargraph-architect rule -->
|
||||
<architecture>
|
||||
<layer name="Domain" />
|
||||
<layer name="App" includes="Domain" />
|
||||
<layer name="Infra" includes="App" />
|
||||
<connector from="App" to="Domain" />
|
||||
<connector from="Infra" to="App" />
|
||||
<metric type="afferent_coupling" max="20" />
|
||||
<metric type="cyclic_groups" max="0" />
|
||||
</architecture>
|
||||
```
|
||||
|
||||
### Triage workflow — annotate intentional exception
|
||||
```kotlin
|
||||
// When violation is *intended* (rare), document explicitly via ADR + suppress
|
||||
@SuppressArchTest(
|
||||
rule = "domain_independent",
|
||||
reason = "ADR-0034: bridge to legacy DAO during 6-month migration",
|
||||
expiresOn = "2026-12-01"
|
||||
)
|
||||
class LegacyBridgeAdapter { /* ... */ }
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// Companion test enforces expiry
|
||||
@ArchTest
|
||||
val no_expired_suppressions: ArchRule = noClasses()
|
||||
.should().beAnnotatedWith(SuppressArchTest::class.java)
|
||||
.andShould(haveExpiredSuppression()) // custom condition
|
||||
```
|
||||
|
||||
### Visualize violations — dependency matrix (D3)
|
||||
```javascript
|
||||
// scripts/depmatrix.mjs — render violation heatmap
|
||||
import { cruise } from "dependency-cruiser";
|
||||
import fs from "node:fs";
|
||||
const r = await cruise(["src"], { ruleSet: require("./.dependency-cruiser.cjs") });
|
||||
const violations = r.output.summary.violations;
|
||||
const matrix = buildAdjacencyMatrix(r.output.modules, violations);
|
||||
fs.writeFileSync("violations.json", JSON.stringify(matrix));
|
||||
// Then render with d3-matrix or observable-plot
|
||||
```
|
||||
|
||||
### CI gating with delta — only new violations fail
|
||||
```bash
|
||||
# Compare current vs main — fail PR only on new violations (not legacy debt)
|
||||
npx depcruise src --config .dependency-cruiser.cjs --output-type json > head.json
|
||||
git checkout main -- .
|
||||
npx depcruise src --config .dependency-cruiser.cjs --output-type json > main.json
|
||||
node scripts/diff-violations.mjs main.json head.json # exit 1 on new violations
|
||||
```
|
||||
|
||||
### Erosion KPI dashboard (per service / quarter)
|
||||
```sql
|
||||
-- violations_history table (populated nightly by CI)
|
||||
SELECT
|
||||
service,
|
||||
DATE_TRUNC('week', detected_at) AS wk,
|
||||
COUNT(*) FILTER (WHERE severity='error') AS errs,
|
||||
COUNT(*) FILTER (WHERE severity='warn') AS warns
|
||||
FROM violations_history
|
||||
WHERE detected_at > NOW() - INTERVAL '12 weeks'
|
||||
GROUP BY service, wk
|
||||
ORDER BY service, wk;
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| New violation detected in PR | Block merge until fixed or ADR exception added |
|
||||
| Legacy debt — many existing violations | Baseline & gate on delta (no new) |
|
||||
| Intentional bridge | Suppress + ADR + expiry date |
|
||||
| Runtime call mismatch | Trace-based alerting + service mesh policy |
|
||||
| Unclear if violation | Run reflexion model — document intended first |
|
||||
|
||||
**기본값**: ArchUnit/dep-cruiser at PR time + delta gating on legacy code + ADR-tracked suppressions with expiry.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Software Architecture]] · [[Architectural-Constraint-Enforcement]]
|
||||
- 변형: [[Architecture Erosion]] · [[Architecture Drift]]
|
||||
- 응용: [[Architecture Review (아키텍처 및 설계 리뷰)]] · [[Architecture_Refactor]] · [[Technical Debt]]
|
||||
- Adjacent: [[ArchUnit]] · [[OpenTelemetry]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: classify violation as erosion vs drift vs intentional, draft suppression ADR with expiry, generate refactor plan from violation report, summarize weekly violations dashboard for tech lead.
|
||||
**언제 X**: do not blindly accept LLM "this violation is fine" — every suppression needs ADR + expiry; LLM judgment is suggestion, not authority.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Suppression without expiry**: permanent escape hatch — original intent lost.
|
||||
- **No baseline**: blocking PRs on existing legacy violations buries team.
|
||||
- **Documentation-only intended architecture**: cannot detect drift — no ground truth.
|
||||
- **One-shot detection**: run once, never again — violations re-grow.
|
||||
- **Fix-the-symptom**: rename file vs fix actual coupling.
|
||||
- **Auto-suppress**: tool generates suppressions silently — true violations hidden.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Perry & Wolf "Foundations for the Study of Software Architecture" 1992, Murphy "Reflexion Models" 1995, ArchUnit / Sonargraph / dependency-cruiser docs).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — erosion vs drift, reflexion model, delta gating |
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
---
|
||||
id: wiki-2026-0508-architectural-constraint-enforce
|
||||
title: Architectural Constraint Enforcement
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [archunit, dependency-cruiser, fitness-functions]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [architecture, fitness-functions, archunit, constraints]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: java
|
||||
framework: archunit
|
||||
---
|
||||
|
||||
# Architectural Constraint Enforcement
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 architecture 의 자동으로 enforce 한다"**. 매 ArchUnit (2018, Java/Kotlin), Dependency-Cruiser (JS/TS), NetArchTest (.NET), Konsist (Kotlin) 의 fitness function 의 CI 통합. 매 *Building Evolutionary Architectures* (Ford, Parsons, Kua, 2017; 2nd ed 2023) 의 paradigm.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 enforced rules (typical)
|
||||
- **Layer dependency**: 매 controller → service → repository — 매 reverse 의 X.
|
||||
- **Package isolation**: `domain` 의 framework import 의 X.
|
||||
- **Naming**: 매 `*Service` class 의 `service` package 의 only.
|
||||
- **Cyclic dependency**: 매 always X.
|
||||
- **API surface**: 매 `internal/*` 의 external module 의 import 의 X.
|
||||
|
||||
### 매 fitness function categories (Ford)
|
||||
- **Atomic vs Holistic**: single attribute / system-wide.
|
||||
- **Triggered vs Continual**: CI gate / runtime probe.
|
||||
- **Static vs Dynamic**: code analysis / runtime metric.
|
||||
- **Automated vs Manual**: prefer automated.
|
||||
|
||||
### 매 응용
|
||||
1. CI gate — 매 PR 의 violation 의 block.
|
||||
2. Refactor safety — 매 large refactor 의 invariant 의 hold.
|
||||
3. Onboarding — 매 implicit rule 의 explicit code 의 됨.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### ArchUnit — layer enforcement (Java)
|
||||
```java
|
||||
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;
|
||||
import com.tngtech.archunit.junit.AnalyzeClasses;
|
||||
import com.tngtech.archunit.junit.ArchTest;
|
||||
import com.tngtech.archunit.lang.ArchRule;
|
||||
|
||||
@AnalyzeClasses(packages = "com.acme.banking")
|
||||
class LayerArchTest {
|
||||
@ArchTest
|
||||
static final ArchRule controllers_only_call_services =
|
||||
classes().that().resideInAPackage("..controller..")
|
||||
.should().onlyDependOnClassesThat()
|
||||
.resideInAnyPackage("..controller..", "..service..", "java..", "org.springframework..");
|
||||
|
||||
@ArchTest
|
||||
static final ArchRule no_cycles =
|
||||
slices().matching("..banking.(*)..").should().beFreeOfCycles();
|
||||
|
||||
@ArchTest
|
||||
static final ArchRule services_named_correctly =
|
||||
classes().that().resideInAPackage("..service..")
|
||||
.and().areNotInterfaces()
|
||||
.should().haveSimpleNameEndingWith("Service");
|
||||
}
|
||||
```
|
||||
|
||||
### Dependency-Cruiser (TypeScript)
|
||||
```js
|
||||
// .dependency-cruiser.cjs
|
||||
module.exports = {
|
||||
forbidden: [
|
||||
{
|
||||
name: 'no-circular',
|
||||
severity: 'error',
|
||||
from: {},
|
||||
to: { circular: true }
|
||||
},
|
||||
{
|
||||
name: 'domain-pure',
|
||||
severity: 'error',
|
||||
from: { path: '^src/domain' },
|
||||
to: { path: '^(src/infra|node_modules/express)' }
|
||||
},
|
||||
{
|
||||
name: 'no-test-in-prod',
|
||||
severity: 'error',
|
||||
from: { pathNot: '\\.test\\.ts$' },
|
||||
to: { path: '\\.test\\.ts$' }
|
||||
}
|
||||
],
|
||||
options: { tsConfig: { fileName: 'tsconfig.json' } }
|
||||
};
|
||||
```
|
||||
|
||||
```bash
|
||||
npx depcruise src --config .dependency-cruiser.cjs
|
||||
npx depcruise src --output-type dot | dot -T svg > deps.svg
|
||||
```
|
||||
|
||||
### NetArchTest (.NET 8)
|
||||
```csharp
|
||||
using NetArchTest.Rules;
|
||||
using Xunit;
|
||||
|
||||
public class ArchitectureTests {
|
||||
[Fact]
|
||||
public void Domain_ShouldNotDependOnInfrastructure() {
|
||||
var result = Types.InAssembly(typeof(Domain.Marker).Assembly)
|
||||
.That().ResideInNamespace("Acme.Domain")
|
||||
.ShouldNot().HaveDependencyOn("Acme.Infrastructure")
|
||||
.GetResult();
|
||||
Assert.True(result.IsSuccessful, string.Join(",", result.FailingTypeNames ?? []));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Konsist (Kotlin)
|
||||
```kotlin
|
||||
import com.lemonappdev.konsist.api.Konsist
|
||||
import com.lemonappdev.konsist.api.verify.assertTrue
|
||||
|
||||
class CleanArchitectureTest {
|
||||
@Test
|
||||
fun `domain layer does not depend on data layer`() {
|
||||
Konsist.scopeFromProduction()
|
||||
.files
|
||||
.filter { it.packagee?.fullyQualifiedName?.contains("domain") == true }
|
||||
.assertTrue { file ->
|
||||
file.imports.none { it.name.contains(".data.") }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Go — go-arch-lint
|
||||
```yaml
|
||||
# .go-arch-lint.yml
|
||||
version: 3
|
||||
workdir: internal
|
||||
components:
|
||||
domain: { in: domain/** }
|
||||
app: { in: app/** }
|
||||
infra: { in: infra/** }
|
||||
deps:
|
||||
domain: {}
|
||||
app: { mayDependOn: [domain] }
|
||||
infra: { mayDependOn: [domain, app] }
|
||||
```
|
||||
|
||||
### Python — import-linter
|
||||
```ini
|
||||
# .importlinter
|
||||
[importlinter]
|
||||
root_package = acme
|
||||
|
||||
[importlinter:contract:layers]
|
||||
name = Layered architecture
|
||||
type = layers
|
||||
layers =
|
||||
acme.api
|
||||
acme.service
|
||||
acme.domain
|
||||
```
|
||||
|
||||
### CI integration (GitHub Actions)
|
||||
```yaml
|
||||
- name: Architecture tests
|
||||
run: |
|
||||
./gradlew archTest
|
||||
npx depcruise src --config .dependency-cruiser.cjs
|
||||
lint-imports
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| Stack | Tool |
|
||||
|---|---|
|
||||
| Java/Kotlin | ArchUnit, Konsist |
|
||||
| JS/TS | Dependency-Cruiser, eslint-plugin-boundaries |
|
||||
| .NET | NetArchTest |
|
||||
| Go | go-arch-lint |
|
||||
| Python | import-linter |
|
||||
| Polyglot | Sonargraph, Structure101 (commercial) |
|
||||
|
||||
**기본값**: 매 ArchUnit (JVM) / Dependency-Cruiser (Node) — 매 OSS + CI-first.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Software Architecture]] · [[Fitness Functions]]
|
||||
- 변형: [[Static-Analysis]]
|
||||
- 응용: [[CI CD]] · [[Architecture_Refactor]]
|
||||
- Adjacent: [[Architecture Erosion (아키텍처 침식)]] · [[Modular Monolith]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 plain English rule → ArchUnit/depcruise config 의 translation, 매 violation message 의 fix suggestion.
|
||||
**언제 X**: 매 architecture 의 design 의 LLM-only delegation — 매 human ownership 의 필수.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Test exists, never runs**: 매 CI 의 not wired — 매 dead rule.
|
||||
- **Over-broad rule**: 매 100% violations 의 noise — 매 graduated rollback (allowlist).
|
||||
- **Rule without rationale**: 매 ADR-less rule — 매 future deletion 의 blocker.
|
||||
- **Ignore-list explosion**: 매 exception 의 100+ — 매 architecture 의 already eroded 의 sign.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Ford et al., *Building Evolutionary Architectures* 2nd ed; ArchUnit user guide).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — fitness functions + ArchUnit/depcruise/NetArchTest patterns |
|
||||
@@ -0,0 +1,197 @@
|
||||
---
|
||||
id: wiki-2026-0508-architecture-description-아키텍처-명세
|
||||
title: Architecture Description (아키텍처 명세)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Architecture Description, 아키텍처 명세, AD, Software Architecture Document, SAD]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [architecture, documentation, c4, 4plus1-view, iso-42010]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: markdown
|
||||
framework: Structurizr / C4 / arc42
|
||||
---
|
||||
|
||||
# Architecture Description (아키텍처 명세)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 architecture description 은 system 의 multiple stakeholder concern 을 multiple view 로 documenting"**. ISO/IEC/IEEE 42010 standard 기반 — 매 stakeholder, concern, viewpoint, view, model 의 conceptual chain. 매 modern practice 는 4+1 (Kruchten) → C4 (Brown) → arc42 (Starke) → DAR (Decision Records) 의 layered combination.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 ISO/IEC/IEEE 42010 framework
|
||||
- **Stakeholder**: dev, ops, security, PM, customer — 매 다른 concern.
|
||||
- **Concern**: performance, security, deployability, cost, regulatory.
|
||||
- **Viewpoint**: concern 의 lens (e.g., "security viewpoint", "deployment viewpoint").
|
||||
- **View**: viewpoint 적용 결과의 specific artifact.
|
||||
- **Model**: view 안의 box-and-line / sequence / state diagram.
|
||||
|
||||
### 매 4+1 view (Kruchten 1995, still relevant)
|
||||
- **Logical view**: domain model, class, package — dev concern.
|
||||
- **Process view**: runtime behavior, threading, concurrency — perf concern.
|
||||
- **Development view**: module, layer, build — dev productivity.
|
||||
- **Physical view**: deployment topology — ops concern.
|
||||
- **+1 Scenario**: use cases tying 4 views together.
|
||||
|
||||
### 매 C4 model (Simon Brown, modern default)
|
||||
1. **Context (L1)**: system + external actor/system — non-technical stakeholder 용.
|
||||
2. **Container (L2)**: deployable unit (web app, API, DB) — tech overview.
|
||||
3. **Component (L3)**: container 내부 module — dev 용.
|
||||
4. **Code (L4, optional)**: class diagram — auto-gen, rarely manual.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Structurizr DSL (modern C4 standard, 2026)
|
||||
```dsl
|
||||
workspace "BankingApp" {
|
||||
model {
|
||||
customer = person "Customer" "A bank customer"
|
||||
bankingSystem = softwareSystem "Internet Banking" {
|
||||
webApp = container "Web App" "React SPA" "TypeScript/React"
|
||||
api = container "API" "REST backend" "Kotlin/Spring"
|
||||
db = container "Database" "Customer + tx data" "PostgreSQL 16"
|
||||
webApp -> api "Calls" "HTTPS/JSON"
|
||||
api -> db "Reads/writes" "JDBC"
|
||||
}
|
||||
customer -> webApp "Uses" "HTTPS"
|
||||
}
|
||||
views {
|
||||
systemContext bankingSystem { include * autoLayout }
|
||||
container bankingSystem { include * autoLayout }
|
||||
theme default
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### arc42 template skeleton (Markdown, 12 sections)
|
||||
```markdown
|
||||
# 1. Introduction & Goals
|
||||
## 1.1 Requirements Overview
|
||||
## 1.2 Quality Goals (top 3-5)
|
||||
## 1.3 Stakeholders
|
||||
|
||||
# 2. Architecture Constraints
|
||||
# 3. Context & Scope (C4 L1 here)
|
||||
# 4. Solution Strategy
|
||||
# 5. Building Block View (C4 L2/L3)
|
||||
# 6. Runtime View (sequence / activity)
|
||||
# 7. Deployment View
|
||||
# 8. Crosscutting Concepts (security, logging, i18n)
|
||||
# 9. Architecture Decisions (link to ADRs)
|
||||
# 10. Quality Requirements (scenarios)
|
||||
# 11. Risks & Technical Debt
|
||||
# 12. Glossary
|
||||
```
|
||||
|
||||
### ADR (Architecture Decision Record, Michael Nygard format)
|
||||
```markdown
|
||||
# ADR-0042: Use PostgreSQL over MongoDB for tx store
|
||||
|
||||
## Status
|
||||
Accepted (2026-04-15) — supersedes ADR-0021.
|
||||
|
||||
## Context
|
||||
Transactional integrity 의 critical. Document flexibility 의 secondary.
|
||||
PostgreSQL 16 의 JSONB columns 의 hybrid 의 enable.
|
||||
|
||||
## Decision
|
||||
PostgreSQL 16 with JSONB for flexible attributes.
|
||||
|
||||
## Consequences
|
||||
+ ACID guarantees, mature tooling, strong ecosystem.
|
||||
- Schema migrations more rigid than Mongo.
|
||||
- Team needs PG expertise (training budget allocated).
|
||||
```
|
||||
|
||||
### Mermaid C4 diagram (lightweight, GitHub-native)
|
||||
```mermaid
|
||||
C4Context
|
||||
title System Context — Banking
|
||||
Person(customer, "Customer")
|
||||
System(banking, "Banking System", "Online banking")
|
||||
System_Ext(email, "Email System", "SMTP")
|
||||
Rel(customer, banking, "Uses", "HTTPS")
|
||||
Rel(banking, email, "Sends notifications", "SMTP")
|
||||
```
|
||||
|
||||
### Architecture-as-code: PlantUML C4 macro
|
||||
```plantuml
|
||||
@startuml
|
||||
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
|
||||
|
||||
Person(user, "User")
|
||||
System_Boundary(c1, "App") {
|
||||
Container(spa, "SPA", "React 19")
|
||||
Container(api, "API", "Kotlin/Ktor")
|
||||
ContainerDb(db, "DB", "PostgreSQL 16")
|
||||
}
|
||||
Rel(user, spa, "Uses", "HTTPS")
|
||||
Rel(spa, api, "JSON/REST")
|
||||
Rel(api, db, "JDBC")
|
||||
@enduml
|
||||
```
|
||||
|
||||
### Quality scenario (ATAM-style, embeddable in AD)
|
||||
```yaml
|
||||
scenario: "API survives 10x traffic spike"
|
||||
source: External users
|
||||
stimulus: Sudden 10x request burst
|
||||
artifact: API container
|
||||
environment: Production, normal ops
|
||||
response: Auto-scale, no errors >0.1%
|
||||
response_measure:
|
||||
- p99 latency < 800ms
|
||||
- error_rate < 0.1%
|
||||
- autoscale_lag < 60s
|
||||
```
|
||||
|
||||
### Living docs — auto-extract from code (jQAssistant + Structurizr)
|
||||
```bash
|
||||
# Generate Structurizr workspace from code annotations
|
||||
./gradlew structurizrExport
|
||||
structurizr-cli push -workspace workspace.dsl \
|
||||
-id $WORKSPACE_ID -key $API_KEY -secret $API_SECRET
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Greenfield, small-mid team | C4 + arc42 + ADR |
|
||||
| Enterprise, regulatory | ISO 42010 strict + 4+1 |
|
||||
| OSS project | Mermaid C4 in README |
|
||||
| Microservices, many teams | Structurizr DSL + ADR per service |
|
||||
| Legacy reverse-engineered | Auto-gen (jQAssistant) + manual context |
|
||||
|
||||
**기본값**: C4 (Structurizr DSL) + arc42 sections + ADR — 매 modern combination.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Software Architecture]]
|
||||
- 변형: [[C4 Model (Architecture Documentation)]] · [[arc42]]
|
||||
- 응용: [[Architecture_Diagramming_Standards]] · [[Architecture Review (아키텍처 및 설계 리뷰)]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: bootstrap arc42 template from a codebase scan, generate C4 Container diagrams from package structure, draft ADR Context/Consequences from PR descriptions.
|
||||
**언제 X**: do not let an LLM author quality scenarios without measurable response criteria — vague AI-generated "should be fast" fails ATAM review.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Diagram-only AD**: pictures without prose context — stakeholder cannot infer intent.
|
||||
- **One-view-fits-all**: single deployment diagram trying to satisfy security, perf, dev concerns simultaneously.
|
||||
- **Stale Visio**: AD frozen on day 1, drifts from implementation within 6 months.
|
||||
- **Pseudo-UML**: ad-hoc boxes labeled "UML" with no notation discipline.
|
||||
- **Decision-less AD**: structures documented without WHY — readers cannot evaluate tradeoffs.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (ISO/IEC/IEEE 42010:2022, Kruchten 1995, Brown C4 spec, Starke arc42 v8.2).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — full AD spec with C4/4+1/arc42/ADR patterns |
|
||||
@@ -0,0 +1,188 @@
|
||||
---
|
||||
id: wiki-2026-0508-architecture-diagramming-standar
|
||||
title: Architecture Diagramming Standards
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [c4-model, arch-diagrams]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [architecture, diagrams, c4, documentation]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: mermaid
|
||||
framework: c4-plantuml
|
||||
---
|
||||
|
||||
# Architecture Diagramming Standards
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 diagram 의 audience 의 결정한다"**. 매 C4 model (Simon Brown, 2018) 의 Context → Container → Component → Code 의 4-level zoom 의 default standard 의 됨. 매 2026 의 PlantUML + Mermaid + Structurizr DSL 의 most-used toolchain.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 C4 Model levels
|
||||
- **Level 1 — System Context**: 매 system + users + external systems. 매 non-technical audience.
|
||||
- **Level 2 — Container**: 매 deployable units (web app, DB, queue). 매 technical overview.
|
||||
- **Level 3 — Component**: 매 container 안의 logical components. 매 dev team.
|
||||
- **Level 4 — Code**: 매 class diagram (rarely used — 매 IDE 의 generation 의 default).
|
||||
|
||||
### 매 supplementary diagrams
|
||||
- **Sequence**: 매 request flow / interaction.
|
||||
- **Deployment**: 매 infra topology (k8s, regions).
|
||||
- **Dynamic**: 매 runtime view, message sequence.
|
||||
- **State**: 매 entity lifecycle.
|
||||
|
||||
### 매 응용
|
||||
1. ADR 의 embedded diagram — 매 context.
|
||||
2. Onboarding 의 system overview.
|
||||
3. Incident postmortem 의 affected component highlight.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### C4 Context (Mermaid)
|
||||
```mermaid
|
||||
C4Context
|
||||
title System Context for Banking App
|
||||
Person(customer, "Customer", "Bank customer with accounts")
|
||||
System(banking, "Internet Banking", "Allows customers to view info")
|
||||
System_Ext(mainframe, "Mainframe", "Stores account, txn info")
|
||||
System_Ext(email, "Email System", "SMTP relay")
|
||||
Rel(customer, banking, "Uses", "HTTPS")
|
||||
Rel(banking, mainframe, "Reads/writes", "XML/HTTPS")
|
||||
Rel(banking, email, "Sends emails", "SMTP")
|
||||
```
|
||||
|
||||
### C4 Container (PlantUML + C4-PlantUML)
|
||||
```plantuml
|
||||
@startuml
|
||||
!include <C4/C4_Container>
|
||||
Person(user, "User")
|
||||
System_Boundary(c1, "Banking System") {
|
||||
Container(spa, "SPA", "React/TS", "UI")
|
||||
Container(api, "API", "Go/Gin", "JSON/HTTPS")
|
||||
ContainerDb(db, "Database", "Postgres 17", "Customers, accounts")
|
||||
Container(worker, "Worker", "Go", "Async jobs")
|
||||
ContainerQueue(mq, "Queue", "NATS JetStream")
|
||||
}
|
||||
Rel(user, spa, "Uses", "HTTPS")
|
||||
Rel(spa, api, "Calls", "JSON/HTTPS")
|
||||
Rel(api, db, "Reads/writes")
|
||||
Rel(api, mq, "Publishes")
|
||||
Rel(worker, mq, "Consumes")
|
||||
@enduml
|
||||
```
|
||||
|
||||
### Structurizr DSL (workspace-as-code)
|
||||
```
|
||||
workspace "Banking" {
|
||||
model {
|
||||
user = person "Customer"
|
||||
bank = softwareSystem "Banking" {
|
||||
spa = container "SPA" "React/TS"
|
||||
api = container "API" "Go/Gin"
|
||||
db = container "Database" "Postgres 17" "Database"
|
||||
}
|
||||
user -> spa "Uses"
|
||||
spa -> api "JSON/HTTPS"
|
||||
api -> db "SQL"
|
||||
}
|
||||
views {
|
||||
container bank "Containers" { include * autolayout lr }
|
||||
theme default
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sequence (Mermaid)
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor U as User
|
||||
participant S as SPA
|
||||
participant A as API
|
||||
participant D as DB
|
||||
U->>S: Click "Pay"
|
||||
S->>A: POST /payments
|
||||
A->>D: BEGIN; INSERT; COMMIT
|
||||
A-->>S: 201 Created
|
||||
S-->>U: Success toast
|
||||
```
|
||||
|
||||
### Deployment (Mermaid)
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph k8s[Kubernetes / us-east-1]
|
||||
api[API Pods x3]
|
||||
worker[Worker Pods x2]
|
||||
end
|
||||
subgraph rds[RDS]
|
||||
pg[(Postgres 17 Multi-AZ)]
|
||||
end
|
||||
ALB --> api
|
||||
api --> pg
|
||||
worker --> pg
|
||||
worker --> SQS
|
||||
```
|
||||
|
||||
### Diagrams as code (Python diagrams lib)
|
||||
```python
|
||||
from diagrams import Diagram, Cluster
|
||||
from diagrams.aws.compute import ECS
|
||||
from diagrams.aws.database import RDS
|
||||
from diagrams.aws.network import ELB
|
||||
|
||||
with Diagram("Web", show=False, direction="LR"):
|
||||
lb = ELB("lb")
|
||||
with Cluster("Services"):
|
||||
svc = [ECS("svc1"), ECS("svc2")]
|
||||
db = RDS("primary")
|
||||
lb >> svc >> db
|
||||
```
|
||||
|
||||
### Auto-generated from infra (Terraform → diagram)
|
||||
```bash
|
||||
# Inframap / Pluralith / Cloudcraft import
|
||||
terraform graph | dot -Tpng > infra.png
|
||||
inframap generate terraform.tfstate | dot -Tpng > clean.png
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Quick sketch | Excalidraw / Whiteboard |
|
||||
| Reusable, version-controlled | Mermaid (md-embedded) |
|
||||
| Strict C4 + theming | Structurizr DSL |
|
||||
| Cloud infra reality | Terraform → Inframap |
|
||||
| Slide deck / exec | PlantUML + C4 theme + export |
|
||||
|
||||
**기본값**: Mermaid (C4 plugin) — 매 GitHub/Obsidian native rendering.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Software Architecture]]
|
||||
- 변형: [[ADR]] · [[RFC]]
|
||||
- 응용: [[Onboarding]] · [[Postmortem]]
|
||||
- Adjacent: [[Architecture Review (아키텍처 및 설계 리뷰)]] · [[Architecture_Refactor]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: text spec → Mermaid/PlantUML 의 generation, code → C4 inference.
|
||||
**언제 X**: production diagram 의 unverified auto-generation — 매 hallucinated component 의 risk.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Box-and-line soup**: 매 layer/audience 의 mix 의 unreadable diagram.
|
||||
- **Stale .png in repo**: 매 source-less binary — 매 update 의 X. Mermaid/DSL 의 사용.
|
||||
- **Over-leveling**: 매 Level 4 (Code) 의 manually 의 maintain — 매 IDE 의 generate.
|
||||
- **No legend**: 매 shape/color semantics 의 없는 diagram — 매 reader 의 confusion.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Simon Brown, *Software Architecture for Developers*; c4model.com).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — C4 model + Mermaid/Structurizr/diagrams.py patterns |
|
||||
@@ -0,0 +1,198 @@
|
||||
---
|
||||
id: wiki-2026-0508-architecture-refactor
|
||||
title: Architecture Refactor
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [strangler-fig, big-bang-rewrite, modernization]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [architecture, refactor, strangler-fig, modernization]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: polyglot
|
||||
framework: strangler-fig
|
||||
---
|
||||
|
||||
# Architecture Refactor
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 incremental 의 always wins"**. 매 architecture refactor 의 default approach 의 Martin Fowler 의 Strangler Fig (2004) — 매 old system 의 around 의 new system 의 gradual 의 grow. 매 big-bang rewrite 의 historically failure rate >70% (Ousterhout, Brooks). 매 2026 의 typical pattern: 매 monolith → modular monolith → selective service extraction.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 refactor strategies
|
||||
- **Strangler Fig**: 매 facade 의 routing — 매 traffic 의 incrementally 의 new system 의 redirect.
|
||||
- **Branch by Abstraction**: 매 abstraction layer 의 introduce — 매 dual 의 implementation — 매 old 의 remove.
|
||||
- **Parallel Run**: 매 old + new 의 동시 실행 — 매 output 의 compare.
|
||||
- **Big Bang Rewrite**: 매 last resort — 매 only 매 system 의 small + scope 의 frozen 의 가능.
|
||||
|
||||
### 매 refactor 의 prerequisite
|
||||
- **Test coverage**: 매 characterization tests (Feathers) 의 minimum.
|
||||
- **Observability**: 매 metrics + traces 의 before/after diff.
|
||||
- **Feature flag**: 매 reversibility 의 prerequisite.
|
||||
- **ADR**: 매 decision rationale 의 documented.
|
||||
|
||||
### 매 응용
|
||||
1. Monolith → modular monolith — 매 module boundary 의 enforce (ArchUnit).
|
||||
2. Service extraction — 매 bounded context 의 따라.
|
||||
3. Database split — 매 most expensive — 매 last 의 do.
|
||||
4. Framework upgrade — 매 incremental migration.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Strangler Fig — Nginx routing facade
|
||||
```nginx
|
||||
upstream legacy { server legacy.internal:8080; }
|
||||
upstream new { server new.internal:8080; }
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
|
||||
# New endpoints — gradual rollout
|
||||
location /api/v2/orders { proxy_pass http://new; }
|
||||
location /api/v2/users { proxy_pass http://new; }
|
||||
|
||||
# Everything else still legacy
|
||||
location / { proxy_pass http://legacy; }
|
||||
}
|
||||
```
|
||||
|
||||
### Branch by Abstraction (Java)
|
||||
```java
|
||||
// Step 1 — extract interface
|
||||
interface PaymentGateway {
|
||||
Receipt charge(Order o);
|
||||
}
|
||||
|
||||
// Step 2 — wrap legacy
|
||||
class LegacyStripeGateway implements PaymentGateway { /* ... */ }
|
||||
|
||||
// Step 3 — new impl behind flag
|
||||
class AdyenGateway implements PaymentGateway { /* ... */ }
|
||||
|
||||
// Step 4 — feature-flag pick
|
||||
class GatewayFactory {
|
||||
PaymentGateway pick(String tenant) {
|
||||
return flags.isOn("adyen", tenant)
|
||||
? new AdyenGateway()
|
||||
: new LegacyStripeGateway();
|
||||
}
|
||||
}
|
||||
// Step 5 — once 100% rollout, delete LegacyStripeGateway
|
||||
```
|
||||
|
||||
### Parallel Run (shadow traffic)
|
||||
```ts
|
||||
async function chargeShadow(order: Order): Promise<Receipt> {
|
||||
const [primary, shadow] = await Promise.allSettled([
|
||||
legacy.charge(order),
|
||||
newImpl.charge(order) // never persists, just observes
|
||||
]);
|
||||
if (shadow.status === 'fulfilled' && primary.status === 'fulfilled') {
|
||||
diff.record('charge', primary.value, shadow.value);
|
||||
}
|
||||
return primary.status === 'fulfilled' ? primary.value : Promise.reject(primary.reason);
|
||||
}
|
||||
```
|
||||
|
||||
### Database Strangling — dual write + read switch
|
||||
```python
|
||||
# Phase 1: dual write
|
||||
def save_order(o: Order):
|
||||
legacy_db.insert(o)
|
||||
try:
|
||||
new_db.insert(o)
|
||||
except Exception as e:
|
||||
log.warn("shadow write failed", e=e)
|
||||
|
||||
# Phase 2: dual read + diff
|
||||
def get_order(id: str):
|
||||
a = legacy_db.get(id)
|
||||
b = new_db.get(id)
|
||||
if a != b: metrics.increment("order.read.diverge")
|
||||
return a # legacy still source of truth
|
||||
|
||||
# Phase 3: flip read source
|
||||
def get_order(id: str):
|
||||
return new_db.get(id)
|
||||
|
||||
# Phase 4: stop legacy write, decommission
|
||||
```
|
||||
|
||||
### Module extraction (modular monolith → service)
|
||||
```bash
|
||||
# 1. Codify module boundary first (ArchUnit)
|
||||
# 2. Replace direct calls with in-process interface
|
||||
# 3. Add feature flag: in-process vs HTTP/gRPC
|
||||
# 4. Deploy as separate process behind flag
|
||||
# 5. Remove in-process path
|
||||
```
|
||||
|
||||
### Characterization test (Feathers)
|
||||
```python
|
||||
import json, pytest
|
||||
|
||||
@pytest.mark.parametrize("fixture", load_fixtures("legacy_outputs/*.json"))
|
||||
def test_legacy_behavior_preserved(fixture):
|
||||
inputs = fixture["input"]
|
||||
expected = fixture["legacy_output"]
|
||||
actual = new_impl.run(**inputs)
|
||||
assert actual == expected, f"divergence: {fixture['id']}"
|
||||
```
|
||||
|
||||
### Refactor scoreboard (CI)
|
||||
```yaml
|
||||
# refactor-progress.yml — auto-generated dashboard
|
||||
metrics:
|
||||
- name: legacy-endpoints-remaining
|
||||
cmd: grep -r "@legacy" src/ | wc -l
|
||||
- name: new-coverage
|
||||
cmd: jacoco --module=new-impl
|
||||
- name: traffic-on-new
|
||||
promql: sum(rate(http_requests_total{service="new"}[5m]))
|
||||
/ sum(rate(http_requests_total[5m]))
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Strategy |
|
||||
|---|---|
|
||||
| Active legacy + traffic | Strangler Fig |
|
||||
| Library/abstraction swap | Branch by Abstraction |
|
||||
| Risk-critical (payment, billing) | Parallel Run |
|
||||
| DB schema change | Dual-write + flip read |
|
||||
| Tiny system, frozen scope | Big Bang (rare) |
|
||||
| Distributed monolith | Reverse first → modular monolith → extract |
|
||||
|
||||
**기본값**: Strangler Fig + feature flag + parallel-run on critical paths.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Software Architecture]] · [[Refactoring_Best_Practices|Refactoring]]
|
||||
- 변형: [[Strangler-Fig]] · [[Branch-by-Abstraction]]
|
||||
- 응용: [[Modular Monolith]] · [[Microservices]]
|
||||
- Adjacent: [[Architecture Erosion (아키텍처 침식)]] · [[Architectural-Constraint-Enforcement]] · [[Architecture Review (아키텍처 및 설계 리뷰)]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 legacy code → modern equivalent translation, 매 codemod plan generation, 매 ADR draft.
|
||||
**언제 X**: 매 production cutover decision — 매 human + traffic data 의 필수.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Big bang rewrite**: 매 70%+ failure rate — 매 last resort.
|
||||
- **Refactor without tests**: 매 silent regression 의 guarantee.
|
||||
- **No flag, no rollback**: 매 forward-only deploy — 매 incident magnification.
|
||||
- **Premature service extraction**: 매 distributed monolith 의 worst-of-both.
|
||||
- **Stop midway**: 매 두 system 의 forever maintain — 매 cost 의 doubled.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Fowler — *StranglerFigApplication*; Feathers — *Working Effectively with Legacy Code*; Newman — *Monolith to Microservices*).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Strangler Fig + Branch-by-Abstraction + parallel-run patterns |
|
||||
@@ -0,0 +1,203 @@
|
||||
---
|
||||
id: wiki-2026-0508-arrangement-and-composition
|
||||
title: Arrangement and Composition
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [composition-over-inheritance, function-composition, ui-composition]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [composition, design, architecture, functional]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: react
|
||||
---
|
||||
|
||||
# Arrangement and Composition
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 small piece 의 arrange 의 emergent capability 의 만든다"**. 매 *Design Patterns* (GoF, 1994) 의 "favor composition over inheritance" 의 most-cited principle. 매 2026 의 React Server Components, Unix pipes, fp-ts pipe, kubernetes operator composition 의 same idea 의 different incarnation.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 composition modes
|
||||
- **Function composition**: `f ∘ g` — output 의 input 의 chain.
|
||||
- **Object composition**: 매 has-a > is-a — delegation, mixin, trait.
|
||||
- **Component composition**: 매 React/Vue children, slot. 매 layout 의 declarative.
|
||||
- **Process composition**: 매 Unix pipes, k8s sidecar, DAG (Airflow).
|
||||
- **Type composition**: 매 union, intersection, generics.
|
||||
|
||||
### 매 arrangement axes
|
||||
- **Horizontal**: 매 sibling 의 parallel — fan-out / fan-in.
|
||||
- **Vertical**: 매 layer / pipeline — sequential.
|
||||
- **Tree**: 매 hierarchy — 매 component tree / DOM.
|
||||
- **Graph**: 매 arbitrary edges — 매 dataflow / DAG.
|
||||
|
||||
### 매 응용
|
||||
1. UI — 매 component slot, render prop, children-as-function.
|
||||
2. Data pipeline — 매 dbt model graph, Airflow DAG.
|
||||
3. Function-level — 매 pipe / compose / Result.flatMap.
|
||||
4. Service mesh — 매 sidecar composition (Envoy + app).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Function composition (TypeScript / fp-ts)
|
||||
```ts
|
||||
import { pipe, flow } from 'fp-ts/function';
|
||||
|
||||
const trim = (s: string) => s.trim();
|
||||
const lower = (s: string) => s.toLowerCase();
|
||||
const slug = (s: string) => s.replace(/\s+/g, '-');
|
||||
|
||||
const slugify = flow(trim, lower, slug); // point-free
|
||||
const out = pipe(' Hello World ', trim, lower, slug); // value-first
|
||||
console.log(slugify(' Hello World ')); // "hello-world"
|
||||
```
|
||||
|
||||
### Composition over inheritance (Go — embedding)
|
||||
```go
|
||||
type Logger struct{ prefix string }
|
||||
func (l Logger) Log(msg string) { fmt.Println(l.prefix, msg) }
|
||||
|
||||
type Counter struct{ n int }
|
||||
func (c *Counter) Inc() { c.n++ }
|
||||
|
||||
// HTTP server composes both — no inheritance
|
||||
type Server struct {
|
||||
Logger // embedded → Server.Log() works
|
||||
*Counter // embedded pointer
|
||||
addr string
|
||||
}
|
||||
|
||||
s := &Server{Logger: Logger{"[srv]"}, Counter: &Counter{}, addr: ":8080"}
|
||||
s.Log("starting"); s.Inc()
|
||||
```
|
||||
|
||||
### React component composition
|
||||
```tsx
|
||||
// "Slot" composition — children as primary API
|
||||
function Card({ header, footer, children }: {
|
||||
header?: ReactNode; footer?: ReactNode; children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="card">
|
||||
{header && <header>{header}</header>}
|
||||
<main>{children}</main>
|
||||
{footer && <footer>{footer}</footer>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
<Card
|
||||
header={<h2>Order #1234</h2>}
|
||||
footer={<button>Cancel</button>}
|
||||
>
|
||||
<OrderDetails id="1234" />
|
||||
</Card>
|
||||
```
|
||||
|
||||
### Higher-order component / wrapper composition
|
||||
```tsx
|
||||
const withAuth = <P,>(C: ComponentType<P>) =>
|
||||
(props: P) => useUser() ? <C {...props} /> : <Login />;
|
||||
|
||||
const withLogging = <P,>(C: ComponentType<P>, name: string) =>
|
||||
(props: P) => { console.log(name, props); return <C {...props} />; };
|
||||
|
||||
// Compose
|
||||
const Page = withAuth(withLogging(Dashboard, 'Dashboard'));
|
||||
```
|
||||
|
||||
### Unix pipes (process composition)
|
||||
```bash
|
||||
cat access.log \
|
||||
| grep '\.html"' \
|
||||
| awk '{print $7}' \
|
||||
| sort \
|
||||
| uniq -c \
|
||||
| sort -rn \
|
||||
| head -20
|
||||
```
|
||||
|
||||
### DAG composition (Airflow / Dagster)
|
||||
```python
|
||||
from dagster import asset
|
||||
|
||||
@asset
|
||||
def raw_orders(): return load_csv("orders.csv")
|
||||
|
||||
@asset
|
||||
def cleaned_orders(raw_orders): return raw_orders.dropna()
|
||||
|
||||
@asset
|
||||
def daily_revenue(cleaned_orders):
|
||||
return cleaned_orders.groupby("day")["total"].sum()
|
||||
# Dagster builds DAG from inputs → outputs automatically
|
||||
```
|
||||
|
||||
### Type composition (TypeScript)
|
||||
```ts
|
||||
type Timestamped = { createdAt: Date; updatedAt: Date };
|
||||
type Identified = { id: string };
|
||||
type Soft Deletable = { deletedAt: Date | null };
|
||||
|
||||
// Intersection composition
|
||||
type Entity<T> = T & Identified & Timestamped & SoftDeletable;
|
||||
type User = Entity<{ email: string; name: string }>;
|
||||
```
|
||||
|
||||
### Algebraic composition — Result.flatMap (Rust)
|
||||
```rust
|
||||
fn parse(s: &str) -> Result<i32, String> { s.parse().map_err(|e| e.to_string()) }
|
||||
fn double(n: i32) -> Result<i32, String> { Ok(n * 2) }
|
||||
fn check_positive(n: i32) -> Result<i32, String> {
|
||||
if n > 0 { Ok(n) } else { Err("non-positive".into()) }
|
||||
}
|
||||
|
||||
let out: Result<i32, _> = parse("21")
|
||||
.and_then(double)
|
||||
.and_then(check_positive); // composition via and_then
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Mode |
|
||||
|---|---|
|
||||
| Shared state across siblings | Composition (props / context) > inheritance |
|
||||
| Behavior reuse, no state | Mixin / trait / function |
|
||||
| Conditional capability | HOC / decorator |
|
||||
| Linear pipeline | pipe / compose |
|
||||
| Branching workflow | DAG (Dagster, Airflow) |
|
||||
| Multi-axis variation | Strategy / Plugin |
|
||||
|
||||
**기본값**: 매 favor composition — 매 inheritance 의 only 매 strict is-a + immutable hierarchy.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Design Patterns]] · [[Functional Programming]]
|
||||
- 변형: [[Composition over Inheritance]]
|
||||
- 응용: [[React]] · [[Airflow]]
|
||||
- Adjacent: [[Base_Layouts]] · [[Architectural-Constraint-Enforcement]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 inheritance hierarchy → composition 의 refactor suggestion, 매 pipeline의 DAG 의 visualize.
|
||||
**언제 X**: 매 deep domain modeling — 매 human 의 boundary judgment 의 필수.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Inheritance for code reuse**: 매 fragile base class — 매 composition 의 사용.
|
||||
- **Composition explosion**: 매 100-deep wrapper — 매 readability 의 X. 매 refactor.
|
||||
- **God component**: 매 50-prop slot — 매 split.
|
||||
- **Implicit composition order**: 매 HOC stacking 의 order-dependent — 매 explicit naming.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Gamma et al., *Design Patterns*; React docs — composition vs inheritance; Wadler — *Theorems for Free*).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — function/object/component/process composition patterns |
|
||||
@@ -0,0 +1,224 @@
|
||||
---
|
||||
id: wiki-2026-0508-aspect-oriented-programming-aop
|
||||
title: Aspect Oriented Programming (AOP)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [AOP, Aspect Programming, Cross-cutting Concerns]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [architecture, paradigm, spring, decorator]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: Java / TypeScript / Python
|
||||
framework: Spring AOP / AspectJ / NestJS
|
||||
---
|
||||
|
||||
# Aspect-Oriented Programming (AOP)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 cross-cutting concern을 separate aspect로 modularize."**. 1997년 Gregor Kiczales (Xerox PARC) 의 AspectJ 가 시초. 2026 현재 Spring AOP 6.x, NestJS interceptor, Python decorator 가 주류 — logging/transaction/security 같이 매 객체 가로지르는 logic을 매 한 곳에 모음.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 용어
|
||||
- **Aspect**: 매 cross-cutting concern 의 modular 단위 (e.g. `LoggingAspect`).
|
||||
- **Join Point**: 매 program execution 의 한 지점 (method call, field access).
|
||||
- **Pointcut**: 매 join point 의 selection expression.
|
||||
- **Advice**: 매 pointcut 매칭 시 실행할 code (`@Before`, `@After`, `@Around`).
|
||||
- **Weaving**: 매 aspect 와 base code 결합 — compile-time / load-time / runtime.
|
||||
|
||||
### 매 cross-cutting concerns
|
||||
- Logging / tracing
|
||||
- Transaction management
|
||||
- Security / authorization
|
||||
- Caching
|
||||
- Performance monitoring
|
||||
- Error handling / retry
|
||||
- Audit trail
|
||||
|
||||
### 매 응용
|
||||
1. Spring `@Transactional` — DB transaction 자동 commit/rollback.
|
||||
2. NestJS `@UseInterceptors` — request/response transform.
|
||||
3. Python `@functools.lru_cache` — pure caching aspect.
|
||||
4. OpenTelemetry auto-instrumentation — runtime byte-code weaving.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Spring AOP — @Around aspect
|
||||
```java
|
||||
@Aspect
|
||||
@Component
|
||||
public class LoggingAspect {
|
||||
|
||||
@Around("execution(* com.example.service.*.*(..))")
|
||||
public Object logExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
|
||||
long start = System.currentTimeMillis();
|
||||
try {
|
||||
Object result = pjp.proceed();
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
log.info("{} took {} ms", pjp.getSignature(), elapsed);
|
||||
return result;
|
||||
} catch (Throwable t) {
|
||||
log.error("{} threw {}", pjp.getSignature(), t.getMessage());
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Spring — @Transactional declarative TX
|
||||
```java
|
||||
@Service
|
||||
public class OrderService {
|
||||
|
||||
@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
|
||||
public Order placeOrder(OrderRequest req) {
|
||||
Order order = orderRepo.save(new Order(req));
|
||||
inventoryRepo.decrement(req.getItems());
|
||||
// Any RuntimeException → automatic rollback via AOP proxy
|
||||
return order;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### NestJS — Interceptor (AOP-style)
|
||||
```typescript
|
||||
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
|
||||
import { Observable, tap } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class TimingInterceptor implements NestInterceptor {
|
||||
intercept(ctx: ExecutionContext, next: CallHandler): Observable<unknown> {
|
||||
const start = Date.now();
|
||||
return next.handle().pipe(
|
||||
tap(() => console.log(`${ctx.getHandler().name} took ${Date.now() - start}ms`)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Controller('orders')
|
||||
@UseInterceptors(TimingInterceptor)
|
||||
export class OrdersController { /* ... */ }
|
||||
```
|
||||
|
||||
### Python — Decorator as aspect
|
||||
```python
|
||||
import functools, time, logging
|
||||
|
||||
def timed(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
start = time.perf_counter()
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
elapsed = time.perf_counter() - start
|
||||
logging.info(f"{func.__name__} took {elapsed*1000:.1f}ms")
|
||||
return wrapper
|
||||
|
||||
@timed
|
||||
def expensive_calc(n: int) -> int:
|
||||
return sum(i * i for i in range(n))
|
||||
```
|
||||
|
||||
### Python — Class-based retry aspect
|
||||
```python
|
||||
def retry(times: int = 3, on: tuple = (Exception,)):
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
last = None
|
||||
for attempt in range(times):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except on as e:
|
||||
last = e
|
||||
time.sleep(2 ** attempt)
|
||||
raise last
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
@retry(times=5, on=(httpx.HTTPError,))
|
||||
def fetch_remote(url: str) -> dict:
|
||||
return httpx.get(url).json()
|
||||
```
|
||||
|
||||
### AspectJ pointcut DSL
|
||||
```java
|
||||
@Pointcut("execution(public * com.example.repo.*.find*(..))")
|
||||
public void anyFinder() {}
|
||||
|
||||
@Pointcut("@annotation(com.example.Audited)")
|
||||
public void auditedMethod() {}
|
||||
|
||||
@Before("anyFinder() && auditedMethod()")
|
||||
public void audit(JoinPoint jp) {
|
||||
auditService.log(jp.getSignature().toShortString(), Instant.now());
|
||||
}
|
||||
```
|
||||
|
||||
### TypeScript — method decorator
|
||||
```typescript
|
||||
function Cached(ttlMs: number): MethodDecorator {
|
||||
const cache = new Map<string, { value: unknown; exp: number }>();
|
||||
return (_t, _k, desc: PropertyDescriptor) => {
|
||||
const original = desc.value;
|
||||
desc.value = function (...args: unknown[]) {
|
||||
const key = JSON.stringify(args);
|
||||
const entry = cache.get(key);
|
||||
if (entry && entry.exp > Date.now()) return entry.value;
|
||||
const value = original.apply(this, args);
|
||||
cache.set(key, { value, exp: Date.now() + ttlMs });
|
||||
return value;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
class Pricing {
|
||||
@Cached(60_000)
|
||||
fetchRate(currency: string) { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Java enterprise, declarative TX | Spring AOP (proxy-based) |
|
||||
| 매 method 매칭 + field access weaving | AspectJ (compile/load-time) |
|
||||
| Node.js HTTP layer | NestJS Interceptor / Guard |
|
||||
| Python script / function-level | Decorator |
|
||||
| Cross-language tracing | OpenTelemetry auto-instrumentation |
|
||||
|
||||
**기본값**: framework-native (Spring AOP / NestJS interceptor / decorator). 매 raw AspectJ 는 매 매우 specific 한 경우만.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Object-Oriented-Programming]] · [[Software-Architecture]]
|
||||
- 변형: [[Middleware]]
|
||||
- 응용: [[Spring Framework]] · [[NestJS]] · [[OpenTelemetry]]
|
||||
- Adjacent: [[Dependency_Injection_(DI)|Dependency-Injection]] · [[Cross-Cutting Concerns]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: cross-cutting concern (logging/TX/security/caching) 이 매 여러 service 에 반복, declarative style 선호, framework 가 AOP 지원.
|
||||
**언제 X**: 매 한두 곳만 쓰는 logic (직접 호출이 명확), 매 magic 회피 culture, 매 debug 어려움 감수 못할 때.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Aspect 안 business logic**: 매 aspect 는 매 cross-cutting 만 — 매 domain rule 넣지 X.
|
||||
- **너무 broad pointcut**: `execution(* *.*(..))` — 매 unintended weaving.
|
||||
- **Self-invocation 의 proxy bypass**: 매 같은 class 안 method call 은 매 proxy 통과 안 함 (Spring).
|
||||
- **Order 의존**: 매 multiple aspect 매 ordering 명시 X 면 매 비결정적.
|
||||
- **Aspect explosion**: 매 100+ aspect → 매 control flow 추적 불가.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Spring Framework 6.x docs, AspectJ programming guide, NestJS docs).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — AOP full content with Spring/NestJS/Python patterns |
|
||||
@@ -0,0 +1,168 @@
|
||||
---
|
||||
id: wiki-2026-0508-automated-refactoring-tools
|
||||
title: Automated Refactoring Tools
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Refactoring Tools, IDE Refactoring, Codemod Tools]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [refactoring, tooling, ast, codemod]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: ts-morph,jscodeshift,comby,ast-grep
|
||||
---
|
||||
|
||||
# Automated Refactoring Tools
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 AST-level 의 transform — 매 sed 의 X, 매 syntax-aware 의 mass-edit"**. 매 IDE refactor (Rename · Extract) 의 80년대 Smalltalk 의 root, 2026 의 LLM-augmented codemod (Claude Opus 4.7 + ast-grep) 의 mainstream.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 3 tier 의 tool
|
||||
- **IDE-builtin**: IntelliJ · VS Code · Rider — 매 single-file 의 dominant.
|
||||
- **Codemod**: jscodeshift · ts-morph · comby · ast-grep — 매 cross-cutting mass change.
|
||||
- **LLM-driven**: Claude Code · Cursor · GitHub Copilot Workspace — 매 semantic-aware 의 modernization.
|
||||
|
||||
### 매 refactor 종류
|
||||
- Rename (symbol-aware).
|
||||
- Extract function/variable/component.
|
||||
- Inline.
|
||||
- Move (file/module).
|
||||
- Change signature.
|
||||
- Convert (e.g. `var → const`, `function → arrow`).
|
||||
|
||||
### 매 응용
|
||||
1. 매 framework migration (Vue 2 → Vue 3, React class → hook).
|
||||
2. API breaking change 의 fan-out fix.
|
||||
3. Code style 의 mechanical enforcement.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### ts-morph: rename + add property
|
||||
```typescript
|
||||
import { Project } from "ts-morph";
|
||||
|
||||
const project = new Project({ tsConfigFilePath: "tsconfig.json" });
|
||||
|
||||
for (const sf of project.getSourceFiles()) {
|
||||
for (const cls of sf.getClasses()) {
|
||||
if (cls.getName()?.endsWith("Service")) {
|
||||
cls.rename(cls.getName()!.replace(/Service$/, "Manager"));
|
||||
cls.addProperty({ name: "createdAt", type: "Date", initializer: "new Date()" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await project.save();
|
||||
```
|
||||
|
||||
### ast-grep: pattern → rewrite (yaml rule)
|
||||
```yaml
|
||||
id: useEffect-cleanup
|
||||
language: tsx
|
||||
rule:
|
||||
pattern: useEffect(() => { $$$BODY }, $DEPS)
|
||||
fix: |
|
||||
useEffect(() => {
|
||||
$$$BODY
|
||||
return () => { /* cleanup */ };
|
||||
}, $DEPS)
|
||||
```
|
||||
```bash
|
||||
ast-grep scan -r useEffect-cleanup.yml --update-all
|
||||
```
|
||||
|
||||
### jscodeshift: Vue Options → Composition API
|
||||
```javascript
|
||||
export default function transformer(file, api) {
|
||||
const j = api.jscodeshift;
|
||||
const root = j(file.source);
|
||||
|
||||
root.find(j.ObjectExpression)
|
||||
.filter(p => p.node.properties.some(pr => pr.key?.name === "data"))
|
||||
.forEach(path => {
|
||||
// emit setup() function with refs
|
||||
// ...
|
||||
});
|
||||
|
||||
return root.toSource();
|
||||
}
|
||||
```
|
||||
```bash
|
||||
jscodeshift -t vue-comp-api.js src/
|
||||
```
|
||||
|
||||
### comby (language-agnostic structural search)
|
||||
```bash
|
||||
comby 'console.log(:[args])' 'logger.debug(:[args])' .ts -i
|
||||
```
|
||||
|
||||
### LLM codemod (Claude Opus 4.7) — semantic-aware
|
||||
```python
|
||||
import anthropic
|
||||
|
||||
client = anthropic.Anthropic()
|
||||
SYSTEM = "You convert React class components to functional + hooks. Preserve behavior."
|
||||
|
||||
with open("UserCard.tsx") as f:
|
||||
src = f.read()
|
||||
|
||||
resp = client.messages.create(
|
||||
model="claude-opus-4-7",
|
||||
max_tokens=4096,
|
||||
system=SYSTEM,
|
||||
messages=[{"role": "user", "content": src}],
|
||||
)
|
||||
print(resp.content[0].text)
|
||||
```
|
||||
|
||||
### IDE script: IntelliJ structural search
|
||||
```text
|
||||
$method$($args$) { $body$ }
|
||||
→ Constraints: method matches ".*Async$"
|
||||
→ Replace with: async $method$($args$) { $body$ }
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Tool |
|
||||
|---|---|
|
||||
| 매 single-file rename in IDE | IDE builtin |
|
||||
| TS-only · type-aware | ts-morph |
|
||||
| TS/JS · less type-aware · faster | jscodeshift |
|
||||
| Multi-language · structural | comby · ast-grep |
|
||||
| 매 semantic refactor · framework migration | LLM codemod |
|
||||
| Mass mechanical regex-safe | sed/perl (드물게) |
|
||||
|
||||
**기본값**: TypeScript 의 ts-morph, polyglot 의 ast-grep, 매 semantic 의 LLM.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Refactoring_Best_Practices|Refactoring]] · [[AST_Traversal|Abstract-Syntax-Tree-Traversal]]
|
||||
- 응용: [[Architecture_Refactor]]
|
||||
- Adjacent: [[AI-Assisted Refactoring (AI 기반 리팩토링)]] · [[AST_Traversal]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 semantic refactor (class→hook, callback→async/await), 매 deterministic rule 의 hard 의 case.
|
||||
**언제 X**: 매 mechanical rename 의 case — IDE 가 cheaper · safer. LLM 의 hallucination risk 의 require code review.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Regex-only refactor**: 매 string match 의 false positive (comment · string literal).
|
||||
- **Test 없음 refactor**: 매 mass codemod 의 test suite 의 gate 의 require.
|
||||
- **PR 의 single huge codemod**: 매 review 의 X — file-by-file commit 의 split.
|
||||
- **LLM output 의 blind merge**: 매 type-check + test 의 gate 의 mandatory.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (ts-morph docs, Meta jscodeshift, comby.dev, ast-grep.github.io, Anthropic Claude API docs).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — 3-tier taxonomy + ts-morph/ast-grep/comby/LLM examples |
|
||||
@@ -0,0 +1,202 @@
|
||||
---
|
||||
id: wiki-2026-0508-bloc
|
||||
title: BLoC
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [BLoC, Business Logic Component, flutter_bloc]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [flutter, state-management, dart, reactive]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: dart
|
||||
framework: flutter,flutter_bloc
|
||||
---
|
||||
|
||||
# BLoC
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 UI ↔ business logic 의 strict separation — Event in · State out · Stream 으로 의 mediate"**. 2018 Felix Angelov · Paolo Soares Google I/O 의 introduce, 2026 의 flutter_bloc 9.x · Cubit · Hydrated 의 Flutter ecosystem 의 dominant state-management.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 contract
|
||||
- **Event**: input (button tap · API tick).
|
||||
- **State**: output (immutable snapshot).
|
||||
- **Bloc**: `mapEventToState` (legacy) → `on<Event>` handler (modern 8.x+).
|
||||
- **Cubit**: simpler — 매 event 의 X, method 의 emit 의 directly.
|
||||
|
||||
### 매 component
|
||||
- `BlocProvider` — DI · scope.
|
||||
- `BlocBuilder` — rebuild 의 state change 의 reactive.
|
||||
- `BlocListener` — side-effect (snackbar · navigation).
|
||||
- `BlocConsumer` — builder + listener.
|
||||
- `RepositoryProvider` — data layer DI.
|
||||
|
||||
### 매 응용
|
||||
1. 매 Flutter 의 production app (BMW · Nubank · Alibaba).
|
||||
2. Form validation (매 reactive validation per field).
|
||||
3. Authentication flow.
|
||||
4. Hydrated state (매 disk-persisted, app restart 의 survive).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Bloc (event-driven)
|
||||
```dart
|
||||
sealed class CounterEvent {}
|
||||
class Increment extends CounterEvent {}
|
||||
class Decrement extends CounterEvent {}
|
||||
|
||||
class CounterBloc extends Bloc<CounterEvent, int> {
|
||||
CounterBloc() : super(0) {
|
||||
on<Increment>((event, emit) => emit(state + 1));
|
||||
on<Decrement>((event, emit) => emit(state - 1));
|
||||
}
|
||||
}
|
||||
|
||||
// usage
|
||||
BlocProvider(
|
||||
create: (_) => CounterBloc(),
|
||||
child: BlocBuilder<CounterBloc, int>(
|
||||
builder: (ctx, count) => Text('$count'),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### Cubit (simpler · method-based)
|
||||
```dart
|
||||
class CounterCubit extends Cubit<int> {
|
||||
CounterCubit() : super(0);
|
||||
void increment() => emit(state + 1);
|
||||
void decrement() => emit(state - 1);
|
||||
}
|
||||
|
||||
// trigger
|
||||
context.read<CounterCubit>().increment();
|
||||
```
|
||||
|
||||
### Async event handler with state machine
|
||||
```dart
|
||||
sealed class LoginState {}
|
||||
class LoginInitial extends LoginState {}
|
||||
class LoginLoading extends LoginState {}
|
||||
class LoginSuccess extends LoginState { final User user; LoginSuccess(this.user); }
|
||||
class LoginFailure extends LoginState { final String msg; LoginFailure(this.msg); }
|
||||
|
||||
class LoginBloc extends Bloc<LoginEvent, LoginState> {
|
||||
final AuthRepository auth;
|
||||
LoginBloc(this.auth) : super(LoginInitial()) {
|
||||
on<SubmitLogin>(_onSubmit);
|
||||
}
|
||||
|
||||
Future<void> _onSubmit(SubmitLogin e, Emitter<LoginState> emit) async {
|
||||
emit(LoginLoading());
|
||||
try {
|
||||
final user = await auth.login(e.email, e.password);
|
||||
emit(LoginSuccess(user));
|
||||
} catch (err) {
|
||||
emit(LoginFailure(err.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### BlocListener (side-effect)
|
||||
```dart
|
||||
BlocListener<LoginBloc, LoginState>(
|
||||
listener: (ctx, state) {
|
||||
if (state is LoginSuccess) Navigator.pushNamed(ctx, '/home');
|
||||
if (state is LoginFailure) ScaffoldMessenger.of(ctx).showSnackBar(
|
||||
SnackBar(content: Text(state.msg)),
|
||||
);
|
||||
},
|
||||
child: LoginForm(),
|
||||
)
|
||||
```
|
||||
|
||||
### Hydrated Bloc (auto-persist)
|
||||
```dart
|
||||
class SettingsCubit extends HydratedCubit<Settings> {
|
||||
SettingsCubit() : super(const Settings(theme: 'light'));
|
||||
|
||||
void setTheme(String t) => emit(state.copyWith(theme: t));
|
||||
|
||||
@override
|
||||
Settings fromJson(Map<String, dynamic> json) => Settings.fromJson(json);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson(Settings state) => state.toJson();
|
||||
}
|
||||
```
|
||||
|
||||
### Stream-debounced search Bloc
|
||||
```dart
|
||||
class SearchBloc extends Bloc<SearchQuery, SearchState> {
|
||||
SearchBloc(this.api) : super(SearchInitial()) {
|
||||
on<SearchQuery>(
|
||||
_onSearch,
|
||||
transformer: (events, mapper) => events
|
||||
.debounceTime(const Duration(milliseconds: 300))
|
||||
.switchMap(mapper),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSearch(SearchQuery e, Emitter<SearchState> emit) async {
|
||||
emit(SearchLoading());
|
||||
final results = await api.search(e.term);
|
||||
emit(SearchLoaded(results));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test (bloc_test)
|
||||
```dart
|
||||
blocTest<CounterBloc, int>(
|
||||
'emits [1] when Increment is added',
|
||||
build: () => CounterBloc(),
|
||||
act: (b) => b.add(Increment()),
|
||||
expect: () => [1],
|
||||
);
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Simple state · single screen | Cubit |
|
||||
| Complex event flow · undo/redo | Bloc |
|
||||
| Persisted state | HydratedBloc/Cubit |
|
||||
| Server-driven · realtime | Bloc + StreamSubscription |
|
||||
| Multi-screen shared | BlocProvider 의 root |
|
||||
| 매 small project | Provider · Riverpod |
|
||||
|
||||
**기본값**: Cubit 의 first, Bloc 의 event 의 explicit 의 require 의 case.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[State Management]]
|
||||
- 응용: [[Clean Architecture]]
|
||||
- Adjacent: [[Provider]] · [[Riverpod]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: Bloc · Cubit · Event · State 의 boilerplate 의 generate, blocTest 의 draft.
|
||||
**언제 X**: 매 actual event sequencing · concurrency strategy (debounce/restartable/sequential) — 매 domain 의 understand 의 require.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **State 의 mutable**: 매 immutable + copyWith 의 mandatory.
|
||||
- **BlocBuilder 의 entire screen 의 wrap**: granular rebuild 의 lose — multiple builder 의 split.
|
||||
- **Bloc 의 navigation 의 directly do**: side-effect 의 BlocListener 의 isolate.
|
||||
- **Singleton bloc 의 manual instantiate**: BlocProvider 의 use — lifecycle 의 leak prevent.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (bloclibrary.dev, flutter_bloc 9.x docs, Felix Angelov GitHub).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Bloc + Cubit + Hydrated + bloc_test patterns |
|
||||
@@ -0,0 +1,224 @@
|
||||
---
|
||||
id: wiki-2026-0508-bpm
|
||||
title: BPM (Business Process Management)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Business Process Management, Workflow Automation, Process Orchestration]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [enterprise, workflow, bpmn, orchestration]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: BPMN 2.0 / Java / TypeScript
|
||||
framework: Camunda 8 / Temporal / AWS Step Functions
|
||||
---
|
||||
|
||||
# BPM (Business Process Management)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 business process 를 매 explicit model 로 만들어 매 measure / improve / automate."**. 1990s 의 BPR (re-engineering) → 2000s BPMS workflow engine → 2010s BPMN 2.0 표준 → 2026 현재 매 cloud-native orchestration (Camunda 8, Temporal, Step Functions) 가 매 microservice + AI agent 의 process backbone. 매 별도로 BPM = Beats Per Minute (music tempo) 도 동음이의 — 매 본 문서는 매 business process 중심.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 BPM lifecycle
|
||||
1. **Design**: BPMN diagram, swim lanes, gateway logic.
|
||||
2. **Modeling**: simulation, validation.
|
||||
3. **Execution**: workflow engine 이 매 instance 실행.
|
||||
4. **Monitoring**: KPI, SLA, bottleneck.
|
||||
5. **Optimization**: process mining, AI suggestions.
|
||||
|
||||
### 매 BPMN 2.0 element
|
||||
- **Activity** (rounded rect): task — user task, service task, script task.
|
||||
- **Event** (circle): start, intermediate, end (timer, message, error).
|
||||
- **Gateway** (diamond): exclusive (XOR), parallel (AND), inclusive (OR).
|
||||
- **Sequence flow** (arrow).
|
||||
- **Pool / Lane**: organizational boundary.
|
||||
- **Data object**: process variable.
|
||||
|
||||
### 매 응용
|
||||
1. Loan approval workflow (origination → underwriting → decision).
|
||||
2. Order-to-cash (e-commerce → fulfillment → invoicing).
|
||||
3. Employee onboarding (HR forms → IT provisioning → training).
|
||||
4. Insurance claim processing (intake → adjuster → payout).
|
||||
5. AI agent orchestration (매 LLM agent 의 매 multi-step workflow).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Camunda 8 — BPMN service task (Zeebe)
|
||||
```typescript
|
||||
import { ZBClient } from 'zeebe-node';
|
||||
|
||||
const zbc = new ZBClient();
|
||||
|
||||
zbc.createWorker({
|
||||
taskType: 'charge-credit-card',
|
||||
taskHandler: async (job) => {
|
||||
const { orderId, amount, customerId } = job.variables;
|
||||
try {
|
||||
const result = await stripe.charges.create({
|
||||
amount, currency: 'usd', customer: customerId,
|
||||
});
|
||||
return job.complete({ chargeId: result.id, paid: true });
|
||||
} catch (e) {
|
||||
return job.fail(`Stripe error: ${e.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### BPMN 2.0 XML — order process (sketch)
|
||||
```xml
|
||||
<bpmn:process id="OrderProcess" isExecutable="true">
|
||||
<bpmn:startEvent id="Start" />
|
||||
<bpmn:serviceTask id="ValidateOrder" name="Validate Order"
|
||||
zeebe:taskDefinition type="validate-order" />
|
||||
<bpmn:exclusiveGateway id="StockGateway" name="In stock?" />
|
||||
<bpmn:serviceTask id="ChargeCard" zeebe:taskDefinition type="charge-credit-card" />
|
||||
<bpmn:userTask id="ManualReview" name="Manual review" />
|
||||
<bpmn:endEvent id="End" />
|
||||
<bpmn:sequenceFlow source="Start" target="ValidateOrder" />
|
||||
<bpmn:sequenceFlow source="ValidateOrder" target="StockGateway" />
|
||||
<bpmn:sequenceFlow source="StockGateway" target="ChargeCard">
|
||||
<bpmn:conditionExpression>=stockAvailable</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
<bpmn:sequenceFlow source="StockGateway" target="ManualReview">
|
||||
<bpmn:conditionExpression>=not(stockAvailable)</bpmn:conditionExpression>
|
||||
</bpmn:sequenceFlow>
|
||||
</bpmn:process>
|
||||
```
|
||||
|
||||
### Temporal — code-first workflow
|
||||
```typescript
|
||||
import { proxyActivities, sleep } from '@temporalio/workflow';
|
||||
import * as activities from './activities';
|
||||
|
||||
const { validateOrder, chargeCard, shipOrder } =
|
||||
proxyActivities<typeof activities>({
|
||||
startToCloseTimeout: '1 minute',
|
||||
retry: { maximumAttempts: 5 },
|
||||
});
|
||||
|
||||
export async function orderWorkflow(order: Order): Promise<void> {
|
||||
await validateOrder(order);
|
||||
const charge = await chargeCard(order.amount, order.customerId);
|
||||
await sleep('5 minutes'); // hold before fulfillment
|
||||
await shipOrder(order.id, charge.id);
|
||||
}
|
||||
```
|
||||
|
||||
### AWS Step Functions — state machine JSON
|
||||
```json
|
||||
{
|
||||
"StartAt": "Validate",
|
||||
"States": {
|
||||
"Validate": {
|
||||
"Type": "Task",
|
||||
"Resource": "arn:aws:lambda:us-east-1:123:function:Validate",
|
||||
"Next": "InStock?"
|
||||
},
|
||||
"InStock?": {
|
||||
"Type": "Choice",
|
||||
"Choices": [
|
||||
{ "Variable": "$.inStock", "BooleanEquals": true, "Next": "Charge" }
|
||||
],
|
||||
"Default": "ManualReview"
|
||||
},
|
||||
"Charge": { "Type": "Task", "Resource": "arn:.../Charge", "End": true },
|
||||
"ManualReview": { "Type": "Task", "Resource": "arn:.../HumanReview", "End": true }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Process mining — discover from logs
|
||||
```python
|
||||
import pm4py
|
||||
|
||||
log = pm4py.read_xes("orders_event_log.xes")
|
||||
process_tree = pm4py.discover_process_tree_inductive(log)
|
||||
bpmn = pm4py.convert_to_bpmn(process_tree)
|
||||
pm4py.view_bpmn(bpmn)
|
||||
|
||||
# Conformance check
|
||||
diagnostics = pm4py.conformance_diagnostics_token_based_replay(log, bpmn)
|
||||
print(f"Fitness: {diagnostics['fitness']:.2f}")
|
||||
```
|
||||
|
||||
### Saga pattern (compensation)
|
||||
```typescript
|
||||
export async function bookTripSaga(req: TripReq) {
|
||||
const compensations: Array<() => Promise<void>> = [];
|
||||
try {
|
||||
const flight = await bookFlight(req);
|
||||
compensations.push(() => cancelFlight(flight.id));
|
||||
const hotel = await bookHotel(req);
|
||||
compensations.push(() => cancelHotel(hotel.id));
|
||||
const car = await bookCar(req);
|
||||
return { flight, hotel, car };
|
||||
} catch (e) {
|
||||
for (const undo of compensations.reverse()) await undo();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Human task (UserTask) form
|
||||
```typescript
|
||||
zbc.createWorker({
|
||||
taskType: 'manual-approval',
|
||||
taskHandler: async (job) => {
|
||||
// 매 push to UI inbox; resolve when human submits decision
|
||||
await pushToInbox({
|
||||
taskId: job.key,
|
||||
assignee: job.customHeaders.assignee,
|
||||
formSchema: job.customHeaders.formKey,
|
||||
});
|
||||
// job is completed by REST callback when human acts
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Engine |
|
||||
|---|---|
|
||||
| Visual BPMN, business analyst editing | Camunda 8 / Activiti |
|
||||
| Code-first, complex retry/long-running | Temporal |
|
||||
| AWS-only stack, simple flow | Step Functions |
|
||||
| Open-source self-host, light | n8n / Prefect |
|
||||
| AI agent multi-step | Temporal + LangGraph |
|
||||
|
||||
**기본값**: matrix:
|
||||
- enterprise w/ analyst → Camunda 8
|
||||
- engineer-driven, durability-critical → Temporal
|
||||
- serverless on AWS → Step Functions
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Workflow-Automation]] · [[Enterprise-Architecture]]
|
||||
- 변형: [[State-Machine]]
|
||||
- 응용: [[Temporal]]
|
||||
- Adjacent: [[Microservices]] · [[Event-Driven-Architecture]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: long-running multi-step process, human-in-loop required, audit trail 필수, retry/compensation 복잡, AI agent multi-tool orchestration.
|
||||
**언제 X**: 매 simple synchronous CRUD, 매 sub-100ms latency 필수, 매 stateless transform.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **BPMN diagram = code 동기화 안 됨**: 매 visual model 만 update, 실제 code drift.
|
||||
- **Workflow engine 의 business logic 과다**: 매 service task 안에 매 거대 conditional → 매 BPM diagram 의 의미 상실.
|
||||
- **No timeout / no retry**: 매 stuck instance pile up.
|
||||
- **Saga 의 compensation 누락**: 매 partial state 영구 leak.
|
||||
- **매 모든 thing 을 workflow**: 매 simple op 도 workflow → 매 latency / debugging cost 폭증.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (BPMN 2.0 OMG spec, Camunda 8 / Temporal 2026 docs, "Process Mining" by van der Aalst).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — BPM (business process) full content with Camunda/Temporal patterns |
|
||||
@@ -0,0 +1,218 @@
|
||||
---
|
||||
id: wiki-2026-0508-base-layouts
|
||||
title: Base Layouts
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [layout-primitives, app-shell, holy-grail-layout]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [layout, css, ui, responsive, shell]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: tsx
|
||||
framework: tailwind
|
||||
---
|
||||
|
||||
# Base Layouts
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 layout 의 primitive 의 reuse 한다"**. 매 base layout 의 application shell — 매 header / sidebar / main / footer 의 stable scaffolding. 매 2026 의 CSS Grid + Flexbox + Container Queries (baseline 2024) + `dvh/svh/lvh` viewport units 의 holy-grail 의 trivial 의 만들었다.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 canonical layouts
|
||||
- **Stack**: 매 vertical flow — header/main/footer.
|
||||
- **Sidebar**: 매 nav + content — 2-column.
|
||||
- **Holy Grail**: 매 header + nav + main + aside + footer.
|
||||
- **Centered (Cover)**: 매 hero / login.
|
||||
- **Split-pane**: 매 editor / preview, mail client.
|
||||
- **Dashboard grid**: 매 card grid — 매 auto-fit minmax.
|
||||
|
||||
### 매 modern primitives (2024-2026)
|
||||
- **CSS Grid `subgrid`**: 매 nested alignment (Firefox 71, Chrome 117, Safari 16).
|
||||
- **Container queries**: 매 component-level responsive (`@container`).
|
||||
- **`dvh` / `svh` / `lvh`**: 매 mobile address-bar 의 stable viewport.
|
||||
- **`:has()`**: 매 parent selector — 매 layout 의 child-state-driven.
|
||||
- **Anchor positioning** (Chrome 125+): 매 popover / tooltip 의 native.
|
||||
|
||||
### 매 응용
|
||||
1. Admin dashboard.
|
||||
2. Editor (VS Code-style).
|
||||
3. Marketing landing.
|
||||
4. Mobile app shell — 매 bottom nav.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Holy Grail — CSS Grid (12 lines)
|
||||
```css
|
||||
.app {
|
||||
display: grid;
|
||||
min-height: 100dvh;
|
||||
grid-template:
|
||||
"header header header" auto
|
||||
"nav main aside" 1fr
|
||||
"footer footer footer" auto
|
||||
/ 200px 1fr 200px;
|
||||
}
|
||||
.header { grid-area: header; }
|
||||
.nav { grid-area: nav; }
|
||||
.main { grid-area: main; }
|
||||
.aside { grid-area: aside; }
|
||||
.footer { grid-area: footer; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.app {
|
||||
grid-template:
|
||||
"header" auto
|
||||
"nav" auto
|
||||
"main" 1fr
|
||||
"aside" auto
|
||||
"footer" auto / 1fr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Tailwind app shell (React)
|
||||
```tsx
|
||||
export function AppShell({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<div className="grid min-h-dvh grid-rows-[auto_1fr_auto] grid-cols-[16rem_1fr]">
|
||||
<header className="col-span-2 border-b bg-white px-4 py-3">
|
||||
<Logo /> <UserMenu className="ml-auto" />
|
||||
</header>
|
||||
<aside className="border-r overflow-y-auto">
|
||||
<Nav />
|
||||
</aside>
|
||||
<main className="overflow-y-auto p-6">{children}</main>
|
||||
<footer className="col-span-2 border-t px-4 py-2 text-sm text-gray-500">
|
||||
© 2026
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Centered (Cover)
|
||||
```css
|
||||
.cover {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-height: 100dvh;
|
||||
padding: 1rem;
|
||||
}
|
||||
.cover > * { max-inline-size: 60ch; }
|
||||
```
|
||||
|
||||
### Split-pane (resizable)
|
||||
```tsx
|
||||
// react-resizable-panels (2025 default)
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
|
||||
<PanelGroup direction="horizontal">
|
||||
<Panel defaultSize={30} minSize={20}><FileTree /></Panel>
|
||||
<PanelResizeHandle className="w-1 bg-gray-200 hover:bg-blue-500" />
|
||||
<Panel><Editor /></Panel>
|
||||
<PanelResizeHandle className="w-1 bg-gray-200" />
|
||||
<Panel defaultSize={30}><Preview /></Panel>
|
||||
</PanelGroup>
|
||||
```
|
||||
|
||||
### Auto-fit dashboard grid
|
||||
```css
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
```
|
||||
|
||||
### Container queries
|
||||
```css
|
||||
.card { container-type: inline-size; }
|
||||
|
||||
@container (min-width: 400px) {
|
||||
.card .layout {
|
||||
display: grid;
|
||||
grid-template-columns: 120px 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sidebar with `:has()` (zero JS)
|
||||
```css
|
||||
.app:has(#nav-toggle:checked) .sidebar { transform: translateX(0); }
|
||||
.sidebar { transform: translateX(-100%); transition: transform .2s; }
|
||||
```
|
||||
|
||||
### Mobile app shell — bottom nav (safe area)
|
||||
```css
|
||||
.bottom-nav {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
padding-block-end: env(safe-area-inset-bottom);
|
||||
background: white;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
.scroll-area {
|
||||
height: 100dvh;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
```
|
||||
|
||||
### Subgrid (nested card alignment)
|
||||
```css
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
.card {
|
||||
display: grid;
|
||||
grid-template-rows: subgrid;
|
||||
grid-row: span 3; /* title / body / footer aligned across siblings */
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| Layout | Tool |
|
||||
|---|---|
|
||||
| App shell, 2D | CSS Grid named areas |
|
||||
| 1D linear | Flexbox |
|
||||
| Component-level responsive | Container queries |
|
||||
| Resizable panes | react-resizable-panels / Radix Splitter |
|
||||
| Mobile shell | dvh + safe-area-inset |
|
||||
| Multi-card alignment | `subgrid` |
|
||||
|
||||
**기본값**: Grid for 2D + named areas, Flex for 1D, Tailwind utility for prototyping.
|
||||
|
||||
## 🔗 Graph
|
||||
- 변형: [[Holy-Grail-Layout]] · [[App-Shell]]
|
||||
- Adjacent: [[Arrangement-and-Composition]] · [[Architecture_Diagramming_Standards]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 ASCII wireframe → CSS Grid 의 generation, Tailwind class 의 suggestion.
|
||||
**언제 X**: 매 pixel-perfect design — 매 designer + visual review 의 필수.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **`100vh` on mobile**: 매 address-bar overflow — 매 `100dvh` 의 사용.
|
||||
- **Nested flex hell**: 매 5+ flex level — 매 grid 의 use.
|
||||
- **Fixed pixel breakpoint**: 매 zoom 의 break — 매 `em`/`rem` 의 use.
|
||||
- **Hidden overflow without scroll**: 매 content 의 cut.
|
||||
- **Magic number margins**: 매 design token / spacing scale 의 use.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (CSS Grid spec — W3C; Every Layout — Bell & Andrew; web.dev — container queries).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Grid/Flex/container-queries/subgrid base layout patterns |
|
||||
@@ -0,0 +1,182 @@
|
||||
---
|
||||
id: wiki-2026-0508-bayesian-inference
|
||||
title: Bayesian Inference
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Bayesian Inference, Bayesian Statistics, Posterior Inference]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.95
|
||||
verification_status: applied
|
||||
tags: [statistics, ml, probabilistic, mcmc]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: python
|
||||
framework: pymc,numpyro,stan
|
||||
---
|
||||
|
||||
# Bayesian Inference
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 prior + likelihood = posterior — 매 belief 의 evidence 에 의해 의 update"**. Bayes 1763 의 origin, 20세기 frequentist 의 dominance, 2026 의 NumPyro/PyMC + GPU MCMC + variational inference 의 mainstream — 매 LLM uncertainty quantification 의 backbone.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Bayes rule
|
||||
P(θ|D) = P(D|θ) P(θ) / P(D)
|
||||
- **P(θ)**: prior — 매 data 이전 의 belief.
|
||||
- **P(D|θ)**: likelihood — 매 model 의 data fit.
|
||||
- **P(θ|D)**: posterior — updated belief.
|
||||
- **P(D)**: evidence (marginal likelihood).
|
||||
|
||||
### 매 4 inference 방법
|
||||
- **Conjugate**: closed-form (Beta-Bernoulli · Gaussian-Gaussian).
|
||||
- **MCMC**: HMC · NUTS · Gibbs — exact (asymptotic) · slow.
|
||||
- **Variational (VI)**: posterior 의 simpler family 의 approximate — fast · biased.
|
||||
- **Sequential MC**: particle filter — 매 streaming · state-space.
|
||||
|
||||
### 매 응용
|
||||
1. A/B test (Bayesian alternative 의 frequentist).
|
||||
2. Hierarchical model (분류 multi-level).
|
||||
3. ML calibration · uncertainty (BNN · Gaussian process).
|
||||
4. LLM logit calibration · RAG confidence.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### NumPyro: Bayesian linear regression (NUTS)
|
||||
```python
|
||||
import numpyro
|
||||
import numpyro.distributions as dist
|
||||
from numpyro.infer import NUTS, MCMC
|
||||
import jax.numpy as jnp
|
||||
import jax.random as random
|
||||
|
||||
def model(X, y=None):
|
||||
alpha = numpyro.sample("alpha", dist.Normal(0., 10.))
|
||||
beta = numpyro.sample("beta", dist.Normal(jnp.zeros(X.shape[1]), 1.))
|
||||
sigma = numpyro.sample("sigma", dist.HalfNormal(1.))
|
||||
mu = alpha + X @ beta
|
||||
numpyro.sample("obs", dist.Normal(mu, sigma), obs=y)
|
||||
|
||||
mcmc = MCMC(NUTS(model), num_warmup=1000, num_samples=2000, num_chains=4)
|
||||
mcmc.run(random.PRNGKey(0), X, y)
|
||||
mcmc.print_summary()
|
||||
```
|
||||
|
||||
### PyMC: Bayesian A/B test (Beta-Bernoulli)
|
||||
```python
|
||||
import pymc as pm
|
||||
|
||||
with pm.Model():
|
||||
p_a = pm.Beta("p_a", alpha=1, beta=1)
|
||||
p_b = pm.Beta("p_b", alpha=1, beta=1)
|
||||
pm.Binomial("obs_a", n=n_a, p=p_a, observed=conv_a)
|
||||
pm.Binomial("obs_b", n=n_b, p=p_b, observed=conv_b)
|
||||
diff = pm.Deterministic("lift", p_b - p_a)
|
||||
idata = pm.sample(2000, tune=1000, chains=4)
|
||||
|
||||
prob_b_better = (idata.posterior["lift"] > 0).mean().item()
|
||||
```
|
||||
|
||||
### Hierarchical model (varying intercept)
|
||||
```python
|
||||
def hierarchical(X, group_idx, y=None, n_groups=10):
|
||||
mu_a = numpyro.sample("mu_a", dist.Normal(0., 5.))
|
||||
sigma_a = numpyro.sample("sigma_a", dist.HalfNormal(1.))
|
||||
a = numpyro.sample("a", dist.Normal(mu_a, sigma_a).expand([n_groups]))
|
||||
beta = numpyro.sample("beta", dist.Normal(0., 1.))
|
||||
sigma = numpyro.sample("sigma", dist.HalfNormal(1.))
|
||||
mu = a[group_idx] + beta * X
|
||||
numpyro.sample("obs", dist.Normal(mu, sigma), obs=y)
|
||||
```
|
||||
|
||||
### Variational inference (SVI)
|
||||
```python
|
||||
from numpyro.infer import SVI, Trace_ELBO
|
||||
from numpyro.infer.autoguide import AutoNormal
|
||||
|
||||
guide = AutoNormal(model)
|
||||
svi = SVI(model, guide, numpyro.optim.Adam(1e-3), Trace_ELBO())
|
||||
state = svi.init(random.PRNGKey(0), X, y)
|
||||
|
||||
for i in range(2000):
|
||||
state, loss = svi.update(state, X, y)
|
||||
params = svi.get_params(state)
|
||||
```
|
||||
|
||||
### Conjugate update (Beta-Bernoulli online)
|
||||
```python
|
||||
class BetaBernoulli:
|
||||
def __init__(self, alpha=1, beta=1):
|
||||
self.alpha, self.beta = alpha, beta
|
||||
|
||||
def update(self, success: bool):
|
||||
self.alpha += int(success)
|
||||
self.beta += int(not success)
|
||||
|
||||
def mean(self): return self.alpha / (self.alpha + self.beta)
|
||||
def credible_interval(self, q=0.95):
|
||||
from scipy.stats import beta
|
||||
return beta.interval(q, self.alpha, self.beta)
|
||||
```
|
||||
|
||||
### Bayesian neural net (Pyro Bayesian layer)
|
||||
```python
|
||||
import torch
|
||||
import pyro
|
||||
import pyro.nn as pnn
|
||||
|
||||
class BNN(pnn.PyroModule):
|
||||
def __init__(self, in_d, out_d):
|
||||
super().__init__()
|
||||
self.linear = pnn.PyroModule[torch.nn.Linear](in_d, out_d)
|
||||
self.linear.weight = pnn.PyroSample(dist.Normal(0., 1.).expand([out_d, in_d]).to_event(2))
|
||||
self.linear.bias = pnn.PyroSample(dist.Normal(0., 1.).expand([out_d]).to_event(1))
|
||||
|
||||
def forward(self, x, y=None):
|
||||
mean = self.linear(x).squeeze(-1)
|
||||
with pyro.plate("data", x.shape[0]):
|
||||
pyro.sample("obs", dist.Normal(mean, 0.1), obs=y)
|
||||
return mean
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Method |
|
||||
|---|---|
|
||||
| Conjugate model · streaming | Conjugate update |
|
||||
| 매 small model · accurate posterior | NUTS/HMC |
|
||||
| Large data · fast approx | SVI · ADVI |
|
||||
| State-space · time-series | Particle filter |
|
||||
| Deep model · scale | BNN + variational · MC dropout |
|
||||
| 매 hyperparameter optimization | Gaussian process + acquisition |
|
||||
|
||||
**기본값**: NumPyro + NUTS — 매 GPU/JAX 의 fast.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Probability Theory]] · [[Statistical Inference]]
|
||||
- 변형: [[MCMC]] · [[Variational Inference]]
|
||||
- 응용: [[Belief-System]]
|
||||
- Adjacent: [[Causal Inference]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: model 의 prior · likelihood 의 spec 의 draft, posterior plot 의 interpret, NumPyro/PyMC code 의 generate.
|
||||
**언제 X**: 매 convergence diagnostic (R-hat · ESS · trace plot) — LLM 의 statistical judgment 의 unreliable, statistician 의 review 의 require.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Flat prior 의 always**: weak data + flat prior → unstable posterior. Weakly informative prior 의 use.
|
||||
- **No convergence check**: R-hat > 1.01 · ESS < 400 → posterior 의 invalid.
|
||||
- **Single chain MCMC**: multi-chain 의 mix check 의 mandatory.
|
||||
- **Posterior point-estimate 의 only**: 매 distribution 의 entirety 의 use — credible interval · posterior predictive.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Gelman *Bayesian Data Analysis* 3rd, NumPyro/PyMC/Stan docs, McElreath *Statistical Rethinking* 2nd).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Bayes rule + 4 methods + NumPyro/PyMC patterns |
|
||||
@@ -0,0 +1,163 @@
|
||||
---
|
||||
id: wiki-2026-0508-beat-saber
|
||||
title: Beat Saber
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Beat Saber, VR Rhythm Game]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.85
|
||||
verification_status: applied
|
||||
tags: [vr, game-architecture, rhythm-game, unity, ecs]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: csharp
|
||||
framework: unity,unity-dots,openxr
|
||||
---
|
||||
|
||||
# Beat Saber
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 VR rhythm game 의 architecture 의 reference — 90Hz minimum framerate · sub-20ms motion-to-photon · ECS-style note pool"**. 2018 Beat Games (Meta acq 2019) release, 2026 의 Quest 3/Vision Pro 의 cross-platform mainstream, "framerate 의 holy 의 above 의 nothing" 의 architecture 의 lesson.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 architecture 제약
|
||||
- **Framerate floor**: 90 fps (Quest 2) · 120 fps (Quest 3 · Vision Pro).
|
||||
- **Motion-to-photon**: < 20 ms.
|
||||
- **GC 의 hostile**: 매 frame 의 spike 의 nausea 의 cause → object pool · Burst · Job System.
|
||||
- **Determinism**: scoring 의 reproducible — 매 input · note 의 fixed seed.
|
||||
|
||||
### 매 component
|
||||
- **Beatmap loader**: `.dat` JSON parse → preallocated note buffer.
|
||||
- **Note spawner**: 매 future 6 sec 의 lookahead, pool 에서 의 pull.
|
||||
- **Saber controller**: hand pose tracking + velocity smoothing.
|
||||
- **Cut detector**: plane intersection · direction match · score.
|
||||
- **Audio sync**: NJS (Note Jump Speed) + offset 의 calibrate.
|
||||
|
||||
### 매 응용
|
||||
1. 매 fitness app (Supernatural, FitXR) 의 rhythm pattern 의 inherit.
|
||||
2. Trainer/simulator (medical · drill) 의 timing-critical UX.
|
||||
3. Education app (language drill · typing tutor) 의 VR variant.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Unity DOTS: note spawn 의 zero-alloc
|
||||
```csharp
|
||||
[BurstCompile]
|
||||
public partial struct NoteSpawnSystem : ISystem
|
||||
{
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var beatmap = SystemAPI.GetSingleton<BeatmapBuffer>();
|
||||
var time = SystemAPI.Time.ElapsedTime;
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
for (int i = beatmap.NextIndex; i < beatmap.Notes.Length; i++)
|
||||
{
|
||||
var n = beatmap.Notes[i];
|
||||
if (n.Time - time > LookaheadSeconds) break;
|
||||
|
||||
var e = ecb.Instantiate(beatmap.NotePrefab);
|
||||
ecb.SetComponent(e, new Translation { Value = n.SpawnPosition });
|
||||
ecb.SetComponent(e, new NoteData { CutDirection = n.Direction, HitTime = n.Time });
|
||||
beatmap.NextIndex = i + 1;
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Saber-cut detector (plane intersection)
|
||||
```csharp
|
||||
public bool TryCut(Vector3 saberTip, Vector3 saberBase, Vector3 saberVelocity, NoteData note,
|
||||
out CutResult result)
|
||||
{
|
||||
var plane = new Plane(saberVelocity.normalized, saberBase);
|
||||
if (!plane.Raycast(new Ray(note.Position, note.Forward), out float enter)) {
|
||||
result = default; return false;
|
||||
}
|
||||
|
||||
float speed = saberVelocity.magnitude;
|
||||
float angle = Vector3.Angle(saberVelocity, note.RequiredCutDirection);
|
||||
float accuracy = 1f - Mathf.Clamp01(angle / 60f);
|
||||
|
||||
result = new CutResult {
|
||||
Score = Mathf.RoundToInt(speed * accuracy * 115f),
|
||||
IsValid = speed > 2f && angle < 60f,
|
||||
};
|
||||
return result.IsValid;
|
||||
}
|
||||
```
|
||||
|
||||
### Audio-visual sync (NJS-based)
|
||||
```csharp
|
||||
// Note 의 spawn position 의 calc
|
||||
float jumpDist = njs * (60f / bpm) * halfJumpDuration * 2f;
|
||||
Vector3 spawnPos = playerPosition + Vector3.forward * jumpDist;
|
||||
|
||||
// Note 의 frame 마다 의 lerp
|
||||
float t = (Time.timeAsDouble - spawnTime) / (hitTime - spawnTime);
|
||||
note.transform.position = Vector3.Lerp(spawnPos, hitPos, (float)t);
|
||||
```
|
||||
|
||||
### Object pool (zero-GC frame)
|
||||
```csharp
|
||||
public class NotePool
|
||||
{
|
||||
readonly Stack<Note> pool = new(256);
|
||||
readonly Note prefab;
|
||||
|
||||
public Note Get() => pool.Count > 0 ? pool.Pop() : Object.Instantiate(prefab);
|
||||
public void Return(Note n) {
|
||||
n.gameObject.SetActive(false);
|
||||
pool.Push(n);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### OpenXR foveated rendering hint (Quest 3)
|
||||
```csharp
|
||||
var feature = OpenXRSettings.Instance.GetFeature<FoveationFeature>();
|
||||
feature.foveatedRenderingLevel = FoveatedRenderingLevel.High;
|
||||
feature.useDynamicFoveatedRendering = true; // eye-tracked on Quest 3
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| < 100 active note · prototype | MonoBehaviour + pool |
|
||||
| > 200 active note · production | DOTS/ECS + Burst |
|
||||
| Cross-platform (PCVR + Quest) | Unity URP + multi-quality preset |
|
||||
| Modding support | Open beatmap format (`.dat`) + PluginLoader |
|
||||
| 매 90fps 미달 의 perf budget | Foveation · LOD · GPU instancing |
|
||||
|
||||
**기본값**: Unity DOTS + URP + OpenXR — 매 Quest 3 의 baseline 의 fit.
|
||||
|
||||
## 🔗 Graph
|
||||
- 응용: [[bitECS와_SharedArrayBuffer의_실제_코드_통합]]
|
||||
- Adjacent: [[Object Pool]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: beatmap 의 procedural generation, cut-direction pattern 의 difficulty curve 의 tune, NJS · offset 의 starter value 의 suggest.
|
||||
**언제 X**: 매 hand-crafted choreography (top mapper 의 art form) — LLM 의 generate 의 bland.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **GC 의 frame 의 allocate**: 매 90fps 의 break → motion sickness.
|
||||
- **Animation curve 의 audio sync**: dt 의 drift → pop. NJS-based linear lerp 의 use.
|
||||
- **Saber 의 trigger collider**: physics step 의 sub-frame miss — manual raycast/plane 의 use.
|
||||
- **Per-note GameObject Instantiate**: pool 의 mandatory.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Beat Games postmortem GDC 2019, Unity DOTS 1.3 docs, OpenXR 1.1 spec, BSMG modding wiki).
|
||||
- 신뢰도 A-.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — VR architecture constraints + DOTS spawner + cut detector |
|
||||
@@ -0,0 +1,174 @@
|
||||
---
|
||||
id: wiki-2026-0508-belief-system
|
||||
title: Belief System
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Belief System, Agent Belief, Knowledge Base, BDI Beliefs]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.85
|
||||
verification_status: applied
|
||||
tags: [agent-architecture, ai, bdi, knowledge-representation]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: python
|
||||
framework: bdi,langchain,owl-rdf
|
||||
---
|
||||
|
||||
# Belief System
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 agent 의 'world 가 such-and-such' 의 internal model — 매 perception update · inference 로 부터 의 derive"**. Bratman 1987 BDI (Belief-Desire-Intention) 의 origin, 2026 의 LLM agent 의 working memory · scratchpad · long-term memory store 의 modern incarnation.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 component
|
||||
- **Belief base**: fact set (logical · probabilistic · embedding-vector).
|
||||
- **Update rule**: perception → belief revision (AGM postulates).
|
||||
- **Query interface**: 매 plan 의 condition 의 check.
|
||||
- **Consistency check**: contradiction 의 detect · resolve.
|
||||
|
||||
### 매 representation
|
||||
- **Symbolic**: Prolog · Datalog · OWL/RDF.
|
||||
- **Probabilistic**: Bayesian network · Markov logic.
|
||||
- **Vector/embedding**: 매 modern LLM agent — chunked text + embedding store.
|
||||
- **Hybrid**: structured fact + free-text (most 2026 agent system).
|
||||
|
||||
### 매 응용
|
||||
1. 매 BDI agent (JADE · Jadex · Jason).
|
||||
2. Robot world model (ROS knowledge_base).
|
||||
3. LLM agent scratchpad (Claude · LangGraph state).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### BDI-style belief base (Python)
|
||||
```python
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
@dataclass
|
||||
class BeliefBase:
|
||||
facts: dict[str, Any] = field(default_factory=dict)
|
||||
confidence: dict[str, float] = field(default_factory=dict)
|
||||
|
||||
def add(self, key: str, value: Any, conf: float = 1.0):
|
||||
if key in self.facts and self.facts[key] != value:
|
||||
# contradiction → keep higher-confidence
|
||||
if conf <= self.confidence[key]: return
|
||||
self.facts[key] = value
|
||||
self.confidence[key] = conf
|
||||
|
||||
def query(self, key: str) -> tuple[Any, float] | None:
|
||||
if key not in self.facts: return None
|
||||
return self.facts[key], self.confidence[key]
|
||||
|
||||
bb = BeliefBase()
|
||||
bb.add("door_open", True, 0.9)
|
||||
bb.add("battery_pct", 73, 1.0)
|
||||
```
|
||||
|
||||
### LLM agent: belief 의 prompt-injected scratchpad
|
||||
```python
|
||||
def build_prompt(beliefs: BeliefBase, task: str) -> str:
|
||||
facts = "\n".join(
|
||||
f"- {k}: {v} (conf={c:.2f})"
|
||||
for k, (v, c) in [(k, (bb.facts[k], bb.confidence[k])) for k in bb.facts]
|
||||
)
|
||||
return f"""You are an agent with the following beliefs:
|
||||
{facts}
|
||||
|
||||
Task: {task}
|
||||
Plan your next action and explain reasoning."""
|
||||
```
|
||||
|
||||
### Bayesian belief update (sensor fusion)
|
||||
```python
|
||||
def bayes_update(prior: float, likelihood: float, evidence: float) -> float:
|
||||
"""P(H|E) = P(E|H) * P(H) / P(E)"""
|
||||
return likelihood * prior / evidence
|
||||
|
||||
# Door 의 open 의 prior 0.5, sensor 의 reading "open" 의 likelihood 0.9, evidence 0.6
|
||||
posterior = bayes_update(0.5, 0.9, 0.6) # 0.75
|
||||
```
|
||||
|
||||
### Vector belief store (long-term memory)
|
||||
```python
|
||||
import chromadb
|
||||
|
||||
client = chromadb.PersistentClient("./agent_memory")
|
||||
beliefs = client.get_or_create_collection("beliefs")
|
||||
|
||||
beliefs.upsert(
|
||||
ids=["b-001"],
|
||||
documents=["User prefers vegetarian restaurants in 5km radius"],
|
||||
metadatas=[{"source": "user_pref_2026-04-12", "conf": 0.95}],
|
||||
)
|
||||
|
||||
hits = beliefs.query(query_texts=["restaurant recommendation"], n_results=5)
|
||||
```
|
||||
|
||||
### Belief revision (AGM contraction)
|
||||
```python
|
||||
def contract(bb: BeliefBase, fact: str):
|
||||
"""Remove fact + minimal additional facts to restore consistency."""
|
||||
if fact in bb.facts:
|
||||
del bb.facts[fact]
|
||||
del bb.confidence[fact]
|
||||
# Naive — production uses entrenchment ordering
|
||||
```
|
||||
|
||||
### LangGraph stateful belief
|
||||
```python
|
||||
from langgraph.graph import StateGraph
|
||||
from typing import TypedDict
|
||||
|
||||
class AgentState(TypedDict):
|
||||
beliefs: dict
|
||||
desires: list[str]
|
||||
intentions: list[str]
|
||||
|
||||
def perceive(state: AgentState) -> AgentState:
|
||||
state["beliefs"]["weather"] = call_weather_api()
|
||||
return state
|
||||
|
||||
graph = StateGraph(AgentState).add_node("perceive", perceive)
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Representation |
|
||||
|---|---|
|
||||
| 매 deterministic rule-based | Datalog · Prolog |
|
||||
| Uncertain sensor data | Bayesian network |
|
||||
| Open-domain text · LLM agent | Vector embedding + scratchpad |
|
||||
| Robot · spatial | Probabilistic occupancy grid + KB |
|
||||
| Multi-agent · negotiation | KQML · FIPA + symbolic KB |
|
||||
|
||||
**기본값**: hybrid — symbolic skeleton (key fact 의 dict) + vector store (free-text long-term).
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Agent Architecture]] · [[Knowledge Representation]]
|
||||
- 변형: [[Bayesian_Inference]]
|
||||
- Adjacent: [[A2A]] · [[Belief Revision]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: belief base 의 natural-language form 의 store (e.g. user preference summary), scratchpad 의 reasoning step 의 record.
|
||||
**언제 X**: 매 safety-critical inference (medical · industrial) — LLM 의 hallucinate · 매 symbolic verifiable 의 require.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **No revision policy**: belief 의 add-only — contradictory state 의 accumulate.
|
||||
- **Confidence 의 ignore**: 매 fact 의 binary present/absent — sensor noise 의 not handle.
|
||||
- **Global belief blob**: 매 agent 의 share — privacy · ownership 의 unclear → BDI per-agent KB.
|
||||
- **Vector-only memory**: 매 deterministic recall (e.g. "what is user's name") 의 unreliable — symbolic key/value 의 mix.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Bratman 1987, AGM 1985, FIPA agent specs, LangGraph docs, ChromaDB docs).
|
||||
- 신뢰도 A-.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — BDI + 2026 LLM hybrid (vector + symbolic) |
|
||||
@@ -0,0 +1,167 @@
|
||||
---
|
||||
id: wiki-2026-0508-big-data
|
||||
title: Big Data
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Big Data, Large-Scale Data Processing]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [data, distributed-systems, analytics, lakehouse]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: python
|
||||
framework: spark,duckdb,iceberg,polars
|
||||
---
|
||||
|
||||
# Big Data
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 single-machine 의 fit 의 X · single-pass 의 fit 의 X — 매 distributed compute · columnar storage 의 require"**. 2003 Google MapReduce 논문 의 origin, Hadoop → Spark → Lakehouse (Iceberg+Delta+Hudi) 의 evolve, 2026 의 single-node DuckDB/Polars 의 "Big Data is dead" 의 movement 의 mainstream.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 5 V
|
||||
- **Volume**: TB-PB scale.
|
||||
- **Velocity**: streaming · near-real-time.
|
||||
- **Variety**: structured + semi + unstructured.
|
||||
- **Veracity**: data quality · trust.
|
||||
- **Value**: ROI of analytics.
|
||||
|
||||
### 매 stack 2026
|
||||
- **Storage**: object store (S3/GCS) + open table format (Iceberg · Delta · Hudi).
|
||||
- **Compute**: Spark · Trino · DuckDB · Polars · Snowflake · BigQuery.
|
||||
- **Orchestration**: Airflow · Dagster · Prefect.
|
||||
- **Stream**: Kafka · Flink · Kinesis.
|
||||
- **Catalog**: Unity · Polaris · Nessie · Glue.
|
||||
|
||||
### 매 응용
|
||||
1. 매 BI 의 dashboard.
|
||||
2. ML training pipeline (feature store).
|
||||
3. Operational analytics (real-time fraud, ad bidding).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Iceberg table 의 Spark 에서 의 write
|
||||
```python
|
||||
from pyspark.sql import SparkSession
|
||||
|
||||
spark = SparkSession.builder \
|
||||
.config("spark.sql.catalog.local", "org.apache.iceberg.spark.SparkCatalog") \
|
||||
.config("spark.sql.catalog.local.type", "hadoop") \
|
||||
.config("spark.sql.catalog.local.warehouse", "s3a://lake/warehouse") \
|
||||
.getOrCreate()
|
||||
|
||||
df = spark.read.json("s3a://raw/events/*.json")
|
||||
df.writeTo("local.events.daily").partitionedBy("date").createOrReplace()
|
||||
```
|
||||
|
||||
### DuckDB: 매 single-node "big data" (laptop 의 100GB)
|
||||
```python
|
||||
import duckdb
|
||||
|
||||
con = duckdb.connect()
|
||||
con.sql("""
|
||||
SELECT user_id, COUNT(*) AS events, SUM(amount) AS revenue
|
||||
FROM read_parquet('s3://lake/events/2026/*.parquet')
|
||||
WHERE event_type = 'purchase'
|
||||
GROUP BY user_id
|
||||
ORDER BY revenue DESC
|
||||
LIMIT 100
|
||||
""").show()
|
||||
```
|
||||
|
||||
### Polars: out-of-core lazy
|
||||
```python
|
||||
import polars as pl
|
||||
|
||||
df = (
|
||||
pl.scan_parquet("s3://lake/events/*.parquet")
|
||||
.filter(pl.col("event_type") == "purchase")
|
||||
.group_by("user_id")
|
||||
.agg(pl.len().alias("events"), pl.col("amount").sum().alias("revenue"))
|
||||
.sort("revenue", descending=True)
|
||||
.limit(100)
|
||||
)
|
||||
print(df.collect(streaming=True))
|
||||
```
|
||||
|
||||
### Flink: streaming aggregation
|
||||
```java
|
||||
DataStream<Event> events = env.fromSource(kafkaSource, ...);
|
||||
|
||||
events.keyBy(Event::userId)
|
||||
.window(TumblingEventTimeWindows.of(Time.minutes(5)))
|
||||
.aggregate(new RevenueAggregator())
|
||||
.sinkTo(icebergSink);
|
||||
```
|
||||
|
||||
### Iceberg time-travel + schema evolution
|
||||
```sql
|
||||
-- snapshot 의 query
|
||||
SELECT * FROM events FOR VERSION AS OF 8723649283746;
|
||||
SELECT * FROM events FOR TIMESTAMP AS OF '2026-05-09 00:00:00';
|
||||
|
||||
-- column add (no rewrite)
|
||||
ALTER TABLE events ADD COLUMN device_id STRING;
|
||||
|
||||
-- partition evolution
|
||||
ALTER TABLE events ADD PARTITION FIELD bucket(16, user_id);
|
||||
```
|
||||
|
||||
### Spark: dynamic partition pruning + AQE
|
||||
```python
|
||||
spark.conf.set("spark.sql.adaptive.enabled", "true")
|
||||
spark.conf.set("spark.sql.adaptive.coalescePartitions.enabled", "true")
|
||||
spark.conf.set("spark.sql.adaptive.skewJoin.enabled", "true")
|
||||
|
||||
# AQE 의 plan 의 runtime 의 reoptimize
|
||||
df = spark.sql("""
|
||||
SELECT u.name, e.event_type, COUNT(*)
|
||||
FROM events e JOIN users u ON e.user_id = u.id
|
||||
WHERE e.date >= '2026-05-01'
|
||||
GROUP BY u.name, e.event_type
|
||||
""")
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Stack |
|
||||
|---|---|
|
||||
| < 100 GB · single node | DuckDB · Polars |
|
||||
| 100GB - 10TB batch | Spark + Iceberg |
|
||||
| > 10 TB / day | Spark/Trino + Iceberg + Snowflake |
|
||||
| Streaming < 1s latency | Flink + Kafka |
|
||||
| Ad-hoc SQL | Trino · DuckDB |
|
||||
| ML training | Spark + Petastorm 또는 Ray Data |
|
||||
|
||||
**기본값**: Iceberg-on-S3 + Spark/DuckDB 의 hybrid — 매 modern lakehouse 의 standard.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Distributed Systems]] · [[Data Engineering]]
|
||||
- 변형: [[Lakehouse]] · [[Data Warehouse]]
|
||||
- 응용: [[Apache Ignite]]
|
||||
- Adjacent: [[Append-only log]] · [[Stream-Processing-Architectures|Stream Processing]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: SQL 의 generate, partitioning strategy 의 advise, schema evolution diff 의 explain, Iceberg 의 table maintenance 의 query 의 draft.
|
||||
**언제 X**: 매 production tuning (shuffle partition · executor sizing) — metric-driven 의 require, LLM hint 의 starting point 만.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Premature distribution**: 매 < 50GB 의 case 의 Spark — DuckDB 의 100x faster.
|
||||
- **Small file problem**: Spark 의 1KB parquet 의 millions — compaction 의 require.
|
||||
- **Hive-style partition explosion**: 매 high-cardinality column 의 partition (e.g. user_id) — Iceberg bucket transform 의 use.
|
||||
- **Schema-on-read 의 over-rely**: governance 의 erode — open table format 의 use.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Apache Iceberg/Spark/Flink docs, MotherDuck "Big Data is Dead" 2023, Databricks Lakehouse paper).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — 5V + 2026 lakehouse stack + DuckDB/Polars/Flink patterns |
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
category: Other
|
||||
tags: [auto-wikified, technical-documentation, other]
|
||||
title: Boilerplate
|
||||
description: "**보일러플레이트(Boilerplate)**는 핵심 비즈니스 로직을 지원하기 위해 반복적으로 작성해야 하는 가치가 낮고 판에 박힌 코드를 의미한다 [1]."
|
||||
last_updated: 2026-05-04
|
||||
---
|
||||
|
||||
# Boilerplate
|
||||
|
||||
## 📌 Brief Summary
|
||||
**보일러플레이트(Boilerplate)**는 핵심 비즈니스 로직을 지원하기 위해 반복적으로 작성해야 하는 가치가 낮고 판에 박힌 코드를 의미한다 [1]. 현대 소프트웨어 개발 프레임워크와 라이브러리들은 이러한 보일러플레이트 코드를 줄여 개발 생산성을 높이고 유지보수성을 향상시키는 방향으로 발전하고 있다 [2-4]. 이를 위해 프레임워크 차원의 추상화, 상태 관리 도구의 최적화, 그리고 코드 자동 생성 기술이 적극적으로 활용된다 [5, 6].
|
||||
|
||||
## 📖 Core Content
|
||||
* **보일러플레이트의 문제점**: 귀중한 핵심 비즈니스 로직이 방대한 양의 가치 없는 보일러플레이트 코드와 섞이게 되면, 전체적인 코드를 이해하고 유지보수하기가 매우 어려워진다 [1].
|
||||
* **프론트엔드 생태계의 보일러플레이트 감소**:
|
||||
* **Vue.js**: Vue 3의 Composition API를 도입하면 개발 주기가 단축되며 **보일러플레이트 코드를 30%가량 줄일 수 있다** [2]. 또한, 상태 관리 라이브러리로 기존의 Vuex 대신 Pinia를 사용하면 불필요한 보일러플레이트를 제거하고 타입스크립트 지원을 크게 강화할 수 있다 [3].
|
||||
* **React**: 기존의 Redux Toolkit처럼 보일러플레이트가 많은 도구 대신, 최근에는 **보일러플레이트가 적은 Zustand나 서버 상태 관리에 특화된 TanStack Query(React Query)가 실전 표준**으로 자리 잡고 있다 [4]. React Query 사용 시 발생하는 중복된 프리페치(prefetch) 등의 보일러플레이트 코드는 별도의 헬퍼 함수로 추출하여 코드를 깔끔하게 유지하는 것이 권장된다 [7].
|
||||
* **백엔드 및 모바일 환경의 자동화 및 추상화**:
|
||||
* **Spring Boot**: Spring Boot 프레임워크 자체의 핵심 도입 목적 중 하나가 보일러플레이트의 축소이다 [8]. **Lombok 라이브러리**를 사용하여 자바의 반복적인 보일러플레이트 코드를 대폭 줄일 수 있으며 [9, 10], **Spring Data JPA**는 리포지토리 패턴과 쿼리 자동 생성을 통해 데이터베이스 접근에 필요한 보일러플레이트를 크게 제거하여 개발 시간을 단축시킨다 [6, 11]. 또한 Spring Cloud는 분산 시스템 조정에 필요한 보일러플레이트 패턴을 신속하게 구현할 수 있도록 돕는다 [12].
|
||||
* **React Native**: 새로운 아키텍처(New Architecture) 환경에서는 **Codegen 도구**가 TypeScript나 Flow 타입 정의를 분석하여 JavaScript와 네이티브(Native) 영역을 연결하는 데 필요한 C++ 보일러플레이트 코드를 자동으로 생성해 준다 [5].
|
||||
|
||||
## ⚖️ Trade-offs & Caveats
|
||||
* **AI 생성 도구 의존에 따른 일관성 결여 위험**: 최근의 생성형 AI 도구들은 보일러플레이트 코드를 매우 빠르게 생성할 수 있지만, 정해진 템플릿 없이 매번 코드를 새롭게 지어내기 때문에 **코드의 일관성을 보장하지 못한다** [13]. AI가 생성한 보일러플레이트에 과도하게 의존하면 미세한 기능적, 외관상 차이가 발생할 수 있으며, 이로 인해 코드베이스 전체에 걸쳐 일괄적인 패턴 변경이나 리팩토링(예: 글로벌 에러 처리 방식 변경 등)을 수동으로 추적하고 수정해야 하는 치명적인 단점이 발생한다 [13, 14].
|
||||
* **과도한 추상화로 인한 가시성 저하 및 디버깅 난이도 상승**: 보일러플레이트를 없애기 위해 관점 지향 프로그래밍(AOP) 도구나 프레임워크의 자동 구성 기능을 사용할 경우, 코드는 깔끔해지지만 **핵심 로직이 '마법처럼' 숨겨지게 되는 트레이드오프**가 발생한다 [15, 16]. 이는 프로젝트에 새로 합류한 개발자가 코드의 실행 흐름을 파악하기 어렵게 만들며, 런타임 오류나 예상치 못한 동작이 발생했을 때 디버깅의 난이도를 크게 높이는 제약 사항이 된다 [15].
|
||||
|
||||
---
|
||||
*Last updated: 2026-05-03*
|
||||
@@ -0,0 +1,180 @@
|
||||
---
|
||||
id: wiki-2026-0508-bottom-up-approach
|
||||
title: Bottom Up Approach
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Bottom-Up Design, Bottom-Up vs Top-Down, Composition-First Design]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [design, methodology, top-down, bottom-up, architecture]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: Polyglot
|
||||
framework: Methodology
|
||||
---
|
||||
|
||||
# Bottom Up Approach
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 bottom-up = 매 작은 building block 부터 만들고 합쳐 system 으로 키우는 composition-first 접근."**. 매 top-down 이 spec → decompose 인 반면, bottom-up 은 primitive → compose. 매 2026 modern 실무는 hybrid (meet-in-the-middle) — 매 spike/prototype 은 bottom-up, architecture 는 top-down, 매 LLM-driven 합성에서 bottom-up 의 compositional reasoning 이 다시 부상.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 distinction
|
||||
- **Top-down**: 매 high-level spec → subsystem → module → function. 매 known-domain 적합.
|
||||
- **Bottom-up**: 매 primitive utility → combinator → application. 매 unknown-domain / discovery 적합.
|
||||
- **Meet-in-the-middle**: 매 양쪽 동시 → 중간에서 만남.
|
||||
- **Outside-in (TDD)**: 매 acceptance test → unit — 매 top-down 변형.
|
||||
|
||||
### 매 strengths
|
||||
- **Reusability**: 매 primitive 가 여러 system 에 공통.
|
||||
- **Testability**: 매 작은 unit 부터 100% covered.
|
||||
- **Discoverability**: 매 unknown territory 에서 "뭐가 가능한지" 발견.
|
||||
- **Compositional**: 매 functional programming, Lisp, Forth 의 핵심.
|
||||
|
||||
### 매 weaknesses
|
||||
- **No big picture**: 매 individual unit 우수해도 system 일관성 부재 가능.
|
||||
- **YAGNI risk**: 매 안 쓰일 primitive 까지 만들 수 있음.
|
||||
- **Integration debt**: 매 끝에 가서야 합쳐지는 surprise.
|
||||
|
||||
### 매 응용
|
||||
1. **Functional libraries**: 매 Lodash, Ramda — combinator 가 primitive.
|
||||
2. **Forth / Concatenative langs**: 매 word 정의 후 합성.
|
||||
3. **Embedded firmware**: 매 driver → HAL → app.
|
||||
4. **ML pipeline**: 매 ops → layers → model.
|
||||
5. **Spike / discovery prototype**: 매 architecture 모르는 phase.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Functional combinator (bottom-up)
|
||||
```haskell
|
||||
-- primitives
|
||||
add1 :: Int -> Int
|
||||
add1 x = x + 1
|
||||
|
||||
double :: Int -> Int
|
||||
double x = x * 2
|
||||
|
||||
-- composition (no top-level spec needed)
|
||||
addThenDouble :: Int -> Int
|
||||
addThenDouble = double . add1
|
||||
|
||||
-- application emerges by composing
|
||||
process :: [Int] -> [Int]
|
||||
process = map (double . add1) . filter (> 0)
|
||||
```
|
||||
|
||||
### Lisp — define primitives, then compose
|
||||
```lisp
|
||||
(defun double (x) (* x 2))
|
||||
(defun increment (x) (+ x 1))
|
||||
(defun process (xs)
|
||||
(mapcar (lambda (x) (double (increment x)))
|
||||
(remove-if-not #'plusp xs)))
|
||||
```
|
||||
|
||||
### Embedded — HAL up
|
||||
```c
|
||||
// 1. register-level primitive
|
||||
void gpio_set_high(uint32_t pin) { GPIO_BSRR = 1u << pin; }
|
||||
|
||||
// 2. driver
|
||||
void led_on(led_t led) { gpio_set_high(led.pin); }
|
||||
|
||||
// 3. application
|
||||
void heartbeat(void) {
|
||||
while (1) { led_on(STATUS_LED); delay_ms(500); led_off(STATUS_LED); delay_ms(500); }
|
||||
}
|
||||
```
|
||||
|
||||
### React UI — primitive components up
|
||||
```tsx
|
||||
// 1. atoms
|
||||
const Button = ({ children, ...props }) => <button className="btn" {...props}>{children}</button>;
|
||||
const Input = ({ ...props }) => <input className="input" {...props} />;
|
||||
|
||||
// 2. molecules
|
||||
const SearchBox = () => (
|
||||
<div className="search">
|
||||
<Input placeholder="Search..." />
|
||||
<Button>Go</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
// 3. page (emerges)
|
||||
const HomePage = () => <header><Logo /><SearchBox /></header>;
|
||||
```
|
||||
|
||||
### Spike-driven discovery
|
||||
```python
|
||||
# Day 1 — bottom-up exploration before architecture exists
|
||||
def parse_log_line(s): ... # primitive
|
||||
def filter_errors(lines): ... # combinator
|
||||
def aggregate_by_minute(lines): ...
|
||||
def render_chart(buckets): ...
|
||||
# After 2 days you SEE the pipeline → THEN write architecture doc
|
||||
```
|
||||
|
||||
### LLM compositional planning (modern bottom-up)
|
||||
```python
|
||||
# Claude tool-use bottom-up: define small tools, let model compose
|
||||
tools = [
|
||||
{"name": "search_db", "description": "..."},
|
||||
{"name": "render_chart", "description": "..."},
|
||||
{"name": "send_email", "description": "..."},
|
||||
]
|
||||
# Model receives high-level task and composes a plan from primitives
|
||||
client.messages.create(
|
||||
model="claude-opus-4-7",
|
||||
tools=tools,
|
||||
messages=[{"role": "user", "content": "Send a weekly sales summary"}],
|
||||
)
|
||||
```
|
||||
|
||||
### Forth — concatenative composition
|
||||
```forth
|
||||
: SQUARE DUP * ; \ primitive
|
||||
: CUBE DUP SQUARE * ; \ compose
|
||||
: AREA SQUARE 3.14159 * ; \ compose with literal
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Domain unknown / spike | Bottom-up |
|
||||
| Spec frozen, deadline | Top-down |
|
||||
| Reusable library | Bottom-up |
|
||||
| End-to-end product | Hybrid (meet in middle) |
|
||||
| TDD acceptance | Outside-in (top-down variant) |
|
||||
| LLM tool-using agent | Bottom-up primitives, top-down task |
|
||||
|
||||
**기본값**: hybrid — 매 architecture skeleton (top-down) + 매 primitive layer (bottom-up) → 매 meet middle.
|
||||
|
||||
## 🔗 Graph
|
||||
- 응용: [[Functional_Programming]] · [[Atomic_Design]]
|
||||
- Adjacent: [[Composition]] · [[YAGNI]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: unknown domain spike, library design, primitive 조합 탐색, agentic tool composition.
|
||||
**언제 X**: regulated/safety system 의 spec-driven part — 매 top-down 우선.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Pure bottom-up without integration plan**: 매 primitive 100개 + 통합 안 됨 = 0 가치.
|
||||
- **Premature primitives**: 매 안 쓰일 utility 양산 (YAGNI 위반).
|
||||
- **Bottom-up for legally-specified systems**: 매 spec drift → compliance fail.
|
||||
- **Skipping integration tests**: 매 unit 모두 green 인데 system fail.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Abelson&Sussman SICP 1985, Forth dictionary docs, "Out of the Tar Pit" 2006).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — bottom-up vs top-down, composition patterns, hybrid default |
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
category: Architecture
|
||||
tags: [auto-wikified, technical-documentation, architecture]
|
||||
title: Bounded Context
|
||||
description: "Bounded Context(제한된 컨텍스트)는 도메인 주도 설계(DDD)에서 유래한 용어로, 단일하고 응집력 있는 도메인 개념에 매핑되는 코드 조직의 기본 단위를 의미합니다 [1]."
|
||||
last_updated: 2026-05-04
|
||||
---
|
||||
|
||||
# Bounded Context
|
||||
|
||||
## 📌 Brief Summary
|
||||
Bounded Context(제한된 컨텍스트)는 도메인 주도 설계(DDD)에서 유래한 용어로, 단일하고 응집력 있는 도메인 개념에 매핑되는 코드 조직의 기본 단위를 의미합니다 [1]. 이상적인 Bounded Context는 명확한 단일 책임을 지니며, 내부 로직을 독립적으로 추론할 수 있어야 합니다 [1]. 프레임워크 실전 아키텍처 패턴에서 이는 다른 모듈과의 결합도(coupling)를 최소화하여 시스템의 모듈성 및 유지보수성을 극대화하는 핵심 원리로 작용합니다 [1].
|
||||
|
||||
## 📖 Core Content
|
||||
* **단일 책임과 독립적 패키징:** 프레임워크(예: Django)에서 Bounded Context로 기능하는 단위(App 등)는 이론적으로 별도의 패키지나 마이크로서비스로 쉽게 추출될 수 있을 만큼 독립적이어야 합니다 [1, 2]. 반면 `utils`, `helpers`, `misc`와 같이 다용도의 의미를 지닌 명명은 단일 도메인 개념을 위반하고 앱이 너무 많은 역할을 수행하고 있다는 경고 신호로 간주됩니다 [1].
|
||||
* **모델 소유권과 API를 통한 캡슐화:** 각 Bounded Context는 고유한 데이터 모델을 직접 소유해야 합니다 [3]. 실전 전자상거래 백엔드 사례를 보면, 주문(`orders/`) 컨텍스트가 재고(`inventory/`)를 확인할 때 상대방의 데이터 모델을 직접 임포트하지 않고 반드시 공개된 셀렉터(Selector) API를 통해서만 상호작용하도록 데이터 접근을 격리합니다 [3].
|
||||
* **마이크로서비스 경계로의 매핑:** 시스템이 성장함에 따라 잘 분리된 Bounded Context(예: NestJS의 모듈 시스템 등)는 향후 모놀리식 아키텍처를 해체할 때 자연스럽게 마이크로서비스의 경계로 확장 및 매핑될 수 있는 강력한 구조적 기반을 제공합니다 [2].
|
||||
|
||||
## ⚖️ Trade-offs & Caveats
|
||||
* **경계 식별의 어려움과 복잡성 전가:** 시스템 내에서 Bounded Context의 경계(Boundary)를 올바르게 정의하는 것은 매우 까다로운 작업입니다 [4]. 컴포넌트를 깔끔하게 분리하지 못할 경우, 내부의 복잡성을 해소하지 못한 채 단지 컴포넌트 간의 연결(통신) 지점으로 복잡성만 전가하는 역효과를 초래할 수 있습니다 [4].
|
||||
* **직접적인 모델 접근 제약과 간접성 증가:** Bounded Context 간의 독립성을 철저히 보장하기 위해 다른 도메인의 모델을 직접 참조하는 것이 금지됩니다 [3]. 모든 데이터 접근이 퍼블릭 API를 통해서만 이루어져야 하므로 [3], 시스템 내부적으로 간접 호출 계층과 인터페이스가 증가하여 설계 초기의 구현 오버헤드가 발생할 수 있습니다.
|
||||
|
||||
---
|
||||
*Last updated: 2026-05-03*
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
id: wiki-2026-0508-branded-types
|
||||
title: Branded Types
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Branded Types, Nominal Types, Opaque Types, Tagged Types]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.95
|
||||
verification_status: applied
|
||||
tags: [typescript, type-system, nominal-typing, domain-modeling]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: typescript-5.x,zod,effect-ts
|
||||
---
|
||||
|
||||
# Branded Types
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 structural type 의 nominal 의 emulate — 매 `string` 두 개 의 distinguish 의 enforce"**. TypeScript 의 structural typing 의 long-standing pain (UserId vs OrderId · Email vs string) 의 solve, 2026 의 Effect-TS Brand · Zod brand · Type-Fest Tagged 의 ergonomic API 의 mainstream.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 problem
|
||||
- TS structural: 매 `string` 의 모든 `string` 의 assignable.
|
||||
- `function sendEmail(userId: string, email: string)` — 매 swap 의 type check pass.
|
||||
- 매 nominal hint 의 require — but TS 의 native 의 no.
|
||||
|
||||
### 매 mechanism
|
||||
- Intersection 의 phantom property: `string & { __brand: "UserId" }`.
|
||||
- 매 runtime 의 plain string — 매 zero cost.
|
||||
- Constructor function 의 cast — 매 single point 의 validate.
|
||||
|
||||
### 매 응용
|
||||
1. Domain ID (UserId · OrderId · ProductId).
|
||||
2. Validated string (Email · URL · UUID · NonEmptyString).
|
||||
3. Numeric unit (Meters · Seconds · USD · Cents).
|
||||
4. Sanitized input (HtmlSafeString · SqlEscapedString).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Vanilla TS brand
|
||||
```typescript
|
||||
type Brand<T, B> = T & { readonly __brand: B };
|
||||
|
||||
type UserId = Brand<string, "UserId">;
|
||||
type OrderId = Brand<string, "OrderId">;
|
||||
|
||||
const UserId = (s: string): UserId => s as UserId;
|
||||
const OrderId = (s: string): OrderId => s as OrderId;
|
||||
|
||||
function getUser(id: UserId) { /* ... */ }
|
||||
|
||||
const u = UserId("u-123");
|
||||
const o = OrderId("o-456");
|
||||
|
||||
getUser(u); // OK
|
||||
getUser(o); // ❌ Type error — 매 nominal 의 distinguish
|
||||
getUser("u-123"); // ❌ raw string 의 reject
|
||||
```
|
||||
|
||||
### Branded with smart constructor (validate + brand)
|
||||
```typescript
|
||||
type Email = Brand<string, "Email">;
|
||||
|
||||
function Email(s: string): Email {
|
||||
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(s)) {
|
||||
throw new Error(`invalid email: ${s}`);
|
||||
}
|
||||
return s as Email;
|
||||
}
|
||||
|
||||
// safer Result variant
|
||||
function tryEmail(s: string): { ok: true; value: Email } | { ok: false; error: string } {
|
||||
return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(s)
|
||||
? { ok: true, value: s as Email }
|
||||
: { ok: false, error: "invalid email" };
|
||||
}
|
||||
```
|
||||
|
||||
### Zod brand (runtime + type)
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
|
||||
const UserIdSchema = z.string().uuid().brand<"UserId">();
|
||||
type UserId = z.infer<typeof UserIdSchema>;
|
||||
|
||||
const id = UserIdSchema.parse("550e8400-e29b-41d4-a716-446655440000");
|
||||
// id is branded UserId — 매 raw uuid string 의 reject elsewhere
|
||||
```
|
||||
|
||||
### Effect-TS Brand
|
||||
```typescript
|
||||
import { Brand } from "effect";
|
||||
|
||||
type USD = number & Brand.Brand<"USD">;
|
||||
const USD = Brand.refined<USD>(
|
||||
(n) => Number.isInteger(n) && n >= 0,
|
||||
(n) => Brand.error(`expected positive integer cents, got ${n}`),
|
||||
);
|
||||
|
||||
const price = USD(1999); // OK
|
||||
const bad = USD(-5); // BrandErrors
|
||||
```
|
||||
|
||||
### Numeric units (compile-time dimensional)
|
||||
```typescript
|
||||
type Meters = Brand<number, "Meters">;
|
||||
type Seconds = Brand<number, "Seconds">;
|
||||
type MPS = Brand<number, "MetersPerSecond">;
|
||||
|
||||
const m = (n: number) => n as Meters;
|
||||
const s = (n: number) => n as Seconds;
|
||||
|
||||
function speed(d: Meters, t: Seconds): MPS {
|
||||
return (d / t) as MPS;
|
||||
}
|
||||
|
||||
speed(m(100), s(9.58)); // OK
|
||||
speed(s(9.58), m(100)); // ❌ swapped
|
||||
```
|
||||
|
||||
### Type-Fest Tagged (lighter syntax)
|
||||
```typescript
|
||||
import type { Tagged } from "type-fest";
|
||||
|
||||
type Email = Tagged<string, "Email">;
|
||||
type UserId = Tagged<string, "UserId">;
|
||||
```
|
||||
|
||||
### Phantom param (alternate encoding)
|
||||
```typescript
|
||||
declare const brand: unique symbol;
|
||||
type Brand<T, K> = T & { readonly [brand]: K };
|
||||
```
|
||||
|
||||
### Runtime parsing layer (input boundary)
|
||||
```typescript
|
||||
// API boundary — single point of validation
|
||||
function handleRequest(raw: { userId: string; email: string }) {
|
||||
const userId = UserId(raw.userId); // brand at boundary
|
||||
const email = Email(raw.email); // validate + brand
|
||||
return processOrder(userId, email); // internal: branded everywhere
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Encoding |
|
||||
|---|---|
|
||||
| Lightweight ID 의 distinguish | Vanilla `__brand` |
|
||||
| Runtime validation + brand | Zod brand · Effect Brand |
|
||||
| Functional pipeline · refinement | Effect-TS Brand |
|
||||
| Library ergonomics | Type-Fest `Tagged` |
|
||||
| Numeric unit · dimensional | Brand<number, "Unit"> |
|
||||
|
||||
**기본값**: domain ID 의 vanilla brand, validated value 의 Zod, numeric/refinement 의 Effect Brand.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[TypeScript Type System]]
|
||||
- 변형: [[Nominal-Typing-in-TypeScript|Nominal Typing]] · [[Opaque Types (TypeScript)]]
|
||||
- 응용: [[Parse Don't Validate]]
|
||||
- Adjacent: [[Ambient_Declarations]] · [[Schema Validation]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: domain ID 의 brand 의 generate, smart constructor + validator 의 draft, Zod schema 의 brand 의 add.
|
||||
**언제 X**: 매 brand 의 over-applying — 매 internal helper 의 brand 의 X · 매 boundary 의 only.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Brand 의 everywhere**: function signature 의 noise 의 explode — 매 domain primitive 만.
|
||||
- **Cast 의 sprinkled**: 매 single constructor 의 collapse — validation 의 single source.
|
||||
- **Brand 의 leak through serialization**: JSON.parse 의 raw string 의 return — boundary parse 의 require.
|
||||
- **Multiple brand 의 same value**: `Brand<string, "A"> & Brand<string, "B">` — 매 union 의 use 의 X · 매 새 brand 의 prefer.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (TypeScript handbook, Effect-TS docs, Zod 4 docs, Type-Fest Tagged docs, "Parse Don't Validate" Alexis King).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — vanilla + Zod + Effect-TS + Type-Fest variants |
|
||||
@@ -0,0 +1,258 @@
|
||||
---
|
||||
id: wiki-2026-0508-breaking-dependencies
|
||||
title: Breaking Dependencies
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Decoupling, Dependency Breaking, Refactoring Legacy]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [refactoring, architecture, legacy-code, testing]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: Java / TypeScript / Python
|
||||
framework: language-agnostic
|
||||
---
|
||||
|
||||
# Breaking Dependencies
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 untestable code 를 매 test 가능하게 만드는 첫 단계 = 매 hard dependency 를 매 break."**. Michael Feathers 의 *Working Effectively with Legacy Code* (2004) 의 24 가지 dependency-breaking technique 가 매 canonical reference. 2026 현재 modern DI / 매 hexagonal architecture 가 매 prevention layer, but 매 legacy 에는 여전히 매 Sprout Method / Extract Interface / Adapter 가 핵심.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 dependency types
|
||||
- **Construction dependency**: 매 `new SomeService()` hardcoded.
|
||||
- **Static dependency**: 매 `Date.now()`, `Math.random()`, `Singleton.getInstance()`.
|
||||
- **Hidden dependency**: 매 method 안에서 file / DB / network 직접 호출.
|
||||
- **Temporal coupling**: 매 method A → B 순서 강요.
|
||||
- **Inheritance dependency**: 매 base class 가 매 hard-to-test 행위 가짐.
|
||||
|
||||
### 매 Feathers 의 핵심 24 technique 중 top 8
|
||||
1. **Sprout Method**: 매 new behavior 를 매 새 method 로 분리해 매 test 가능하게.
|
||||
2. **Sprout Class**: 매 new behavior 를 매 new class 로.
|
||||
3. **Extract Interface**: 매 concrete dep → 매 interface → mock.
|
||||
4. **Extract and Override Call**: 매 hard call 을 매 protected method 로 → subclass 에서 override.
|
||||
5. **Subclass and Override Method**: 매 testing subclass.
|
||||
6. **Adapt Parameter**: 매 untestable arg → 매 wrapper interface.
|
||||
7. **Break Out Method Object**: 매 거대 method → 매 클래스.
|
||||
8. **Introduce Instance Delegator**: 매 static → 매 instance method 로.
|
||||
|
||||
### 매 응용
|
||||
1. Legacy Java/C# enterprise codebase 의 unit test 도입.
|
||||
2. Module 간 cyclic dependency 끊기.
|
||||
3. 매 third-party SDK 의 hard wiring 제거.
|
||||
4. Microservice extraction 전 prep.
|
||||
5. 매 monolith 의 plugin 화.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Extract Interface (Java)
|
||||
```java
|
||||
// BEFORE — hard wired
|
||||
public class OrderService {
|
||||
public Receipt placeOrder(Order o) {
|
||||
EmailSender sender = new EmailSender(); // hard dep
|
||||
sender.send(o.customerEmail, "...");
|
||||
return Receipt.from(o);
|
||||
}
|
||||
}
|
||||
|
||||
// AFTER — Extract Interface + DI
|
||||
public interface Notifier {
|
||||
void send(String to, String body);
|
||||
}
|
||||
|
||||
public class EmailSender implements Notifier { /* impl */ }
|
||||
|
||||
public class OrderService {
|
||||
private final Notifier notifier;
|
||||
public OrderService(Notifier notifier) { this.notifier = notifier; }
|
||||
public Receipt placeOrder(Order o) {
|
||||
notifier.send(o.customerEmail, "...");
|
||||
return Receipt.from(o);
|
||||
}
|
||||
}
|
||||
|
||||
// Test
|
||||
@Test void placesOrderAndNotifies() {
|
||||
var fake = mock(Notifier.class);
|
||||
var svc = new OrderService(fake);
|
||||
svc.placeOrder(testOrder());
|
||||
verify(fake).send(eq("a@b.com"), anyString());
|
||||
}
|
||||
```
|
||||
|
||||
### Sprout Method (TypeScript)
|
||||
```ts
|
||||
// BEFORE — 매 huge method, 매 add new logic 못 testable
|
||||
class Invoice {
|
||||
process(): void {
|
||||
// 200 lines 의 legacy
|
||||
// 매 new logic 추가하면 매 테스트 X
|
||||
}
|
||||
}
|
||||
|
||||
// AFTER — sprout 로 새 행동만 분리
|
||||
class Invoice {
|
||||
process(): void {
|
||||
// 200 lines 의 legacy (그대로)
|
||||
this.applyLoyaltyDiscount(); // 매 sprouted
|
||||
}
|
||||
|
||||
applyLoyaltyDiscount(): number { // 매 testable in isolation
|
||||
if (this.customer.tier === 'gold') return this.total * 0.1;
|
||||
if (this.customer.tier === 'silver') return this.total * 0.05;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Extract and Override Call (Python)
|
||||
```python
|
||||
# BEFORE
|
||||
class Job:
|
||||
def run(self):
|
||||
now = datetime.utcnow() # 매 hard
|
||||
if now.weekday() >= 5: return
|
||||
self.do_work()
|
||||
|
||||
# AFTER
|
||||
class Job:
|
||||
def run(self):
|
||||
if self._now().weekday() >= 5: return
|
||||
self.do_work()
|
||||
|
||||
def _now(self) -> datetime: # 매 protected — override in test
|
||||
return datetime.utcnow()
|
||||
|
||||
class TestJob(Job):
|
||||
def __init__(self, fixed_dt): self.fixed_dt = fixed_dt
|
||||
def _now(self): return self.fixed_dt
|
||||
|
||||
# Test
|
||||
def test_skips_weekend():
|
||||
j = TestJob(datetime(2026, 5, 9)) # 매 Saturday
|
||||
j.do_work = MagicMock()
|
||||
j.run()
|
||||
j.do_work.assert_not_called()
|
||||
```
|
||||
|
||||
### Adapt Parameter (Java)
|
||||
```java
|
||||
// BEFORE — HttpServletRequest hard to construct in test
|
||||
public Result handle(HttpServletRequest req) {
|
||||
String userId = req.getHeader("X-User-Id");
|
||||
return repo.find(userId);
|
||||
}
|
||||
|
||||
// AFTER — adapter interface
|
||||
public interface RequestAdapter {
|
||||
String header(String name);
|
||||
}
|
||||
|
||||
public Result handle(RequestAdapter req) {
|
||||
return repo.find(req.header("X-User-Id"));
|
||||
}
|
||||
|
||||
// Production wraps; test = trivial map
|
||||
class ServletRequestAdapter implements RequestAdapter {
|
||||
private final HttpServletRequest delegate;
|
||||
public String header(String n) { return delegate.getHeader(n); }
|
||||
}
|
||||
```
|
||||
|
||||
### Subclass and Override (Python — break DB)
|
||||
```python
|
||||
class ReportGenerator:
|
||||
def fetch(self) -> list[dict]:
|
||||
return db.query("SELECT * FROM orders") # 매 hard
|
||||
|
||||
def render(self) -> str:
|
||||
rows = self.fetch()
|
||||
return "\n".join(f"{r['id']}: {r['total']}" for r in rows)
|
||||
|
||||
class TestableReport(ReportGenerator):
|
||||
def __init__(self, fake_rows): self.fake_rows = fake_rows
|
||||
def fetch(self): return self.fake_rows
|
||||
|
||||
def test_renders():
|
||||
r = TestableReport([{"id": 1, "total": 100}])
|
||||
assert r.render() == "1: 100"
|
||||
```
|
||||
|
||||
### Wrap Method (legacy 호환 유지)
|
||||
```java
|
||||
// 매 old API 깨면 안 됨 → 매 wrap
|
||||
public void payInvoice(Invoice i) {
|
||||
audit.log(i); // 매 new behavior
|
||||
payInvoiceLegacy(i); // 매 unchanged
|
||||
}
|
||||
|
||||
private void payInvoiceLegacy(Invoice i) { /* 매 untouched */ }
|
||||
```
|
||||
|
||||
### Seam mapping checklist
|
||||
```
|
||||
1. List 매 untestable thing in target method
|
||||
- Static call? → Introduce Instance Delegator / Replace Function with Function Object
|
||||
- new? → Extract Interface + inject
|
||||
- Time/random? → Extract & Override Call
|
||||
- File/DB/HTTP? → Repository / Adapter
|
||||
2. Pick 1 dep / day. 매 small step + commit.
|
||||
3. 매 Test 먼저 (characterization test)
|
||||
- 매 current behavior 를 lock down
|
||||
4. Refactor under green tests.
|
||||
```
|
||||
|
||||
### Hexagonal post-state (prevention)
|
||||
```
|
||||
[Domain Core] ← [Port (interface)] ← [Adapter (DB / HTTP / FS)]
|
||||
매 NO direct import 매 from core to adapter
|
||||
매 Test = stub adapter against port
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Technique |
|
||||
|---|---|
|
||||
| 매 add new feature 매 large method | Sprout Method |
|
||||
| 매 hard dep on concrete class | Extract Interface + DI |
|
||||
| 매 static / time / random | Extract and Override Call |
|
||||
| 매 framework type 의 arg | Adapt Parameter |
|
||||
| 매 file / DB / network | Repository / Port-Adapter |
|
||||
| 매 huge method 의 internal logic | Break Out Method Object |
|
||||
| 매 base class behavior bad | Subclass and Override |
|
||||
|
||||
**기본값**: characterization test 먼저 → 매 minimal mechanical refactor (compiler-driven) → 매 small commit.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Refactoring_Best_Practices|Refactoring]]
|
||||
- 변형: [[Sprout-Method]]
|
||||
- 응용: [[Test-Driven Development]] · [[Hexagonal Architecture]]
|
||||
- Adjacent: [[Dependency_Injection_(DI)|Dependency-Injection]] · [[SOLID Principles]] · [[Mocking]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: legacy code unit test 도입, untestable method 마주침, monolith 분해 직전 prep, third-party SDK lock-in 제거.
|
||||
**언제 X**: 매 already clean architecture (over-refactor 금지), 매 throwaway prototype.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Big-bang rewrite**: 매 일부 break → 매 전체 unstable.
|
||||
- **Test 없이 refactor**: 매 behavior 변경 모름 → 매 silent regression.
|
||||
- **너무 많은 mock**: 매 test 가 매 implementation 에 결합.
|
||||
- **Interface 의 1 implementation 영구**: 매 YAGNI — 매 두 번째 user 생기면 그때 추출.
|
||||
- **God adapter**: 매 1 interface 에 30 method.
|
||||
- **Refactor + feature 동시 commit**: 매 review 불가.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Feathers "Working Effectively with Legacy Code" 2004, Fowler "Refactoring" 2nd ed).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Breaking Dependencies with Feathers techniques |
|
||||
@@ -0,0 +1,202 @@
|
||||
---
|
||||
id: wiki-2026-0508-broker-topology
|
||||
title: Broker Topology
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Event Broker Topology, Pub-Sub Topology]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [event-driven, architecture, kafka, messaging]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: kafka-nats-rabbitmq
|
||||
---
|
||||
|
||||
# Broker Topology
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 distributed pub-sub mesh — 매 central mediator 없이, broker (queue/topic) 가 routing fabric."**. Broker topology는 Event-Driven Architecture 의 두 종류 중 하나 (vs Mediator topology). 매 single-purpose events 의 fan-out 에 최적. 2026년 Kafka, NATS JetStream, AWS EventBridge, Redpanda, Pulsar 가 dominant brokers.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Broker vs Mediator
|
||||
| 측면 | Broker | Mediator |
|
||||
|---|---|---|
|
||||
| Coordination | None — events fan out | Central mediator orchestrates |
|
||||
| Coupling | Loosest | Some (mediator knows steps) |
|
||||
| Use case | Simple fan-out, async notify | Complex multi-step workflow |
|
||||
| Failure recovery | Each consumer handles | Mediator retries/compensates |
|
||||
| Examples | Kafka topics, NATS subjects | Apache Camel, Step Functions |
|
||||
|
||||
### 매 컴포넌트
|
||||
- **Producer**: events 의 publish.
|
||||
- **Broker**: topic/subject/queue store + routing.
|
||||
- **Consumer**: subscribe + react.
|
||||
- **Schema Registry** (Confluent, Apicurio): event contract 의 versioning.
|
||||
- **Dead Letter Queue (DLQ)**: failed messages.
|
||||
|
||||
### 매 broker 종류
|
||||
- **Log-based** (Kafka, Redpanda, Pulsar): replay 가능, partitioned, ordered per partition.
|
||||
- **Queue-based** (RabbitMQ, SQS): consumed once, no replay.
|
||||
- **Subject-based** (NATS): lightweight, JetStream 으로 persistence.
|
||||
- **Cloud-native** (EventBridge, Pub/Sub, EventHubs).
|
||||
|
||||
### 매 응용
|
||||
1. Order events (e-commerce fan-out).
|
||||
2. CDC (Debezium → Kafka → multiple sinks).
|
||||
3. IoT telemetry ingestion.
|
||||
4. Analytics event pipeline.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Kafka producer (TypeScript / KafkaJS)
|
||||
```typescript
|
||||
import { Kafka } from 'kafkajs';
|
||||
|
||||
const kafka = new Kafka({
|
||||
clientId: 'orders',
|
||||
brokers: ['kafka-1:9092', 'kafka-2:9092'],
|
||||
});
|
||||
const producer = kafka.producer();
|
||||
|
||||
await producer.connect();
|
||||
await producer.send({
|
||||
topic: 'order.created',
|
||||
messages: [{
|
||||
key: order.id,
|
||||
value: JSON.stringify(order),
|
||||
headers: { 'content-type': 'application/json' },
|
||||
}],
|
||||
});
|
||||
```
|
||||
|
||||
### Kafka consumer (consumer group fan-out)
|
||||
```typescript
|
||||
const consumer = kafka.consumer({ groupId: 'shipping-svc' });
|
||||
await consumer.connect();
|
||||
await consumer.subscribe({ topic: 'order.created' });
|
||||
|
||||
await consumer.run({
|
||||
eachMessage: async ({ message }) => {
|
||||
const order = JSON.parse(message.value!.toString());
|
||||
await createShipment(order);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### NATS JetStream
|
||||
```typescript
|
||||
import { connect, JSONCodec } from 'nats';
|
||||
|
||||
const nc = await connect({ servers: 'nats://localhost:4222' });
|
||||
const js = nc.jetstream();
|
||||
const jc = JSONCodec();
|
||||
|
||||
await js.publish('orders.created', jc.encode(order));
|
||||
|
||||
const sub = await js.subscribe('orders.>', {
|
||||
config: { durable_name: 'shipping' },
|
||||
});
|
||||
for await (const m of sub) {
|
||||
const order = jc.decode(m.data);
|
||||
await createShipment(order);
|
||||
m.ack();
|
||||
}
|
||||
```
|
||||
|
||||
### Schema Registry (Avro + Confluent)
|
||||
```typescript
|
||||
import { SchemaRegistry } from '@kafkajs/confluent-schema-registry';
|
||||
|
||||
const registry = new SchemaRegistry({ host: 'http://schema-registry:8081' });
|
||||
const schema = `{
|
||||
"type": "record",
|
||||
"name": "OrderCreated",
|
||||
"fields": [
|
||||
{ "name": "id", "type": "string" },
|
||||
{ "name": "amount", "type": "double" }
|
||||
]
|
||||
}`;
|
||||
|
||||
const { id } = await registry.register({ type: 'AVRO', schema });
|
||||
const encoded = await registry.encode(id, order);
|
||||
await producer.send({ topic: 'order.created', messages: [{ value: encoded }] });
|
||||
```
|
||||
|
||||
### DLQ pattern
|
||||
```typescript
|
||||
await consumer.run({
|
||||
eachMessage: async ({ topic, message }) => {
|
||||
try {
|
||||
await handleOrder(JSON.parse(message.value!.toString()));
|
||||
} catch (err) {
|
||||
await producer.send({
|
||||
topic: `${topic}.dlq`,
|
||||
messages: [{
|
||||
value: message.value,
|
||||
headers: {
|
||||
...message.headers,
|
||||
error: String(err),
|
||||
retryCount: '0',
|
||||
},
|
||||
}],
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Partitioning for ordering
|
||||
```typescript
|
||||
// Same orderId always goes to same partition → ordered per order
|
||||
await producer.send({
|
||||
topic: 'order.events',
|
||||
messages: [{
|
||||
key: order.id, // partitioner uses key hash
|
||||
value: JSON.stringify(event),
|
||||
}],
|
||||
});
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| High throughput, replay 필요 | Kafka / Redpanda |
|
||||
| Lightweight, low-latency | NATS JetStream |
|
||||
| Cloud serverless | EventBridge, Pub/Sub |
|
||||
| Strong work-queue + ack | RabbitMQ, SQS |
|
||||
| Multi-tenant, geo | Pulsar |
|
||||
|
||||
**기본값**: Kafka (or Redpanda — Kafka API compat) + Schema Registry + Avro/Protobuf for events.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Event-Driven Architecture]]
|
||||
- 변형: [[Mediator Topology]] · [[Choreography]]
|
||||
- 응용: [[Kafka]] · [[NATS]]
|
||||
- Adjacent: [[CDC]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: fan-out events, decoupled microservices, CDC pipelines, telemetry ingestion.
|
||||
**언제 X**: synchronous request-reply (use gRPC/HTTP), small monolith (events 가 overhead).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **No schema registry**: 모든 producer/consumer 가 schema drift — production 깨짐.
|
||||
- **Topic per service**: scalability impossible — topic per event type.
|
||||
- **No DLQ**: poison messages 가 consumer halt — DLQ + alerting.
|
||||
- **Sync over async**: HTTP request-reply 를 broker 위에 — synchronous 면 broker 의 X.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Richards "Software Architecture Patterns" / Confluent docs / NATS docs 2026).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Broker vs Mediator + Kafka/NATS/DLQ 패턴 |
|
||||
@@ -0,0 +1,183 @@
|
||||
---
|
||||
id: wiki-2026-0508-browser
|
||||
title: Browser
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Web Browser, Browser Internals, Rendering Engine]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [browser, web, rendering, javascript, networking]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: C++/Rust
|
||||
framework: Chromium/Gecko/WebKit
|
||||
---
|
||||
|
||||
# Browser
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 browser = networking stack + parsing + rendering + JS engine + sandbox 의 거대한 OS-like 시스템."**. 매 2026 현재 Chromium-derived (Chrome, Edge, Brave, Arc) 가 ~70% market share, Firefox/Gecko 와 Safari/WebKit 이 나머지. 매 process-per-site isolation, GPU-accelerated compositing, V8 의 Sparkplug+Maglev+TurboFan tier 가 modern baseline.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 multi-process architecture
|
||||
- **Browser process**: 매 UI, networking, IPC orchestration.
|
||||
- **Renderer process**: 매 site-per-process — Blink + V8.
|
||||
- **GPU process**: 매 compositing, WebGL/WebGPU.
|
||||
- **Network service**: 매 별도 process — credential isolation.
|
||||
- **Utility processes**: 매 audio, video decode, storage.
|
||||
|
||||
### 매 rendering pipeline
|
||||
1. **Parse** HTML → DOM.
|
||||
2. **Parse** CSS → CSSOM.
|
||||
3. **Style** — match rules → ComputedStyle.
|
||||
4. **Layout** — geometry computation.
|
||||
5. **Paint** — layer 별 display list.
|
||||
6. **Composite** — GPU 가 layer 합성.
|
||||
|
||||
### 매 JS engine tiers (V8)
|
||||
- **Ignition** (interpreter) → **Sparkplug** (baseline JIT) → **Maglev** (mid-tier) → **TurboFan** (optimizing JIT).
|
||||
- 매 hot function 만 점진적 promotion.
|
||||
|
||||
### 매 응용
|
||||
1. **Web app development**: 매 performance budgeting.
|
||||
2. **Extension / DevTools**: 매 internal API.
|
||||
3. **Embedded webview**: 매 Electron, Tauri, CEF.
|
||||
4. **Headless**: 매 Puppeteer, Playwright scraping/test.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Performance: Critical Rendering Path
|
||||
```html
|
||||
<!-- inline above-the-fold CSS, defer non-critical -->
|
||||
<style>/* critical CSS here */</style>
|
||||
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin>
|
||||
<link rel="stylesheet" href="/non-critical.css" media="print" onload="this.media='all'">
|
||||
<script type="module" src="/app.js" defer></script>
|
||||
```
|
||||
|
||||
### Avoid layout thrash
|
||||
```javascript
|
||||
// BAD — read/write/read/write triggers sync layouts
|
||||
boxes.forEach(b => {
|
||||
const w = b.offsetWidth; // forces layout
|
||||
b.style.width = (w * 2) + 'px'; // invalidates
|
||||
});
|
||||
|
||||
// GOOD — batch reads then writes
|
||||
const widths = boxes.map(b => b.offsetWidth);
|
||||
boxes.forEach((b, i) => b.style.width = (widths[i] * 2) + 'px');
|
||||
```
|
||||
|
||||
### Service worker — offline + cache
|
||||
```javascript
|
||||
// sw.js
|
||||
self.addEventListener('install', e => {
|
||||
e.waitUntil(caches.open('v1').then(c => c.addAll([
|
||||
'/', '/app.js', '/styles.css'
|
||||
])));
|
||||
});
|
||||
self.addEventListener('fetch', e => {
|
||||
e.respondWith(
|
||||
caches.match(e.request).then(r => r || fetch(e.request))
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### Intersection observer (lazy load)
|
||||
```javascript
|
||||
const io = new IntersectionObserver(entries => {
|
||||
for (const e of entries) {
|
||||
if (e.isIntersecting) {
|
||||
e.target.src = e.target.dataset.src;
|
||||
io.unobserve(e.target);
|
||||
}
|
||||
}
|
||||
}, { rootMargin: '200px' });
|
||||
document.querySelectorAll('img[data-src]').forEach(img => io.observe(img));
|
||||
```
|
||||
|
||||
### Web Workers — off-main-thread
|
||||
```javascript
|
||||
// main.js
|
||||
const w = new Worker('worker.js', { type: 'module' });
|
||||
w.postMessage({ buffer: bigArray }, [bigArray.buffer]); // transfer, not copy
|
||||
w.onmessage = e => console.log(e.data);
|
||||
|
||||
// worker.js
|
||||
onmessage = e => {
|
||||
const result = heavyCompute(e.data.buffer);
|
||||
postMessage(result);
|
||||
};
|
||||
```
|
||||
|
||||
### Performance API — measure real users
|
||||
```javascript
|
||||
new PerformanceObserver(list => {
|
||||
for (const entry of list.getEntries()) {
|
||||
if (entry.name === 'LCP') sendBeacon('lcp', entry.startTime);
|
||||
if (entry.entryType === 'layout-shift') sendBeacon('cls', entry.value);
|
||||
if (entry.entryType === 'event') sendBeacon('inp', entry.duration);
|
||||
}
|
||||
}).observe({ type: 'largest-contentful-paint', buffered: true });
|
||||
```
|
||||
|
||||
### CSP header
|
||||
```http
|
||||
Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-AbCd...';
|
||||
style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none';
|
||||
```
|
||||
|
||||
### WebGPU compute (modern)
|
||||
```javascript
|
||||
const adapter = await navigator.gpu.requestAdapter();
|
||||
const device = await adapter.requestDevice();
|
||||
const shader = device.createShaderModule({ code: `
|
||||
@group(0) @binding(0) var<storage, read_write> v: array<f32>;
|
||||
@compute @workgroup_size(64) fn main(@builtin(global_invocation_id) g: vec3u) {
|
||||
v[g.x] = v[g.x] * 2.0;
|
||||
}`});
|
||||
// ... pipeline + dispatch
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Hydration TTI 느림 | islands / partial hydration (Astro, Qwik) |
|
||||
| Heavy compute on UI thread | Web Worker / WebAssembly |
|
||||
| Repeated tiny renders | requestAnimationFrame batch |
|
||||
| Need GPU for ML inference | WebGPU + ONNX Runtime Web |
|
||||
| Cross-origin embed | iframe + CSP frame-ancestors |
|
||||
|
||||
**기본값**: HTTP/3 + service worker cache + defer non-critical JS + Web Vitals monitoring.
|
||||
|
||||
## 🔗 Graph
|
||||
- 변형: [[Chromium]] · [[WebKit]]
|
||||
- 응용: [[Single_Page_Application]] · [[PWA]] · [[Electron]]
|
||||
- Adjacent: [[V8]] · [[WebAssembly]] · [[WebGPU]] · [[Service_Worker]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: web perf 최적화, browser API 선택, rendering pipeline 디버깅.
|
||||
**언제 X**: server-side rendering 내부 (Node/Bun 영역).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **document.write**: 매 parser blocking — 절대 X.
|
||||
- **Synchronous XHR**: 매 main thread freeze — deprecated.
|
||||
- **Layout thrash loop**: 매 read-write 교차 → forced sync layouts.
|
||||
- **Massive bundle (>1MB JS)**: 매 mobile TTI 폭망 — code splitting 필수.
|
||||
- **Too many compositor layers**: 매 will-change 남발 → GPU memory exhaustion.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (web.dev/architecture, Chromium design docs 2024, V8 blog 2025).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — multi-process arch, rendering pipeline, modern web APIs |
|
||||
@@ -0,0 +1,167 @@
|
||||
---
|
||||
id: wiki-2026-0508-c4-modeling-framework
|
||||
title: C4 Modeling Framework
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [C4 Model, C4 Diagrams, Simon Brown C4]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [architecture, c4, diagram, structurizr, documentation]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: DSL
|
||||
framework: Structurizr
|
||||
---
|
||||
|
||||
# C4 Modeling Framework
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 software architecture = 4 zoom levels (Context → Container → Component → Code)."**. 매 Simon Brown 이 2006년경 제안한 lightweight visualization framework. 매 2026 현재 Structurizr DSL + Mermaid C4 plugin 으로 diagram-as-code 가 standard, 매 audience-specific abstraction level 이 핵심 가치.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 4 levels
|
||||
- **L1 — System Context**: 매 system + users + external systems. 매 non-technical stakeholder 용.
|
||||
- **L2 — Container**: 매 deployable unit (web app, API, DB, queue). 매 technical lead 용.
|
||||
- **L3 — Component**: 매 container 내부 logical grouping (controller, service, repo). 매 dev team 용.
|
||||
- **L4 — Code**: 매 class diagram (UML). 매 거의 안 그림 — IDE 가 대신.
|
||||
|
||||
### 매 supplementary diagrams
|
||||
- **Deployment diagram**: 매 container → infrastructure node mapping.
|
||||
- **Dynamic diagram**: 매 runtime sequence/collaboration.
|
||||
- **System landscape**: 매 enterprise-wide multi-system view.
|
||||
|
||||
### 매 응용
|
||||
1. **Onboarding doc**: 매 새 dev 의 codebase orientation.
|
||||
2. **ADR illustration**: 매 architecture decision record 의 visual aid.
|
||||
3. **Stakeholder communication**: 매 PM/exec 에게 system scope 설명.
|
||||
4. **Compliance audit**: 매 data flow / boundary 시각화.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Structurizr DSL (canonical)
|
||||
```dsl
|
||||
workspace "Banking System" {
|
||||
model {
|
||||
customer = person "Customer"
|
||||
bankSystem = softwareSystem "Internet Banking" {
|
||||
webApp = container "Web Application" "Spring Boot" "Java"
|
||||
mobileApp = container "Mobile App" "React Native" "TypeScript"
|
||||
api = container "API" "Spring Boot" "Java"
|
||||
db = container "Database" "PostgreSQL" "RDBMS"
|
||||
|
||||
webApp -> api "uses" "JSON/HTTPS"
|
||||
mobileApp -> api "uses" "JSON/HTTPS"
|
||||
api -> db "reads/writes" "JDBC"
|
||||
}
|
||||
mainframe = softwareSystem "Mainframe" "External"
|
||||
customer -> bankSystem "uses"
|
||||
bankSystem -> mainframe "fetches account data" "MQ"
|
||||
}
|
||||
views {
|
||||
systemContext bankSystem { include * autolayout }
|
||||
container bankSystem { include * autolayout }
|
||||
theme default
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Mermaid C4 (in-repo markdown)
|
||||
```mermaid
|
||||
C4Context
|
||||
title System Context — Banking
|
||||
Person(customer, "Customer")
|
||||
System(bank, "Internet Banking", "Allows customers to view accounts")
|
||||
System_Ext(mainframe, "Mainframe", "Legacy core banking")
|
||||
Rel(customer, bank, "Uses")
|
||||
Rel(bank, mainframe, "Fetches data", "MQ")
|
||||
```
|
||||
|
||||
### Component-level decomposition
|
||||
```dsl
|
||||
api = container "API" {
|
||||
signInController = component "SignIn Controller" "Spring MVC"
|
||||
accountsController = component "Accounts Controller" "Spring MVC"
|
||||
authService = component "Auth Service" "Spring Bean"
|
||||
accountsRepo = component "Accounts Repository" "Spring Data JPA"
|
||||
|
||||
signInController -> authService "uses"
|
||||
accountsController -> accountsRepo "reads"
|
||||
authService -> db "verifies credentials"
|
||||
}
|
||||
```
|
||||
|
||||
### Deployment view
|
||||
```dsl
|
||||
deploymentEnvironment "Production" {
|
||||
deploymentNode "AWS us-east-1" {
|
||||
deploymentNode "EKS Cluster" {
|
||||
containerInstance webApp
|
||||
containerInstance api
|
||||
}
|
||||
deploymentNode "RDS" {
|
||||
containerInstance db
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Generated docs pipeline
|
||||
```bash
|
||||
# CI step — render diagrams from DSL
|
||||
docker run --rm -v $PWD:/usr/local/structurizr structurizr/cli \
|
||||
export -workspace workspace.dsl -format plantuml/c4plantuml
|
||||
plantuml -tsvg *.puml
|
||||
mv *.svg docs/architecture/
|
||||
```
|
||||
|
||||
### ADR + C4 link
|
||||
```markdown
|
||||
# ADR-007: Move auth to dedicated service
|
||||
**Status**: Accepted
|
||||
**Context**: See [Container diagram](../diagrams/banking-containers.svg)
|
||||
**Decision**: Extract `authService` component into its own container.
|
||||
**Consequences**: New diagram in `diagrams/v2-containers.svg`.
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Diagram level |
|
||||
|---|---|
|
||||
| Pitch to exec / new joiner intro | L1 Context |
|
||||
| Tech selection / deployment plan | L2 Container |
|
||||
| Code review / refactor discussion | L3 Component |
|
||||
| Inheritance question | IDE (skip L4) |
|
||||
| Multi-system enterprise view | System Landscape |
|
||||
|
||||
**기본값**: L1 + L2 (둘만 있어도 90% communication 충족).
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Software_Architecture]] · [[Architecture_Documentation]]
|
||||
- 변형: [[arc42]]
|
||||
- 응용: [[ADR]] · [[Structurizr]]
|
||||
- Adjacent: [[Mermaid]] · [[PlantUML]] · [[DDD]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: greenfield architecture 문서화, legacy reverse-engineering, ADR illustration.
|
||||
**언제 X**: real-time runtime monitoring (observability tool 영역).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **All 4 levels for every system**: 매 L4 거의 무의미 — IDE 대체.
|
||||
- **Mixing levels in one diagram**: 매 abstraction 깨짐 → audience 혼란.
|
||||
- **No legend / inconsistent shapes**: 매 reader 가 element type 못 구분.
|
||||
- **Diagram drift**: 매 DSL 없이 manual draw → 6개월 후 stale.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (c4model.com — Simon Brown, Structurizr DSL spec 2024).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — C4 levels, Structurizr DSL, Mermaid C4 examples |
|
||||
@@ -0,0 +1,183 @@
|
||||
---
|
||||
id: wiki-2026-0508-cad-렌더링-최적화
|
||||
title: CAD 렌더링 최적화
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [CAD Rendering Optimization, CAD Performance, Engineering Visualization]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [cad, rendering, gpu, lod, webgpu]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: TypeScript
|
||||
framework: WebGPU/Three.js
|
||||
---
|
||||
|
||||
# CAD 렌더링 최적화
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 millions-of-triangles model 의 60fps 표시 = LOD + culling + GPU instancing 의 합."**. 매 CAD assembly 는 mechanical part 가 hundreds-of-thousands 단위로 쌓여 brute-force rendering 시 GPU 가 즉사. 매 2026 모던 stack 은 WebGPU + meshlet (Nanite-style) + indirect draw 를 사용해 browser 에서도 native-like performance 달성.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 bottleneck axis
|
||||
- **Geometry**: 매 triangle count — 매 fillet/thread 같은 detail 이 수십 million 까지 폭증.
|
||||
- **Draw call**: 매 part 별 separate draw → CPU/GPU sync overhead 가 frame budget 잠식.
|
||||
- **Overdraw**: 매 transparent assembly 의 layered fragment shading.
|
||||
- **Memory**: 매 32-bit index + per-vertex normal/UV/color → VRAM 빠르게 saturate.
|
||||
|
||||
### 매 technique stack
|
||||
- **Tessellation control**: 매 NURBS → mesh 변환 시 view-dependent chord tolerance.
|
||||
- **LOD**: 매 distance / screen-coverage 기반 mesh swap.
|
||||
- **Frustum / occlusion culling**: 매 BVH + Hi-Z buffer.
|
||||
- **Instancing**: 매 동일 part (bolt/screw) 의 single draw call.
|
||||
- **Meshlet (Nanite-like)**: 매 cluster 단위 GPU culling + virtual geometry.
|
||||
- **Deferred shading**: 매 overdraw 비용 절감.
|
||||
|
||||
### 매 응용
|
||||
1. **Onshape / Fusion 360 web**: 매 browser 안 assembly editing.
|
||||
2. **Plant 3D walkthrough**: 매 oil refinery / factory digital twin.
|
||||
3. **AR overlay**: 매 Vision Pro / Quest 3 의 maintenance instruction.
|
||||
4. **VR design review**: 매 stakeholder 의 immersive walkthrough.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Screen-space LOD selection
|
||||
```typescript
|
||||
function pickLOD(part: CadPart, camera: Camera): number {
|
||||
const screenCoverage = projectedRadius(part.bounds, camera) / camera.viewport.height;
|
||||
if (screenCoverage > 0.3) return 0; // full mesh
|
||||
if (screenCoverage > 0.1) return 1; // 1/4 triangles
|
||||
if (screenCoverage > 0.03) return 2; // 1/16 triangles
|
||||
if (screenCoverage > 0.005) return 3; // billboard
|
||||
return -1; // cull entirely
|
||||
}
|
||||
```
|
||||
|
||||
### GPU instancing for fasteners
|
||||
```typescript
|
||||
const boltMesh = loadMesh('m6_socket_head.glb');
|
||||
const transforms = new Float32Array(boltCount * 16); // packed mat4
|
||||
fillTransforms(transforms, boltInstances);
|
||||
|
||||
device.queue.writeBuffer(instanceBuffer, 0, transforms);
|
||||
pass.setPipeline(instancedPipeline);
|
||||
pass.setVertexBuffer(0, boltMesh.vertices);
|
||||
pass.setVertexBuffer(1, instanceBuffer);
|
||||
pass.drawIndexed(boltMesh.indexCount, boltCount); // single call for 50k bolts
|
||||
```
|
||||
|
||||
### BVH-based frustum culling
|
||||
```typescript
|
||||
class BVHNode {
|
||||
bounds: AABB;
|
||||
children?: [BVHNode, BVHNode];
|
||||
parts?: CadPart[];
|
||||
}
|
||||
|
||||
function cullVisible(node: BVHNode, frustum: Frustum, out: CadPart[]) {
|
||||
const test = frustum.testAABB(node.bounds);
|
||||
if (test === 'outside') return;
|
||||
if (test === 'inside' || !node.children) {
|
||||
out.push(...(node.parts ?? collectAll(node)));
|
||||
return;
|
||||
}
|
||||
cullVisible(node.children[0], frustum, out);
|
||||
cullVisible(node.children[1], frustum, out);
|
||||
}
|
||||
```
|
||||
|
||||
### Meshlet cluster (Nanite-style)
|
||||
```wgsl
|
||||
// WebGPU compute shader — cluster culling
|
||||
@group(0) @binding(0) var<storage, read> meshlets: array<Meshlet>;
|
||||
@group(0) @binding(1) var<storage, read_write> visibleList: array<u32>;
|
||||
@group(0) @binding(2) var<uniform> camera: Camera;
|
||||
|
||||
@compute @workgroup_size(64)
|
||||
fn cullMeshlets(@builtin(global_invocation_id) gid: vec3u) {
|
||||
let idx = gid.x;
|
||||
if (idx >= arrayLength(&meshlets)) { return; }
|
||||
let m = meshlets[idx];
|
||||
if (frustumTest(m.boundingSphere, camera) &&
|
||||
coneTest(m.normalCone, camera.position)) {
|
||||
let slot = atomicAdd(&visibleList[0], 1u);
|
||||
visibleList[slot + 1u] = idx;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Indirect draw aggregation
|
||||
```typescript
|
||||
// One draw call dispatches all visible meshlets
|
||||
const drawArgs = new Uint32Array([
|
||||
indexCount, instanceCount, firstIndex, baseVertex, firstInstance
|
||||
]);
|
||||
device.queue.writeBuffer(indirectBuffer, 0, drawArgs);
|
||||
pass.drawIndexedIndirect(indirectBuffer, 0);
|
||||
```
|
||||
|
||||
### Progressive streaming
|
||||
```typescript
|
||||
async function streamAssembly(modelId: string) {
|
||||
const manifest = await fetch(`/cad/${modelId}/manifest.json`).then(r => r.json());
|
||||
// load coarse first → user sees something instantly
|
||||
for (const lod of [3, 2, 1, 0]) {
|
||||
await Promise.all(manifest.parts.map(p =>
|
||||
cache.has(`${p.id}_lod${lod}`) ? null : loadPart(p, lod)
|
||||
));
|
||||
requestRedraw();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Hi-Z occlusion
|
||||
```typescript
|
||||
// Down-sampled depth pyramid → occluder test before draw
|
||||
const hiZ = buildHiZPyramid(depthTexture);
|
||||
for (const part of visibleAfterFrustum) {
|
||||
if (occludedByHiZ(part.bounds, hiZ, camera)) continue;
|
||||
drawList.push(part);
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| < 100k triangles, single part | brute force, no LOD |
|
||||
| 1M-10M triangles, assembly | BVH + frustum culling + LOD |
|
||||
| 10M-100M triangles | + GPU instancing + meshlets |
|
||||
| > 100M (plant/ship) | virtual geometry + streaming + occlusion |
|
||||
| Mobile / VR | aggressive LOD + foveated rendering |
|
||||
|
||||
**기본값**: BVH culling + 4-tier LOD + instanced fasteners (covers 90% mid-size assemblies).
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Computer_Graphics]] · [[GPU Architecture]]
|
||||
- 응용: [[Digital_Twin]]
|
||||
- Adjacent: [[WebGPU]] · [[Three.js]] · [[Level_of_Detail]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: CAD/BIM viewer 설계, performance bottleneck 분석, LOD threshold tuning.
|
||||
**언제 X**: photorealistic offline rendering (path tracing 영역).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Per-part separate draw call**: 매 50k draws/frame 은 어떤 GPU 도 죽음.
|
||||
- **CPU-side culling only**: 매 GPU-driven culling 없이는 modern bandwidth 활용 불가.
|
||||
- **Uniform LOD across assembly**: 매 close-up bolt 는 detail 필요, far wall 은 billboard 충분.
|
||||
- **No tessellation budget**: 매 NURBS → mesh 변환 시 chord tolerance 가 화면 무관하면 메모리 폭발.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Onshape engineering blog 2025, Unreal Nanite SIGGRAPH 2021, WebGPU spec 2024).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — CAD rendering pipeline, LOD, meshlet, WebGPU patterns |
|
||||
@@ -0,0 +1,185 @@
|
||||
---
|
||||
id: wiki-2026-0508-cst
|
||||
title: CST
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Concrete Syntax Tree, Parse Tree, Lossless Syntax Tree]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.95
|
||||
verification_status: applied
|
||||
tags: [parser, compiler, ast, cst, ide, refactoring]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: Polyglot
|
||||
framework: tree-sitter/Roslyn/rust-analyzer
|
||||
---
|
||||
|
||||
# CST
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 Concrete Syntax Tree (CST) = 매 source text 의 모든 문자 (whitespace, comment, trivia) 까지 보존하는 lossless tree."**. 매 AST 가 의미만 남기고 trivia 를 버린 반면, CST 는 매 round-trip print 가 가능. 매 2026 모던 IDE / formatter / refactor tool 의 backbone — tree-sitter, Roslyn, rust-analyzer 가 모두 CST.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 AST vs CST
|
||||
- **AST**: 매 abstract — 매 keyword/punctuation 생략, semantics 만.
|
||||
- **CST**: 매 concrete — 매 every token + whitespace + comment.
|
||||
- **Round-trip**: 매 CST → exact source text 복원 가능.
|
||||
- **Incremental update**: 매 edit 시 affected subtree 만 reparse.
|
||||
|
||||
### 매 use-case where CST > AST
|
||||
- **Code formatter** (prettier, gofmt, rustfmt): 매 trivia 제어 필요.
|
||||
- **Refactor tool**: 매 comment 보존 + 정확한 source range.
|
||||
- **Linter with auto-fix**: 매 source position 정확.
|
||||
- **Syntax highlighting**: 매 every token 위치 필요.
|
||||
- **IDE error tolerance**: 매 partial parse 도 tree 반환.
|
||||
|
||||
### 매 응용
|
||||
1. **tree-sitter**: 매 GitHub/Atom/Neovim grammar — 매 incremental.
|
||||
2. **Roslyn (C#)**: 매 `SyntaxNode` 가 CST.
|
||||
3. **rust-analyzer**: 매 `rowan` library 의 lossless syntax tree.
|
||||
4. **Babel `@babel/parser`**: 매 AST + tokens (semi-CST).
|
||||
5. **Prettier**: 매 input 의 doc-IR 변환.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### tree-sitter — incremental CST
|
||||
```javascript
|
||||
const Parser = require('tree-sitter');
|
||||
const Java = require('tree-sitter-java');
|
||||
const parser = new Parser();
|
||||
parser.setLanguage(Java);
|
||||
|
||||
const source = `class A { int x = 1; }`;
|
||||
const tree = parser.parse(source);
|
||||
|
||||
// edit: insert at offset 17
|
||||
const newSource = `class A { int x = 1; int y = 2; }`;
|
||||
tree.edit({
|
||||
startIndex: 19, oldEndIndex: 19, newEndIndex: 31,
|
||||
startPosition: {row: 0, column: 19},
|
||||
oldEndPosition: {row: 0, column: 19},
|
||||
newEndPosition: {row: 0, column: 31},
|
||||
});
|
||||
const newTree = parser.parse(newSource, tree); // reuses unchanged subtrees
|
||||
```
|
||||
|
||||
### Roslyn — preserve trivia in refactor
|
||||
```csharp
|
||||
var tree = CSharpSyntaxTree.ParseText(source);
|
||||
var root = tree.GetRoot();
|
||||
var oldMethod = root.DescendantNodes().OfType<MethodDeclarationSyntax>().First();
|
||||
var newMethod = oldMethod
|
||||
.WithIdentifier(SyntaxFactory.Identifier("RenamedMethod"))
|
||||
.WithLeadingTrivia(oldMethod.GetLeadingTrivia())
|
||||
.WithTrailingTrivia(oldMethod.GetTrailingTrivia());
|
||||
var newRoot = root.ReplaceNode(oldMethod, newMethod);
|
||||
File.WriteAllText(path, newRoot.ToFullString()); // round-trip, comments intact
|
||||
```
|
||||
|
||||
### rust-analyzer rowan
|
||||
```rust
|
||||
use rowan::{GreenNode, SyntaxNode};
|
||||
|
||||
// Green tree = immutable, shareable
|
||||
let green: GreenNode = parse(source);
|
||||
// Red tree = lazy wrapper with parent pointers + offset
|
||||
let syntax: SyntaxNode<MyLang> = SyntaxNode::new_root(green);
|
||||
|
||||
for token in syntax.descendants_with_tokens() {
|
||||
println!("{:?} @ {:?}", token.kind(), token.text_range());
|
||||
// includes whitespace, comments, error tokens
|
||||
}
|
||||
```
|
||||
|
||||
### Lossless tree → AST view
|
||||
```rust
|
||||
// AST is a typed view over CST
|
||||
pub struct FnDef(SyntaxNode);
|
||||
impl FnDef {
|
||||
pub fn name(&self) -> Option<Name> {
|
||||
self.0.children().find_map(Name::cast)
|
||||
}
|
||||
pub fn body(&self) -> Option<Block> {
|
||||
self.0.children().find_map(Block::cast)
|
||||
}
|
||||
}
|
||||
// Original text, comments, whitespace = always retrievable via self.0.text()
|
||||
```
|
||||
|
||||
### CST-based formatter
|
||||
```typescript
|
||||
function format(node: CSTNode, ctx: PrintCtx): Doc {
|
||||
switch (node.type) {
|
||||
case 'function_declaration':
|
||||
return concat([
|
||||
node.leadingTrivia.filter(isComment), // preserve doc comments
|
||||
'function ',
|
||||
format(node.name, ctx),
|
||||
format(node.params, ctx),
|
||||
' ',
|
||||
format(node.body, ctx),
|
||||
]);
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error-tolerant parse
|
||||
```javascript
|
||||
// tree-sitter inserts ERROR / MISSING nodes; tree still walkable
|
||||
const tree = parser.parse(`function f() { return ; }`);
|
||||
tree.rootNode.descendantsOfType('ERROR').forEach(n => {
|
||||
console.log('parse error at', n.startPosition);
|
||||
});
|
||||
// IDE keeps working even with incomplete code
|
||||
```
|
||||
|
||||
### Source range — exact byte spans
|
||||
```csharp
|
||||
foreach (var diag in compilation.GetDiagnostics()) {
|
||||
var span = diag.Location.SourceSpan;
|
||||
Console.WriteLine($"[{span.Start}..{span.End}] {diag.GetMessage()}");
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | CST or AST |
|
||||
|---|---|
|
||||
| Code formatter | CST (trivia 필요) |
|
||||
| Compiler optimization pass | AST (semantics 만) |
|
||||
| IDE / refactor tool | CST |
|
||||
| Pure code generation (template) | AST |
|
||||
| Syntax highlighter | CST tokens |
|
||||
| Bytecode emit | AST + lowered IR |
|
||||
|
||||
**기본값**: developer-tool 빌드 = CST first (tree-sitter or rowan); compiler = CST → AST → IR.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Parser]]
|
||||
- 변형: [[AST]] · [[Lossless_Syntax_Tree]]
|
||||
- 응용: [[Code_Formatter]] · [[IDE]] · [[Refactoring_Tools]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: parser tool 선택, refactor 구현, formatter 설계.
|
||||
**언제 X**: runtime semantic analysis (type checker 영역 — AST 기반).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Regex 로 source rewrite**: 매 trivia 망가지고 nested 구조 무시.
|
||||
- **AST refactor + reprint**: 매 comment 모두 사라짐.
|
||||
- **Full reparse on every keystroke**: 매 IDE freeze — incremental 필수.
|
||||
- **Mutable shared CST**: 매 race condition; immutable green tree 사용.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (tree-sitter docs, Roslyn API ref, "Lossless Syntax Tree" Aleksey Kladov 2020).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — CST vs AST, tree-sitter, Roslyn, rowan patterns |
|
||||
@@ -0,0 +1,171 @@
|
||||
---
|
||||
id: wiki-2026-0508-call-stack-analysis
|
||||
title: Call Stack Analysis
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Call Stack, Stack Trace Analysis, Flame Graph, Profiling Stack]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.95
|
||||
verification_status: applied
|
||||
tags: [profiling, performance, flame-graph, debugging, observability]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: Polyglot
|
||||
framework: perf/eBPF/pprof
|
||||
---
|
||||
|
||||
# Call Stack Analysis
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 performance bug 의 95% 는 'where is CPU time spent?' — 매 call stack sampling 이 답한다."**. 매 stack trace 를 statistical 하게 sampling → flame graph 로 visualize 하면 hot path 가 즉시 보임. 매 2026 표준 stack 은 Linux perf + eBPF, 매 inferno / pyroscope / Datadog Continuous Profiler.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 sampling vs instrumentation
|
||||
- **Sampling profiler**: 매 N Hz (보통 99/999Hz) 마다 stack capture → low overhead, statistical.
|
||||
- **Instrumented profiler**: 매 every entry/exit hook → exact, but 10-100x overhead.
|
||||
- **현대 default**: 매 sampling — 매 production-safe.
|
||||
|
||||
### 매 stack source
|
||||
- **Frame pointer (RBP) walk**: 매 fastest, requires `-fno-omit-frame-pointer`.
|
||||
- **DWARF unwind**: 매 .eh_frame 사용 — frame pointer 불필요하나 expensive.
|
||||
- **ORC unwinder**: 매 Linux kernel 의 lightweight DWARF subset.
|
||||
- **eBPF stackmap**: 매 user+kernel stack 통합.
|
||||
|
||||
### 매 visualization
|
||||
- **Flame graph (Brendan Gregg)**: 매 x=share of samples, y=stack depth, width=hot.
|
||||
- **Icicle graph**: 매 flipped flame — root at top.
|
||||
- **Differential flame graph**: 매 두 profile diff — perf regression 사냥.
|
||||
|
||||
### 매 응용
|
||||
1. **CPU bottleneck 진단**: 매 hot function 식별.
|
||||
2. **Lock contention**: 매 off-CPU profile + futex stack.
|
||||
3. **GC pressure**: 매 alloc-stack profile.
|
||||
4. **Cold start**: 매 startup phase flame graph.
|
||||
5. **Continuous profiling**: 매 prod 24/7 sample → regression alerting.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Linux perf — basic
|
||||
```bash
|
||||
# 30s sample at 99Hz
|
||||
perf record -F 99 -a -g --call-graph dwarf -- sleep 30
|
||||
perf script > out.stack
|
||||
# render
|
||||
git clone https://github.com/brendangregg/FlameGraph
|
||||
./FlameGraph/stackcollapse-perf.pl out.stack | \
|
||||
./FlameGraph/flamegraph.pl > flame.svg
|
||||
```
|
||||
|
||||
### eBPF profile (BCC)
|
||||
```bash
|
||||
profile-bpfcc -F 99 -f 30 > out.folded
|
||||
flamegraph.pl out.folded > flame.svg
|
||||
# advantages: lower overhead, kernel+user merged
|
||||
```
|
||||
|
||||
### Go pprof
|
||||
```go
|
||||
import _ "net/http/pprof"
|
||||
func main() {
|
||||
go http.ListenAndServe(":6060", nil)
|
||||
// ... app
|
||||
}
|
||||
```
|
||||
```bash
|
||||
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30
|
||||
# interactive flame graph in browser
|
||||
```
|
||||
|
||||
### Python py-spy (no code change)
|
||||
```bash
|
||||
py-spy record -o flame.svg -d 30 --pid $(pgrep -f myapp.py)
|
||||
# zero instrumentation, samples a running process
|
||||
```
|
||||
|
||||
### Node.js / V8
|
||||
```bash
|
||||
node --prof app.js
|
||||
# ... run workload ...
|
||||
node --prof-process isolate-0xNNN-v8.log > profile.txt
|
||||
# or 0x: npx 0x -- node app.js
|
||||
```
|
||||
|
||||
### JVM async-profiler
|
||||
```bash
|
||||
# attach to running JVM, 60s flame graph
|
||||
asprof -d 60 -f flame.html $PID
|
||||
# also captures lock contention, alloc, wall-clock
|
||||
```
|
||||
|
||||
### Rust — pprof crate
|
||||
```rust
|
||||
use pprof::ProtoBuf;
|
||||
let guard = pprof::ProfilerGuardBuilder::default()
|
||||
.frequency(999)
|
||||
.blocklist(&["libc", "libgcc", "pthread"])
|
||||
.build()?;
|
||||
// ... workload ...
|
||||
let report = guard.report().build()?;
|
||||
let mut file = std::fs::File::create("profile.pb")?;
|
||||
report.pprof()?.encode(&mut file)?;
|
||||
```
|
||||
|
||||
### Continuous profiling (Pyroscope / Grafana)
|
||||
```yaml
|
||||
# pyroscope agent — runs alongside app
|
||||
pyroscope:
|
||||
server: http://pyroscope:4040
|
||||
app_name: api-prod
|
||||
spy_name: ebpfspy # auto-detect language
|
||||
sample_rate: 100
|
||||
```
|
||||
|
||||
### Differential flame graph
|
||||
```bash
|
||||
./FlameGraph/difffolded.pl before.folded after.folded | \
|
||||
./FlameGraph/flamegraph.pl > diff.svg
|
||||
# red = got slower, blue = got faster
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Tool |
|
||||
|---|---|
|
||||
| Linux native (C/C++/Rust/Go) | perf + FlameGraph |
|
||||
| Container / k8s (no SYS_ADMIN) | pprof endpoint |
|
||||
| Python prod | py-spy |
|
||||
| JVM prod | async-profiler |
|
||||
| Continuous 24/7 | Pyroscope / Datadog |
|
||||
| Off-CPU (lock/IO) | offcputime-bpfcc |
|
||||
|
||||
**기본값**: 99Hz sampling → folded → flamegraph.pl. 매 첫 5분 안에 hot path 보임.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Observability]]
|
||||
- 변형: [[Flame_Graph]]
|
||||
- Adjacent: [[eBPF]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: hot function 분석, regression diff, profile 결과 해석.
|
||||
**언제 X**: tail latency / distributed trace (분산 환경은 OpenTelemetry).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Time.time printf 로그 profiling**: 매 statistical 안 되고 hot loop 망침.
|
||||
- **Frame pointer 없는 build**: 매 unwind 망가짐 — `-fno-omit-frame-pointer` 필수.
|
||||
- **너무 낮은 sample rate (10Hz)**: 매 30초 = 300 samples — noise dominate.
|
||||
- **너무 높은 rate (10kHz)**: 매 self-overhead 가 측정 결과 왜곡.
|
||||
- **Single-run profile 만 보기**: 매 variance — minimum 5 runs 권장.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Brendan Gregg "Systems Performance" 2nd ed 2020, Linux perf docs, async-profiler README 2024).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — sampling profilers, flame graphs, multi-language tooling |
|
||||
@@ -0,0 +1,223 @@
|
||||
---
|
||||
id: wiki-2026-0508-characterization-tests-특성화-테스트
|
||||
title: Characterization Tests (특성화 테스트)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Golden Master Tests, Approval Tests, Pinning Tests, Snapshot Tests]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [testing, legacy-code, feathers, refactoring]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript-python
|
||||
framework: jest-pytest-approvaltests
|
||||
---
|
||||
|
||||
# Characterization Tests (특성화 테스트)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 legacy code 의 actual behavior 의 capture — 매 spec 이 X, 매 photo 가 O."**. Michael Feathers 의 *Working Effectively with Legacy Code* (2004) 에서 정립. 매 untested legacy 의 refactoring 시작점. 매 "what should it do" X — 매 "what does it do right now" 의 lock down. 2026년 snapshot tests, ApprovalTests, golden master tests 모두 같은 family.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 정의 (Feathers)
|
||||
> "A characterization test is a test that characterizes the actual behavior of a piece of code. There's no 'correct'... it just records what the system does."
|
||||
|
||||
### 매 vs unit/spec test
|
||||
| 측면 | Spec Test | Characterization Test |
|
||||
|---|---|---|
|
||||
| Source of truth | Requirements / spec | Actual current behavior |
|
||||
| Failing means | Code has bug | Behavior changed (maybe intended) |
|
||||
| When to write | TDD, before code | Before refactoring legacy code |
|
||||
| Update on change | Fix code | Review diff, accept if intended |
|
||||
|
||||
### 매 procedure (Feathers)
|
||||
1. Pick code 의 region.
|
||||
2. Write test 가 invokes the code with realistic inputs.
|
||||
3. Assert with **placeholder** (e.g. `assert result == "FILL_ME"`).
|
||||
4. Run, capture actual output.
|
||||
5. Replace placeholder with actual output.
|
||||
6. Now test pins behavior — refactor with confidence.
|
||||
|
||||
### 매 variants
|
||||
- **Snapshot test** (Jest, Vitest): serialize output, compare next run.
|
||||
- **Approval test** (ApprovalTests): write to `.approved.txt`, manual review on diff.
|
||||
- **Golden master**: large input/output pair, often UI screenshot.
|
||||
- **Property-based regression**: random inputs, save outputs as golden.
|
||||
|
||||
### 매 응용
|
||||
1. Refactoring legacy monolith without specs.
|
||||
2. Migration (framework upgrade, language port).
|
||||
3. Compiler / parser output stability.
|
||||
4. Report generation (PDFs, CSVs).
|
||||
5. UI visual regression (Percy, Chromatic).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Feathers procedure (TypeScript + Jest)
|
||||
```typescript
|
||||
import { calculateInvoice } from './legacy';
|
||||
|
||||
describe('calculateInvoice — characterization', () => {
|
||||
it('records current behavior for typical input', () => {
|
||||
const result = calculateInvoice({
|
||||
items: [
|
||||
{ sku: 'A', qty: 2, price: 19.99 },
|
||||
{ sku: 'B', qty: 1, price: 5.00 },
|
||||
],
|
||||
customerType: 'premium',
|
||||
country: 'KR',
|
||||
});
|
||||
|
||||
// Step 1: write `expect(result).toBe('PLACEHOLDER')`
|
||||
// Step 2: run, observe actual:
|
||||
// { subtotal: 44.98, discount: 4.50, vat: 4.05, total: 44.53 }
|
||||
// Step 3: paste it as expected
|
||||
expect(result).toEqual({
|
||||
subtotal: 44.98,
|
||||
discount: 4.50,
|
||||
vat: 4.05,
|
||||
total: 44.53,
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Jest snapshot
|
||||
```typescript
|
||||
import { renderInvoiceHtml } from './render';
|
||||
|
||||
test('invoice html — characterized', () => {
|
||||
const html = renderInvoiceHtml(SAMPLE_INVOICE);
|
||||
expect(html).toMatchSnapshot();
|
||||
});
|
||||
// First run: writes __snapshots__/invoice.test.ts.snap
|
||||
// Future runs: diffs. Use `--update-snapshot` after intentional change.
|
||||
```
|
||||
|
||||
### ApprovalTests (Python)
|
||||
```python
|
||||
from approvaltests import verify
|
||||
|
||||
def test_pdf_layout():
|
||||
pdf_text = render_pdf(sample_data)
|
||||
verify(pdf_text)
|
||||
# Writes `test_pdf_layout.received.txt`, compares to `.approved.txt`.
|
||||
# CI fails on diff; dev reviews then renames received→approved.
|
||||
```
|
||||
|
||||
### Golden master with multiple inputs
|
||||
```python
|
||||
import json
|
||||
from pathlib import Path
|
||||
from legacy_pricing import compute_price
|
||||
|
||||
def test_pricing_golden_master():
|
||||
cases = json.loads(Path("fixtures/cases.json").read_text())
|
||||
actual = [compute_price(c["input"]) for c in cases]
|
||||
expected = json.loads(Path("fixtures/expected.json").read_text())
|
||||
assert actual == expected
|
||||
```
|
||||
|
||||
### Generating the golden initially
|
||||
```python
|
||||
# tools/regenerate_golden.py — run once, then commit
|
||||
import json
|
||||
from pathlib import Path
|
||||
from legacy_pricing import compute_price
|
||||
|
||||
cases = json.loads(Path("fixtures/cases.json").read_text())
|
||||
out = [compute_price(c["input"]) for c in cases]
|
||||
Path("fixtures/expected.json").write_text(json.dumps(out, indent=2))
|
||||
```
|
||||
|
||||
### Differential testing (old vs refactored)
|
||||
```typescript
|
||||
import { computePriceLegacy } from './pricing-legacy';
|
||||
import { computePriceNew } from './pricing-new';
|
||||
import fc from 'fast-check';
|
||||
|
||||
test('refactor preserves behavior', () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.record({
|
||||
qty: fc.integer({ min: 1, max: 100 }),
|
||||
unitPrice: fc.float({ min: 0.01, max: 9999 }),
|
||||
}),
|
||||
(input) => {
|
||||
expect(computePriceNew(input))
|
||||
.toBeCloseTo(computePriceLegacy(input), 2);
|
||||
},
|
||||
),
|
||||
{ numRuns: 1000 },
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### Capture I/O of legacy via instrumentation
|
||||
```python
|
||||
# Wrap legacy fn, log all inputs+outputs in production for a week,
|
||||
# then replay as test fixtures
|
||||
from functools import wraps
|
||||
import json, time
|
||||
|
||||
def record(path):
|
||||
def deco(fn):
|
||||
@wraps(fn)
|
||||
def wrapper(*args, **kwargs):
|
||||
result = fn(*args, **kwargs)
|
||||
with open(path, "a") as f:
|
||||
f.write(json.dumps({
|
||||
"ts": time.time(),
|
||||
"args": args, "kwargs": kwargs,
|
||||
"result": result,
|
||||
}, default=str) + "\n")
|
||||
return result
|
||||
return wrapper
|
||||
return deco
|
||||
|
||||
@record("fixtures/legacy_calls.jsonl")
|
||||
def legacy_compute(...): ...
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Pure function legacy | Inline assertion (Feathers procedure) |
|
||||
| Large structured output | Snapshot or ApprovalTests |
|
||||
| Visual UI | Storybook + Chromatic / Percy |
|
||||
| Two impls (refactor) | Differential testing + property-based |
|
||||
| Production behavior unknown | Record-replay from instrumentation |
|
||||
|
||||
**기본값**: Snapshot test for serializable output, ApprovalTests for human-reviewed diffs (PDF, HTML), differential test during refactor.
|
||||
|
||||
## 🔗 Graph
|
||||
- 변형: [[Approval Tests]]
|
||||
- 응용: [[Refactoring_Best_Practices|Refactoring]] · [[Visual Regression]]
|
||||
- Adjacent: [[Property-Based Testing]] · [[TDD]] · [[Test Doubles]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: legacy code refactor 시작, untested codebase 첫 test net, framework migration, output-shape stability (PDF/CSV/JSON).
|
||||
**언제 X**: greenfield TDD (use spec tests), rapidly evolving design (snapshots churn), security-critical (need real spec).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Treating snapshot as spec**: 매 snapshot fail — auto-update without review. 매 bug 의 silent merge.
|
||||
- **Huge unreadable snapshot**: 1000-line JSON — split into focused snapshots.
|
||||
- **No fixture review process**: golden master changes auto-merge — require reviewer.
|
||||
- **Characterization without refactor goal**: tests forever pin "legacy bug" 의 behavior — note bugs explicitly, fix later.
|
||||
- **Time/random in capture**: nondeterministic snapshots — freeze clock/seed.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Feathers "Working Effectively with Legacy Code" 2004 / Jest docs / ApprovalTests / Beck "Test Driven Development").
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Feathers procedure + snapshot/approval/differential 패턴 |
|
||||
@@ -0,0 +1,180 @@
|
||||
---
|
||||
id: wiki-2026-0508-choreography
|
||||
title: Choreography
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Event Choreography, Saga Choreography]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [microservices, distributed-systems, events, saga]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: kafka-temporal
|
||||
---
|
||||
|
||||
# Choreography
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 service 가 자기 part 의 dance step 을 알고 events 으로 react — 매 central conductor 없음."**. Choreography는 distributed workflow를 events 의 emit/subscribe 로 구현하는 pattern. Orchestration 의 반대 — 매 decoupling 매 high, 매 visibility 매 low. 2026년 event-driven microservices, EDA, Saga pattern 의 핵심 선택지.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Choreography vs Orchestration
|
||||
| 측면 | Choreography | Orchestration |
|
||||
|---|---|---|
|
||||
| Control | Distributed | Centralized (orchestrator) |
|
||||
| Coupling | Low (events) | Higher (orchestrator knows all) |
|
||||
| Visibility | Hard (trace tools 필요) | Easy (one workflow definition) |
|
||||
| Add new step | Just subscribe | Edit orchestrator |
|
||||
| Failure handling | Each service handles own | Orchestrator decides |
|
||||
| Examples | Kafka events, NATS | Temporal, AWS Step Functions |
|
||||
|
||||
### 매 패턴 종류
|
||||
- **Event-carried state transfer**: event 가 모든 필요 data 포함.
|
||||
- **Event notification**: event 는 trigger only, state 는 query.
|
||||
- **Saga choreography**: distributed transaction 의 compensating events.
|
||||
|
||||
### 매 응용
|
||||
1. E-commerce order flow (OrderCreated → Payment → Shipping events).
|
||||
2. User signup pipeline (UserRegistered → emails → analytics).
|
||||
3. IoT event processing (sensor → multiple consumers).
|
||||
4. Domain events (DDD bounded context integration).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Saga choreography (TypeScript + Kafka)
|
||||
```typescript
|
||||
// Order service
|
||||
async function createOrder(req: CreateOrderRequest) {
|
||||
const order = await db.orders.insert({ ...req, status: 'pending' });
|
||||
await kafka.produce('order.created', {
|
||||
orderId: order.id,
|
||||
userId: order.userId,
|
||||
amount: order.total,
|
||||
});
|
||||
return order;
|
||||
}
|
||||
|
||||
// Order service also listens for compensations
|
||||
kafka.consume('payment.failed', async (evt) => {
|
||||
await db.orders.update(evt.orderId, { status: 'cancelled' });
|
||||
await kafka.produce('order.cancelled', { orderId: evt.orderId });
|
||||
});
|
||||
```
|
||||
|
||||
### Payment service reacts independently
|
||||
```typescript
|
||||
kafka.consume('order.created', async (evt) => {
|
||||
try {
|
||||
const charge = await stripe.charges.create({
|
||||
amount: evt.amount * 100,
|
||||
customer: evt.userId,
|
||||
});
|
||||
await kafka.produce('payment.succeeded', {
|
||||
orderId: evt.orderId,
|
||||
chargeId: charge.id,
|
||||
});
|
||||
} catch (err) {
|
||||
await kafka.produce('payment.failed', {
|
||||
orderId: evt.orderId,
|
||||
reason: String(err),
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Shipping service
|
||||
```typescript
|
||||
kafka.consume('payment.succeeded', async (evt) => {
|
||||
const shipment = await shippingApi.create({ orderId: evt.orderId });
|
||||
await kafka.produce('shipment.created', { ...shipment });
|
||||
});
|
||||
```
|
||||
|
||||
### Event schema (Avro / JSON Schema)
|
||||
```json
|
||||
{
|
||||
"type": "record",
|
||||
"name": "OrderCreated",
|
||||
"fields": [
|
||||
{ "name": "orderId", "type": "string" },
|
||||
{ "name": "userId", "type": "string" },
|
||||
{ "name": "amount", "type": "double" },
|
||||
{ "name": "createdAt", "type": "string" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Idempotent consumer
|
||||
```typescript
|
||||
async function handleOrderCreated(evt: OrderCreated) {
|
||||
// Dedup by event id
|
||||
const seen = await redis.set(
|
||||
`processed:${evt.eventId}`,
|
||||
'1',
|
||||
{ NX: true, EX: 86400 },
|
||||
);
|
||||
if (!seen) return; // already processed
|
||||
|
||||
await processOrder(evt);
|
||||
}
|
||||
```
|
||||
|
||||
### Distributed tracing (OTel) 매 visibility 회복
|
||||
```typescript
|
||||
import { trace, context, propagation } from '@opentelemetry/api';
|
||||
|
||||
async function publishWithTrace(topic: string, evt: any) {
|
||||
const span = trace.getActiveSpan();
|
||||
const carrier: Record<string, string> = {};
|
||||
propagation.inject(context.active(), carrier);
|
||||
await kafka.produce(topic, {
|
||||
...evt,
|
||||
_trace: carrier,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Simple linear workflow | Orchestration (Temporal) |
|
||||
| Many independent reactors | Choreography |
|
||||
| Strong consistency 필요 | Orchestration + 2PC 또는 Temporal |
|
||||
| Fan-out events | Choreography |
|
||||
| Need visual workflow + retry | Orchestration |
|
||||
| Event-driven domain (DDD) | Choreography |
|
||||
|
||||
**기본값**: 매 simple 2-3 step domain workflow → orchestration (Temporal). 매 broad fan-out + autonomous teams → choreography.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Event-Driven Architecture]] · [[Microservices]]
|
||||
- 변형: [[Pub-Sub]]
|
||||
- 응용: [[Broker Topology]] · [[Event Sourcing]]
|
||||
- Adjacent: [[Orchestration]] · [[CQRS]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: event-driven microservice design, decoupling teams, fan-out workflows, autonomous services.
|
||||
**언제 X**: short transactional flow needing visibility, strict ordering 필요한 critical financial workflows (orchestrator + Temporal 가 better).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Distributed monolith via events**: events 가 actually RPC 호출 — coupling 그대로.
|
||||
- **No event versioning**: schema 변경 시 모든 consumer 깨짐 — Schema Registry 필수.
|
||||
- **Lost trace**: span 이 event boundary 에서 끊김 — propagation injection 필수.
|
||||
- **Implicit ordering assumption**: 매 services 가 임의 order 로 react — explicit ordering 필요 시 partition key.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Hohpe "Enterprise Integration Patterns" / Microservices.io / Confluent docs 2026).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Saga choreography + Kafka + OTel 패턴 |
|
||||
@@ -0,0 +1,205 @@
|
||||
---
|
||||
id: wiki-2026-0508-code-refactoring
|
||||
title: Code Refactoring
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Refactoring, Code Improvement, Fowler Refactoring]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.95
|
||||
verification_status: applied
|
||||
tags: [refactoring, code-quality, fowler, ide, llm-refactor]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: Polyglot
|
||||
framework: IDE/AST-based-tools
|
||||
---
|
||||
|
||||
# Code Refactoring
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 refactoring = 매 외부 동작은 그대로 두고 internal 구조만 개선하는 disciplined transformation."**. 매 Martin Fowler 1999 "Refactoring" 이 catalog 화한 70+ named transformation 이 backbone, 매 2026 현재 IDE automated refactor + LLM-assisted refactor (Claude Opus 4.7, Cursor) 가 manual rewrite 대체. 매 핵심 disciplines = small steps + green tests + commit per refactor.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 catalog (대표)
|
||||
- **Extract Function / Method**: 매 코드 블록 → 새 function.
|
||||
- **Inline Function**: 매 trivial wrapper 제거.
|
||||
- **Rename**: 매 의미 명확한 이름.
|
||||
- **Extract Variable / Inline Variable**.
|
||||
- **Move Function / Field**: 매 right class/module 로.
|
||||
- **Replace Conditional with Polymorphism**.
|
||||
- **Replace Magic Number with Constant**.
|
||||
- **Introduce Parameter Object**.
|
||||
- **Decompose Conditional**.
|
||||
- **Extract Class / Inline Class**.
|
||||
|
||||
### 매 disciplines
|
||||
- **Tests first**: 매 refactor 전 green test suite.
|
||||
- **Small steps**: 매 single refactor → run tests → commit.
|
||||
- **No mixing**: 매 behavior change 와 refactor 동시 X.
|
||||
- **Reversibility**: 매 step 별 git revert 가능.
|
||||
|
||||
### 매 응용
|
||||
1. **Legacy modernization**: 매 strangler fig + extract.
|
||||
2. **Performance**: 매 inline hot function, extract slow path.
|
||||
3. **Test enabling**: 매 dependency 분리.
|
||||
4. **Onboarding**: 매 confusing code rename + decompose.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Extract Function
|
||||
```python
|
||||
# BEFORE
|
||||
def print_owing(invoice):
|
||||
print_banner()
|
||||
outstanding = sum(o.amount for o in invoice.orders)
|
||||
print(f"name: {invoice.customer}")
|
||||
print(f"amount: {outstanding}")
|
||||
|
||||
# AFTER
|
||||
def print_owing(invoice):
|
||||
print_banner()
|
||||
outstanding = calculate_outstanding(invoice)
|
||||
print_details(invoice, outstanding)
|
||||
|
||||
def calculate_outstanding(invoice):
|
||||
return sum(o.amount for o in invoice.orders)
|
||||
|
||||
def print_details(invoice, outstanding):
|
||||
print(f"name: {invoice.customer}")
|
||||
print(f"amount: {outstanding}")
|
||||
```
|
||||
|
||||
### Replace Conditional with Polymorphism
|
||||
```typescript
|
||||
// BEFORE
|
||||
function pay(employee: Employee) {
|
||||
switch (employee.type) {
|
||||
case 'engineer': return baseSalary(employee);
|
||||
case 'salesman': return baseSalary(employee) + commission(employee);
|
||||
case 'manager': return baseSalary(employee) + bonus(employee);
|
||||
}
|
||||
}
|
||||
|
||||
// AFTER
|
||||
abstract class EmployeeType {
|
||||
abstract pay(e: Employee): number;
|
||||
}
|
||||
class Engineer extends EmployeeType { pay(e) { return baseSalary(e); } }
|
||||
class Salesman extends EmployeeType { pay(e) { return baseSalary(e) + commission(e); } }
|
||||
class Manager extends EmployeeType { pay(e) { return baseSalary(e) + bonus(e); } }
|
||||
```
|
||||
|
||||
### Introduce Parameter Object
|
||||
```python
|
||||
# BEFORE
|
||||
def amount_invoiced(start_date, end_date, customer_id, currency, ...): ...
|
||||
|
||||
# AFTER
|
||||
@dataclass
|
||||
class InvoiceQuery:
|
||||
range: DateRange
|
||||
customer_id: str
|
||||
currency: str
|
||||
|
||||
def amount_invoiced(q: InvoiceQuery): ...
|
||||
```
|
||||
|
||||
### Decompose Conditional
|
||||
```javascript
|
||||
// BEFORE
|
||||
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
|
||||
charge = quantity * winterRate + winterServiceCharge;
|
||||
} else {
|
||||
charge = quantity * summerRate;
|
||||
}
|
||||
|
||||
// AFTER
|
||||
charge = isSummer(date) ? summerCharge(quantity) : winterCharge(quantity);
|
||||
function isSummer(d) { return !d.before(SUMMER_START) && !d.after(SUMMER_END); }
|
||||
function summerCharge(q) { return q * summerRate; }
|
||||
function winterCharge(q) { return q * winterRate + winterServiceCharge; }
|
||||
```
|
||||
|
||||
### Strangler Fig (legacy migration)
|
||||
```typescript
|
||||
// route table — gradually shift endpoints to new service
|
||||
const routes = {
|
||||
'/users': process.env.NEW_USERS === 'on' ? newUsers : legacyUsers,
|
||||
'/orders': process.env.NEW_ORDERS === 'on' ? newOrders : legacyOrders,
|
||||
'/billing': legacyBilling, // not migrated yet
|
||||
};
|
||||
// flip flags one at a time, rollback by env var
|
||||
```
|
||||
|
||||
### IDE-driven (IntelliJ / Roslyn)
|
||||
```bash
|
||||
# JetBrains command-line refactor (CI batch)
|
||||
idea.sh inspect $PROJECT inspectionProfile.xml output/ \
|
||||
-d $MODULE -v2
|
||||
|
||||
# C# Roslyn analyzer + code fix runs in `dotnet format analyzers --verify-no-changes`
|
||||
```
|
||||
|
||||
### LLM-assisted refactor (Claude Opus 4.7)
|
||||
```bash
|
||||
# 1. select scope, 2. generate diff, 3. tests must stay green
|
||||
claude refactor \
|
||||
--scope src/billing/ \
|
||||
--instruction "Extract OrderTotalCalculator class; preserve all behavior" \
|
||||
--validate "pytest tests/billing/"
|
||||
# Claude proposes diff → run tests → commit if green
|
||||
```
|
||||
|
||||
### Test characterization (legacy without tests)
|
||||
```python
|
||||
# Before refactor of untested code: write golden tests first
|
||||
def test_legacy_charge_2025_invoice():
|
||||
inv = load_fixture('2025_invoice.json')
|
||||
expected = legacy.calc(inv) # capture current behavior
|
||||
assert refactored.calc(inv) == expected # equality, not "correctness"
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Refactor |
|
||||
|---|---|
|
||||
| Long function (> 30 lines) | Extract Function |
|
||||
| Same param list 5+ places | Introduce Parameter Object |
|
||||
| `switch (type)` repeated | Replace Conditional with Polymorphism |
|
||||
| Confusing name | Rename |
|
||||
| Class doing 2 things | Extract Class |
|
||||
| Legacy without tests | Characterization tests first |
|
||||
| Mass code movement | LLM batch + test-gate |
|
||||
|
||||
**기본값**: small step + commit per refactor + tests green; LLM 으로 mass rename / extract 가속.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Code_Quality]]
|
||||
- 변형: [[Strangler_Fig]]
|
||||
- 응용: [[Legacy_Modernization]] · [[Test-Driven Development]]
|
||||
- Adjacent: [[Clean_Code]] · [[Design_Patterns]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: bulk rename, extract function, polymorphism conversion, parameter object 도입.
|
||||
**언제 X**: subtle concurrency / lock-free invariants — 매 manual review 필수.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Refactor + feature in same commit**: 매 review/revert 불가능.
|
||||
- **Refactor without tests**: 매 silent behavior change.
|
||||
- **Big-bang rewrite**: 매 6개월 후 production 되돌리기 불가능.
|
||||
- **Premature abstraction**: 매 1번 쓰인 패턴 인터페이스화 — YAGNI.
|
||||
- **Trust-LLM-and-skip-tests**: 매 LLM hallucinated edge case 통과시킴.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Fowler "Refactoring" 2nd ed 2018, "Working Effectively with Legacy Code" Feathers 2004).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Fowler catalog, disciplines, IDE/LLM-assisted patterns |
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
id: wiki-2026-0508-combat-timeline-difficulty-scali
|
||||
title: Combat Timeline Difficulty Scaling
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Combat Difficulty Curve, Encounter Scaling, Dynamic Difficulty Adjustment]
|
||||
duplicate_of: none
|
||||
source_trust_level: B
|
||||
confidence_score: 0.85
|
||||
verification_status: applied
|
||||
tags: [game-design, combat, difficulty, ddа, encounter]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: C#/Python
|
||||
framework: Game-engine-agnostic
|
||||
---
|
||||
|
||||
# Combat Timeline Difficulty Scaling
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 combat encounter 의 매력 = 매 player skill 곡선과 challenge 곡선의 일치 (flow channel)."**. 매 timeline-based scaling 은 encounter 의 시작/중반/끝 phase 별로 enemy 능력을 progress 시키며, 매 player metric (DPS, HP%, time-to-kill) 을 feedback 으로 받아 dynamic 하게 조정. 매 2026 modern AAA (Elden Ring DLC, Helldivers 2) 가 telemetry-driven scaling 채택.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 scaling axis
|
||||
- **Static curve**: 매 fixed level → enemy stat lookup table.
|
||||
- **Dynamic (DDA)**: 매 runtime player perf 로 parameter 조정.
|
||||
- **Timeline phase**: 매 encounter 내부 phase 분할 (intro → escalation → climax).
|
||||
- **Hybrid**: 매 baseline curve + DDA correction.
|
||||
|
||||
### 매 measurable metric
|
||||
- **TTK** (time to kill enemy).
|
||||
- **TTD** (time to death — player).
|
||||
- **Damage taken / second**.
|
||||
- **Resource economy** (potions/ammo per minute).
|
||||
- **Player retry count**.
|
||||
|
||||
### 매 응용
|
||||
1. **Soulslike boss phase transition**: 매 50%/25% HP → new moveset.
|
||||
2. **Roguelike floor scaling**: 매 depth × player level 함수.
|
||||
3. **MMO raid enrage timer**: 매 hard cutoff DPS check.
|
||||
4. **Casual mode auto-easing**: 매 3회 사망 → enemy HP -10%.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Phase-based encounter timeline
|
||||
```csharp
|
||||
public class BossEncounter : MonoBehaviour {
|
||||
enum Phase { Intro, Build, Climax, Enrage }
|
||||
Phase phase = Phase.Intro;
|
||||
float t;
|
||||
|
||||
void Update() {
|
||||
t += Time.deltaTime;
|
||||
var hpFrac = boss.HP / boss.MaxHP;
|
||||
var newPhase = phase;
|
||||
|
||||
if (hpFrac < 0.25f) newPhase = Phase.Climax;
|
||||
else if (hpFrac < 0.60f) newPhase = Phase.Build;
|
||||
else if (t > 180f) newPhase = Phase.Enrage;
|
||||
|
||||
if (newPhase != phase) { OnPhaseEnter(newPhase); phase = newPhase; }
|
||||
ApplyPhaseModifiers(phase);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Stat curve table
|
||||
```yaml
|
||||
# enemies/orc_warrior.yaml
|
||||
levels:
|
||||
1: { hp: 100, dmg: 10, speed: 3.0 }
|
||||
10: { hp: 250, dmg: 22, speed: 3.5 }
|
||||
20: { hp: 600, dmg: 50, speed: 4.0 }
|
||||
50: { hp: 5000, dmg: 220, speed: 5.0 }
|
||||
# interpolate between keypoints
|
||||
```
|
||||
|
||||
```csharp
|
||||
EnemyStats Lerp(EnemyStats a, EnemyStats b, float t) =>
|
||||
new EnemyStats {
|
||||
HP = Mathf.Lerp(a.HP, b.HP, t),
|
||||
Damage = Mathf.Lerp(a.Damage, b.Damage, t),
|
||||
Speed = Mathf.Lerp(a.Speed, b.Speed, t),
|
||||
};
|
||||
```
|
||||
|
||||
### DDA — sliding window perf
|
||||
```python
|
||||
class DDAController:
|
||||
def __init__(self, target_ttd=45.0, window=5):
|
||||
self.target = target_ttd
|
||||
self.history = collections.deque(maxlen=window)
|
||||
self.scale = 1.0
|
||||
|
||||
def record_death(self, ttd_seconds):
|
||||
self.history.append(ttd_seconds)
|
||||
if len(self.history) < 3: return
|
||||
avg = sum(self.history) / len(self.history)
|
||||
# too easy → ramp up; too hard → ease
|
||||
delta = (avg - self.target) / self.target # +0.2 = 20% too easy
|
||||
self.scale = clamp(self.scale + delta * 0.05, 0.7, 1.5)
|
||||
|
||||
def apply(self, enemy):
|
||||
enemy.hp *= self.scale
|
||||
enemy.dmg *= math.sqrt(self.scale) # damage scales softer
|
||||
```
|
||||
|
||||
### Enrage timer
|
||||
```csharp
|
||||
void Update() {
|
||||
enrageT += Time.deltaTime;
|
||||
if (enrageT > enrageStart) {
|
||||
var f = Mathf.Clamp01((enrageT - enrageStart) / 30f);
|
||||
boss.DamageMultiplier = 1f + f * 4f; // 5x dmg over 30s
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Skill-bracket matchmaking adjust
|
||||
```python
|
||||
def select_encounter(player_mmr: int, depth: int):
|
||||
target_difficulty = base_curve(depth) * mmr_multiplier(player_mmr)
|
||||
candidates = [e for e in pool if abs(e.difficulty - target_difficulty) < 0.15]
|
||||
return random.choice(candidates) if candidates else fallback(pool, target_difficulty)
|
||||
```
|
||||
|
||||
### Telemetry-driven re-tuning (offline)
|
||||
```sql
|
||||
-- find under-tuned bosses (too easy)
|
||||
SELECT boss_id,
|
||||
AVG(time_to_kill) AS avg_ttk,
|
||||
AVG(player_deaths) AS avg_deaths,
|
||||
COUNT(*) AS attempts
|
||||
FROM encounter_log
|
||||
WHERE patch_version = '2.4.0'
|
||||
GROUP BY boss_id
|
||||
HAVING avg_ttk < 30 AND avg_deaths < 0.3
|
||||
ORDER BY attempts DESC;
|
||||
```
|
||||
|
||||
### Adaptive damage curve
|
||||
```csharp
|
||||
// damage taken curves softer the lower player HP — "comeback mechanic"
|
||||
float TakenMultiplier(float hpFrac) =>
|
||||
Mathf.Lerp(1.2f, 0.7f, 1f - hpFrac); // low HP = less dmg taken
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Scaling |
|
||||
|---|---|
|
||||
| Linear narrative game | static curve |
|
||||
| Roguelike / replayable | static + run-level scale |
|
||||
| Live-service skill gap | DDA + bracket matchmaking |
|
||||
| Boss fight 10+ min | phase-based + enrage |
|
||||
| Accessibility mode | one-way DDA (only ease) |
|
||||
|
||||
**기본값**: phase-based timeline + soft DDA (max ±20%) + telemetry retune per patch.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Game_Design]] · [[Combat_System]]
|
||||
- 변형: [[Dynamic_Difficulty_Adjustment]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: encounter design, difficulty curve tuning, DDA controller 설계.
|
||||
**언제 X**: pure narrative pacing (story beats 영역).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **HP-sponge scaling**: 매 단순 HP × 10 → boring, not harder.
|
||||
- **Hidden DDA without disclosure**: 매 player 가 눈치채면 frustration.
|
||||
- **No floor on DDA**: 매 player 일부러 죽으며 game 망가뜨림.
|
||||
- **One curve for all enemies**: 매 archetype 별 differentiation 부재.
|
||||
- **Untested enrage timer**: 매 DPS check 가 unfair RNG 가 되면 community 폭발.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (GDC talks "Resident Evil 4 DDA" 2005, "Helldivers 2 mission director" 2024).
|
||||
- 신뢰도 B.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — phase timeline, DDA controller, telemetry retuning |
|
||||
@@ -0,0 +1,148 @@
|
||||
---
|
||||
id: wiki-2026-0508-complex-event-processing-cep
|
||||
title: Complex Event Processing (CEP)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [CEP, Event Stream Processing, 복합 이벤트 처리]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.88
|
||||
verification_status: applied
|
||||
tags: [cep, streaming, event-driven, flink, esper]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: java
|
||||
framework: flink
|
||||
---
|
||||
|
||||
# Complex Event Processing (CEP)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 stream of simple events → meaningful complex pattern"**. David Luckham (Stanford, 2002) 가 정의한 paradigm. 2026 현재 Apache Flink CEP, Kafka Streams, Esper NEsper 가 main implementation; fraud detection, IoT anomaly, algorithmic trading 의 backbone.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 개념
|
||||
- **Event**: timestamped 의 fact (transaction, sensor reading, click).
|
||||
- **Pattern**: temporal/causal relationship 의 events (A followed by B within 5s).
|
||||
- **Window**: sliding/tumbling/session 시간 frame.
|
||||
- **Aggregation**: count, sum, avg over window.
|
||||
- **Correlation**: 다중 stream 매 join (e.g., trades + market data).
|
||||
|
||||
### 매 pattern operator
|
||||
- **Sequence**: A → B → C (in order).
|
||||
- **Conjunction**: A AND B (any order, in window).
|
||||
- **Negation**: A NOT followed by B.
|
||||
- **Iteration**: A repeated N times.
|
||||
- **Within**: temporal constraint.
|
||||
|
||||
### 매 응용
|
||||
1. Fraud detection — card swipes 매 different countries within 1h.
|
||||
2. IoT — sensor reading exceeds threshold for 3 consecutive readings.
|
||||
3. Trading — bid/ask spread anomaly detection.
|
||||
4. Network security — port scan pattern (many SYN, few ACK).
|
||||
5. SLA monitoring — 5xx error rate spike correlated with deploy event.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Flink CEP — 3 failed login pattern
|
||||
```java
|
||||
Pattern<LoginEvent, ?> failedLogins = Pattern
|
||||
.<LoginEvent>begin("first")
|
||||
.where(e -> !e.success)
|
||||
.next("second").where(e -> !e.success)
|
||||
.next("third").where(e -> !e.success)
|
||||
.within(Time.minutes(5));
|
||||
|
||||
CEP.pattern(loginStream.keyBy(e -> e.userId), failedLogins)
|
||||
.select(match -> new Alert(match.get("first").get(0).userId))
|
||||
.addSink(alertSink);
|
||||
```
|
||||
|
||||
### Esper EPL — fraud detection
|
||||
```sql
|
||||
-- swipe in different countries within 1 hour
|
||||
SELECT a.cardId, a.country, b.country
|
||||
FROM pattern [
|
||||
every a=Swipe -> b=Swipe(cardId=a.cardId, country!=a.country)
|
||||
where timer:within(1 hour)
|
||||
];
|
||||
```
|
||||
|
||||
### Kafka Streams — sliding window aggregation
|
||||
```java
|
||||
KStream<String, Click> clicks = builder.stream("clicks");
|
||||
|
||||
clicks.groupByKey()
|
||||
.windowedBy(SlidingWindows.ofTimeDifferenceWithNoGrace(Duration.ofMinutes(5)))
|
||||
.count()
|
||||
.filter((k, count) -> count > 1000)
|
||||
.toStream()
|
||||
.to("anomalies");
|
||||
```
|
||||
|
||||
### Flink — session window
|
||||
```java
|
||||
stream.keyBy(e -> e.userId)
|
||||
.window(EventTimeSessionWindows.withGap(Time.minutes(30)))
|
||||
.aggregate(new SessionStats())
|
||||
.addSink(...);
|
||||
```
|
||||
|
||||
### Pattern with negation (NO heartbeat in 30s)
|
||||
```java
|
||||
Pattern.<HeartbeatEvent>begin("start")
|
||||
.notFollowedBy("missing")
|
||||
.where(e -> true)
|
||||
.within(Time.seconds(30));
|
||||
```
|
||||
|
||||
### Modern: Materialize / RisingWave (SQL-native streaming)
|
||||
```sql
|
||||
CREATE MATERIALIZED VIEW fraud_alerts AS
|
||||
SELECT user_id, COUNT(*) as failed_count
|
||||
FROM logins
|
||||
WHERE success = false
|
||||
AND ts > NOW() - INTERVAL '5 minutes'
|
||||
GROUP BY user_id
|
||||
HAVING COUNT(*) >= 3;
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Java/JVM, complex patterns | Flink CEP |
|
||||
| Kafka-centric, simple aggregation | Kafka Streams |
|
||||
| SQL-first, low ops | Materialize / RisingWave |
|
||||
| In-process, low-volume | Esper |
|
||||
| Cloud-native, serverless | AWS Kinesis Data Analytics |
|
||||
|
||||
**기본값**: Flink CEP for complex patterns, Materialize for SQL-native streaming.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Event-Driven Architecture]] · [[Stream-Processing-Architectures|Stream Processing]]
|
||||
- 변형: [[Event Sourcing]] · [[CQRS]]
|
||||
- Adjacent: [[Apache Flink]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: pattern definition 매 natural language → EPL/Flink translation, alert explanation.
|
||||
**언제 X**: micro-second latency hot path (LLM 매 too slow).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Unbounded state**: window 없이 group-by → memory blowup.
|
||||
- **Wall-clock instead of event-time**: out-of-order event 매 wrong result.
|
||||
- **Pattern explosion**: NFA state count 매 exponential, pattern 너무 복잡.
|
||||
- **No watermark**: late event 매 silently lost.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Luckham 2002 *Power of Events*, Apache Flink CEP docs 2026).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — full content with Flink CEP, Esper, Materialize |
|
||||
@@ -0,0 +1,184 @@
|
||||
---
|
||||
id: wiki-2026-0508-complexity-theory
|
||||
title: Complexity Theory
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Complex Systems, Complexity Science]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.85
|
||||
verification_status: applied
|
||||
tags: [complexity, systems, emergence, cynefin]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: agnostic
|
||||
framework: agnostic
|
||||
---
|
||||
|
||||
# Complexity Theory
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 system 의 behavior 가 매 part 의 sum 보다 크다 — 매 emergence, nonlinearity, feedback."**. Complexity theory는 Santa Fe Institute (1984~) 가 정립한 cross-disciplinary field. Software 에서는 Cynefin framework (Snowden), Brooks 의 essential vs accidental complexity, Promise Theory, distributed systems 의 emergent behavior 로 산다. 2026년 ML systems 의 emergent capabilities 도 매 핵심 case.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 complex vs complicated
|
||||
| Complicated | Complex |
|
||||
|---|---|
|
||||
| Many parts, knowable | Many parts, emergent |
|
||||
| Aircraft, watch | Ecosystem, market, brain, microservices fleet |
|
||||
| Decompose & analyze | Probe → sense → respond |
|
||||
| Predictable | Unpredictable in detail |
|
||||
|
||||
### 매 Cynefin (Snowden)
|
||||
- **Clear (Simple)**: cause→effect 자명. Best practice.
|
||||
- **Complicated**: expert analysis 필요. Good practice.
|
||||
- **Complex**: emergent, retrospective coherence. Probe → sense → respond.
|
||||
- **Chaotic**: no cause→effect. Act → sense → respond.
|
||||
- **Confusion** (Disorder): which domain unclear.
|
||||
|
||||
### 매 essential vs accidental complexity (Brooks)
|
||||
- **Essential**: 매 problem itself 의 complexity (irreducible).
|
||||
- **Accidental**: tools, languages, infra 가 만든 complexity (reducible).
|
||||
- 매 silver bullet 없음 → essential complexity 의 tackling.
|
||||
|
||||
### 매 emergent properties
|
||||
- Self-organization (ant colonies, market prices).
|
||||
- Phase transitions (water → ice, network connectivity).
|
||||
- Power laws (Zipf, scale-free networks).
|
||||
- Adaptive feedback (immune systems, ML training dynamics).
|
||||
|
||||
### 매 응용 in software
|
||||
1. Microservice fleet behavior (cascading failures, retry storms).
|
||||
2. ML emergent capabilities (in-context learning at scale).
|
||||
3. Distributed consensus (CAP, FLP impossibility).
|
||||
4. Tech debt accumulation (compound complexity).
|
||||
5. Team scaling (Brooks' law as complexity manifestation).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Cynefin-driven decision (in code review)
|
||||
```python
|
||||
def categorize_problem(problem):
|
||||
if known_solution(problem):
|
||||
return "Clear: apply best practice"
|
||||
if expertise_resolves(problem):
|
||||
return "Complicated: expert analysis"
|
||||
if requires_experimentation(problem):
|
||||
return "Complex: probe-sense-respond"
|
||||
if no_cause_effect(problem):
|
||||
return "Chaotic: act-sense-respond"
|
||||
return "Disorder: clarify first"
|
||||
```
|
||||
|
||||
### Probe → sense → respond (chaos engineering)
|
||||
```typescript
|
||||
// Netflix Chaos Monkey style — controlled probe of complex system
|
||||
import { ChaosClient } from '@netflix/chaos';
|
||||
|
||||
const chaos = new ChaosClient();
|
||||
await chaos.experiment({
|
||||
name: 'kill-random-pod-payment-svc',
|
||||
hypothesis: 'system handles single pod loss within 30s',
|
||||
blast_radius: 'one pod',
|
||||
rollback_on: 'p99 > 500ms',
|
||||
observe: ['error_rate', 'latency_p99', 'saturation'],
|
||||
});
|
||||
```
|
||||
|
||||
### Reduce accidental complexity — replace shell with compiled tool
|
||||
```python
|
||||
# Accidental: bash script with 5 sed/awk/jq pipes
|
||||
# Essential: extract user emails from JSON
|
||||
|
||||
# After: simple, type-checked
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
emails = [
|
||||
user["email"]
|
||||
for user in json.loads(Path("users.json").read_text())
|
||||
if user.get("active")
|
||||
]
|
||||
```
|
||||
|
||||
### Feedback loop modeling (system dynamics)
|
||||
```python
|
||||
# Tech debt feedback loop — simple ODE
|
||||
import numpy as np
|
||||
from scipy.integrate import odeint
|
||||
|
||||
def tech_debt(state, t, capacity, debt_growth, paydown_rate):
|
||||
debt, velocity = state
|
||||
d_debt = debt_growth - paydown_rate * velocity
|
||||
d_velocity = capacity * (1 - debt / 100) - velocity * 0.1
|
||||
return [d_debt, d_velocity]
|
||||
|
||||
# Emergent: nonlinear collapse when debt > capacity
|
||||
sol = odeint(tech_debt, [10, 5], np.linspace(0, 100, 200),
|
||||
args=(10, 2, 0.5))
|
||||
```
|
||||
|
||||
### Power-law detection (scale-free service dependency)
|
||||
```python
|
||||
import numpy as np
|
||||
import powerlaw
|
||||
|
||||
# In-degree of microservice call graph
|
||||
in_degrees = compute_in_degrees(service_graph)
|
||||
fit = powerlaw.Fit(in_degrees)
|
||||
print(f"alpha={fit.power_law.alpha:.2f}") # ~2-3 → scale-free
|
||||
# Implication: targeted attack on hubs is catastrophic
|
||||
```
|
||||
|
||||
### Promise Theory (Burgess) — autonomous agents
|
||||
```yaml
|
||||
# Each service makes promises, others assess
|
||||
service: payment-svc
|
||||
promises:
|
||||
- id: p99_latency_under_300ms
|
||||
conditions: [load < 1000rps]
|
||||
valid_until: 2026-12-31
|
||||
- id: idempotent_charge_endpoint
|
||||
conditions: []
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Clear problem | Apply best practice, automate |
|
||||
| Complicated | Expert review, formal analysis |
|
||||
| Complex (emergent) | Probe with chaos engineering, observability |
|
||||
| Chaotic (incident) | Act first, stabilize, then sense |
|
||||
| Tech debt | Distinguish essential vs accidental |
|
||||
|
||||
**기본값**: Most production distributed systems 매 Complex domain 매 산다 → SLO + chaos + observability + post-incident review.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Systems Theory]] · [[Cybernetics Foundations|Cybernetics]]
|
||||
- 변형: [[Chaos Engineering]]
|
||||
- 응용: [[Distributed Systems]] · [[Microservices]] · [[SRE]]
|
||||
- Adjacent: [[Conceptual Integrity]] · [[Emergent Behavior]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: incident retrospective, architecture decision in distributed system, tech debt classification, organizational design, ML system behavior analysis.
|
||||
**언제 X**: simple CRUD app design, single-node algorithm, bounded local logic.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Best-practice in complex domain**: clear-domain solution 을 complex domain 에 강제.
|
||||
- **Ignoring accidental complexity**: 매 essential 처럼 취급 → tooling 의 미개선.
|
||||
- **Predicting emergent behavior**: complex system 의 detail prediction 시도 — probe 가 답.
|
||||
- **No feedback loops in design**: system dynamics 무시 → 매 surprise outage.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Brooks "No Silver Bullet" / Snowden Cynefin / Mitchell "Complexity: A Guided Tour" / Burgess "Thinking in Promises").
|
||||
- 신뢰도 A-.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Cynefin + Brooks essential/accidental + chaos engineering |
|
||||
@@ -0,0 +1,216 @@
|
||||
---
|
||||
id: wiki-2026-0508-component-library-architecture
|
||||
title: Component Library Architecture
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Design System Architecture, UI Library Design]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [components, design-system, react, shadcn, radix]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: react-radix-shadcn
|
||||
---
|
||||
|
||||
# Component Library Architecture
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 unstyled headless primitives + 매 style layer + 매 composition API."**. 2026년 React component library 의 dominant model = Radix/Ark UI (headless behavior + a11y) + Tailwind/CSS variables (theming) + shadcn/ui (copy-not-install) + tokens (DTCG). MUI 5 의 monolithic theme 시대 → headless 시대로 매 shift.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 3 layer architecture
|
||||
1. **Behavior layer (headless)**: a11y, keyboard, focus, ARIA — Radix, Ark UI, React Aria.
|
||||
2. **Style layer**: Tailwind, CSS-in-JS (vanilla-extract), CSS variables.
|
||||
3. **Composition layer**: app-specific compositions of 1+2.
|
||||
|
||||
### 매 distribution model
|
||||
- **Package install** (MUI, Chakra v3): npm install, version locked, hard to customize.
|
||||
- **Copy-paste** (shadcn/ui): generator copies source into your repo, you own it, fully customizable.
|
||||
- **Hybrid** (Radix + custom wrapper): primitives via npm, your styles in repo.
|
||||
|
||||
### 매 token system (DTCG / Style Dictionary)
|
||||
- Primitive tokens (color.blue.500 = #3b82f6).
|
||||
- Semantic tokens (color.action.primary = color.blue.500).
|
||||
- Component tokens (button.bg.primary = color.action.primary).
|
||||
|
||||
### 매 응용
|
||||
1. Internal design system (Vercel Geist, GitHub Primer).
|
||||
2. Open-source kits (shadcn/ui, Mantine, Park UI).
|
||||
3. White-label products (multi-brand 동일 codebase).
|
||||
4. Cross-framework via Web Components (Spectrum Web Components).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Headless primitive (Radix)
|
||||
```typescript
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
|
||||
export function ConfirmDialog({ open, onClose, onConfirm, children }: Props) {
|
||||
return (
|
||||
<Dialog.Root open={open} onOpenChange={onClose}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="fixed inset-0 bg-black/50" />
|
||||
<Dialog.Content className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-lg bg-white p-6">
|
||||
<Dialog.Title className="text-lg font-semibold">Confirm</Dialog.Title>
|
||||
<Dialog.Description>{children}</Dialog.Description>
|
||||
<div className="mt-4 flex justify-end gap-2">
|
||||
<Dialog.Close className="btn-ghost">Cancel</Dialog.Close>
|
||||
<button className="btn-primary" onClick={onConfirm}>OK</button>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### shadcn-style copy-paste component
|
||||
```typescript
|
||||
// components/ui/button.tsx — owned by your repo
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md text-sm font-medium transition',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
primary: 'bg-blue-600 text-white hover:bg-blue-700',
|
||||
ghost: 'hover:bg-gray-100',
|
||||
},
|
||||
size: {
|
||||
sm: 'h-8 px-3',
|
||||
md: 'h-10 px-4',
|
||||
lg: 'h-12 px-6',
|
||||
},
|
||||
},
|
||||
defaultVariants: { variant: 'primary', size: 'md' },
|
||||
},
|
||||
);
|
||||
|
||||
type Props = React.ButtonHTMLAttributes<HTMLButtonElement>
|
||||
& VariantProps<typeof buttonVariants>;
|
||||
|
||||
export function Button({ className, variant, size, ...rest }: Props) {
|
||||
return (
|
||||
<button
|
||||
className={cn(buttonVariants({ variant, size }), className)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Design tokens (DTCG JSON)
|
||||
```json
|
||||
{
|
||||
"color": {
|
||||
"blue": {
|
||||
"500": { "$value": "#3b82f6", "$type": "color" }
|
||||
},
|
||||
"action": {
|
||||
"primary": { "$value": "{color.blue.500}", "$type": "color" }
|
||||
}
|
||||
},
|
||||
"space": {
|
||||
"4": { "$value": "16px", "$type": "dimension" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CSS variables driven theming
|
||||
```css
|
||||
:root {
|
||||
--color-primary: #3b82f6;
|
||||
--radius-md: 0.5rem;
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
--color-primary: #60a5fa;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--color-primary);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
```
|
||||
|
||||
### Polymorphic component (asChild pattern)
|
||||
```typescript
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
|
||||
type Props = {
|
||||
asChild?: boolean;
|
||||
} & React.HTMLAttributes<HTMLElement>;
|
||||
|
||||
export function Card({ asChild, ...rest }: Props) {
|
||||
const Comp = asChild ? Slot : 'div';
|
||||
return <Comp className="rounded-lg border p-4" {...rest} />;
|
||||
}
|
||||
|
||||
// Usage:
|
||||
<Card asChild>
|
||||
<a href="/post/1">Click me</a>
|
||||
</Card>
|
||||
```
|
||||
|
||||
### Storybook 8 + a11y addon
|
||||
```typescript
|
||||
// Button.stories.tsx
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Button } from './button';
|
||||
|
||||
const meta: Meta<typeof Button> = {
|
||||
component: Button,
|
||||
parameters: { a11y: { test: 'error' } },
|
||||
};
|
||||
export default meta;
|
||||
|
||||
export const Primary: StoryObj<typeof Button> = {
|
||||
args: { children: 'Save', variant: 'primary' },
|
||||
};
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Internal design system, full control | shadcn/ui copy-paste + Tailwind |
|
||||
| Quick prototype, full kit | Mantine, Chakra v3 |
|
||||
| Cross-framework | Lit + Web Components |
|
||||
| Strong a11y 요구 | Radix or React Aria |
|
||||
| Multi-brand white-label | Token-driven CSS variables |
|
||||
|
||||
**기본값**: React 19 + Radix primitives + Tailwind v4 + shadcn/ui structure + DTCG tokens.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Component-Based Architecture (CBA)]] · [[Design Systems]]
|
||||
- 변형: [[Component-Composition|Compound Components]] · [[Headless UI]]
|
||||
- 응용: [[shadcn-ui]] · [[Radix UI]] · [[Storybook]]
|
||||
- Adjacent: [[CSS_Architecture_and_Styling|Tailwind CSS]] · [[Design Tokens]] · [[A11y]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: starting design system, refactoring 중복 UI, multi-product/brand consolidation.
|
||||
**언제 X**: 1-page landing site (overhead 큼), heavily 3D/canvas UI (HTML primitive 안 맞음).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Tightly styled primitives**: behavior + style 의 강결합 — replace 시 rewrite 필요.
|
||||
- **Theme prop drilling**: 매 component 가 theme prop 받음 — CSS variables 로.
|
||||
- **No tokens**: hex color 직접 사용 — token system 부재 시 multi-brand 불가.
|
||||
- **Locked-in vendor library**: MUI v3→v5 같은 대규모 breaking change — headless + own styles 가 안전.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (shadcn/ui docs 2026 / Radix UI / DTCG W3C draft / React 19 docs).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — 3-layer model + shadcn/Radix/DTCG 패턴 |
|
||||
@@ -0,0 +1,194 @@
|
||||
---
|
||||
id: wiki-2026-0508-component-based-architecture-cba
|
||||
title: Component Based Architecture (CBA)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [CBA, Component-Based Software Engineering, CBSE]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [architecture, components, modularity, reuse]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: react-vue-angular
|
||||
---
|
||||
|
||||
# Component Based Architecture (CBA)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 시스템 = 매 independent, replaceable, composable component 의 합."**. CBSE는 1960s OOP의 reuse 약속을 modular contract로 실현한 패러다임. 2026년 frontend (React/Vue/Angular), microservices, design systems, embedded (OSGi), game engines (Unity ECS) 모두 CBA의 변형.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 component 의 정의
|
||||
- **Encapsulation**: state + behavior + interface = 단일 unit.
|
||||
- **Well-defined contract**: input props/events, output events, side effects 명시.
|
||||
- **Replaceable**: 동일 interface 의 다른 impl 으로 swap 가능.
|
||||
- **Independently deployable** (microservices) 또는 **independently versionable** (libraries).
|
||||
|
||||
### 매 4 properties (Szyperski)
|
||||
1. **Independent deployment** unit.
|
||||
2. **Composition** by third parties.
|
||||
3. **No persistent state** (pure or state-injected).
|
||||
4. **Explicit context dependencies** (interface 로 명시).
|
||||
|
||||
### 매 응용
|
||||
1. Frontend UI (React component tree).
|
||||
2. Microservices (service = component).
|
||||
3. Plugin systems (VSCode extensions, Figma plugins).
|
||||
4. Game engines (Unity GameObject + Components).
|
||||
5. Web Components (Custom Elements, Shadow DOM).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### React functional component
|
||||
```typescript
|
||||
type ButtonProps = {
|
||||
variant: 'primary' | 'ghost';
|
||||
onClick: () => void;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export function Button({ variant, onClick, children }: ButtonProps) {
|
||||
return (
|
||||
<button
|
||||
className={`btn btn-${variant}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Web Component (framework-agnostic)
|
||||
```typescript
|
||||
class TodoItem extends HTMLElement {
|
||||
static observedAttributes = ['done', 'label'];
|
||||
|
||||
attributeChangedCallback(name: string, _old: string, value: string) {
|
||||
this.render();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this.render();
|
||||
}
|
||||
|
||||
private render() {
|
||||
if (!this.shadowRoot) return;
|
||||
const done = this.hasAttribute('done');
|
||||
this.shadowRoot.innerHTML = `
|
||||
<style>:host { display: block; }</style>
|
||||
<label>
|
||||
<input type="checkbox" ${done ? 'checked' : ''} />
|
||||
<span>${this.getAttribute('label') ?? ''}</span>
|
||||
</label>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('todo-item', TodoItem);
|
||||
```
|
||||
|
||||
### Composition over inheritance
|
||||
```typescript
|
||||
// Bad: deep inheritance
|
||||
class AdminButton extends IconButton extends Button { }
|
||||
|
||||
// Good: composition
|
||||
function AdminButton({ icon, ...rest }: AdminButtonProps) {
|
||||
return (
|
||||
<Button {...rest}>
|
||||
<Icon name={icon} />
|
||||
<span>{rest.children}</span>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Dependency injection (explicit context)
|
||||
```typescript
|
||||
type Logger = { info(msg: string): void };
|
||||
|
||||
export function createPaymentService({ logger, gateway }: {
|
||||
logger: Logger;
|
||||
gateway: PaymentGateway;
|
||||
}) {
|
||||
return {
|
||||
charge: async (amount: number) => {
|
||||
logger.info(`charging ${amount}`);
|
||||
return gateway.charge(amount);
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### ECS component (Unity / Bevy style)
|
||||
```rust
|
||||
// Bevy ECS
|
||||
#[derive(Component)]
|
||||
struct Position { x: f32, y: f32 }
|
||||
|
||||
#[derive(Component)]
|
||||
struct Velocity { dx: f32, dy: f32 }
|
||||
|
||||
fn movement_system(mut query: Query<(&mut Position, &Velocity)>) {
|
||||
for (mut pos, vel) in &mut query {
|
||||
pos.x += vel.dx;
|
||||
pos.y += vel.dy;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Microservice as component
|
||||
```typescript
|
||||
// Each service exposes a typed contract (gRPC / OpenAPI)
|
||||
interface OrderService {
|
||||
createOrder(req: CreateOrderRequest): Promise<Order>;
|
||||
getOrder(id: string): Promise<Order>;
|
||||
}
|
||||
|
||||
// Replaceable impl: REST, gRPC, in-memory mock
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| UI rendering | React/Vue functional components |
|
||||
| Cross-framework UI | Web Components (Lit) |
|
||||
| Backend domain split | Microservice components (gRPC contract) |
|
||||
| Game logic | ECS components (Bevy, Unity DOTS) |
|
||||
| Plugin systems | Component + manifest + sandbox (VSCode model) |
|
||||
|
||||
**기본값**: React 19 functional components + composition; backend은 module 단위 component → 필요 시 microservice 로 추출.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Modularity]] · [[Software Architecture]]
|
||||
- 변형: [[Microservices]] · [[Web Components]] · [[Entity Component System]]
|
||||
- 응용: [[Component Library Architecture]] · [[Component-Composition|Compound Components]] · [[Design Systems]]
|
||||
- Adjacent: [[Conceptual Integrity]] · [[Dependency Injection]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: UI/system 의 reusable units 으로 분해, contract-driven team work, plugin ecosystem.
|
||||
**언제 X**: 단일 prototype, throwaway script, 하나의 tight algorithm (component overhead 가 cost > benefit).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **God component**: 하나의 component 가 too many props/responsibilities — split.
|
||||
- **Prop drilling**: 5+ levels 깊이 prop 전달 — Context/store 로 lift.
|
||||
- **Hidden coupling**: component A 가 component B 의 internal state 의 직접 접근 — contract violation.
|
||||
- **Premature genericization**: 1번만 쓰는 것을 generic 으로 추상화 — YAGNI.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Szyperski "Component Software" 1998 / Brooks 1995 / React docs 2026).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — CBA 4-property canonical + React/ECS/microservice 패턴 |
|
||||
@@ -0,0 +1,261 @@
|
||||
---
|
||||
id: wiki-2026-0508-compound-components
|
||||
title: Compound Components
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Compound Component Pattern, React Compound Components]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.95
|
||||
verification_status: applied
|
||||
tags: [react, patterns, components, composition, context]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: react
|
||||
---
|
||||
|
||||
# Compound Components
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 parent 가 매 child 들과 implicit state share — 매 expressive composable API."**. Compound Component Pattern은 React (그리고 Vue/Solid) 에서 `<Tabs><Tab/><Tab.Panel/></Tabs>` 류의 declarative API 를 만드는 표준. Radix UI, Headless UI, Ariakit, shadcn/ui 의 dominant pattern. 2026년 React 19 + Server Components 시대에도 그대로 valid.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 problem solved
|
||||
- 단일 monolithic component 의 props explosion (`<Tabs items={...} renderTab={...} ...>`).
|
||||
- 매 caller 가 매 children 의 layout/order 를 control 의 X.
|
||||
- Slot composition 의 부재.
|
||||
|
||||
### 매 solution
|
||||
- Parent 가 Context 로 shared state 의 publish.
|
||||
- Children 이 Context 를 consume — explicit prop drilling 의 X.
|
||||
- Children 의 component 가 namespace pattern (`Tabs.Tab`) 또는 separate exports.
|
||||
|
||||
### 매 patterns
|
||||
- **React Context based** (most common 2026).
|
||||
- **`React.Children` cloning** (legacy, fragile with refs/server components).
|
||||
- **State reducer** (advanced — caller customizes reducer).
|
||||
- **`asChild` slot** (Radix — wraps user element).
|
||||
|
||||
### 매 응용
|
||||
1. Tabs, Accordion, Disclosure.
|
||||
2. Menu, Dropdown, Combobox.
|
||||
3. Dialog, Drawer, Popover.
|
||||
4. Form fields with label/error/description.
|
||||
5. DataTable column children.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Context-based Tabs
|
||||
```typescript
|
||||
import { createContext, useContext, useId, useState } from 'react';
|
||||
|
||||
type TabsContextValue = {
|
||||
activeId: string;
|
||||
setActiveId: (id: string) => void;
|
||||
baseId: string;
|
||||
};
|
||||
const TabsContext = createContext<TabsContextValue | null>(null);
|
||||
|
||||
function useTabs() {
|
||||
const ctx = useContext(TabsContext);
|
||||
if (!ctx) throw new Error('Tabs.* must be used inside <Tabs>');
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export function Tabs({
|
||||
defaultValue,
|
||||
children,
|
||||
}: { defaultValue: string; children: React.ReactNode }) {
|
||||
const [activeId, setActiveId] = useState(defaultValue);
|
||||
const baseId = useId();
|
||||
return (
|
||||
<TabsContext.Provider value={{ activeId, setActiveId, baseId }}>
|
||||
<div className="tabs">{children}</div>
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function List({ children }: { children: React.ReactNode }) {
|
||||
return <div role="tablist" className="flex gap-2">{children}</div>;
|
||||
}
|
||||
|
||||
function Trigger({ value, children }: { value: string; children: React.ReactNode }) {
|
||||
const { activeId, setActiveId, baseId } = useTabs();
|
||||
const selected = activeId === value;
|
||||
return (
|
||||
<button
|
||||
role="tab"
|
||||
id={`${baseId}-trigger-${value}`}
|
||||
aria-selected={selected}
|
||||
aria-controls={`${baseId}-panel-${value}`}
|
||||
tabIndex={selected ? 0 : -1}
|
||||
onClick={() => setActiveId(value)}
|
||||
className={selected ? 'tab tab-active' : 'tab'}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function Panel({ value, children }: { value: string; children: React.ReactNode }) {
|
||||
const { activeId, baseId } = useTabs();
|
||||
if (activeId !== value) return null;
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
id={`${baseId}-panel-${value}`}
|
||||
aria-labelledby={`${baseId}-trigger-${value}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Tabs.List = List;
|
||||
Tabs.Trigger = Trigger;
|
||||
Tabs.Panel = Panel;
|
||||
```
|
||||
|
||||
### Usage
|
||||
```tsx
|
||||
<Tabs defaultValue="overview">
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
|
||||
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel value="overview"><OverviewView /></Tabs.Panel>
|
||||
<Tabs.Panel value="settings"><SettingsView /></Tabs.Panel>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
### Controlled variant
|
||||
```typescript
|
||||
type Props = {
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
onValueChange?: (value: string) => void;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export function Tabs({ value, defaultValue, onValueChange, children }: Props) {
|
||||
const [internal, setInternal] = useState(defaultValue ?? '');
|
||||
const isControlled = value !== undefined;
|
||||
const activeId = isControlled ? value : internal;
|
||||
const setActiveId = (next: string) => {
|
||||
if (!isControlled) setInternal(next);
|
||||
onValueChange?.(next);
|
||||
};
|
||||
// ...rest
|
||||
}
|
||||
```
|
||||
|
||||
### asChild slot pattern (Radix)
|
||||
```typescript
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
|
||||
function Trigger({ asChild, children, value }: {
|
||||
asChild?: boolean;
|
||||
value: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { setActiveId } = useTabs();
|
||||
const Comp = asChild ? Slot : 'button';
|
||||
return (
|
||||
<Comp onClick={() => setActiveId(value)} role="tab">
|
||||
{children}
|
||||
</Comp>
|
||||
);
|
||||
}
|
||||
|
||||
// Usage: customize button as anchor
|
||||
<Tabs.Trigger value="x" asChild>
|
||||
<a href="#x">Section X</a>
|
||||
</Tabs.Trigger>
|
||||
```
|
||||
|
||||
### Form field compound
|
||||
```typescript
|
||||
const FieldContext = createContext<{ id: string; errorId: string } | null>(null);
|
||||
|
||||
export function Field({ children }: { children: React.ReactNode }) {
|
||||
const id = useId();
|
||||
return (
|
||||
<FieldContext.Provider value={{ id, errorId: `${id}-error` }}>
|
||||
<div className="field">{children}</div>
|
||||
</FieldContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function Label({ children }: { children: React.ReactNode }) {
|
||||
const { id } = useContext(FieldContext)!;
|
||||
return <label htmlFor={id}>{children}</label>;
|
||||
}
|
||||
|
||||
function Input(props: React.InputHTMLAttributes<HTMLInputElement>) {
|
||||
const { id, errorId } = useContext(FieldContext)!;
|
||||
return <input id={id} aria-describedby={errorId} {...props} />;
|
||||
}
|
||||
|
||||
function Error({ children }: { children: React.ReactNode }) {
|
||||
const { errorId } = useContext(FieldContext)!;
|
||||
return <span id={errorId} role="alert">{children}</span>;
|
||||
}
|
||||
|
||||
Field.Label = Label;
|
||||
Field.Input = Input;
|
||||
Field.Error = Error;
|
||||
```
|
||||
|
||||
### Server Components note
|
||||
```typescript
|
||||
// Compound components 의 Context — Client Component only.
|
||||
// 매 Server Component layout 에서:
|
||||
// <Tabs> ← Client Component (provides context)
|
||||
// <Tabs.Panel value="x"> ← Client (consumes context)
|
||||
// <ServerOnlyData /> ← can be Server Component child
|
||||
// </Tabs.Panel>
|
||||
// </Tabs>
|
||||
'use client';
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Standard widgets (tabs, menu) | Context-based compound |
|
||||
| Wrap user element | asChild + Slot |
|
||||
| Caller controls state | Controlled variant |
|
||||
| Behavior-only library | Headless compound (Radix style) |
|
||||
| Server Components mixed | 'use client' on the parent + child triggers |
|
||||
|
||||
**기본값**: Context-based, namespace exports (`Tabs.Trigger`), controlled+uncontrolled support, `useId` for a11y, `useContext` 로 misuse 방지.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Component-Based Architecture (CBA)]] · [[React Patterns]]
|
||||
- 변형: [[Headless UI]] · [[Render_Props|Render Props]]
|
||||
- 응용: [[Component Library Architecture]] · [[Radix UI]] · [[shadcn-ui]]
|
||||
- Adjacent: [[A11y]] · [[Modern_Web_Rendering_and_Optimization|Server Components]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: building reusable widgets (tabs/menu/dialog), design system primitives, replacing prop-explosion components, headless library.
|
||||
**언제 X**: 1-shot static UI, simple presentational components, server-only components (Context 의 X).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **`React.Children` cloning**: brittle with refs, breaks with fragments — Context 가 표준.
|
||||
- **Missing context throw**: child used standalone gives undefined error — explicit throw with helpful message.
|
||||
- **Over-namespacing**: 5+ children types — split into multiple components.
|
||||
- **Forgetting a11y**: tabs without role/aria-* — Radix-style attribute mapping 필수.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Kent C. Dodds compound components / Radix UI source / React 19 docs / WAI-ARIA 1.2).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Context-based Tabs + asChild + form field 패턴 |
|
||||
@@ -0,0 +1,235 @@
|
||||
---
|
||||
id: wiki-2026-0508-compute-shaders
|
||||
title: Compute Shaders
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [GPU Compute, GPGPU Shaders, WebGPU Compute]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [gpu, shaders, webgpu, parallel, wgsl, cuda]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: wgsl-glsl-cuda
|
||||
framework: webgpu-vulkan
|
||||
---
|
||||
|
||||
# Compute Shaders
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 GPU program 매 graphics pipeline 의 X — 매 arbitrary parallel computation."**. Compute shader는 vertex/fragment shader 와 다르게 rendering 의 X — 매 raw SIMT 의 power. 2026년 WebGPU (browser GPU compute), CUDA, Vulkan compute, Metal compute, ML inference, particle sims, image processing 의 dominant. ML 의 attention/matmul 도 compute shader 본질.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 model
|
||||
- **Workgroup**: 매 group of threads 가 same shader 실행 (e.g. 64 or 256 threads).
|
||||
- **Invocation**: single thread.
|
||||
- **Shared memory** (workgroup): fast, intra-group.
|
||||
- **Storage buffer**: GPU global memory (read/write).
|
||||
- **Uniform buffer**: small, read-only constants.
|
||||
- **Dispatch**: CPU 가 launches N workgroups.
|
||||
|
||||
### 매 hardware mapping
|
||||
- NVIDIA: warp (32 threads), SM (streaming multiprocessor).
|
||||
- AMD: wave (64 threads, RDNA: 32), CU.
|
||||
- Apple: simdgroup (32), GPU core.
|
||||
- Intel: subgroup, EU.
|
||||
|
||||
### 매 languages 2026
|
||||
- **WGSL** (WebGPU): cross-platform, modern.
|
||||
- **HLSL** (DirectX, Vulkan via DXC).
|
||||
- **GLSL** (OpenGL, Vulkan).
|
||||
- **MSL** (Metal Shading Language).
|
||||
- **CUDA C++**: NVIDIA only, but mature.
|
||||
- **Triton** (OpenAI): Python-like ML kernel DSL.
|
||||
|
||||
### 매 응용
|
||||
1. ML inference (matmul, attention, conv).
|
||||
2. Image filters (blur, edge, color grading).
|
||||
3. Particle systems / fluid sim.
|
||||
4. Physics (cloth, soft body, mass-spring).
|
||||
5. Cryptography (proof-of-work, hash collisions).
|
||||
6. Video encode/decode prep.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### WGSL compute shader — vector add
|
||||
```wgsl
|
||||
@group(0) @binding(0) var<storage, read> a : array<f32>;
|
||||
@group(0) @binding(1) var<storage, read> b : array<f32>;
|
||||
@group(0) @binding(2) var<storage, read_write> c : array<f32>;
|
||||
|
||||
@compute @workgroup_size(64)
|
||||
fn main(@builtin(global_invocation_id) gid : vec3u) {
|
||||
let i = gid.x;
|
||||
if (i >= arrayLength(&a)) { return; }
|
||||
c[i] = a[i] + b[i];
|
||||
}
|
||||
```
|
||||
|
||||
### WebGPU dispatch (TypeScript)
|
||||
```typescript
|
||||
const adapter = await navigator.gpu.requestAdapter();
|
||||
const device = await adapter!.requestDevice();
|
||||
|
||||
const module = device.createShaderModule({ code: WGSL_SOURCE });
|
||||
const pipeline = device.createComputePipeline({
|
||||
layout: 'auto',
|
||||
compute: { module, entryPoint: 'main' },
|
||||
});
|
||||
|
||||
const N = 1_000_000;
|
||||
const buf = (data: Float32Array) => {
|
||||
const b = device.createBuffer({
|
||||
size: data.byteLength,
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
|
||||
});
|
||||
device.queue.writeBuffer(b, 0, data);
|
||||
return b;
|
||||
};
|
||||
|
||||
const a = buf(new Float32Array(N).fill(1));
|
||||
const b = buf(new Float32Array(N).fill(2));
|
||||
const c = device.createBuffer({
|
||||
size: N * 4,
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
||||
});
|
||||
|
||||
const bindGroup = device.createBindGroup({
|
||||
layout: pipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{ binding: 0, resource: { buffer: a } },
|
||||
{ binding: 1, resource: { buffer: b } },
|
||||
{ binding: 2, resource: { buffer: c } },
|
||||
],
|
||||
});
|
||||
|
||||
const enc = device.createCommandEncoder();
|
||||
const pass = enc.beginComputePass();
|
||||
pass.setPipeline(pipeline);
|
||||
pass.setBindGroup(0, bindGroup);
|
||||
pass.dispatchWorkgroups(Math.ceil(N / 64));
|
||||
pass.end();
|
||||
device.queue.submit([enc.finish()]);
|
||||
```
|
||||
|
||||
### Shared memory reduction (workgroup-local)
|
||||
```wgsl
|
||||
var<workgroup> shared : array<f32, 256>;
|
||||
|
||||
@compute @workgroup_size(256)
|
||||
fn reduce(
|
||||
@builtin(local_invocation_id) lid : vec3u,
|
||||
@builtin(global_invocation_id) gid : vec3u,
|
||||
) {
|
||||
shared[lid.x] = input[gid.x];
|
||||
workgroupBarrier();
|
||||
|
||||
var stride : u32 = 128u;
|
||||
loop {
|
||||
if (stride == 0u) { break; }
|
||||
if (lid.x < stride) {
|
||||
shared[lid.x] = shared[lid.x] + shared[lid.x + stride];
|
||||
}
|
||||
workgroupBarrier();
|
||||
stride = stride / 2u;
|
||||
}
|
||||
|
||||
if (lid.x == 0u) { output[gid.x / 256u] = shared[0]; }
|
||||
}
|
||||
```
|
||||
|
||||
### CUDA matmul kernel
|
||||
```cuda
|
||||
__global__ void matmul(const float* A, const float* B, float* C, int N) {
|
||||
int row = blockIdx.y * blockDim.y + threadIdx.y;
|
||||
int col = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (row >= N || col >= N) return;
|
||||
|
||||
float sum = 0.0f;
|
||||
for (int k = 0; k < N; ++k) {
|
||||
sum += A[row * N + k] * B[k * N + col];
|
||||
}
|
||||
C[row * N + col] = sum;
|
||||
}
|
||||
// Launch: matmul<<<dim3((N+15)/16, (N+15)/16), dim3(16,16)>>>(A, B, C, N);
|
||||
```
|
||||
|
||||
### Triton kernel (Python ML)
|
||||
```python
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
@triton.jit
|
||||
def add_kernel(x_ptr, y_ptr, out_ptr, n, BLOCK: tl.constexpr):
|
||||
pid = tl.program_id(0)
|
||||
offsets = pid * BLOCK + tl.arange(0, BLOCK)
|
||||
mask = offsets < n
|
||||
x = tl.load(x_ptr + offsets, mask=mask)
|
||||
y = tl.load(y_ptr + offsets, mask=mask)
|
||||
tl.store(out_ptr + offsets, x + y, mask=mask)
|
||||
|
||||
# Launch
|
||||
add_kernel[(triton.cdiv(N, 1024),)](x, y, out, N, BLOCK=1024)
|
||||
```
|
||||
|
||||
### Image blur compute (storage texture)
|
||||
```wgsl
|
||||
@group(0) @binding(0) var src : texture_2d<f32>;
|
||||
@group(0) @binding(1) var dst : texture_storage_2d<rgba8unorm, write>;
|
||||
|
||||
@compute @workgroup_size(8, 8)
|
||||
fn blur(@builtin(global_invocation_id) gid : vec3u) {
|
||||
var sum = vec4f(0);
|
||||
for (var dy = -1; dy <= 1; dy++) {
|
||||
for (var dx = -1; dx <= 1; dx++) {
|
||||
let p = vec2i(gid.xy) + vec2i(dx, dy);
|
||||
sum = sum + textureLoad(src, p, 0);
|
||||
}
|
||||
}
|
||||
textureStore(dst, vec2i(gid.xy), sum / 9.0);
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Browser, cross-platform | WebGPU + WGSL |
|
||||
| Native cross-platform | Vulkan compute + GLSL/HLSL |
|
||||
| NVIDIA-only, max perf | CUDA |
|
||||
| Apple ecosystem | Metal compute (MSL) |
|
||||
| ML kernel research | Triton (Python) |
|
||||
| Production ML inference | Pre-built (cuDNN, MLX, vLLM kernels) |
|
||||
|
||||
**기본값**: 매 web/cross-platform → WebGPU + WGSL. 매 ML research → Triton. 매 production NVIDIA ML → CUDA + cuDNN/cuBLAS.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[GPU Programming]] · [[Parallel Computing]]
|
||||
- 변형: [[Vertex Shader]] · [[Fragment Shader]] · [[CUDA]]
|
||||
- 응용: [[WebGPU]]
|
||||
- Adjacent: [[Triton]] · [[Vulkan]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: heavy parallel data (image, ML, sim), browser GPU compute, custom ML kernels.
|
||||
**언제 X**: small data (<10k items, CPU faster after transfer cost), branch-heavy serial logic, very small kernels (launch overhead).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Divergent branching in warp**: 매 thread 가 different path → serialization → 매 SIMT 의 X.
|
||||
- **Uncoalesced memory access**: random pattern → bandwidth waste — adjacent threads should read adjacent memory.
|
||||
- **Tiny dispatch**: 100 threads → launch overhead > work — batch.
|
||||
- **Forgetting workgroupBarrier**: race condition on shared memory.
|
||||
- **CPU↔GPU ping-pong**: every step copies back — keep data on GPU.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (WebGPU spec 2026 W3C / CUDA Programming Guide 12.x / Triton docs / Apple MSL).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — WGSL/CUDA/Triton + workgroup model |
|
||||
@@ -0,0 +1,165 @@
|
||||
---
|
||||
id: wiki-2026-0508-conceptual-integrity
|
||||
title: Conceptual Integrity
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Architectural Integrity, Brooks Conceptual Integrity]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [architecture, design, brooks, mythical-man-month]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: agnostic
|
||||
framework: agnostic
|
||||
---
|
||||
|
||||
# Conceptual Integrity
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 system design 의 the most important consideration — 매 unified set of design ideas, 매 single mind 의 product."**. Brooks 의 *Mythical Man-Month* (1975) 의 central thesis. 매 great system 은 매 small group of architects 의 vision. 2026년 design systems, API design, programming language design, framework architecture 에 핵심 원칙으로 산다.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Brooks 의 정의
|
||||
> "Conceptual integrity is the most important consideration in system design. It is better to have a system omit certain anomalous features and improvements, but to reflect one set of design ideas, than to have one that contains many good but independent and uncoordinated ideas."
|
||||
|
||||
### 매 implications
|
||||
- **Architect/designer** 의 separation from implementation team.
|
||||
- **Say no** to features that violate the model.
|
||||
- **Programmer's manual** = single source of truth.
|
||||
- **Small architect group** > committee — 매 "design by committee" 의 X.
|
||||
|
||||
### 매 modern manifestations
|
||||
- **API design**: consistent verbs, naming, error model (Stripe, GitHub APIs).
|
||||
- **Programming languages**: Go's "less is more", Rust's ownership model.
|
||||
- **Frameworks**: Rails "convention over configuration", Django "batteries included with one way".
|
||||
- **Design systems**: single token vocabulary, predictable behaviors.
|
||||
|
||||
### 매 Tension
|
||||
- Conceptual integrity vs feature completeness.
|
||||
- Single architect vs distributed teams.
|
||||
- Stability vs evolution.
|
||||
|
||||
### 매 응용
|
||||
1. API design (REST resource model 의 일관성).
|
||||
2. Language design (Go, Elm).
|
||||
3. UI framework (SwiftUI, Jetpack Compose).
|
||||
4. CLI design (git, kubectl).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Consistent API model (Stripe-style)
|
||||
```typescript
|
||||
// Every resource follows: list, retrieve, create, update, delete
|
||||
GET /v1/customers // list
|
||||
GET /v1/customers/:id // retrieve
|
||||
POST /v1/customers // create
|
||||
POST /v1/customers/:id // update (Stripe uses POST not PUT)
|
||||
DELETE /v1/customers/:id // delete
|
||||
|
||||
// Same shape for charges, subscriptions, invoices, ...
|
||||
```
|
||||
|
||||
### Consistent error model
|
||||
```typescript
|
||||
// Every error response follows the same shape
|
||||
type ApiError = {
|
||||
error: {
|
||||
type: 'invalid_request' | 'authentication' | 'rate_limit' | 'server';
|
||||
code: string;
|
||||
message: string;
|
||||
param?: string;
|
||||
request_id: string;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### Convention over configuration (Rails-like)
|
||||
```ruby
|
||||
# Filename, class name, table name, route — all derived by convention
|
||||
# app/models/article.rb → Article class → articles table
|
||||
# app/controllers/articles_controller.rb → /articles routes
|
||||
class Article < ApplicationRecord
|
||||
# No XML config needed
|
||||
end
|
||||
```
|
||||
|
||||
### Single mental model — Go's error handling
|
||||
```go
|
||||
// One pattern for errors, used everywhere
|
||||
result, err := doThing()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("doThing failed: %w", err)
|
||||
}
|
||||
// No exceptions, no Result<T,E>, just (T, error)
|
||||
```
|
||||
|
||||
### Architecture Decision Records (ADRs) preserve integrity
|
||||
```markdown
|
||||
# ADR-007: All write APIs return the full resource
|
||||
|
||||
## Status
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
Mixing return shapes (id-only vs full object) violates conceptual integrity.
|
||||
|
||||
## Decision
|
||||
All POST/PUT/PATCH endpoints return the full updated resource.
|
||||
|
||||
## Consequences
|
||||
- Slightly larger response bodies.
|
||||
- Clients never need a follow-up GET after write.
|
||||
```
|
||||
|
||||
### "Say no" guard — RFC review
|
||||
```markdown
|
||||
## Proposal
|
||||
Add a special-case `/users/me/notifications/digest` that returns
|
||||
HTML instead of JSON.
|
||||
|
||||
## Reviewer
|
||||
Rejected. All API endpoints return JSON. Render HTML in client
|
||||
or build a separate `/email-templates/*` namespace.
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Public API design | Strict consistency, single architect, "no" gate |
|
||||
| Internal tooling | Conventions documented, ADRs |
|
||||
| Framework | One canonical way per task |
|
||||
| Multi-team product | Style guide + design system + design council |
|
||||
| Research/exploration | Looser — ideas before integrity |
|
||||
|
||||
**기본값**: Small architect council (2-4 people), written design principles, ADRs, explicit "rejected" log.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Software Architecture]]
|
||||
- 변형: [[Design Principles]]
|
||||
- 응용: [[API Design]] · [[Design Systems]] · [[ADR]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: API design review, framework architecture, design system kickoff, language design, contested feature decisions.
|
||||
**언제 X**: 1-week prototype, research spike, throwaway script.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Design by committee**: 모두가 한 마디씩 — 매 disjoint feature soup.
|
||||
- **Feature creep without rejection**: 매 yes — 매 integrity 의 erosion.
|
||||
- **Multiple ways to do same thing**: Python's "one obvious way" violation — choice paralysis.
|
||||
- **No written principles**: implicit consensus — 매 architect 떠나면 깨짐.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Brooks "Mythical Man-Month" 1975/1995 anniversary ed. / Stripe API design / Go FAQ).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Brooks definition + modern API/framework 적용 |
|
||||
@@ -0,0 +1,174 @@
|
||||
---
|
||||
id: wiki-2026-0508-concurrent-rendering
|
||||
title: Concurrent Rendering
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [React Concurrent, Concurrent Mode, React 18 Concurrent]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [react, concurrent, rendering, scheduler, transitions]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: react
|
||||
---
|
||||
|
||||
# Concurrent Rendering
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 render 매 interruptible, prioritizable, abandonable"**. React 18 (2022) 에 stable. Fiber architecture (2017) 의 결실. 2026 현재 React 19+ 의 default; `useTransition`, `useDeferredValue`, Suspense, streaming SSR, React Compiler 매 모든 것 위에 빌드.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 핵심 idea
|
||||
- **Interruptible** — render 중 high-priority work (input, animation) 매 들어오면 yield.
|
||||
- **Concurrent (NOT parallel)** — single-threaded JS 위에서 cooperative scheduling.
|
||||
- **Time-slicing** — 5ms chunk 매 yield to browser (`scheduler` package).
|
||||
- **Multiple in-progress trees** — current + work-in-progress; double buffering.
|
||||
- **Lane-based priority** — sync, default, transition, idle lane.
|
||||
|
||||
### 매 hook / API
|
||||
- **`useTransition`** — non-urgent update (filter, navigation).
|
||||
- **`useDeferredValue`** — debounce-like, 매 lower priority value.
|
||||
- **`startTransition`** — imperative version.
|
||||
- **`Suspense`** — async boundary, fallback UI.
|
||||
- **`use` hook** (React 19) — read promise during render.
|
||||
|
||||
### 매 응용
|
||||
1. Search-as-you-type — input 매 sync, list filter 매 transition.
|
||||
2. Tab switching — 매 instant feel, 매 expensive subtree 의 background render.
|
||||
3. SSR streaming — 매 progressive shell + island hydration.
|
||||
4. Route navigation — `<Link>` prefetch + transition.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### useTransition — heavy filter
|
||||
```tsx
|
||||
function Search() {
|
||||
const [query, setQuery] = useState('');
|
||||
const [list, setList] = useState<Item[]>(allItems);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
value={query}
|
||||
onChange={(e) => {
|
||||
setQuery(e.target.value); // urgent
|
||||
startTransition(() => { // non-urgent
|
||||
setList(allItems.filter(i => i.name.includes(e.target.value)));
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{isPending && <Spinner />}
|
||||
<List items={list} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### useDeferredValue
|
||||
```tsx
|
||||
function Page({ query }: { query: string }) {
|
||||
const deferred = useDeferredValue(query);
|
||||
return <ExpensiveResults query={deferred} />;
|
||||
}
|
||||
```
|
||||
|
||||
### Suspense + use (React 19+)
|
||||
```tsx
|
||||
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
|
||||
const user = use(userPromise); // suspends if pending
|
||||
return <h1>{user.name}</h1>;
|
||||
}
|
||||
|
||||
<Suspense fallback={<Skeleton />}>
|
||||
<UserProfile userPromise={fetchUser(id)} />
|
||||
</Suspense>
|
||||
```
|
||||
|
||||
### Streaming SSR (Next.js 15 / React 19)
|
||||
```tsx
|
||||
// app/page.tsx
|
||||
export default async function Page() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Suspense fallback={<FeedSkeleton />}>
|
||||
<Feed /> {/* streamed once data ready */}
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### React Compiler (2026, automatic memoization)
|
||||
```tsx
|
||||
// no useMemo / useCallback needed — compiler inserts
|
||||
function Cart({ items }: { items: Item[] }) {
|
||||
const total = items.reduce((s, i) => s + i.price, 0); // auto-memoized
|
||||
return <div>{total}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### Selective hydration
|
||||
```tsx
|
||||
// chat panel hydrates first if user clicks it,
|
||||
// even if header is still loading
|
||||
<Suspense fallback={<Sk />}>
|
||||
<Header />
|
||||
</Suspense>
|
||||
<Suspense fallback={<Sk />}>
|
||||
<Chat />
|
||||
</Suspense>
|
||||
```
|
||||
|
||||
### Cancel stale render (transition supersession)
|
||||
```tsx
|
||||
// older transition automatically discarded when new one starts
|
||||
startTransition(() => setQuery('a')); // discarded
|
||||
startTransition(() => setQuery('ab')); // wins
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Input + heavy derived UI | `useTransition` |
|
||||
| External lib slow value | `useDeferredValue` |
|
||||
| Async data | `Suspense` + `use` |
|
||||
| SSR with slow data | streaming + Suspense islands |
|
||||
| Manual memoization (legacy) | replace with React Compiler |
|
||||
|
||||
**기본값**: React 19+ with Compiler; transitions for any non-urgent update.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[React]] · [[Rendering Pipeline]]
|
||||
- 변형: [[Fiber_Architecture|Fiber Architecture]] · [[Time_Slicing|Time Slicing]]
|
||||
- 응용: [[Streaming SSR]]
|
||||
- Adjacent: [[Virtual DOM과 Reconciliation|Virtual DOM]] · [[Reconciliation]] · [[React Compiler]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: identify component 매 transition 으로 wrap 할 후보, code review 매 missed Suspense boundary.
|
||||
**언제 X**: scheduler internals 의 deep debug (need devtools profiler).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **`startTransition` for urgent input**: input lag.
|
||||
- **No Suspense fallback**: 매 entire tree freeze 까지 falling back.
|
||||
- **Manual memoization with React Compiler**: redundant + sometimes 더 느림.
|
||||
- **Async setState in transition without race control**: stale data.
|
||||
- **Deferred value of huge object reference**: 매 GC pressure.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (React 18 release notes 2022, React 19 docs 2026, Acdlite/Sebastian Markbåge talks).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — full content with React 19 + Compiler patterns |
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
category: Architecture
|
||||
tags: [auto-wikified, technical-documentation, architecture]
|
||||
title: Constructor Injection
|
||||
description: "Constructor Injection(생성자 주입)은 클래스의 생성자를 통해 객체가 필요로 하는 의존성을 명시적으로 선언하고 프레임워크로부터 해당 객체를 제공받는 의존성 주입(DI) 방식입니다 [1-3]."
|
||||
last_updated: 2026-05-04
|
||||
---
|
||||
|
||||
# Constructor Injection
|
||||
|
||||
## 📌 Brief Summary
|
||||
Constructor Injection(생성자 주입)은 클래스의 생성자를 통해 객체가 필요로 하는 의존성을 명시적으로 선언하고 프레임워크로부터 해당 객체를 제공받는 의존성 주입(DI) 방식입니다 [1-3]. 모던 Spring Boot와 NestJS 같은 최신 엔터프라이즈 프레임워크에서 가장 선호되고 권장되는 접근법입니다 [1, 2]. 이 방식을 사용하면 수동으로 객체를 생성하거나 연결할 필요 없이, 프레임워크가 의존성을 자동으로 감지하고 인스턴스화하여 주입합니다 [2, 3].
|
||||
|
||||
## 📖 Core Content
|
||||
* **프레임워크의 자동 의존성 해결**: Spring Boot와 NestJS와 같은 프레임워크에서는 클래스 생성자에 필요한 의존성을 선언하기만 하면 됩니다 [1, 2]. 프레임워크의 제어 역전(IoC) 혹은 DI 컨테이너가 생성자를 감지하여 알맞은 빈(Bean)이나 프로바이더를 자동으로 주입하므로, 팩토리 메서드나 수동 배선(manual wiring) 코드를 생략할 수 있습니다 [2].
|
||||
* **결합도 완화와 구조적 명확성**: 의존성을 내부에서 직접 인스턴스화하거나 하드 코딩하여 임포트하는 대신, 생성자를 통해 외부에서 주입받는 방식을 취합니다 [1]. 이러한 접근은 컴포넌트 간의 강한 결합도를 낮추고 시스템을 더 유연하게 만듭니다 [1, 3].
|
||||
* **테스트 가능성(Testability) 극대화**: 생성자 주입의 가장 큰 기술적 이점은 단위 테스트의 용이성입니다 [1, 3]. 테스트 과정에서 비즈니스 로직을 전혀 변경하지 않고도, 생성자의 인자를 통해 실제 서비스 대신 모의 객체(Mock)를 손쉽게 교체하여 주입할 수 있어 독립적이고 격리된 테스트 환경을 구축하기 매우 수월해집니다 [1].
|
||||
|
||||
## ⚖️ Trade-offs & Caveats
|
||||
* **기반 클래스(Base Class) 상속 시의 관리 부담**: 상속을 활용해 기반 클래스에 공통 로직을 구성할 때 의존성이 개입되면, 이를 상속받는 모든 하위 구현체의 생성자에 해당 의존성을 전달하도록 코드를 작성해야 하는 제약과 부담이 발생합니다 [4].
|
||||
* **의존성 변경에 따른 연쇄 수정 비용**: 위의 상속 구조 등에서 새로운 의존성이 추가되는 설계 변경이 발생할 경우, 기반 클래스를 상속받는 모든 하위 클래스의 생성자 서명(Signature)을 일일이 찾아 수정해야 하는 유지보수 상의 비용과 번거로움이 발생할 수 있습니다 [4].
|
||||
|
||||
---
|
||||
*Last updated: 2026-05-03*
|
||||
@@ -0,0 +1,168 @@
|
||||
---
|
||||
id: wiki-2026-0508-continuous-integration-ci
|
||||
title: Continuous Integration (CI)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [CI, Continuous Integration, 지속적 통합]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.92
|
||||
verification_status: applied
|
||||
tags: [ci, devops, automation, github-actions]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: yaml
|
||||
framework: github-actions
|
||||
---
|
||||
|
||||
# Continuous Integration (CI)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 push마다 build + test, fast feedback"**. Grady Booch (1991) 가 제안, Martin Fowler 가 대중화. 2026 현재 GitHub Actions / GitLab CI 가 표준이며, AI-assisted PR review (Claude Opus 4.7) 와 결합되어 매 commit 검증 cycle 이 분 단위로 압축됨.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 원칙
|
||||
- **Mainline integration 빈번**: 매 developer 매일 main 에 merge.
|
||||
- **Automated build**: 매 commit trigger build pipeline.
|
||||
- **Automated test**: unit + integration + lint 매 자동 실행.
|
||||
- **Fast feedback**: <10 min 안에 결과. 길어지면 dev 매 ignore.
|
||||
- **Single source of truth**: 매 single repo, single mainline.
|
||||
|
||||
### 매 stages
|
||||
- **Lint** (10s) — eslint, ruff, gofmt.
|
||||
- **Unit test** (1-3 min) — vitest, pytest, go test.
|
||||
- **Integration test** (3-8 min) — testcontainers, ephemeral DB.
|
||||
- **Build artifact** (1-2 min) — docker image, npm tarball.
|
||||
- **Static analysis** (parallel) — SonarQube, Snyk, CodeQL.
|
||||
|
||||
### 매 응용
|
||||
1. SaaS product 매 trunk-based development.
|
||||
2. Open-source project 매 PR validation.
|
||||
3. Monorepo 매 affected-only build (Turborepo, Nx).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Modern GitHub Actions (2026)
|
||||
```yaml
|
||||
name: CI
|
||||
on:
|
||||
push: { branches: [main] }
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v5
|
||||
with: { node-version: '22', cache: 'pnpm' }
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm lint
|
||||
- run: pnpm test --coverage
|
||||
- uses: codecov/codecov-action@v5
|
||||
```
|
||||
|
||||
### Matrix build
|
||||
```yaml
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-24.04, macos-14, windows-2022]
|
||||
node: [20, 22]
|
||||
runs-on: ${{ matrix.os }}
|
||||
```
|
||||
|
||||
### Affected-only (monorepo)
|
||||
```yaml
|
||||
- run: pnpm exec turbo run test --filter=...[origin/main]
|
||||
```
|
||||
|
||||
### Reusable workflow
|
||||
```yaml
|
||||
# .github/workflows/reusable-test.yml
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
node-version: { type: string, default: '22' }
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-24.04
|
||||
steps: [ ... ]
|
||||
```
|
||||
|
||||
### Container-based test (testcontainers)
|
||||
```python
|
||||
# pytest with ephemeral postgres
|
||||
from testcontainers.postgres import PostgresContainer
|
||||
|
||||
def test_repo():
|
||||
with PostgresContainer("postgres:17") as pg:
|
||||
url = pg.get_connection_url()
|
||||
# run integration test against real DB
|
||||
```
|
||||
|
||||
### Cache layers (docker buildx)
|
||||
```yaml
|
||||
- uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: ghcr.io/org/app:${{ github.sha }}
|
||||
```
|
||||
|
||||
### AI PR review (2026)
|
||||
```yaml
|
||||
- uses: anthropic-experimental/claude-code-action@v1
|
||||
with:
|
||||
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
model: claude-opus-4-7
|
||||
review-mode: pr-comment
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Open-source GitHub repo | GitHub Actions |
|
||||
| Self-hosted, private | GitLab CI / Drone |
|
||||
| Monorepo | Turborepo + affected-only |
|
||||
| Polyglot, complex | Buildkite / Bazel |
|
||||
| Mobile (iOS/Android) | Bitrise / Xcode Cloud |
|
||||
|
||||
**기본값**: GitHub Actions + concurrency cancel + matrix + cache.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[DevOps]]
|
||||
- 변형: [[Continuous Deployment]] · [[Trunk-Based Development]]
|
||||
- 응용: [[GitHub Actions]]
|
||||
- Adjacent: [[Test Automation]] · [[Static Analysis]] · [[153_pre-commit과_품질_게이트|Pre-commit Hooks]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: PR diff review, flaky test detection, commit message generation, changelog 생성.
|
||||
**언제 X**: secret-handling pipeline (보안), production deploy gate (deterministic 해야 함).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Slow pipeline (>20 min)**: dev 매 ignore, "broken main" 정상화.
|
||||
- **Flaky test 방치**: trust collapse → 매 retry, retry, retry.
|
||||
- **No mainline protection**: 직접 push to main, PR 없음.
|
||||
- **Build on developer machine only**: "works on my machine" 재현.
|
||||
- **Secrets in logs**: env 출력, token leak.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Fowler 2006 *Continuous Integration* article, GitHub Actions docs 2026).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — full content with modern GitHub Actions patterns |
|
||||
@@ -0,0 +1,227 @@
|
||||
---
|
||||
id: wiki-2026-0508-control-points
|
||||
title: Control Points
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Capture Points, KOTH, Domination]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.85
|
||||
verification_status: applied
|
||||
tags: [game-design, multiplayer, objectives, fps]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: csharp-gdscript
|
||||
framework: unity-godot-unreal
|
||||
---
|
||||
|
||||
# Control Points
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 spatial objective — 매 team 이 매 zone 의 occupy 통해 score/win."**. Control Points는 multiplayer game 의 가장 ubiquitous objective primitive. Domination, KOTH, Capture-and-Hold, Push, Hardpoint 모두 변형. Team Fortress 2, Battlefield, Overwatch, Apex, CS Bombsite (variant), Splatoon (area-based) 의 핵심. 2026년 server-authoritative netcode + lag compensation 패턴 매 stable.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 variants
|
||||
| Variant | Mechanic |
|
||||
|---|---|
|
||||
| **Domination** | Multiple points, hold majority for tickets/score |
|
||||
| **KOTH** | Single point, hold-time wins |
|
||||
| **Capture & Hold** | Capture sequentially, last team standing |
|
||||
| **Hardpoint** | Single rotating point, score over time |
|
||||
| **Push/Payload** | Mobile control point along track |
|
||||
| **Linear (5CP TF2)** | Sequential capture, central pivot |
|
||||
|
||||
### 매 mechanics
|
||||
- **Capture progress**: 0~100%, increases with attackers in zone.
|
||||
- **Multi-capture rate**: more attackers → faster (capped, e.g. 2x at 2+).
|
||||
- **Contest**: defenders inside → progress paused.
|
||||
- **Lock/unlock**: previous point capture unlocks next.
|
||||
- **Decay**: progress drops when zone empty (configurable).
|
||||
- **Overtime**: contested point prevents game end.
|
||||
|
||||
### 매 design principles
|
||||
- **Sightline balance**: defenders 의 advantage 와 attacker chokes 의 균형.
|
||||
- **Capture time**: too short → trivial, too long → stalemate. 5-15s typical.
|
||||
- **Cap-zone size**: encourages clustering vs spread.
|
||||
- **Spawn distance**: defender respawn 가 너무 가까우면 attack 불가.
|
||||
|
||||
### 매 응용
|
||||
1. FPS multiplayer modes (Overwatch, BF, CoD).
|
||||
2. MOBA jungle camps / objectives (Roshan, Drake areas).
|
||||
3. RTS resource nodes (StarCraft expansions).
|
||||
4. MMO PvP zones (WoW battlegrounds).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Unity C# — control point trigger
|
||||
```csharp
|
||||
using UnityEngine;
|
||||
using Unity.Netcode;
|
||||
|
||||
public class ControlPoint : NetworkBehaviour {
|
||||
public NetworkVariable<float> Progress = new(0f);
|
||||
public NetworkVariable<int> OwnerTeam = new(-1);
|
||||
|
||||
[SerializeField] private float captureRate = 10f; // pct/sec per attacker
|
||||
[SerializeField] private float maxRate = 20f;
|
||||
|
||||
private readonly Dictionary<int, int> teamCount = new();
|
||||
|
||||
private void OnTriggerEnter(Collider other) {
|
||||
if (!IsServer) return;
|
||||
if (other.TryGetComponent<Player>(out var p)) {
|
||||
teamCount.TryGetValue(p.Team, out var n);
|
||||
teamCount[p.Team] = n + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerExit(Collider other) {
|
||||
if (!IsServer) return;
|
||||
if (other.TryGetComponent<Player>(out var p)
|
||||
&& teamCount.TryGetValue(p.Team, out var n)) {
|
||||
teamCount[p.Team] = Mathf.Max(0, n - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void FixedUpdate() {
|
||||
if (!IsServer) return;
|
||||
|
||||
var teams = new List<KeyValuePair<int,int>>(teamCount);
|
||||
teams.Sort((a, b) => b.Value.CompareTo(a.Value));
|
||||
|
||||
if (teams.Count == 0 || teams[0].Value == 0) return;
|
||||
|
||||
// Contested: top two teams equal & nonzero
|
||||
if (teams.Count > 1 && teams[0].Value == teams[1].Value) return;
|
||||
|
||||
var attacker = teams[0];
|
||||
var rate = Mathf.Min(maxRate, captureRate * Mathf.Sqrt(attacker.Value));
|
||||
|
||||
if (OwnerTeam.Value == attacker.Key) {
|
||||
Progress.Value = Mathf.Min(100f, Progress.Value + rate * Time.fixedDeltaTime);
|
||||
} else {
|
||||
Progress.Value = Mathf.Max(0f, Progress.Value - rate * Time.fixedDeltaTime);
|
||||
if (Progress.Value <= 0f) OwnerTeam.Value = attacker.Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Godot 4 / GDScript
|
||||
```gdscript
|
||||
extends Area3D
|
||||
class_name ControlPoint
|
||||
|
||||
@export var capture_rate: float = 10.0
|
||||
var progress: float = 0.0
|
||||
var owner_team: int = -1
|
||||
var team_in_zone: Dictionary = {}
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not multiplayer.is_server(): return
|
||||
|
||||
var top_team := -1
|
||||
var top_count := 0
|
||||
var contested := false
|
||||
|
||||
for team in team_in_zone:
|
||||
var n: int = team_in_zone[team]
|
||||
if n > top_count:
|
||||
top_count = n
|
||||
top_team = team
|
||||
contested = false
|
||||
elif n == top_count and n > 0:
|
||||
contested = true
|
||||
|
||||
if contested or top_count == 0: return
|
||||
|
||||
var rate = capture_rate * sqrt(top_count)
|
||||
if owner_team == top_team:
|
||||
progress = min(100.0, progress + rate * delta)
|
||||
else:
|
||||
progress = max(0.0, progress - rate * delta)
|
||||
if progress <= 0.0:
|
||||
owner_team = top_team
|
||||
_notify_capture(top_team)
|
||||
```
|
||||
|
||||
### Server-authoritative state with lag compensation
|
||||
```csharp
|
||||
// Server stores state snapshots for last 1s
|
||||
private readonly CircularBuffer<Snapshot> history = new(60);
|
||||
|
||||
public bool WasInsideAtTime(Vector3 playerPos, float clientTime) {
|
||||
var snap = history.SampleAt(clientTime);
|
||||
return snap.Bounds.Contains(playerPos);
|
||||
}
|
||||
```
|
||||
|
||||
### Configurable progression (data-driven)
|
||||
```json
|
||||
{
|
||||
"id": "cp_central",
|
||||
"captureTimeSeconds": 8,
|
||||
"multiCapMultiplier": [1.0, 1.5, 1.75, 2.0],
|
||||
"decayRate": 0.5,
|
||||
"unlockedBy": ["cp_a", "cp_b"],
|
||||
"scoresPerSecond": 10
|
||||
}
|
||||
```
|
||||
|
||||
### UI broadcast (client-side prediction visual)
|
||||
```typescript
|
||||
// Client receives Progress NetworkVariable changes,
|
||||
// interpolates between ticks for smooth bar fill
|
||||
useEffect(() => {
|
||||
const start = performance.now();
|
||||
const startVal = displayedProgress;
|
||||
const targetVal = serverProgress;
|
||||
|
||||
const tick = () => {
|
||||
const t = Math.min(1, (performance.now() - start) / 100);
|
||||
setDisplayedProgress(startVal + (targetVal - startVal) * t);
|
||||
if (t < 1) requestAnimationFrame(tick);
|
||||
};
|
||||
tick();
|
||||
}, [serverProgress]);
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Casual fast-match | KOTH, single-point, 3-5 min rounds |
|
||||
| Competitive | Linear/5CP, longer rounds, overtime |
|
||||
| Asymmetric | Push/Payload (attack vs defend) |
|
||||
| Objective rotation | Hardpoint (rotating zone keeps action moving) |
|
||||
| Large maps | Domination (multiple distributed) |
|
||||
|
||||
**기본값**: server-authoritative + 5-10s capture + multi-cap multiplier + decay + contested-pause + overtime.
|
||||
|
||||
## 🔗 Graph
|
||||
- 변형: [[Domination]]
|
||||
- Adjacent: [[Lag Compensation]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: multiplayer mode design, level layout review, balance tuning, netcode design for objectives.
|
||||
**언제 X**: single-player, asynchronous (turn-based), pure deathmatch (no objective).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Client-authoritative capture**: trivially exploitable — server-side only.
|
||||
- **Spawn too close to objective**: defender immortal — distance + lockout window.
|
||||
- **No contested-pause**: solo defender can't stall — feels unfair.
|
||||
- **Capture too short**: zerg wins, no skill — 8-12s standard.
|
||||
- **No decay**: half-cap then leave is permanent — partial progress decay.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Valve TF2 design / Blizzard Overwatch dev blogs / GDC talks 2018-2024).
|
||||
- 신뢰도 A-.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — variants + Unity/Godot impl + netcode |
|
||||
@@ -0,0 +1,178 @@
|
||||
---
|
||||
id: wiki-2026-0508-control-systems-engineering
|
||||
title: Control Systems Engineering
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [control-systems, feedback-control, PID-control]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [control-systems, pid, mpc, feedback]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: python
|
||||
framework: do-mpc/control
|
||||
---
|
||||
|
||||
# Control Systems Engineering
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 control 의 매 measure → compare → actuate 의 closed loop"**. 매 PID (1922 Minorsky) 가 매 95% industrial loops, 매 MPC (Model Predictive Control) 가 매 multivariable + constrained problems 의 dominant. 2026 의 매 RL-augmented control + neural ODEs 가 매 emerging — 매 Boston Dynamics, Tesla Autopilot, NVIDIA GR00T 가 hybrid.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 building blocks
|
||||
- **Plant** — 매 controlled system (motor, reactor, drone).
|
||||
- **Sensor** — 매 measurement (encoder, IMU, thermocouple).
|
||||
- **Controller** — 매 algorithm (PID, MPC, LQR).
|
||||
- **Actuator** — 매 output (PWM, valve, voltage).
|
||||
- **Reference / setpoint** — 매 desired state.
|
||||
|
||||
### 매 stability
|
||||
- **Open-loop**: 매 simple, 매 no feedback — 매 disturbance 에 fragile.
|
||||
- **Closed-loop**: 매 feedback — 매 disturbance reject + setpoint track.
|
||||
- **Stability criteria**: Routh-Hurwitz, Nyquist, Bode (gain margin > 6 dB, phase margin > 45°).
|
||||
|
||||
### 매 controller spectrum
|
||||
1. **Bang-bang** — 매 thermostat.
|
||||
2. **PID** — 매 95% loops.
|
||||
3. **State-space (LQR / pole placement)** — MIMO linear.
|
||||
4. **MPC** — 매 constrained, predictive.
|
||||
5. **Adaptive / gain scheduling** — 매 nonlinear plants.
|
||||
6. **RL / learned policy** — 매 high-dim, 매 simulation 의 train.
|
||||
7. **Robust H∞** — 매 worst-case guarantees.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Discrete PID with anti-windup
|
||||
```python
|
||||
class PID:
|
||||
def __init__(self, kp, ki, kd, dt, u_min, u_max):
|
||||
self.kp, self.ki, self.kd, self.dt = kp, ki, kd, dt
|
||||
self.u_min, self.u_max = u_min, u_max
|
||||
self.i, self.prev_e = 0.0, 0.0
|
||||
|
||||
def step(self, setpoint, measurement):
|
||||
e = setpoint - measurement
|
||||
self.i += e * self.dt
|
||||
d = (e - self.prev_e) / self.dt
|
||||
u = self.kp*e + self.ki*self.i + self.kd*d
|
||||
u_clamped = max(self.u_min, min(self.u_max, u))
|
||||
# 매 anti-windup: 매 saturate 시 integrator 의 back-calc
|
||||
if u != u_clamped:
|
||||
self.i -= (u - u_clamped) / self.ki if self.ki else 0
|
||||
self.prev_e = e
|
||||
return u_clamped
|
||||
```
|
||||
|
||||
### Ziegler-Nichols 의 tune
|
||||
```python
|
||||
def ziegler_nichols(Ku, Tu, kind='PID'):
|
||||
if kind == 'PID':
|
||||
return dict(kp=0.6*Ku, ki=1.2*Ku/Tu, kd=0.075*Ku*Tu)
|
||||
if kind == 'PI':
|
||||
return dict(kp=0.45*Ku, ki=0.54*Ku/Tu, kd=0)
|
||||
```
|
||||
|
||||
### State-space LQR (CartPole)
|
||||
```python
|
||||
import numpy as np
|
||||
from scipy.linalg import solve_continuous_are
|
||||
A = np.array([[0,1,0,0],[0,0,-mp*g/M,0],[0,0,0,1],[0,0,(M+mp)*g/(M*l),0]])
|
||||
B = np.array([[0],[1/M],[0],[-1/(M*l)]])
|
||||
Q = np.diag([1, 1, 10, 10]); R = np.array([[0.1]])
|
||||
P = solve_continuous_are(A, B, Q, R)
|
||||
K = np.linalg.inv(R) @ B.T @ P
|
||||
u = -K @ x # state feedback
|
||||
```
|
||||
|
||||
### MPC with do-mpc
|
||||
```python
|
||||
import do_mpc
|
||||
model = do_mpc.model.Model('continuous')
|
||||
x = model.set_variable('_x', 'x', shape=(2,1))
|
||||
u = model.set_variable('_u', 'u')
|
||||
model.set_rhs('x', np.array([[x[1]],[u - 0.1*x[1]]]))
|
||||
model.setup()
|
||||
mpc = do_mpc.controller.MPC(model)
|
||||
mpc.set_param(n_horizon=20, t_step=0.1)
|
||||
mpc.set_objective(mterm=x[0]**2, lterm=x[0]**2 + 0.01*u**2)
|
||||
mpc.bounds['lower','_u','u'] = -5; mpc.bounds['upper','_u','u'] = 5
|
||||
mpc.setup()
|
||||
```
|
||||
|
||||
### Kalman filter (state estimation)
|
||||
```python
|
||||
def kalman_step(x, P, u, z, A, B, H, Q, R):
|
||||
x_pred = A @ x + B @ u
|
||||
P_pred = A @ P @ A.T + Q
|
||||
K = P_pred @ H.T @ np.linalg.inv(H @ P_pred @ H.T + R)
|
||||
x = x_pred + K @ (z - H @ x_pred)
|
||||
P = (np.eye(len(x)) - K @ H) @ P_pred
|
||||
return x, P
|
||||
```
|
||||
|
||||
### RL policy (PPO via stable-baselines3)
|
||||
```python
|
||||
from stable_baselines3 import PPO
|
||||
import gymnasium as gym
|
||||
env = gym.make("Pendulum-v1")
|
||||
model = PPO("MlpPolicy", env, verbose=1)
|
||||
model.learn(total_timesteps=200_000)
|
||||
```
|
||||
|
||||
### Real-time loop (Linux PREEMPT_RT)
|
||||
```python
|
||||
import time, os
|
||||
os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(80))
|
||||
T = 0.001 # 1 kHz
|
||||
while True:
|
||||
t0 = time.perf_counter()
|
||||
u = pid.step(setpoint, sensor.read())
|
||||
actuator.write(u)
|
||||
while time.perf_counter() - t0 < T: pass
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| 매 SISO + linear | PID |
|
||||
| 매 MIMO + linear | LQR / state-space |
|
||||
| Constrained + slow plant | MPC |
|
||||
| Nonlinear + simulator 가능 | RL (PPO, SAC) |
|
||||
| Safety-critical + uncertain | Robust H∞ / sliding mode |
|
||||
| 매 very fast (>10 kHz) | Hardware PID (FPGA) |
|
||||
|
||||
**기본값**: PID with proper tuning + anti-windup; escalate to MPC if multivariable or constrained.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Cyber-Physical-Systems]] · [[Robotics]]
|
||||
- 변형: [[PID]] · [[MPC]]
|
||||
- 응용: [[Digital Twin]]
|
||||
- Adjacent: [[Kalman-Filter-and-State-Tracking|Kalman-Filter]] · [[Reinforcement-Learning]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 controller derivation explanation, 매 transfer function manipulation, 매 tuning suggestion based on step response, 매 simulation script generation.
|
||||
**언제 X**: 매 actual real-time loop (deterministic 코드 / FPGA). 매 safety certification (formal verification 필요).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **No anti-windup**: 매 actuator saturate 시 integral runaway → overshoot.
|
||||
- **Derivative on error (vs measurement)**: 매 setpoint step 시 derivative kick — derivative-on-PV 사용.
|
||||
- **Tune via trial-and-error only**: 매 system identification 의 사용 (Ziegler-Nichols, FOPDT fit).
|
||||
- **MPC without warm-start**: 매 solve time 의 explode — previous solution 의 reuse.
|
||||
- **No filter on derivative**: 매 measurement noise 가 D term 의 amplify.
|
||||
- **RL on real hardware first**: 매 sim-to-real 의 가야 — safe exploration.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Åström & Murray "Feedback Systems", Skogestad MIMO control, do-mpc docs, IEEE control textbooks).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — control engineering: PID, LQR, MPC, RL, Kalman |
|
||||
@@ -0,0 +1,163 @@
|
||||
---
|
||||
id: wiki-2026-0508-cosmos-플랫폼-netflix
|
||||
title: Cosmos 플랫폼 (Netflix)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Netflix Cosmos, Cosmos Platform, Cosmos]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.85
|
||||
verification_status: applied
|
||||
tags: [netflix, media-processing, workflow, microservices]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: java
|
||||
framework: spring-boot
|
||||
---
|
||||
|
||||
# Cosmos 플랫폼 (Netflix)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 Netflix 의 next-gen media computing platform"**. Reloaded (predecessor) 의 후계, 2018-2020 announce. Microservices + workflow orchestration + serverless functions 매 통합. Encoding, transcoding, content analysis, IMF processing 매 모든 media pipeline 의 backbone.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 architecture (3 layers)
|
||||
- **Optimus (API layer)** — external-facing service, business logic, request validation.
|
||||
- **Plato (Workflow layer)** — Conductor-based workflow orchestration, retry, branching.
|
||||
- **Stratum (Functions layer)** — serverless compute, Titus (container) 위 실행.
|
||||
|
||||
### 매 design 원칙
|
||||
- **Asynchronous everything** — sync call 매 minimum, message-based.
|
||||
- **Schema-first** — Protobuf gRPC 매 contract.
|
||||
- **Workflow as DSL** — JSON workflow definition (Conductor).
|
||||
- **Function granularity** — small, idempotent, stateless.
|
||||
- **Event-driven** — Kafka, SQS 매 inter-service.
|
||||
|
||||
### 매 응용
|
||||
1. Video encoding pipeline — source → multiple bitrates × codecs.
|
||||
2. Image processing — title art, thumbnails, language variants.
|
||||
3. Subtitle / caption processing.
|
||||
4. Content quality control (QC) automation.
|
||||
5. IMF (Interoperable Master Format) ingestion.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Workflow definition (Plato / Netflix Conductor)
|
||||
```json
|
||||
{
|
||||
"name": "encode_video_v3",
|
||||
"version": 3,
|
||||
"tasks": [
|
||||
{
|
||||
"name": "probe",
|
||||
"type": "FUNCTION",
|
||||
"function": "stratum:ffprobe",
|
||||
"inputParameters": { "url": "${workflow.input.sourceUrl}" }
|
||||
},
|
||||
{
|
||||
"name": "fan_out_encode",
|
||||
"type": "FORK_JOIN_DYNAMIC",
|
||||
"dynamicForkTasksParam": "encodeTasks"
|
||||
},
|
||||
{
|
||||
"name": "join_results",
|
||||
"type": "JOIN"
|
||||
},
|
||||
{
|
||||
"name": "publish",
|
||||
"type": "FUNCTION",
|
||||
"function": "stratum:publishToCDN"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Stratum function (Java, Spring Boot)
|
||||
```java
|
||||
@CosmosFunction(name = "ffprobe")
|
||||
public class FfprobeFunction implements Function<ProbeRequest, ProbeResponse> {
|
||||
@Override
|
||||
public ProbeResponse apply(ProbeRequest req) {
|
||||
var info = Ffmpeg.probe(req.url());
|
||||
return new ProbeResponse(info.duration(), info.codec(), info.bitrate());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Optimus service (gRPC)
|
||||
```protobuf
|
||||
service EncodeService {
|
||||
rpc StartEncode(EncodeRequest) returns (EncodeResponse);
|
||||
rpc GetStatus(StatusRequest) returns (stream StatusUpdate);
|
||||
}
|
||||
```
|
||||
|
||||
### Titus container deployment
|
||||
```yaml
|
||||
# titus job definition
|
||||
applicationName: stratum-ffprobe
|
||||
container:
|
||||
image: registry/stratum/ffprobe:v42
|
||||
resources:
|
||||
cpu: 4
|
||||
memoryMB: 8192
|
||||
gpu: 0
|
||||
env:
|
||||
COSMOS_FUNCTION_NAME: ffprobe
|
||||
```
|
||||
|
||||
### Event-driven trigger (Kafka)
|
||||
```java
|
||||
@KafkaListener(topics = "media.uploaded")
|
||||
public void onUpload(MediaUploadedEvent event) {
|
||||
workflowClient.start("encode_video_v3", Map.of(
|
||||
"sourceUrl", event.getUrl(),
|
||||
"movieId", event.getMovieId()
|
||||
));
|
||||
}
|
||||
```
|
||||
|
||||
### Idempotency key (function level)
|
||||
```java
|
||||
@CosmosFunction(idempotencyKey = "${input.movieId}-${input.bitrate}")
|
||||
public class EncodeFunction implements Function<EncodeReq, EncodeResp> { ... }
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Long-running multi-step media job | Plato workflow |
|
||||
| Simple stateless transform | Stratum function |
|
||||
| External-facing API | Optimus service |
|
||||
| Realtime / sub-100ms | NOT Cosmos (sync RPC service 따로) |
|
||||
|
||||
**기본값**: workflow for any multi-step media pipeline; functions for individual steps.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Microservices]]
|
||||
- 변형: [[Temporal]]
|
||||
- Adjacent: [[Spinnaker]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: workflow definition 생성, function template 생성, 운영 incident 매 root cause 분석.
|
||||
**언제 X**: production workflow execution (deterministic 해야 함).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Stateful function**: scale 못 함, retry 깨짐.
|
||||
- **Long sync chain in Optimus**: workflow 가야 할 것 매 sync call 로 묶음.
|
||||
- **No idempotency**: retry 매 duplicate side-effect.
|
||||
- **Workflow too granular**: orchestration overhead > work.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Netflix Tech Blog 2020 *Rebuilding Netflix Video Processing Pipeline with Microservices*).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — full content covering Optimus/Plato/Stratum |
|
||||
@@ -0,0 +1,171 @@
|
||||
---
|
||||
id: wiki-2026-0508-cross-cutting-concerns
|
||||
title: Cross-Cutting Concerns
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Cross Cutting Concerns, AOP Concerns, 횡단 관심사]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [aop, architecture, separation-of-concerns]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: nestjs
|
||||
---
|
||||
|
||||
# Cross-Cutting Concerns
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 module 매 흩어지는 functionality (logging, auth, tx) 의 횡단"**. AOP (Aspect-Oriented Programming, Kiczales 1997) 의 motivation. 2026 현재 NestJS interceptors, Spring AOP, OpenTelemetry auto-instrumentation, Sidecar (Istio) 매 modern realization.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 정의
|
||||
- 단일 module 의 boundary 를 넘어 매 application 매 widely-scattered concern.
|
||||
- Tangled with business logic → SoC (Separation of Concerns) 위반.
|
||||
- Solution: 매 aspect, decorator, middleware, sidecar, interceptor 로 추출.
|
||||
|
||||
### 매 typical concerns
|
||||
- **Logging / tracing** — 매 method entry/exit log.
|
||||
- **Authentication / authorization** — 매 endpoint 매 token check.
|
||||
- **Transaction management** — DB tx begin/commit/rollback.
|
||||
- **Caching** — function result memoization.
|
||||
- **Validation** — input schema check.
|
||||
- **Rate limiting** — request throttling.
|
||||
- **Metrics / observability** — counter, histogram.
|
||||
- **Error handling** — global exception mapper.
|
||||
- **i18n** — locale-aware response.
|
||||
- **Audit** — who/when/what.
|
||||
|
||||
### 매 응용
|
||||
1. Web framework middleware (Express, NestJS).
|
||||
2. Spring `@Transactional` / `@Cacheable`.
|
||||
3. Service mesh (Istio) — mTLS, retry, circuit-break 매 sidecar.
|
||||
4. OpenTelemetry auto-instrumentation 매 tracing 매 zero code change.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### NestJS interceptor — logging + timing
|
||||
```typescript
|
||||
@Injectable()
|
||||
export class LoggingInterceptor implements NestInterceptor {
|
||||
intercept(ctx: ExecutionContext, next: CallHandler) {
|
||||
const start = Date.now();
|
||||
const req = ctx.switchToHttp().getRequest();
|
||||
return next.handle().pipe(
|
||||
tap(() => log.info({ url: req.url, ms: Date.now() - start }))
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### NestJS guard — authz
|
||||
```typescript
|
||||
@Injectable()
|
||||
export class RolesGuard implements CanActivate {
|
||||
canActivate(ctx: ExecutionContext): boolean {
|
||||
const required = Reflect.getMetadata('roles', ctx.getHandler());
|
||||
const user = ctx.switchToHttp().getRequest().user;
|
||||
return required.some(r => user.roles.includes(r));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Spring AOP — `@Transactional`
|
||||
```java
|
||||
@Service
|
||||
public class OrderService {
|
||||
@Transactional
|
||||
public Order place(Order o) {
|
||||
repository.save(o);
|
||||
inventory.decrement(o.itemId);
|
||||
// throw → rollback
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Python decorator — caching
|
||||
```python
|
||||
from functools import lru_cache
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def expensive_lookup(key: str) -> Result:
|
||||
return db.query(key)
|
||||
```
|
||||
|
||||
### TypeScript decorator (TC39 stage 3) — metrics
|
||||
```typescript
|
||||
function timed<This, Args extends any[], Return>(
|
||||
target: (this: This, ...args: Args) => Return,
|
||||
ctx: ClassMethodDecoratorContext
|
||||
) {
|
||||
return function (this: This, ...args: Args): Return {
|
||||
const start = performance.now();
|
||||
try { return target.apply(this, args); }
|
||||
finally { metrics.histogram(ctx.name, performance.now() - start); }
|
||||
};
|
||||
}
|
||||
|
||||
class API {
|
||||
@timed
|
||||
fetchUser(id: string) { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
### OpenTelemetry — zero-code tracing
|
||||
```python
|
||||
# auto-instrumentation, no code changes
|
||||
# pip install opentelemetry-distro opentelemetry-instrumentation-fastapi
|
||||
# opentelemetry-instrument --traces_exporter otlp uvicorn app:app
|
||||
```
|
||||
|
||||
### Service mesh (Istio) — mTLS via sidecar
|
||||
```yaml
|
||||
apiVersion: security.istio.io/v1
|
||||
kind: PeerAuthentication
|
||||
metadata: { name: default, namespace: prod }
|
||||
spec:
|
||||
mtls: { mode: STRICT }
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Single service, JS/TS | NestJS interceptor / Express middleware |
|
||||
| JVM, mature DI | Spring AOP / aspectj |
|
||||
| Polyglot microservices | Service mesh (Istio / Linkerd) |
|
||||
| Tracing only | OpenTelemetry auto-instrumentation |
|
||||
| Function-level caching | language-native decorator |
|
||||
|
||||
**기본값**: framework-native (interceptor/middleware/decorator) for in-process; service mesh for network-level.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Separation of Concerns]] · [[Software Architecture]]
|
||||
- 변형: [[Aspect-Oriented Programming (AOP)]]
|
||||
- 응용: [[NestJS]] · [[Istio]] · [[OpenTelemetry]]
|
||||
- Adjacent: [[Dependency Injection (의존성 주입)]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: identify cross-cutting concern in legacy code, refactor to aspect/middleware.
|
||||
**언제 X**: design-time SoC decision (need human architectural taste).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Concern duplication**: 매 controller 매 try/catch + log + auth 매 copy-paste.
|
||||
- **Aspect overuse**: business logic 까지 aspect 로 → magic, debugging hell.
|
||||
- **Order dependency 무시**: interceptor chain 의 순서 의존 → fragile.
|
||||
- **Side-effect in interceptor**: DB write 매 logging interceptor → tx boundary 깨짐.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Kiczales et al. 1997 *AOP*, Spring/NestJS docs 2026).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — full content covering interceptors, decorators, service mesh |
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
category: Computer_Science_and_Theory
|
||||
tags: [auto-wikified, technical-documentation, computer_science_and_theory]
|
||||
title: Cross-Cutting Concerns (AOP)
|
||||
description: "**횡단 관심사(Cross-Cutting Concerns)**는 로깅, 보안, 캐싱, 예외 처리 등 소프트웨어의 여러 계층과 컴포넌트 전반에 걸쳐 공통으로 요구되는 부차적이고 시스템적인 기능을 의미합니다 [1-4]."
|
||||
last_updated: 2026-05-04
|
||||
---
|
||||
|
||||
# Cross-Cutting Concerns (AOP)
|
||||
|
||||
## 📌 Brief Summary
|
||||
**횡단 관심사(Cross-Cutting Concerns)**는 로깅, 보안, 캐싱, 예외 처리 등 소프트웨어의 여러 계층과 컴포넌트 전반에 걸쳐 공통으로 요구되는 부차적이고 시스템적인 기능을 의미합니다 [1-4]. 이러한 관심사를 핵심 비즈니스 로직과 분리하여 모듈화하는 방법론이 **관점 지향 프로그래밍(AOP, Aspect-Oriented Programming)**입니다 [1, 5]. 이를 적절하게 중앙 집중화하지 않으면 코드의 중복과 강한 결합을 초래하여 대규모 애플리케이션의 유지보수성을 심각하게 저하시킬 수 있습니다 [1, 6].
|
||||
|
||||
## 📖 Core Content
|
||||
* **개념 및 필요성**
|
||||
애플리케이션의 비즈니스 로직이 '핵심 관심사(Core Concerns)'라면, 트랜잭션 관리, 로깅, 유효성 검사 등은 시스템 전체에 적용되어야 하는 '횡단 관심사'로 분류됩니다 [2, 7-10]. 이러한 횡단 관심사를 각 메서드 내부에 하드코딩하면 '단일 책임 원칙(SRP)'과 '반복 방지(DRY)' 원칙을 위반하게 되어 코드가 심하게 오염됩니다 [11]. AOP는 이러한 로직을 한 곳으로 추출하여 각 모듈에 선언적으로 주입합니다 [5, 7].
|
||||
|
||||
* **프레임워크별 실전 구현 패턴**
|
||||
현대 백엔드 프레임워크들은 횡단 관심사 분리를 위해 각자의 설계 철학에 맞는 메커니즘을 제공합니다 [12].
|
||||
* **Spring Boot (Java)**: AOP(AspectJ), 필터(Filter), 인터셉터(Interceptor)를 혼합하여 사용합니다 [12, 13]. 필터는 서블릿 레벨에서 모든 HTTP 요청(CORS, 기본 인증 등)을 가로채고, 인터셉터는 MVC 컨트롤러 실행 전후를 처리합니다 [14, 15]. AOP는 `@Aspect`, `@Before` 어노테이션 등을 통해 서비스나 리포지토리 등 어떤 Spring Bean의 메서드든 가로채어 트랜잭션이나 로깅을 비즈니스 로직 변경 없이 적용할 수 있습니다 [16, 17].
|
||||
* **NestJS (Node.js)**: 가드(Guard), 인터셉터(Interceptor), 파이프(Pipe), 미들웨어(Middleware)로 구성된 파이프라인 형태의 흐름 제어를 제공합니다 [12]. 특히 RxJS 스트림을 활용하여 비동기 작업을 유연하게 처리할 수 있는 일관된 구조를 지원합니다 [12].
|
||||
* **기타 아키텍처 패턴**: .NET 중심의 클린 아키텍처에서는 MediatR의 `IPipelineBehavior`를 통해 파이프라인 내부에서 로깅, 캐싱, 유효성 검사를 독립된 단위로 캡슐화합니다 [18-20].
|
||||
|
||||
* **주요 적용 사례**
|
||||
* **트랜잭션 관리 (Transaction Management)**: 수많은 `try-catch`와 커밋/롤백 블록을 하드코딩하는 대신, AOP 어드바이스 마커를 활용해 트랜잭션 동작을 캡슐화함으로써 코드를 간결하게 유지합니다 [7].
|
||||
* **로깅 및 예외 처리**: 애플리케이션 전반에 흩어진 추적(Tracing) 및 로깅 코드를 걷어내고, 인터셉터나 파이프라인을 통해 전역 예외 처리 및 로깅을 중앙 집중화합니다 [19, 21].
|
||||
|
||||
## ⚖️ Trade-offs & Caveats
|
||||
* **마법 같은 동작(Magic)과 디버깅의 한계**: AOP나 미들웨어 파이프라인은 코드를 런타임에 래핑하거나 컴파일 시점에 동적으로 로직을 주입하므로, 비즈니스 코드와 인프라 코드를 깔끔하게 분리합니다 [22, 23]. 그러나 명시적으로 호출되는 코드가 없기 때문에 **실행 흐름이 불투명**해지며, 특정 로직이 왜 실행되는지 파악하기 어려워 디버깅 난이도가 급격히 상승할 수 있습니다 [22, 23]. 이는 Django의 Signals를 남용할 때 부수 효과(Side Effects)를 추적하기 어려워지는 안티 패턴과 유사한 기술 부채를 유발할 수 있습니다 [24, 25].
|
||||
* **런타임 성능 오버헤드**: C#의 Castle.DynamicProxy와 같은 런타임 인터셉터 방식은 실행 시점에 프록시 래퍼를 동적으로 생성하여 메서드 호출을 가로챕니다 [26]. 이는 로깅과 같이 매우 빈번하게 발생하는 작업에 무분별하게 적용할 경우, 애플리케이션에 성능 비용(Cost)을 초래할 수 있습니다 [26].
|
||||
* **설계 복잡성과 종속성 문제**: 횡단 관심사 로직을 공유하기 위해 '기반 클래스(Base Class)' 상속 패턴을 남용하면, 다중 상속 제약에 부딪히거나 모든 하위 클래스 생성자에 인프라 종속성을 넘겨주어야 하는 유지보수 문제가 발생합니다 [27, 28]. 인프라 컴포넌트(예: Logger 객체)가 시스템의 필수 종속성으로 강제되지 않도록 DI 생명주기(예: Transient 할당 지양)를 신중히 설정해야 합니다 [5].
|
||||
|
||||
---
|
||||
*Last updated: 2026-05-03*
|
||||
@@ -0,0 +1,176 @@
|
||||
---
|
||||
id: wiki-2026-0508-cyclomatic-complexity
|
||||
title: Cyclomatic Complexity
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [McCabe Complexity, Cyclomatic Number, 순환 복잡도]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.92
|
||||
verification_status: applied
|
||||
tags: [code-quality, metrics, static-analysis, mccabe]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: python
|
||||
framework: ruff
|
||||
---
|
||||
|
||||
# Cyclomatic Complexity
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 function 의 linearly independent path 수"**. Thomas McCabe (1976) 가 정의. 매 control-flow graph 매 `M = E − N + 2P` (edges − nodes + 2×components). 2026 현재 ruff, eslint, SonarQube 매 default 로 측정; high CC ↔ test difficulty + bug rate correlation 매 empirical.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 계산
|
||||
- 각 decision point (if, for, while, case, &&, ||, ternary, catch) 마다 +1.
|
||||
- Base 1 (single path) + decisions.
|
||||
- Function 1 → straight-line.
|
||||
- Function 10+ → moderate.
|
||||
- Function 20+ → complex, refactor 권장.
|
||||
- Function 50+ → 매 unmaintainable.
|
||||
|
||||
### 매 의미
|
||||
- **Test path 수** lower bound.
|
||||
- **Reading difficulty** proxy.
|
||||
- **Bug density correlation** — 매 empirical study.
|
||||
- **NOT** measure of correctness, performance, design quality.
|
||||
|
||||
### 매 응용
|
||||
1. CI gate — `max-complexity: 10` lint rule.
|
||||
2. Code review — high-CC function 매 split 요청.
|
||||
3. Refactoring target prioritization.
|
||||
4. Legacy modernization metric.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### CC 계산 example (Python)
|
||||
```python
|
||||
def classify(score): # base 1
|
||||
if score >= 90: # +1
|
||||
return 'A'
|
||||
elif score >= 80: # +1
|
||||
return 'B'
|
||||
elif score >= 70: # +1
|
||||
return 'C'
|
||||
else:
|
||||
return 'F'
|
||||
# CC = 4
|
||||
```
|
||||
|
||||
### Lint config (ruff, 2026)
|
||||
```toml
|
||||
# pyproject.toml
|
||||
[tool.ruff.lint]
|
||||
select = ["C90"] # mccabe
|
||||
|
||||
[tool.ruff.lint.mccabe]
|
||||
max-complexity = 10
|
||||
```
|
||||
|
||||
### ESLint
|
||||
```json
|
||||
{
|
||||
"rules": {
|
||||
"complexity": ["error", { "max": 10 }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Refactor: replace conditional with polymorphism
|
||||
```typescript
|
||||
// before — CC 5
|
||||
function area(shape: Shape): number {
|
||||
if (shape.kind === 'circle') return Math.PI * shape.r ** 2;
|
||||
if (shape.kind === 'square') return shape.s ** 2;
|
||||
if (shape.kind === 'rect') return shape.w * shape.h;
|
||||
if (shape.kind === 'triangle') return 0.5 * shape.b * shape.h;
|
||||
throw new Error('unknown');
|
||||
}
|
||||
|
||||
// after — CC 1 per class
|
||||
abstract class Shape { abstract area(): number; }
|
||||
class Circle extends Shape { area() { return Math.PI * this.r ** 2; } }
|
||||
class Square extends Shape { area() { return this.s ** 2; } }
|
||||
```
|
||||
|
||||
### Refactor: guard clauses (early return)
|
||||
```python
|
||||
# before — CC 4
|
||||
def process(user):
|
||||
if user is not None:
|
||||
if user.active:
|
||||
if user.has_permission:
|
||||
do_work(user)
|
||||
|
||||
# after — CC 4 still, but readability ↑
|
||||
def process(user):
|
||||
if user is None: return
|
||||
if not user.active: return
|
||||
if not user.has_permission: return
|
||||
do_work(user)
|
||||
```
|
||||
|
||||
### Refactor: table dispatch
|
||||
```python
|
||||
# before — CC 6
|
||||
def handle(event_type, payload):
|
||||
if event_type == 'created': return on_created(payload)
|
||||
elif event_type == 'updated': return on_updated(payload)
|
||||
elif event_type == 'deleted': return on_deleted(payload)
|
||||
# ...
|
||||
|
||||
# after — CC 2
|
||||
HANDLERS = {'created': on_created, 'updated': on_updated, 'deleted': on_deleted}
|
||||
def handle(event_type, payload):
|
||||
handler = HANDLERS.get(event_type)
|
||||
if not handler: raise ValueError(event_type)
|
||||
return handler(payload)
|
||||
```
|
||||
|
||||
### radon (Python CLI)
|
||||
```bash
|
||||
$ radon cc -s -a app/
|
||||
app/service.py
|
||||
F 42:0 process_order - C (12)
|
||||
F 88:0 validate - A (3)
|
||||
Average complexity: B (6.2)
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| New code | CC ≤ 10 hard limit |
|
||||
| Legacy refactor | CC > 15 → split 우선 |
|
||||
| Pure data transform | higher CC OK if linear (case/match) |
|
||||
| State machine | use explicit FSM library |
|
||||
|
||||
**기본값**: max-complexity 10 in lint config; warn at 8.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Static Analysis]]
|
||||
- 응용: [[Refactoring_Best_Practices|Refactoring]] · [[Code Review]] · [[CI Gates]]
|
||||
- Adjacent: [[Test Coverage]] · [[SOLID]] (Single Responsibility)
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: high-CC function 매 refactor 제안 (split, polymorphism, table dispatch).
|
||||
**언제 X**: pure metric calculation (deterministic tool 가 더 빠름).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **CC 만 보고 quality 판단**: linear case dispatch 매 high CC 지만 매 simple.
|
||||
- **Hard limit 무조건 enforcement**: 매 split 의 split 매 fragmentation.
|
||||
- **CC ↓ 위해 boolean parameter 추가**: flag argument anti-pattern.
|
||||
- **Cognitive complexity 무시**: 매 nesting depth, recursion 매 더 중요할 수도.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (McCabe 1976 *A Complexity Measure*, ruff/SonarQube docs 2026).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — full content with refactoring patterns |
|
||||
@@ -0,0 +1,171 @@
|
||||
---
|
||||
id: wiki-2026-0508-dom
|
||||
title: DOM (Document Object Model)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [DOM, Document Object Model, DOM Tree]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.95
|
||||
verification_status: applied
|
||||
tags: [dom, web, browser, html, javascript]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: javascript
|
||||
framework: browser
|
||||
---
|
||||
|
||||
# DOM (Document Object Model)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 HTML/XML document 의 tree 표현, language-agnostic API"**. W3C 표준 (1998), 현재 WHATWG DOM Living Standard. 매 browser 매 internal representation; 2026 현재 React/Vue/Solid 매 abstraction layer 위에 있지만, performance/edge cases 매 직접 DOM 이해 매 essential.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 구조
|
||||
- **Document** root.
|
||||
- **Element node** (`<div>`, `<p>`).
|
||||
- **Text node** (literal text).
|
||||
- **Attribute** (element 의 property; not separate child node since DOM4).
|
||||
- **Comment node**.
|
||||
- **DocumentFragment** — lightweight sub-tree, not connected.
|
||||
- **ShadowRoot** — encapsulated sub-tree (Web Components).
|
||||
|
||||
### 매 traversal
|
||||
- `parentNode`, `childNodes`, `firstChild`, `lastChild`, `nextSibling`, `previousSibling`.
|
||||
- `children` (Element only), `firstElementChild`.
|
||||
- `querySelector` / `querySelectorAll` — CSS selector.
|
||||
- `closest(selector)` — ancestor matching.
|
||||
|
||||
### 매 mutation
|
||||
- `appendChild`, `insertBefore`, `removeChild`, `replaceChild` (legacy).
|
||||
- `append`, `prepend`, `before`, `after`, `replaceWith`, `remove` (modern).
|
||||
- `cloneNode(deep)`.
|
||||
|
||||
### 매 응용
|
||||
1. Vanilla JS DOM 조작.
|
||||
2. React reconciler 매 virtual DOM diff → real DOM mutation.
|
||||
3. Web Components Shadow DOM encapsulation.
|
||||
4. Server-side render (jsdom, happy-dom) 매 SSR.
|
||||
5. Browser automation (Playwright, Puppeteer).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Modern element creation (no innerHTML)
|
||||
```javascript
|
||||
const card = Object.assign(document.createElement('article'), {
|
||||
className: 'card',
|
||||
});
|
||||
card.append(
|
||||
Object.assign(document.createElement('h2'), { textContent: title }),
|
||||
Object.assign(document.createElement('p'), { textContent: body }),
|
||||
);
|
||||
container.append(card);
|
||||
```
|
||||
|
||||
### Event delegation
|
||||
```javascript
|
||||
list.addEventListener('click', (e) => {
|
||||
const item = e.target.closest('.item');
|
||||
if (!item || !list.contains(item)) return;
|
||||
handleClick(item.dataset.id);
|
||||
});
|
||||
```
|
||||
|
||||
### DocumentFragment — batch insert
|
||||
```javascript
|
||||
const frag = document.createDocumentFragment();
|
||||
for (const item of items) {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = item.name;
|
||||
frag.append(li);
|
||||
}
|
||||
list.append(frag); // single reflow
|
||||
```
|
||||
|
||||
### MutationObserver
|
||||
```javascript
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const m of mutations) {
|
||||
if (m.type === 'childList') console.log('children changed');
|
||||
}
|
||||
});
|
||||
observer.observe(target, { childList: true, subtree: true, attributes: true });
|
||||
```
|
||||
|
||||
### Shadow DOM (Web Component)
|
||||
```javascript
|
||||
class CounterButton extends HTMLElement {
|
||||
#count = 0;
|
||||
connectedCallback() {
|
||||
const root = this.attachShadow({ mode: 'open' });
|
||||
root.innerHTML = `<style>button{padding:8px}</style><button>0</button>`;
|
||||
root.querySelector('button').onclick = () => {
|
||||
this.#count++;
|
||||
root.querySelector('button').textContent = this.#count;
|
||||
};
|
||||
}
|
||||
}
|
||||
customElements.define('counter-button', CounterButton);
|
||||
```
|
||||
|
||||
### Range & Selection
|
||||
```javascript
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(el);
|
||||
const text = range.toString();
|
||||
```
|
||||
|
||||
### IntersectionObserver — lazy load
|
||||
```javascript
|
||||
const io = new IntersectionObserver((entries) => {
|
||||
for (const e of entries) {
|
||||
if (e.isIntersecting) {
|
||||
e.target.src = e.target.dataset.src;
|
||||
io.unobserve(e.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
document.querySelectorAll('img[data-src]').forEach((img) => io.observe(img));
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| App-level UI | React / Vue / Solid (don't touch DOM) |
|
||||
| Library / web component | Shadow DOM + custom element |
|
||||
| One-off page | Vanilla JS (`querySelector`, `append`) |
|
||||
| Test / SSR | jsdom / happy-dom |
|
||||
| Watch external mutations | MutationObserver |
|
||||
|
||||
**기본값**: framework abstraction; vanilla DOM API for libraries / leaf optimization.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Web Standards]]
|
||||
- 변형: [[Virtual DOM과 Reconciliation|Virtual DOM]] · [[Shadow DOM]]
|
||||
- 응용: [[React]] · [[Web Components]] · [[Playwright]]
|
||||
- Adjacent: [[CSSOM]] · [[Reflow_and_Repaint|Reflow & Repaint]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: DOM snippet 생성, accessibility audit, querySelector 추천.
|
||||
**언제 X**: real-time interaction (LLM 매 round-trip 너무 느림).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **innerHTML with user input**: XSS.
|
||||
- **Layout thrashing**: read-write-read-write 매 force reflow 매 loop.
|
||||
- **No event delegation**: 매 list item 매 listener → memory leak.
|
||||
- **Detached node leak**: removed but reference 보유 → GC 실패.
|
||||
- **Manipulate DOM in framework-controlled subtree**: React reconciler 와 충돌.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (WHATWG DOM Living Standard 2026, MDN).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — full canonical content with modern DOM APIs |
|
||||
@@ -0,0 +1,184 @@
|
||||
---
|
||||
id: wiki-2026-0508-dora-metrics
|
||||
title: DORA Metrics
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [dora, four-keys, accelerate-metrics]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [dora, devops, metrics, delivery-performance]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: sql
|
||||
framework: github-actions/grafana
|
||||
---
|
||||
|
||||
# DORA Metrics
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 software delivery performance 의 매 4 numbers"**: deployment frequency, lead time for changes, change failure rate, mean time to restore (MTTR). 매 Forsgren/Humble/Kim "Accelerate" (2018) + 매 yearly DORA report 가 매 source-of-truth. 2026 의 매 5번째 metric (reliability) 의 official.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 the 4 (now 5)
|
||||
1. **Deployment Frequency (DF)** — production deploys per period. Elite: on-demand (multiple/day).
|
||||
2. **Lead Time for Changes (LT)** — commit → production. Elite: < 1 day.
|
||||
3. **Change Failure Rate (CFR)** — % deploys causing incident/rollback. Elite: 0–5%.
|
||||
4. **Mean Time to Restore (MTTR)** — incident detection → resolution. Elite: < 1 hour.
|
||||
5. **Reliability** (added 2021/2022 reports) — meeting/exceeding SLO targets.
|
||||
|
||||
### 매 performance bands (2024 report)
|
||||
- **Elite** — frequent deploys, < 1 day LT, 0–5% CFR, < 1h MTTR.
|
||||
- **High** — weekly–monthly, 1 day–1 week, 0–10%, < 1 day.
|
||||
- **Medium** — monthly–6m, 1 week–1 month, 0–15%, < 1 week.
|
||||
- **Low** — < 6m, > 6m, > 64%, > 1 week.
|
||||
|
||||
### 매 accelerators (capabilities)
|
||||
- Trunk-based development.
|
||||
- Continuous integration.
|
||||
- Test automation.
|
||||
- Loosely coupled architecture.
|
||||
- Generative culture (Westrum).
|
||||
- Database change automation.
|
||||
- Empowered teams.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Lead time SQL (GitHub + production deploy events)
|
||||
```sql
|
||||
-- 매 first commit in PR → production deploy 의 measure
|
||||
WITH deploys AS (
|
||||
SELECT service, deploy_id, deployed_at, sha
|
||||
FROM deployments WHERE environment = 'production'
|
||||
),
|
||||
commits AS (
|
||||
SELECT pr.merge_commit_sha AS sha, MIN(c.committed_at) AS first_commit_at
|
||||
FROM pull_requests pr JOIN commits c ON c.pr_id = pr.id
|
||||
GROUP BY pr.merge_commit_sha
|
||||
)
|
||||
SELECT d.service,
|
||||
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM d.deployed_at - c.first_commit_at)) AS lt_p50_seconds,
|
||||
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM d.deployed_at - c.first_commit_at)) AS lt_p95_seconds
|
||||
FROM deploys d JOIN commits c USING (sha)
|
||||
WHERE d.deployed_at > NOW() - INTERVAL '90 days'
|
||||
GROUP BY d.service;
|
||||
```
|
||||
|
||||
### GitHub Actions 의 deploy event emit
|
||||
```yaml
|
||||
- name: Record deployment
|
||||
if: success()
|
||||
run: |
|
||||
curl -X POST "$DORA_API/deployments" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-d "{\"service\":\"orders\",\"sha\":\"${{ github.sha }}\",\"environment\":\"production\",\"deployed_at\":\"$(date -u +%FT%TZ)\"}"
|
||||
```
|
||||
|
||||
### Change failure (PagerDuty + deploy correlation)
|
||||
```ts
|
||||
async function changeFailureRate(service: string, days = 30) {
|
||||
const deploys = await db.deployments.count({ service, since: daysAgo(days) });
|
||||
const failedDeploys = await db.deployments.count({
|
||||
service, since: daysAgo(days),
|
||||
correlatedIncidentWithin: '4h', // 매 incident 가 deploy 후 4h 내 의 fail count
|
||||
});
|
||||
return failedDeploys / deploys;
|
||||
}
|
||||
```
|
||||
|
||||
### MTTR from PagerDuty
|
||||
```ts
|
||||
import { api } from '@pagerduty/pdjs';
|
||||
const pd = api({ token: process.env.PD_TOKEN! });
|
||||
const { data } = await pd.get('/incidents', {
|
||||
data: { since: daysAgo(30), service_ids: [SVC_ID], statuses: ['resolved'] },
|
||||
});
|
||||
const durations = data.incidents.map(i =>
|
||||
(new Date(i.resolved_at).getTime() - new Date(i.created_at).getTime()) / 1000);
|
||||
const mttr = durations.reduce((a,b)=>a+b,0) / durations.length;
|
||||
```
|
||||
|
||||
### Four Keys (Google) on BigQuery
|
||||
```sql
|
||||
-- 매 https://github.com/dora-team/fourkeys 의 reference
|
||||
SELECT
|
||||
COUNTIF(event_type = 'deployment') AS deploys,
|
||||
AVG(TIMESTAMP_DIFF(deploy_time, first_commit_time, MINUTE)) AS lt_minutes,
|
||||
COUNTIF(failed) / NULLIF(COUNTIF(event_type = 'deployment'), 0) AS cfr,
|
||||
AVG(TIMESTAMP_DIFF(resolved_time, incident_time, MINUTE)) AS mttr_minutes
|
||||
FROM `project.four_keys.events_raw`
|
||||
WHERE deploy_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY);
|
||||
```
|
||||
|
||||
### Grafana dashboard panel (PromQL-style)
|
||||
```promql
|
||||
# Deployment frequency (deploys per day per service)
|
||||
sum by (service) (rate(deployments_total{env="production"}[7d])) * 86400
|
||||
|
||||
# Change failure rate
|
||||
sum by (service) (deployments_failed_total[30d])
|
||||
/ sum by (service) (deployments_total[30d])
|
||||
|
||||
# Lead time p50
|
||||
histogram_quantile(0.5, sum by (le, service) (rate(deploy_lead_time_seconds_bucket[30d])))
|
||||
```
|
||||
|
||||
### Reliability (SLO-aligned 5th metric)
|
||||
```ts
|
||||
// 매 service 의 SLO 의 meeting-or-exceeding 의 % of measurement windows
|
||||
const reliability = await prom.query(`
|
||||
(sum_over_time(slo_compliance{service="$svc"}[30d]) /
|
||||
count_over_time(slo_compliance{service="$svc"}[30d])) * 100
|
||||
`);
|
||||
```
|
||||
|
||||
### Anti-gaming guardrails
|
||||
```ts
|
||||
// 매 metric 의 isolated 의 game 가 가능 — pair 의 always 의 read
|
||||
const elite = (df > 1/day) && (lt < 1*day) && (cfr < 0.05) && (mttr < 1*hour);
|
||||
// 매 elite 가 X 만 high cfr 의 hide 의 X.
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Greenfield team | Adopt Four Keys (open source) on BigQuery |
|
||||
| GitHub-centric | dora-team/fourkeys + Cloud Run pipelines |
|
||||
| Multi-tool | LinearB / Sleuth / Faros AI / Jellyfish (SaaS) |
|
||||
| Self-host | Apache DevLake (LF AI) |
|
||||
| Enterprise governance | Faros + custom dashboards |
|
||||
|
||||
**기본값**: Apache DevLake (open source) or Four Keys reference impl; weekly review with team; show all 4 (5) together — never single metric.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[DevOps]] · [[Continuous-Delivery]]
|
||||
- 변형: [[Engineering-Metrics]]
|
||||
- 응용: [[Trunk-Based-Development]] · [[Continuous Integration]] · [[SRE]]
|
||||
- Adjacent: [[Postmortem-Culture]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 metric definition explanation, 매 SQL/PromQL query authoring, 매 trend interpretation, 매 retrospective talking points generation.
|
||||
**언제 X**: 매 individual performance evaluation (DORA 의 team-level metric — never individual). 매 metric tuning to look good (gaming).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Single metric optimization**: 매 deploy frequency 의 increase 만 → CFR explodes. 매 4 의 always 의 together 보기.
|
||||
- **Individual performance ranking**: 매 explicitly anti-pattern in DORA research. 매 team-level만.
|
||||
- **Vanity deploys**: 매 empty commits / config-only changes 의 count → meaningless.
|
||||
- **MTTR from "ticket close"**: 매 customer-impact end 의 measure, 매 ticket admin 가 X.
|
||||
- **Comparing teams in different domains**: 매 fintech vs internal tool 의 baselines 가 different.
|
||||
- **No deployment instrumentation**: 매 manual spreadsheet 가 X. 매 auto-emit deploy event.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Forsgren/Humble/Kim "Accelerate" 2018, Google DORA 2024 State of DevOps report, dora-team/fourkeys, Apache DevLake).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — DORA 4(5) metrics, queries, anti-patterns |
|
||||
@@ -0,0 +1,170 @@
|
||||
---
|
||||
id: wiki-2026-0508-deepreadonly
|
||||
title: DeepReadonly
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [deep-readonly, recursive-readonly, immutable-type]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [typescript, type-utility, immutability]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: typescript-5.x
|
||||
---
|
||||
|
||||
# DeepReadonly
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 `Readonly<T>` 의 surface-only — 매 nested mutation 가 still possible"**. 매 DeepReadonly<T> 가 매 recursively 의 every property 의 readonly 의 mark — 매 redux state, config object, frozen domain model 의 essential. TS 5.x 의 매 `const` type parameter + DeepReadonly 가 매 powerful combo.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 vs Readonly
|
||||
- `Readonly<T>` — 매 top-level only. 매 `obj.nested.mutate = X` 가 still allowed.
|
||||
- `DeepReadonly<T>` — 매 every level 의 recursive freeze.
|
||||
|
||||
### 매 type-only vs runtime
|
||||
- DeepReadonly 의 매 compile-time type guarantee — 매 runtime 의 mutation 의 X protect.
|
||||
- 매 runtime freeze 의 `Object.freeze` (shallow) 또는 `deepFreeze` helper 사용.
|
||||
- TypeScript 5.0+ `as const` 의 매 literal-level deep readonly 의 produce.
|
||||
|
||||
### 매 사용처
|
||||
1. Redux/Zustand state shape — 매 mutation prevent.
|
||||
2. Configuration schemas (env config, feature flags).
|
||||
3. API response DTOs after parse.
|
||||
4. Domain entities in DDD value objects.
|
||||
5. Test fixtures (prevent accidental modification).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Basic DeepReadonly
|
||||
```ts
|
||||
type DeepReadonly<T> = T extends (...args: any[]) => any
|
||||
? T
|
||||
: T extends object
|
||||
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
|
||||
: T;
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
profile: { name: string; tags: string[] };
|
||||
}
|
||||
const u: DeepReadonly<User> = { id: '1', profile: { name: 'a', tags: ['x'] } };
|
||||
// u.profile.name = 'b'; // ❌ Cannot assign
|
||||
// u.profile.tags.push('y'); // ❌ readonly array
|
||||
```
|
||||
|
||||
### Handles Map / Set / Array
|
||||
```ts
|
||||
type DeepReadonly<T> =
|
||||
T extends (infer U)[] ? readonly DeepReadonly<U>[]
|
||||
: T extends Map<infer K, infer V> ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
|
||||
: T extends Set<infer U> ? ReadonlySet<DeepReadonly<U>>
|
||||
: T extends Promise<infer U> ? Promise<DeepReadonly<U>>
|
||||
: T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
|
||||
: T;
|
||||
```
|
||||
|
||||
### Brand-aware (preserve nominal types)
|
||||
```ts
|
||||
type Brand<T, B> = T & { readonly __brand: B };
|
||||
type DeepReadonly<T> = T extends Brand<infer U, infer B>
|
||||
? Brand<DeepReadonly<U>, B>
|
||||
: T extends object
|
||||
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
|
||||
: T;
|
||||
```
|
||||
|
||||
### Runtime deepFreeze pair
|
||||
```ts
|
||||
export function deepFreeze<T>(obj: T): DeepReadonly<T> {
|
||||
if (obj && typeof obj === 'object' && !Object.isFrozen(obj)) {
|
||||
Object.freeze(obj);
|
||||
for (const key of Object.keys(obj)) deepFreeze((obj as any)[key]);
|
||||
}
|
||||
return obj as DeepReadonly<T>;
|
||||
}
|
||||
```
|
||||
|
||||
### `as const` + DeepReadonly
|
||||
```ts
|
||||
const config = {
|
||||
features: { newCheckout: true, beta: ['user1', 'user2'] },
|
||||
limits: { rpm: 100 },
|
||||
} as const;
|
||||
// 매 `as const` 가 매 every literal 의 deeply readonly + literal-typed 로 만듦.
|
||||
type Config = typeof config;
|
||||
// Config['features']['beta'] === readonly ['user1', 'user2']
|
||||
```
|
||||
|
||||
### Mutable inverse (DeepWritable)
|
||||
```ts
|
||||
type DeepWritable<T> = T extends (...args: any[]) => any
|
||||
? T
|
||||
: T extends object
|
||||
? { -readonly [K in keyof T]: DeepWritable<T[K]> }
|
||||
: T;
|
||||
|
||||
function clone<T>(x: DeepReadonly<T>): DeepWritable<T> {
|
||||
return structuredClone(x as T) as DeepWritable<T>;
|
||||
}
|
||||
```
|
||||
|
||||
### Zod + DeepReadonly
|
||||
```ts
|
||||
import { z } from 'zod';
|
||||
const UserSchema = z.object({ id: z.string(), tags: z.array(z.string()) }).readonly();
|
||||
type User = DeepReadonly<z.infer<typeof UserSchema>>;
|
||||
```
|
||||
|
||||
### Function args (Redux reducer)
|
||||
```ts
|
||||
function reducer<S, A>(state: DeepReadonly<S>, action: A): S {
|
||||
// state.x = ... // ❌ compile error
|
||||
return { ...(state as S), updated: true } as S;
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Literal config | `as const` |
|
||||
| Generic state shape | `DeepReadonly<T>` utility |
|
||||
| Runtime guarantee 필요 | `deepFreeze` + DeepReadonly |
|
||||
| Performance hot path | 매 type-only DeepReadonly (no runtime cost) |
|
||||
| Library author 가 expose | DeepReadonly + readonly arrays in public API |
|
||||
|
||||
**기본값**: type-only DeepReadonly + `as const` for literals; runtime `deepFreeze` only for security-sensitive boundaries.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[TypeScript-Type-System]] · [[Immutability]]
|
||||
- 변형: [[Readonly]] · [[as const]]
|
||||
- 응용: [[Zustand]] · [[Domain-Driven-Design]]
|
||||
- Adjacent: [[Branded-Types]] · [[Zod]] · [[Structural-Sharing]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 utility type 의 design / extension, 매 type error explanation, 매 readonly violation 의 codemod 작성.
|
||||
**언제 X**: 매 runtime data validation (Zod 사용). 매 hot-path performance tuning (TS types 가 erased — runtime cost 0).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **함수 type 의 readonly 적용**: 매 `(...args) => any` 가 readonly 의 의미 X — special-case 필요.
|
||||
- **Date / RegExp 의 recurse**: 매 built-in instances 가 깨짐 — exclude 의 type guard.
|
||||
- **DeepReadonly + cast away**: `state as Mutable` 가 매 type safety 의 destroy.
|
||||
- **Runtime mutation through cast**: 매 `(state as any).x = 1` — 매 type lie 의 propagate.
|
||||
- **Naive `keyof T` on union**: distributive conditional 의 사용.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (TypeScript 5.x docs, type-fest library, ts-toolbelt).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — DeepReadonly utility type variants and patterns |
|
||||
@@ -0,0 +1,162 @@
|
||||
---
|
||||
id: wiki-2026-0508-dependencies-의존성
|
||||
title: Dependencies (의존성)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [npm-dependencies, package-dependencies, supply-chain]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [dependencies, npm, semver, supply-chain]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: javascript
|
||||
framework: npm/pnpm
|
||||
---
|
||||
|
||||
# Dependencies (의존성)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 dependency 의 liability 가 X asset"**. 매 npm install 이 매 third-party code 를 매 production 에 inject — 매 supply chain attack (event-stream 2018, ua-parser-js 2021, xz-utils 2024 backdoor) 가 매 매년 발생. 2026 modern stack 의 매 pnpm + lockfile + minimum-deps + SBOM (CycloneDX) 가 매 standard.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Dependency 종류
|
||||
- **dependencies**: 매 production runtime 의 사용 (Express, React).
|
||||
- **devDependencies**: 매 build/test only (Vitest, TypeScript, ESLint).
|
||||
- **peerDependencies**: 매 host 가 provide (React plugin 의 React).
|
||||
- **optionalDependencies**: 매 install 실패 가 OK (platform-specific binaries).
|
||||
- **bundledDependencies**: 매 package tarball 안 ship.
|
||||
|
||||
### 매 Semver
|
||||
- `^1.2.3` — minor + patch updates (1.x.x), 매 npm default. 매 unsafe 가 0.x 에서 (^0.2.3 → 0.2.x only).
|
||||
- `~1.2.3` — patch only (1.2.x).
|
||||
- `1.2.3` — exact pin, 매 reproducibility 의 best.
|
||||
- `*` / `latest` — 매 X. 매 절대 사용 X.
|
||||
|
||||
### 매 Lockfile
|
||||
- **pnpm-lock.yaml** / **package-lock.json** / **yarn.lock**: 매 exact resolved versions + integrity hashes.
|
||||
- 매 `npm ci` 사용 (매 install 가 X) — 매 lockfile 강제, deterministic install.
|
||||
- 매 commit 의 must.
|
||||
|
||||
### 매 Supply Chain Risks
|
||||
- **Typosquatting**: `reqeusts`, `lodahs`.
|
||||
- **Compromised maintainer**: 매 ua-parser-js 2021.
|
||||
- **Malicious update**: 매 event-stream 2018, xz-utils 2024.
|
||||
- **Dependency confusion**: 매 internal package name 가 public registry 에 publish 됨.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Pinning + lockfile
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"react": "18.3.1",
|
||||
"express": "~4.21.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": { "node": ">=20.10.0", "pnpm": ">=9.0.0" }
|
||||
}
|
||||
```
|
||||
|
||||
### pnpm 의 strict install
|
||||
```bash
|
||||
# CI 의 deterministic install
|
||||
pnpm install --frozen-lockfile
|
||||
# 매 lockfile mismatch 시 error.
|
||||
|
||||
# 매 audit
|
||||
pnpm audit --audit-level=high
|
||||
```
|
||||
|
||||
### Renovate config
|
||||
```json
|
||||
// renovate.json
|
||||
{
|
||||
"extends": ["config:recommended"],
|
||||
"lockFileMaintenance": { "enabled": true, "schedule": ["before 5am on Monday"] },
|
||||
"vulnerabilityAlerts": { "enabled": true, "labels": ["security"] },
|
||||
"packageRules": [
|
||||
{ "matchUpdateTypes": ["minor", "patch"], "automerge": true, "matchCurrentVersion": "!/^0/" },
|
||||
{ "matchPackagePatterns": ["^@types/"], "automerge": true }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### SBOM 생성 (CycloneDX)
|
||||
```bash
|
||||
npx @cyclonedx/cyclonedx-npm --output-file sbom.json
|
||||
# 매 SLSA / EU CRA compliance 의 사용.
|
||||
```
|
||||
|
||||
### Known-good integrity check
|
||||
```bash
|
||||
# 매 npm install 후 lockfile integrity 검증
|
||||
pnpm install --frozen-lockfile --prefer-offline
|
||||
# Subresource integrity 가 lockfile 에 자동 record.
|
||||
```
|
||||
|
||||
### Allowed-dependencies guard (CI)
|
||||
```ts
|
||||
// scripts/check-deps.ts
|
||||
import pkg from '../package.json' with { type: 'json' };
|
||||
const ALLOWED_LICENSES = new Set(['MIT', 'Apache-2.0', 'BSD-3-Clause', 'ISC']);
|
||||
// 매 license-checker 사용 의 production deps audit.
|
||||
```
|
||||
|
||||
### Provenance verification
|
||||
```bash
|
||||
# 매 npm 9.5+ 의 sigstore provenance
|
||||
npm install --foreground-scripts=false
|
||||
npm audit signatures
|
||||
# 매 GitHub Actions 의 publish 한 package 만 trust.
|
||||
```
|
||||
|
||||
### Dependency removal
|
||||
```bash
|
||||
pnpm dlx depcheck
|
||||
# 매 unused dep 찾기. 매 quarterly cleanup.
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Library author | `peerDependencies` + minimal `dependencies` |
|
||||
| Application | Pin all critical (React, framework), `^` for utilities |
|
||||
| Monorepo | pnpm workspaces + catalogs (pnpm 9.5+) |
|
||||
| 매 high-security (fintech, gov) | Exact pin all, Renovate manual approve, internal mirror |
|
||||
| 매 prototype | `^` everywhere, 매 lockfile commit 만 |
|
||||
|
||||
**기본값**: pnpm + frozen lockfile + Renovate auto-merge minors + SBOM in CI.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Software-Architecture]]
|
||||
- 변형: [[Monorepo]]
|
||||
- 응용: [[Dependency Analysis]] · [[SBOM]]
|
||||
- Adjacent: [[Supply-Chain-Security]] · [[Renovate]] · [[Dependabot]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 package.json review, 매 vulnerability triage, 매 dep upgrade plan generation, 매 SBOM diff explanation.
|
||||
**언제 X**: 매 actual install / build (deterministic tooling 가 better). 매 license decision (legal review 필요).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **`*` or `latest`**: 매 reproducibility destroyed.
|
||||
- **lockfile gitignore**: 매 다른 dev / CI 가 different versions install.
|
||||
- **`npm install` in CI**: 매 `npm ci` / `pnpm install --frozen-lockfile` 사용.
|
||||
- **0.x with `^`**: 매 ^0.2.3 가 0.3.0 으로 jump 가능 — breaking changes.
|
||||
- **Untyped transitive deps**: 매 매 indirect 의 audit X. SBOM 의 review.
|
||||
- **Package without provenance**: 매 2026 의 sigstore signed packages prefer.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (npm docs, pnpm docs, SLSA framework, CycloneDX spec).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — npm dependency management, semver, supply chain hardening |
|
||||
@@ -0,0 +1,175 @@
|
||||
---
|
||||
id: wiki-2026-0508-dependency-analysis
|
||||
title: Dependency Analysis
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [dep-analysis, dependency-graph, code-dependency-tools]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [tooling, dependencies, static-analysis]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: javascript
|
||||
framework: madge/depcheck/knip
|
||||
---
|
||||
|
||||
# Dependency Analysis
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 import graph 가 매 codebase 의 X-ray"**. 매 Madge / dependency-cruiser / Knip / depcheck 가 매 dead code, circular deps, layering violations, unused packages 의 surface. 2026 의 매 Knip + dependency-cruiser + Turbo's prune 가 매 monorepo standard combo.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 question types
|
||||
1. **Module-level**: who imports X? what does X import?
|
||||
2. **Package-level**: which deps are unused? which are dev-only mislabeled?
|
||||
3. **Architectural**: 매 cross-layer 의 import 가 있나?
|
||||
4. **Cycles**: 매 circular dependency.
|
||||
5. **Reachability**: 매 entry-point 의 reachable X 의 dead code.
|
||||
|
||||
### 매 tool matrix
|
||||
- **Madge** — 매 visualization, circular detection (JS/TS).
|
||||
- **dependency-cruiser** — 매 rules engine + violations CI.
|
||||
- **Knip** — 매 unused files/exports/deps (replaces ts-prune + depcheck).
|
||||
- **depcheck** — 매 unused npm deps (older, Knip 가 better).
|
||||
- **ts-morph / typescript-eslint** — 매 custom AST analyzer.
|
||||
- **Nx graph** / **Turborepo prune** — 매 monorepo affected detection.
|
||||
|
||||
### 매 응용
|
||||
1. CI guard — 매 layer violation 시 fail.
|
||||
2. Dead-code removal — 매 quarterly cleanup.
|
||||
3. Bundle reduction — 매 unused dep removal → smaller install + lockfile.
|
||||
4. Refactor planning — 매 high-fan-in module 의 identify.
|
||||
5. License audit — 매 transitive dep tree.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Madge 의 circular detection
|
||||
```bash
|
||||
npx madge --circular --extensions ts,tsx src/
|
||||
# 매 circular 가 있으면 fail.
|
||||
|
||||
npx madge --image graph.svg src/
|
||||
# 매 SVG 의 visualization.
|
||||
```
|
||||
|
||||
### dependency-cruiser rules
|
||||
```js
|
||||
// .dependency-cruiser.cjs
|
||||
module.exports = {
|
||||
forbidden: [
|
||||
{ name: 'no-circular', severity: 'error', from: {}, to: { circular: true } },
|
||||
{ name: 'no-orphans', severity: 'warn', from: { orphan: true, pathNot: '\\.test\\.ts$' }, to: {} },
|
||||
{ name: 'domain-not-import-ui', severity: 'error',
|
||||
from: { path: '^src/domain' }, to: { path: '^src/ui' } },
|
||||
{ name: 'no-deprecated-core', severity: 'error',
|
||||
from: {}, to: { dependencyTypes: ['core'], path: '^(punycode|domain)$' } },
|
||||
],
|
||||
options: { tsConfig: { fileName: 'tsconfig.json' } },
|
||||
};
|
||||
```
|
||||
|
||||
```bash
|
||||
depcruise --config .dependency-cruiser.cjs src/
|
||||
```
|
||||
|
||||
### Knip (unused exports/files/deps)
|
||||
```jsonc
|
||||
// knip.json
|
||||
{
|
||||
"entry": ["src/index.ts", "src/cli.ts"],
|
||||
"project": ["src/**/*.{ts,tsx}"],
|
||||
"ignoreDependencies": ["husky"]
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
npx knip
|
||||
# 매 unused files, unused exports, unused deps 의 list.
|
||||
```
|
||||
|
||||
### Turborepo prune (monorepo)
|
||||
```bash
|
||||
turbo prune --scope=@acme/web --docker
|
||||
# 매 web 의 deps 만 가진 minimal package.json 의 emit — Docker layer cache 의 efficient.
|
||||
```
|
||||
|
||||
### Nx affected graph
|
||||
```bash
|
||||
npx nx graph
|
||||
npx nx affected:test --base=main
|
||||
# 매 변경된 project 의 transitive consumers 만 test.
|
||||
```
|
||||
|
||||
### Custom AST scanner (ts-morph)
|
||||
```ts
|
||||
import { Project } from 'ts-morph';
|
||||
const project = new Project({ tsConfigFilePath: 'tsconfig.json' });
|
||||
const violations: string[] = [];
|
||||
for (const sf of project.getSourceFiles()) {
|
||||
for (const imp of sf.getImportDeclarations()) {
|
||||
const spec = imp.getModuleSpecifierValue();
|
||||
if (sf.getFilePath().includes('/domain/') && spec.startsWith('@/ui')) {
|
||||
violations.push(`${sf.getFilePath()} -> ${spec}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (violations.length) { console.error(violations.join('\n')); process.exit(1); }
|
||||
```
|
||||
|
||||
### Bundle-level (esbuild metafile / vite-bundle-visualizer)
|
||||
```bash
|
||||
vite build --emptyOutDir
|
||||
npx vite-bundle-visualizer
|
||||
# 매 actual shipped bytes per package — install-time deps 의 differ.
|
||||
```
|
||||
|
||||
### License + SBOM cross-check
|
||||
```bash
|
||||
npx license-checker --production --json > licenses.json
|
||||
npx @cyclonedx/cyclonedx-npm --output-file sbom.json
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Quick circular check | Madge |
|
||||
| Layer enforcement in CI | dependency-cruiser |
|
||||
| Unused files/exports/deps | Knip |
|
||||
| Monorepo affected detection | Turbo / Nx |
|
||||
| Custom rules | ts-morph script |
|
||||
| Bundle size (runtime) | vite/esbuild visualizer |
|
||||
|
||||
**기본값**: Knip + dependency-cruiser in CI; Madge ad-hoc for visualization; Turbo/Nx in monorepos.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Static-Analysis]] · [[Code-Quality]]
|
||||
- 변형: [[dependency-cruiser]]
|
||||
- 응용: [[Monorepo]]
|
||||
- Adjacent: [[SBOM]] · [[Turborepo]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 dependency-cruiser rule synthesis from architecture description, 매 Knip output 의 triage (which 의 truly unused), 매 graph interpretation.
|
||||
**언제 X**: 매 actual dead-code removal 의 PR (false positive 의 review 필요). 매 production runtime decisions.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Run only locally**: 매 CI guard 가 X — 매 violation 의 sneak in.
|
||||
- **Knip 의 trust blindly**: 매 dynamic require / framework convention 가 false-positive — `ignore` glob 사용.
|
||||
- **No layer rules**: 매 architecture 가 silently rot.
|
||||
- **Visualization only**: 매 SVG 가 cool 가, 매 enforcement 가 X.
|
||||
- **Run on dist/**: 매 source 의 analyze, 매 bundled 의 X.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Madge docs, dependency-cruiser docs, Knip docs, Turborepo, Nx).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — dependency analysis tools and CI patterns |
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
category: Architecture
|
||||
tags: [auto-wikified, technical-documentation, architecture]
|
||||
title: Dependency Inversion Principle
|
||||
description: "의존성 역전 원칙(Dependency Inversion Principle)은 애플리케이션 서비스가 인프라스트럭처 서비스와 상호 작용할 때 구체적인 구현체(concrete implementations)에 직접 의존하지 않도록 하는 설계 원칙이다 [1]."
|
||||
last_updated: 2026-05-04
|
||||
---
|
||||
|
||||
# Dependency Inversion Principle
|
||||
|
||||
## 📌 Brief Summary
|
||||
의존성 역전 원칙(Dependency Inversion Principle)은 애플리케이션 서비스가 인프라스트럭처 서비스와 상호 작용할 때 구체적인 구현체(concrete implementations)에 직접 의존하지 않도록 하는 설계 원칙이다 [1]. 이 원칙은 대신 클래스의 생성자를 통해 필요한 의존성을 주입(inject)하는 방식을 제안한다 [1]. 이를 통해 시스템 요소 간의 느슨한 결합(loose coupling)을 촉진하며, 시스템을 더 유연하고 테스트하기 쉽게 만든다 [1].
|
||||
|
||||
## 📖 Core Content
|
||||
* **구체적 구현으로부터의 분리**: 의존성 역전 원칙에 따라 애플리케이션 계층은 구체적인 구현 클래스에 직접적으로 종속되지 않는다 [1]. 대신 필요한 의존성은 생성자를 통해 외부에서 주입받는 방식을 취하여 시스템의 결합도를 낮춘다 [1].
|
||||
* **헥사고날 아키텍처(Hexagonal Architecture)에서의 역할**: 헥사고날 아키텍처에서 애플리케이션 서비스가 인프라스트럭처 서비스와 상호작용해야 할 때 기본적으로 이 의존성 역전 원칙을 따른다 [1]. 이를 통해 핵심 비즈니스 로직과 외부 시스템 간의 상호작용을 유연하게 관리할 수 있다 [1].
|
||||
* **프레임워크 단위의 의존성 주입(DI) 지원**: NestJS나 Spring Boot 같은 프레임워크는 이 원칙을 실현하기 위해 강력한 의존성 주입(Dependency Injection) 컨테이너 시스템을 제공한다 [2, 3]. 개발자가 클래스 생성자를 통해 필요한 의존성을 선언하면 프레임워크가 자동으로 인스턴스화하여 주입한다 [2, 3]. 이러한 방식은 의존성을 직접 임포트하여 사용하는 방식에 비해 컴포넌트 간 결합도를 낮추고 단위 테스트(Unit Test) 가능성을 극대화한다 [3].
|
||||
|
||||
## ⚖️ Trade-offs & Caveats
|
||||
소스 내에서 의존성 역전 원칙 자체에 대한 직접적인 단점은 명시되어 있지 않으나, 이를 구현하는 '의존성 주입(DI)' 및 구조적 아키텍처를 실전 프레임워크에 도입할 때 다음과 같은 제약 및 반대 급부(Trade-off)가 동반된다.
|
||||
|
||||
* **가파른 학습 곡선**: 의존성 주입, 데코레이터, 모듈화 등의 개념을 바탕으로 엄격한 아키텍처를 강제하는 프레임워크(예: NestJS, Spring Boot)를 사용할 경우, 개발자가 이를 완벽히 숙지하기 위한 학습 곡선(Learning Curve)이 비교적 가파르다 [4, 5].
|
||||
* **소규모 프로젝트에서의 오버헤드**: 단순한 API나 소규모 팀(2~5명) 환경에서는 의존성 주입 컨테이너나 모듈 시스템을 구성하는 데 드는 비용이 비즈니스 가치 창출보다 클 수 있다 [6]. 즉, 기능 개발보다 프레임워크 구조 설정(wiring)에 과도한 시간이 소모될 수 있는 오버엔지니어링의 위험이 존재한다 [4, 6].
|
||||
* **순환 참조(Circular Dependency) 문제**: 복잡한 의존성 주입 시스템에서는 모듈 및 서비스 간의 순환 참조 문제가 발생할 수 있다 [7]. 프레임워크에서 이를 우회하는 기능(예: NestJS의 `forwardRef()`)을 제공하긴 하지만, 이를 임시방편으로 사용하기보다는 아키텍처적 재설계를 통해 근본적으로 차단하는 것이 바람직하다 [3, 7].
|
||||
|
||||
---
|
||||
*Last updated: 2026-05-03*
|
||||
@@ -0,0 +1,168 @@
|
||||
---
|
||||
id: wiki-2026-0508-digital-twin
|
||||
title: Digital Twin
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [digital-twin, virtual-replica, cyber-physical-system]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [digital-twin, iot, simulation, cps]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: python
|
||||
framework: NVIDIA-Omniverse/Azure-Digital-Twins
|
||||
---
|
||||
|
||||
# Digital Twin
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 digital twin 의 매 physical asset 의 living mirror"**. 매 sensor stream 가 매 simulation model 에 feed → 매 prediction / what-if / control. 2026 의 매 NVIDIA Omniverse + OpenUSD, Azure Digital Twins, AWS IoT TwinMaker 가 매 enterprise standard. 매 LLM-augmented reasoning over twin (Claude Opus 4.7 + DTDL graph query) 의 매 emerging.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 3-tier
|
||||
- **Digital Model** — 매 static representation, 매 sync X.
|
||||
- **Digital Shadow** — 매 one-way sync (physical → digital).
|
||||
- **Digital Twin** — 매 bidirectional sync (digital → physical control 의 가능).
|
||||
|
||||
### 매 ingredient
|
||||
- **3D geometry** (OpenUSD, glTF).
|
||||
- **Telemetry** (MQTT, OPC UA, AVRO over Kafka).
|
||||
- **Physics / behavior** (FMU, Modelica, Isaac Sim, Omniverse PhysX).
|
||||
- **Ontology / DTDL** (Digital Twins Definition Language).
|
||||
- **AI layer** (anomaly detection, forecasting, RL policy).
|
||||
|
||||
### 매 응용
|
||||
1. **Manufacturing**: BMW iFactory — 매 line 의 reconfigure 의 digital first.
|
||||
2. **City** — Singapore Virtual Singapore, Helsinki 3D+.
|
||||
3. **Energy grid** — 매 outage prediction, demand response.
|
||||
4. **Healthcare** — patient-specific cardiac twin (Dassault Living Heart).
|
||||
5. **Robotics fleet** — 매 Isaac Sim 의 sim-to-real RL training.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Azure Digital Twins (DTDL v3)
|
||||
```json
|
||||
{
|
||||
"@context": "dtmi:dtdl:context;3",
|
||||
"@id": "dtmi:com:factory:Pump;1",
|
||||
"@type": "Interface",
|
||||
"contents": [
|
||||
{ "@type": "Property", "name": "serialNumber", "schema": "string" },
|
||||
{ "@type": "Telemetry", "name": "rpm", "schema": "double" },
|
||||
{ "@type": "Telemetry", "name": "temperature", "schema": "double" },
|
||||
{ "@type": "Command", "name": "shutdown" },
|
||||
{ "@type": "Relationship", "name": "feedsInto", "target": "dtmi:com:factory:Tank;1" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### MQTT → twin update (Python)
|
||||
```python
|
||||
import paho.mqtt.client as mqtt
|
||||
from azure.digitaltwins.core import DigitalTwinsClient
|
||||
|
||||
dt = DigitalTwinsClient("https://factory.api.weu.digitaltwins.azure.net", credential)
|
||||
|
||||
def on_msg(client, _, msg):
|
||||
payload = json.loads(msg.payload)
|
||||
patch = [{"op": "replace", "path": "/rpm", "value": payload["rpm"]},
|
||||
{"op": "replace", "path": "/temperature", "value": payload["temp"]}]
|
||||
dt.update_digital_twin(payload["twin_id"], patch)
|
||||
|
||||
c = mqtt.Client()
|
||||
c.on_message = on_msg
|
||||
c.connect("mqtt.factory.local", 1883)
|
||||
c.subscribe("factory/+/telemetry")
|
||||
c.loop_forever()
|
||||
```
|
||||
|
||||
### Twin graph query (Cypher-like)
|
||||
```text
|
||||
SELECT pump, tank
|
||||
FROM DIGITALTWINS pump
|
||||
JOIN tank RELATED pump.feedsInto
|
||||
WHERE pump.temperature > 85
|
||||
AND IS_OF_MODEL(pump, 'dtmi:com:factory:Pump;1')
|
||||
```
|
||||
|
||||
### Omniverse + OpenUSD scene composition
|
||||
```python
|
||||
from pxr import Usd, UsdGeom, Sdf
|
||||
stage = Usd.Stage.CreateNew("factory.usda")
|
||||
factory = UsdGeom.Xform.Define(stage, "/Factory")
|
||||
pump = stage.OverridePrim("/Factory/Pump_42")
|
||||
pump.CreateAttribute("custom:rpm", Sdf.ValueTypeNames.Float).Set(1480.0)
|
||||
pump.CreateAttribute("custom:temperature", Sdf.ValueTypeNames.Float).Set(72.3)
|
||||
stage.Save()
|
||||
```
|
||||
|
||||
### Anomaly detection on twin stream
|
||||
```python
|
||||
from river import anomaly # online learning
|
||||
detector = anomaly.HalfSpaceTrees(seed=42)
|
||||
async for event in kafka_consumer("factory.telemetry"):
|
||||
score = detector.score_one({"rpm": event.rpm, "temp": event.temp})
|
||||
detector.learn_one({"rpm": event.rpm, "temp": event.temp})
|
||||
if score > 0.95:
|
||||
await dt.update_relationships(event.twin_id, "alert_state", "anomaly")
|
||||
```
|
||||
|
||||
### LLM reasoning over twin graph
|
||||
```python
|
||||
graph_context = dt.query_twins("SELECT * FROM digitaltwins WHERE temperature > 80")
|
||||
response = anthropic.messages.create(
|
||||
model="claude-opus-4-7",
|
||||
system="You analyze factory digital twin state for root-cause hypotheses.",
|
||||
messages=[{"role": "user", "content": f"Twins: {graph_context}\nWhy is line 3 throughput dropping?"}],
|
||||
)
|
||||
```
|
||||
|
||||
### Sim-to-real RL (Isaac Sim)
|
||||
```python
|
||||
from omni.isaac.gym.vec_env import VecEnvBase
|
||||
env = VecEnvBase(headless=True)
|
||||
# 매 4096 parallel pump sims 의 train, 매 policy 가 real pump 에 deploy.
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| 매 high-fidelity physics | NVIDIA Omniverse + Isaac Sim |
|
||||
| 매 enterprise IoT graph | Azure Digital Twins (DTDL) |
|
||||
| 매 AWS-native | AWS IoT TwinMaker |
|
||||
| 매 city / GIS | CesiumJS + 3D Tiles |
|
||||
| 매 scientific sim | Modelica + FMU |
|
||||
|
||||
**기본값**: Azure Digital Twins or AWS TwinMaker for graph + telemetry; Omniverse for 3D/physics; OpenUSD for interchange.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Cyber-Physical-Systems]]
|
||||
- 응용: [[Predictive Maintenance]]
|
||||
- Adjacent: [[Control_Systems_Engineering|Control-Systems-Engineering]] · [[MQTT]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 twin graph 의 natural-language query → DTDL/SQL translation, 매 anomaly explanation, 매 maintenance work order generation.
|
||||
**언제 X**: 매 hard-realtime control loop (sub-ms). 매 safety-critical actuation (deterministic controller 의 사용).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **3D model only**: 매 telemetry 가 X — 매 just CAD viewer.
|
||||
- **No bidirectional channel**: 매 just shadow, 매 not twin.
|
||||
- **Monolithic schema**: 매 DTDL inheritance / interfaces 의 사용.
|
||||
- **Synchronous queries on hot path**: 매 read replica / cache.
|
||||
- **No data retention policy**: 매 telemetry storage cost 가 explodes — tiered storage (hot Kafka → warm Parquet → cold S3).
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Microsoft DTDL v3 spec, NVIDIA Omniverse docs, AWS IoT TwinMaker, Gartner 2025 digital twin report).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — digital twin tiers, DTDL, Omniverse, sim-to-real |
|
||||
@@ -0,0 +1,207 @@
|
||||
---
|
||||
id: wiki-2026-0508-discriminated-unions
|
||||
title: Discriminated Unions
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Tagged Unions, Sum Types, Algebraic Data Types, ADT]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.95
|
||||
verification_status: applied
|
||||
tags: [typescript, types, functional, type-narrowing]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: type-system
|
||||
---
|
||||
|
||||
# Discriminated Unions
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 한 literal field 의 매 type narrow"**. 매 TS / F# / Rust enum / Haskell ADT 의 same idea — 매 union 의 each variant 의 unique discriminant (tag) 의 carry, 매 compiler 의 매 switch 시 매 narrow. 매 2026 TS 5.7 의 매 dominant data modeling pattern.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 anatomy
|
||||
```typescript
|
||||
type Result<T, E> =
|
||||
| { kind: "ok"; value: T }
|
||||
| { kind: "err"; error: E };
|
||||
// ^^^^^^^^^^^ 매 discriminant — 매 string / number / boolean literal.
|
||||
```
|
||||
|
||||
### 매 narrowing rules
|
||||
- 매 discriminant 의 literal 의 must.
|
||||
- 매 `switch` / `if` 매 변수 의 narrow.
|
||||
- 매 exhaustive check 의 `never` 의 leverage.
|
||||
|
||||
### 매 vs alternatives
|
||||
- **vs class hierarchy**: 매 closed set, 매 pattern-match easy, 매 no `instanceof`.
|
||||
- **vs enum**: 매 enum 의 variant 별 data 의 X — DU 의 carry.
|
||||
- **vs untagged union**: 매 narrow 의 hard, 매 runtime check 의 brittle.
|
||||
|
||||
### 매 응용
|
||||
1. Result / Option / Either monads.
|
||||
2. State machine state.
|
||||
3. Redux / xstate action / event.
|
||||
4. API response variants.
|
||||
5. AST node types.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Pattern 1: Result / Either
|
||||
```typescript
|
||||
type Result<T, E = Error> =
|
||||
| { ok: true; value: T }
|
||||
| { ok: false; error: E };
|
||||
|
||||
function parseJson<T>(s: string): Result<T> {
|
||||
try { return { ok: true, value: JSON.parse(s) }; }
|
||||
catch (e) { return { ok: false, error: e as Error }; }
|
||||
}
|
||||
|
||||
const r = parseJson<User>(input);
|
||||
if (r.ok) {
|
||||
r.value.name; // 매 narrowed 의 T
|
||||
} else {
|
||||
r.error.message; // 매 narrowed 의 E
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Exhaustive switch with `never`
|
||||
```typescript
|
||||
type Shape =
|
||||
| { kind: "circle"; radius: number }
|
||||
| { kind: "square"; side: number }
|
||||
| { kind: "rectangle"; w: number; h: number };
|
||||
|
||||
function area(s: Shape): number {
|
||||
switch (s.kind) {
|
||||
case "circle": return Math.PI * s.radius ** 2;
|
||||
case "square": return s.side ** 2;
|
||||
case "rectangle": return s.w * s.h;
|
||||
default:
|
||||
const _exhaustive: never = s; // 매 새 variant 추가 시 compile error
|
||||
throw new Error(`unhandled: ${_exhaustive}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 3: State machine state
|
||||
```typescript
|
||||
type FetchState<T> =
|
||||
| { status: "idle" }
|
||||
| { status: "loading"; abortCtrl: AbortController }
|
||||
| { status: "success"; data: T; fetchedAt: Date }
|
||||
| { status: "error"; error: Error; retryCount: number };
|
||||
|
||||
function reducer<T>(s: FetchState<T>, ev: FetchEvent<T>): FetchState<T> {
|
||||
if (s.status === "loading" && ev.type === "abort") {
|
||||
s.abortCtrl.abort();
|
||||
return { status: "idle" };
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 4: Redux-style action
|
||||
```typescript
|
||||
type TodoAction =
|
||||
| { type: "ADD"; text: string }
|
||||
| { type: "TOGGLE"; id: string }
|
||||
| { type: "DELETE"; id: string }
|
||||
| { type: "EDIT"; id: string; text: string };
|
||||
|
||||
function reducer(state: Todo[], action: TodoAction): Todo[] {
|
||||
switch (action.type) {
|
||||
case "ADD": return [...state, { id: uid(), text: action.text, done: false }];
|
||||
case "TOGGLE": return state.map(t => t.id === action.id ? { ...t, done: !t.done } : t);
|
||||
case "DELETE": return state.filter(t => t.id !== action.id);
|
||||
case "EDIT": return state.map(t => t.id === action.id ? { ...t, text: action.text } : t);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 5: API response variants
|
||||
```typescript
|
||||
type ApiResponse<T> =
|
||||
| { status: 200; data: T }
|
||||
| { status: 401; reason: "expired" | "invalid" }
|
||||
| { status: 429; retryAfterSec: number }
|
||||
| { status: 500; traceId: string };
|
||||
|
||||
async function call<T>(url: string): Promise<ApiResponse<T>> { /* ... */ }
|
||||
```
|
||||
|
||||
### Pattern 6: Pattern-match helper (ts-pattern)
|
||||
```typescript
|
||||
import { match, P } from "ts-pattern";
|
||||
|
||||
const message = match(response)
|
||||
.with({ status: 200 }, r => `Got ${r.data.length} items`)
|
||||
.with({ status: 401, reason: "expired" }, () => "Please refresh token")
|
||||
.with({ status: 429 }, r => `Wait ${r.retryAfterSec}s`)
|
||||
.with({ status: 500 }, r => `Error ${r.traceId}`)
|
||||
.exhaustive();
|
||||
```
|
||||
|
||||
### Pattern 7: Zod parsing → DU
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
|
||||
const Event = z.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal("click"), x: z.number(), y: z.number() }),
|
||||
z.object({ type: z.literal("key"), key: z.string() }),
|
||||
z.object({ type: z.literal("scroll"), dy: z.number() }),
|
||||
]);
|
||||
type Event = z.infer<typeof Event>;
|
||||
```
|
||||
|
||||
### Pattern 8: assertNever helper
|
||||
```typescript
|
||||
export function assertNever(x: never): never {
|
||||
throw new Error(`unexpected: ${JSON.stringify(x)}`);
|
||||
}
|
||||
// 매 default case 의 use.
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Closed set of variants | DU |
|
||||
| Open extensible | class + interface |
|
||||
| Boolean flag pair | DU (truthy combos 의 prevent) |
|
||||
| Many variants (~20+) | DU + ts-pattern |
|
||||
| Server boundary | Zod discriminatedUnion |
|
||||
|
||||
**기본값**: 매 union 의 require 시 매 DU + `kind` / `type` / `status` discriminant.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[TypeScript Type System]] · [[Algebraic Data Types]]
|
||||
- 변형: [[Tagged Unions]] · [[Sum Types]]
|
||||
- 응용: [[Result Type]] · [[State Machine]] · [[Zod]]
|
||||
- Adjacent: [[Pattern Matching]] · [[Exhaustiveness Checking]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 type modeling, 매 state machine, 매 API contract, 매 reducer.
|
||||
**언제 X**: 매 single-variant — 매 plain interface.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **No discriminant**: 매 untagged union — 매 narrow 의 hard.
|
||||
- **String discriminant 의 typo**: 매 magic string 의 const 의 hoist.
|
||||
- **Boolean flag combos**: `{loading, success, error}` boolean — 매 DU 의 use.
|
||||
- **Default case 의 swallow**: `default: return state` — 매 새 variant 시 silent miss.
|
||||
- **Class hierarchy 의 simulate**: 매 DU 의 cleaner.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (TS handbook "Narrowing", Rust enum docs, F# DU, Zod docs, ts-pattern docs).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — DU patterns + exhaustive + ts-pattern + Zod |
|
||||
@@ -0,0 +1,191 @@
|
||||
---
|
||||
id: wiki-2026-0508-distributed-systems-fallacies
|
||||
title: Distributed Systems Fallacies
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Fallacies of Distributed Computing, 8 Fallacies, Deutsch Fallacies]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.95
|
||||
verification_status: applied
|
||||
tags: [distributed-systems, architecture, networking, reliability]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: polyglot
|
||||
framework: distributed-systems
|
||||
---
|
||||
|
||||
# Distributed Systems Fallacies
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 network 의 invisible 한 assumption 의 매 production failure 의 source"**. 1994년 Peter Deutsch (Sun) 가 매 7 fallacies 의 articulate, 1997년 James Gosling 가 8th 의 add. 매 2026 cloud-native 시대 에도 매 microservices / serverless / edge compute 의 매 매 valid.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 8 Fallacies
|
||||
1. **Network 의 reliable**: 매 packet drop / partition / DNS failure 의 inevitable.
|
||||
2. **Latency 의 zero**: 매 LAN ~0.5ms, 매 cross-region ~150ms, 매 satellite ~600ms.
|
||||
3. **Bandwidth 의 infinite**: 매 video / ML model weights / log shipping 의 saturate.
|
||||
4. **Network 의 secure**: 매 default 의 insecure — 매 zero-trust assume.
|
||||
5. **Topology 의 안 변함**: 매 autoscaling / k8s pod reschedule / failover 의 매 second.
|
||||
6. **Administrator 의 single**: 매 multi-cloud / multi-region 의 매 다른 policy.
|
||||
7. **Transport cost 의 zero**: 매 serialization / TLS handshake / egress fee 의 real.
|
||||
8. **Network 의 homogeneous**: 매 IPv4/IPv6, 매 protocol versions, 매 MTU mismatch.
|
||||
|
||||
### 매 왜 fallacy 인가
|
||||
- 매 dev 의 localhost / monolith mental model 의 distributed 에 적용 시 fail.
|
||||
- 매 "happy path" coding 의 매 timeout / retry / circuit breaker 의 lack.
|
||||
- 매 50ms RTT 의 매 100 calls 의 5 second user-facing latency.
|
||||
|
||||
### 매 응용
|
||||
1. Microservices design — 매 call graph 의 latency budget 산정.
|
||||
2. Cross-region replication — 매 split-brain / eventual consistency 의 plan.
|
||||
3. Edge computing — 매 intermittent connectivity 의 first-class.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Pattern 1: Timeout + Retry with Exponential Backoff
|
||||
```typescript
|
||||
async function callWithRetry<T>(
|
||||
fn: () => Promise<T>,
|
||||
opts = { maxRetries: 3, baseMs: 100, timeoutMs: 2000 }
|
||||
): Promise<T> {
|
||||
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
||||
try {
|
||||
return await Promise.race([
|
||||
fn(),
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(() => reject(new Error("timeout")), opts.timeoutMs)
|
||||
),
|
||||
]);
|
||||
} catch (err) {
|
||||
if (attempt === opts.maxRetries) throw err;
|
||||
const jitter = Math.random() * opts.baseMs;
|
||||
await new Promise(r => setTimeout(r, opts.baseMs * 2 ** attempt + jitter));
|
||||
}
|
||||
}
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Circuit Breaker (Resilience4j-style)
|
||||
```typescript
|
||||
class CircuitBreaker {
|
||||
private failures = 0;
|
||||
private state: "closed" | "open" | "half-open" = "closed";
|
||||
private openedAt = 0;
|
||||
constructor(private threshold = 5, private cooldownMs = 30_000) {}
|
||||
|
||||
async exec<T>(fn: () => Promise<T>): Promise<T> {
|
||||
if (this.state === "open" && Date.now() - this.openedAt < this.cooldownMs)
|
||||
throw new Error("circuit open");
|
||||
if (this.state === "open") this.state = "half-open";
|
||||
try {
|
||||
const r = await fn();
|
||||
this.failures = 0; this.state = "closed";
|
||||
return r;
|
||||
} catch (e) {
|
||||
if (++this.failures >= this.threshold) {
|
||||
this.state = "open"; this.openedAt = Date.now();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 3: Bulkhead (concurrency limiter)
|
||||
```typescript
|
||||
import pLimit from "p-limit";
|
||||
const dbLimit = pLimit(20); // 매 DB pool 의 isolate
|
||||
const apiLimit = pLimit(50); // 매 external API 의 separate
|
||||
|
||||
async function getUser(id: string) {
|
||||
return dbLimit(() => db.users.findOne({ id }));
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 4: Idempotency Key
|
||||
```typescript
|
||||
async function chargeCard(req: ChargeReq, idemKey: string) {
|
||||
const cached = await redis.get(`idem:${idemKey}`);
|
||||
if (cached) return JSON.parse(cached);
|
||||
const result = await stripe.charges.create(req);
|
||||
await redis.setex(`idem:${idemKey}`, 86400, JSON.stringify(result));
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 5: Latency Budget
|
||||
```typescript
|
||||
// 매 user-facing 200ms 의 budget
|
||||
// API gateway: 20ms
|
||||
// auth check: 10ms (cached)
|
||||
// service call: 50ms (timeout 100ms)
|
||||
// DB query: 30ms (timeout 80ms)
|
||||
// serialization: 10ms
|
||||
// buffer: 80ms
|
||||
// 매 each hop 의 explicit budget — over 시 fail fast.
|
||||
```
|
||||
|
||||
### Pattern 6: Chaos Testing (Toxiproxy)
|
||||
```bash
|
||||
# 매 latency injection
|
||||
toxiproxy-cli toxic add api -t latency -a latency=500 -a jitter=100
|
||||
# 매 packet loss
|
||||
toxiproxy-cli toxic add db -t timeout -a timeout=2000
|
||||
```
|
||||
|
||||
### Pattern 7: Health check with deep probe
|
||||
```typescript
|
||||
app.get("/health/deep", async (_, res) => {
|
||||
const checks = await Promise.allSettled([
|
||||
db.raw("SELECT 1").then(() => ({ db: "ok" })),
|
||||
redis.ping().then(() => ({ redis: "ok" })),
|
||||
fetch(upstream + "/health", { signal: AbortSignal.timeout(500) }),
|
||||
]);
|
||||
const failed = checks.filter(c => c.status === "rejected");
|
||||
res.status(failed.length ? 503 : 200).json({ checks });
|
||||
});
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| LAN microservice call | timeout 1-2s, retry 2x |
|
||||
| Cross-region call | timeout 5-10s, circuit breaker |
|
||||
| 3rd-party API | bulkhead + circuit breaker + idempotency |
|
||||
| Streaming / WebSocket | heartbeat + auto-reconnect |
|
||||
| Critical write | idempotency key 의 mandatory |
|
||||
|
||||
**기본값**: 매 every remote call 의 timeout + retry + circuit breaker 의 wrap.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Distributed Systems]] · [[Software Architecture]]
|
||||
- 변형: [[CAP Theorem & PACELC]] · [[PACELC]]
|
||||
- 응용: [[Microservices Architecture]] · [[Service Mesh]]
|
||||
- Adjacent: [[Resilience Patterns]] · [[Chaos Engineering]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 distributed system design review, 매 incident postmortem, 매 SLO 산정.
|
||||
**언제 X**: 매 single-process monolith, 매 batch job 의 isolated.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Infinite retry**: 매 retry storm — 매 backoff + max attempts 의 mandatory.
|
||||
- **Timeout 의 unset**: 매 default infinite — 매 thread pool exhaustion.
|
||||
- **Synchronous fan-out**: 매 N services 의 sequential await — 매 N×latency.
|
||||
- **Trust LAN security**: 매 zero-trust / mTLS 의 default.
|
||||
- **Ignore tail latency**: 매 p50 의 보고 의 — 매 p99 / p99.9 의 user experience.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Deutsch 1994 / Gosling 1997 original list, AWS Builders' Library, Google SRE book).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — 8 fallacies + resilience patterns |
|
||||
@@ -0,0 +1,208 @@
|
||||
---
|
||||
id: wiki-2026-0508-distributed-tracing
|
||||
title: Distributed Tracing
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [distributed-tracing, opentelemetry-tracing, request-tracing]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [observability, tracing, opentelemetry, jaeger]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: opentelemetry/tempo
|
||||
---
|
||||
|
||||
# Distributed Tracing
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 trace 가 매 cross-service request 의 X-ray"**. 매 span tree 가 매 service hop, latency, error 의 reveal — 매 microservice debugging 의 essential. 2026 의 매 OpenTelemetry (OTel) 가 매 universal standard, 매 Tempo / Jaeger / Honeycomb / Datadog 가 매 backend, 매 W3C Trace Context 가 매 propagation.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 building blocks
|
||||
- **Trace** — 매 root request 의 unique ID (trace_id, 128-bit).
|
||||
- **Span** — 매 single operation (HTTP call, DB query, function); has parent_span_id.
|
||||
- **Context propagation** — `traceparent` HTTP header (W3C) carries trace_id+span_id+flags.
|
||||
- **Baggage** — key/value propagated alongside (user_id, tenant).
|
||||
- **Sampling** — 매 head (decide at ingress) vs tail (decide after seeing whole trace).
|
||||
|
||||
### 매 OTel architecture
|
||||
1. **SDK** — instrumentation 의 in-app (auto + manual).
|
||||
2. **Collector** — 매 receive → process (batch, sample, redact) → export.
|
||||
3. **Backend** — Tempo (Grafana), Jaeger, Honeycomb, Datadog APM.
|
||||
4. **UI** — Grafana, Jaeger UI, vendor.
|
||||
|
||||
### 매 응용
|
||||
1. Latency root cause (which span 의 slow).
|
||||
2. Error correlation (trace 의 `error=true` spans).
|
||||
3. Service dependency map (service graph from spans).
|
||||
4. Capacity planning (RED metrics derived from spans).
|
||||
5. SLO debugging (trace 의 SLO budget burn 의 attribute).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### OTel Node.js auto-instrumentation
|
||||
```ts
|
||||
// otel.ts (loaded with --import / NODE_OPTIONS)
|
||||
import { NodeSDK } from '@opentelemetry/sdk-node';
|
||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
||||
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
||||
import { Resource } from '@opentelemetry/resources';
|
||||
|
||||
const sdk = new NodeSDK({
|
||||
resource: new Resource({ 'service.name': 'orders-api', 'service.version': '1.4.2' }),
|
||||
traceExporter: new OTLPTraceExporter({ url: 'http://otel-collector:4318/v1/traces' }),
|
||||
instrumentations: [getNodeAutoInstrumentations()],
|
||||
});
|
||||
sdk.start();
|
||||
```
|
||||
|
||||
### Manual span (TypeScript)
|
||||
```ts
|
||||
import { trace, SpanStatusCode } from '@opentelemetry/api';
|
||||
const tracer = trace.getTracer('orders');
|
||||
|
||||
export async function placeOrder(input: OrderInput) {
|
||||
return tracer.startActiveSpan('placeOrder', async (span) => {
|
||||
span.setAttributes({ 'order.customer_id': input.customerId, 'order.line_count': input.lines.length });
|
||||
try {
|
||||
const order = await db.orders.insert(input);
|
||||
await kafka.produce('orders.placed', order);
|
||||
span.setStatus({ code: SpanStatusCode.OK });
|
||||
return order;
|
||||
} catch (e) {
|
||||
span.recordException(e as Error);
|
||||
span.setStatus({ code: SpanStatusCode.ERROR, message: (e as Error).message });
|
||||
throw e;
|
||||
} finally {
|
||||
span.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Python (FastAPI auto-instrument)
|
||||
```python
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
||||
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
|
||||
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
||||
|
||||
provider = TracerProvider()
|
||||
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint="otel-collector:4317")))
|
||||
trace.set_tracer_provider(provider)
|
||||
|
||||
app = FastAPI()
|
||||
FastAPIInstrumentor.instrument_app(app)
|
||||
HTTPXClientInstrumentor().instrument()
|
||||
```
|
||||
|
||||
### Trace context propagation (W3C)
|
||||
```text
|
||||
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
|
||||
^ ^ ^ ^
|
||||
version trace_id (32 hex) parent_id (16) flags
|
||||
tracestate: vendor1=value,vendor2=value
|
||||
baggage: userId=42,tenantId=acme
|
||||
```
|
||||
|
||||
### OTel Collector pipeline
|
||||
```yaml
|
||||
# otel-collector.yaml
|
||||
receivers:
|
||||
otlp: { protocols: { grpc: {}, http: {} } }
|
||||
processors:
|
||||
batch: { timeout: 1s }
|
||||
tail_sampling:
|
||||
decision_wait: 30s
|
||||
policies:
|
||||
- { name: errors, type: status_code, status_code: { status_codes: [ERROR] } }
|
||||
- { name: slow, type: latency, latency: { threshold_ms: 1000 } }
|
||||
- { name: rest, type: probabilistic, probabilistic: { sampling_percentage: 5 } }
|
||||
exporters:
|
||||
otlphttp/tempo: { endpoint: http://tempo:4318 }
|
||||
loki: { endpoint: http://loki:3100/loki/api/v1/push }
|
||||
service:
|
||||
pipelines:
|
||||
traces: { receivers: [otlp], processors: [batch, tail_sampling], exporters: [otlphttp/tempo] }
|
||||
```
|
||||
|
||||
### Trace ↔ Log correlation
|
||||
```ts
|
||||
import pino from 'pino';
|
||||
import { trace } from '@opentelemetry/api';
|
||||
const log = pino();
|
||||
function logWithTrace(msg: string, extra: object = {}) {
|
||||
const span = trace.getActiveSpan();
|
||||
const ctx = span?.spanContext();
|
||||
log.info({ ...extra, trace_id: ctx?.traceId, span_id: ctx?.spanId }, msg);
|
||||
}
|
||||
// 매 Loki/Tempo derived field 의 trace 의 jump from log line.
|
||||
```
|
||||
|
||||
### Frontend → backend trace
|
||||
```ts
|
||||
// 매 browser OTel SDK 의 traceparent 의 inject
|
||||
import { trace, context } from '@opentelemetry/api';
|
||||
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
|
||||
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
|
||||
new WebTracerProvider().register();
|
||||
new FetchInstrumentation({
|
||||
propagateTraceHeaderCorsUrls: [/api\.example\.com/],
|
||||
}).enable();
|
||||
```
|
||||
|
||||
### eBPF-based zero-instrumentation (Beyla / Pixie)
|
||||
```bash
|
||||
# Grafana Beyla — 매 Go/Node/Python 의 auto-trace 의 eBPF 의 capture, 매 code change X.
|
||||
beyla --config beyla.yaml
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Greenfield | OTel SDK + Tempo (Grafana stack) |
|
||||
| Multi-cloud SaaS | Honeycomb / Datadog APM |
|
||||
| Polyglot legacy | OTel Collector + auto-instrument per lang |
|
||||
| Zero-code start | eBPF (Beyla, Pixie) |
|
||||
| 매 cost control | Tail sampling on errors+slow, 5% baseline |
|
||||
| Strong cardinality | Honeycomb (designed for high-cardinality) |
|
||||
|
||||
**기본값**: OTel SDK + W3C Trace Context + Collector with tail sampling + Tempo or vendor backend.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Observability]] · [[Microservices]]
|
||||
- 변형: [[Tempo]]
|
||||
- 응용: [[Service Mesh]]
|
||||
- Adjacent: [[OpenTelemetry]] · [[Logs]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 trace tree 의 root-cause hypothesis, 매 sampling policy review, 매 OTel Collector config debug, 매 span attribute schema design.
|
||||
**언제 X**: 매 production sampling decision 의 binding (cost + signal tradeoff 가 deep). 매 PII redaction 의 sole reviewer (security review 필요).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **No sampling**: 매 cost / storage explode — tail sample on errors+slow.
|
||||
- **High-cardinality on every span**: 매 user_id on every span 가 indexable backend 가 X 면 expensive.
|
||||
- **Frontend trace 의 X**: 매 server-side latency 만 가 보임 — 매 user-perceived 의 miss.
|
||||
- **Logs without trace_id**: 매 trace ↔ log jump 가 X.
|
||||
- **Manual span without `end()`**: 매 leak.
|
||||
- **Sync span across async boundary**: 매 context lost — `startActiveSpan` 사용.
|
||||
- **Vendor lock-in via SDK**: 매 OTel SDK + vendor exporter 의 use, vendor SDK 의 X.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (OpenTelemetry spec, W3C Trace Context, Grafana Tempo docs, Honeycomb engineering blog).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — distributed tracing with OpenTelemetry, sampling, propagation |
|
||||
@@ -0,0 +1,212 @@
|
||||
---
|
||||
id: wiki-2026-0508-distributed-system-type-safety
|
||||
title: Distributed System Type Safety
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [cross-service-types, contract-types, schema-driven-types]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [distributed-systems, type-safety, contracts, codegen]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: gRPC/OpenAPI/tRPC
|
||||
---
|
||||
|
||||
# Distributed System Type Safety
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 service boundary 가 매 type system 의 chasm"**. 매 single-process compile-time check 가 매 network call 에서 evaporate — 매 schema-first contract (Protobuf, OpenAPI, GraphQL SDL, Avro) + codegen 의 매 cross-service guarantee. 2026 의 매 Buf + Connect, tRPC over WebSocket, GraphQL Federation v3, gRPC + gRPC-Web 가 매 dominant choices.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 problem
|
||||
- 매 service A 의 type 변경 → 매 service B 가 silently break.
|
||||
- 매 JSON 의 매 stringly-typed wire — 매 runtime error.
|
||||
- 매 versioning 가 매 hard.
|
||||
|
||||
### 매 solution categories
|
||||
1. **Schema-first + codegen**: gRPC/Protobuf, OpenAPI, AsyncAPI.
|
||||
2. **TypeScript-end-to-end**: tRPC, ts-rest, Encore.
|
||||
3. **Federated graph**: GraphQL Federation v3.
|
||||
4. **Event schema registry**: Confluent Schema Registry (Avro/Protobuf), AWS Glue.
|
||||
5. **CRDT + typed sync**: Yjs + typed shape.
|
||||
|
||||
### 매 verification levels
|
||||
- **Compile time** — codegen output type-checked.
|
||||
- **Build time** — schema lint + breaking-change detection (Buf).
|
||||
- **CI** — contract tests (Pact).
|
||||
- **Runtime** — schema validation at boundary (Zod, Pydantic).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Buf + Protobuf workflow
|
||||
```bash
|
||||
# buf.yaml
|
||||
version: v2
|
||||
modules: [{ path: proto }]
|
||||
breaking: { use: [FILE] }
|
||||
lint: { use: [STANDARD] }
|
||||
|
||||
# CI 의 breaking-change check
|
||||
buf breaking --against '.git#branch=main'
|
||||
buf generate
|
||||
```
|
||||
|
||||
```protobuf
|
||||
// proto/order/v1/order.proto
|
||||
syntax = "proto3";
|
||||
package order.v1;
|
||||
message Order {
|
||||
string id = 1;
|
||||
string customer_id = 2;
|
||||
repeated LineItem items = 3;
|
||||
Money total = 4;
|
||||
}
|
||||
service OrderService {
|
||||
rpc GetOrder(GetOrderRequest) returns (Order);
|
||||
rpc StreamOrders(StreamOrdersRequest) returns (stream Order);
|
||||
}
|
||||
```
|
||||
|
||||
### Connect-RPC (HTTP+JSON+Protobuf, browser-friendly)
|
||||
```ts
|
||||
import { createPromiseClient } from "@connectrpc/connect";
|
||||
import { OrderService } from "./gen/order/v1/order_connect";
|
||||
const client = createPromiseClient(OrderService, transport);
|
||||
const order = await client.getOrder({ id: "o_123" });
|
||||
// 매 fully typed end-to-end.
|
||||
```
|
||||
|
||||
### tRPC v11 (TypeScript-only stacks)
|
||||
```ts
|
||||
// server/router.ts
|
||||
import { initTRPC } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
const t = initTRPC.create();
|
||||
export const appRouter = t.router({
|
||||
order: t.router({
|
||||
get: t.procedure.input(z.object({ id: z.string() })).query(({ input }) => db.order.find(input.id)),
|
||||
}),
|
||||
});
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
||||
// client.ts — 매 zero codegen, 매 type 의 inference
|
||||
import type { AppRouter } from '../server/router';
|
||||
const trpc = createTRPCClient<AppRouter>({ links: [...] });
|
||||
const order = await trpc.order.get.query({ id: 'o_123' });
|
||||
```
|
||||
|
||||
### OpenAPI → typed client (orval / openapi-fetch)
|
||||
```ts
|
||||
// 매 openapi-fetch 의 zero-runtime-overhead typed fetch
|
||||
import createClient from 'openapi-fetch';
|
||||
import type { paths } from './gen/api';
|
||||
const api = createClient<paths>({ baseUrl: 'https://api.example.com' });
|
||||
const { data, error } = await api.GET('/orders/{id}', { params: { path: { id: 'o_123' } } });
|
||||
```
|
||||
|
||||
### GraphQL Federation v3 + codegen
|
||||
```graphql
|
||||
# orders subgraph
|
||||
type Order @key(fields: "id") {
|
||||
id: ID!
|
||||
customerId: ID!
|
||||
total: Money!
|
||||
}
|
||||
extend type Customer @key(fields: "id") {
|
||||
id: ID!
|
||||
orders: [Order!]! @requires(fields: "id")
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
graphql-codegen --config codegen.ts
|
||||
# 매 typed hooks (useOrderQuery) generated.
|
||||
```
|
||||
|
||||
### Avro schema registry (Kafka)
|
||||
```json
|
||||
{
|
||||
"type": "record",
|
||||
"name": "OrderPlaced",
|
||||
"namespace": "com.acme.order.v1",
|
||||
"fields": [
|
||||
{"name": "orderId", "type": "string"},
|
||||
{"name": "customerId", "type": "string"},
|
||||
{"name": "totalCents", "type": "long"},
|
||||
{"name": "occurredAt", "type": {"type": "long", "logicalType": "timestamp-millis"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
import { SchemaRegistry, AvroKafka } from '@kafkajs/confluent-schema-registry';
|
||||
const registry = new SchemaRegistry({ host: 'http://schema-registry:8081' });
|
||||
const id = await registry.register({ type: SchemaType.AVRO, schema });
|
||||
await producer.send({ topic: 'orders', messages: [{ value: await registry.encode(id, payload) }] });
|
||||
```
|
||||
|
||||
### Pact contract test (consumer-driven)
|
||||
```ts
|
||||
import { PactV4, MatchersV3 } from '@pact-foundation/pact';
|
||||
const pact = new PactV4({ consumer: 'web', provider: 'order-svc' });
|
||||
await pact.addInteraction()
|
||||
.uponReceiving('a get order request')
|
||||
.withRequest('GET', '/orders/o_123')
|
||||
.willRespondWith(200, b => b.jsonBody({ id: MatchersV3.string('o_123'), total: MatchersV3.integer(100) }))
|
||||
.executeTest(async (mockServer) => { /* call client against mockServer.url */ });
|
||||
```
|
||||
|
||||
### Runtime boundary validation
|
||||
```ts
|
||||
import { z } from 'zod';
|
||||
const OrderDTO = z.object({ id: z.string(), total: z.number().int().nonnegative() });
|
||||
type Order = z.infer<typeof OrderDTO>;
|
||||
// 매 매 untrusted input 의 parse — 매 type lies 의 catch.
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Polyglot microservices | gRPC / Connect + Buf |
|
||||
| TS-only stack | tRPC |
|
||||
| Public API + many consumers | OpenAPI 3.1 + openapi-fetch |
|
||||
| Many clients merging data | GraphQL Federation v3 |
|
||||
| Event-driven Kafka | Avro/Protobuf + Schema Registry |
|
||||
| Browser-to-server typed | Connect-Web or tRPC |
|
||||
|
||||
**기본값**: Buf + Connect for polyglot; tRPC for TS-only; OpenAPI 3.1 for public REST. Always 매 add Zod/runtime validation at trust boundaries.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Distributed-Systems]] · [[API-Design]]
|
||||
- 변형: [[gRPC]] · [[OpenAPI]]
|
||||
- 응용: [[Microservices]] · [[Event-Driven-Architecture]]
|
||||
- Adjacent: [[Pact]] · [[Zod]] · [[Buf]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 schema migration plan, 매 breaking-change explanation, 매 codegen wiring debug, 매 contract test scaffold.
|
||||
**언제 X**: 매 actual schema authority decision (architecture review). 매 wire-format performance tuning (benchmarks 사용).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **JSON without schema**: 매 매 silent break 의 inevitable.
|
||||
- **Hand-written types matching wire**: 매 drift 의 guaranteed.
|
||||
- **Breaking changes 의 bump major silently**: 매 Buf breaking detector 사용.
|
||||
- **No runtime validation at trust boundary**: 매 type system 의 lie.
|
||||
- **Single global GraphQL gateway 의 monolith**: 매 federation 의 사용.
|
||||
- **Avro 의 forward+backward 가 X**: 매 default field 의 always 의 add.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Buf docs, gRPC docs, GraphQL Federation v3 spec, tRPC docs, Pact specification).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — cross-service type safety: schemas, codegen, runtime validation |
|
||||
@@ -0,0 +1,221 @@
|
||||
---
|
||||
id: wiki-2026-0508-downshift
|
||||
title: Downshift
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [downshift-js, useCombobox, useSelect]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [react, ui, accessibility, combobox, hooks]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: react
|
||||
---
|
||||
|
||||
# Downshift
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 headless UI 의 accessibility-first combobox primitive"**. Kent C. Dodds 의 2017 release, 매 render-prop / hook API 의 ARIA 1.2 combobox spec 의 implement. 매 2026 의 매 alternative 의 많음 (Radix, Headless UI, Ariakit, React Aria) 이지만 매 downshift 의 매 mature / battle-tested / tiny (~5KB).
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 무엇 의 solve
|
||||
- 매 native `<select>` 의 styling 의 unable.
|
||||
- 매 custom dropdown 의 매 keyboard / screen reader / focus management 의 hard.
|
||||
- 매 ARIA roles (`combobox`, `listbox`, `option`) 의 correctly wire.
|
||||
|
||||
### 매 API 종류
|
||||
- `useCombobox` — 매 typeahead 의 search input + dropdown.
|
||||
- `useSelect` — 매 native select replacement (no input).
|
||||
- `useMultipleSelection` — 매 tag / chip selection.
|
||||
- Render-prop `<Downshift>` — 매 legacy, 매 hooks 권장.
|
||||
|
||||
### 매 핵심 state
|
||||
- `isOpen` — dropdown open?
|
||||
- `highlightedIndex` — 매 keyboard hover.
|
||||
- `selectedItem` — 매 chosen value.
|
||||
- `inputValue` — 매 typed text.
|
||||
|
||||
### 매 응용
|
||||
1. Autocomplete / type-ahead search.
|
||||
2. Country / timezone picker.
|
||||
3. Multi-select tag input (with `useMultipleSelection`).
|
||||
4. Async-loading dropdowns.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Pattern 1: Basic useCombobox
|
||||
```tsx
|
||||
import { useCombobox } from "downshift";
|
||||
import { useState } from "react";
|
||||
|
||||
const ALL = ["apple","banana","cherry","date","elderberry"];
|
||||
|
||||
export function FruitCombo() {
|
||||
const [items, setItems] = useState(ALL);
|
||||
const cb = useCombobox({
|
||||
items,
|
||||
onInputValueChange: ({ inputValue }) => {
|
||||
setItems(ALL.filter(f => f.includes(inputValue?.toLowerCase() ?? "")));
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label {...cb.getLabelProps()}>Fruit</label>
|
||||
<input {...cb.getInputProps()} />
|
||||
<ul {...cb.getMenuProps()}>
|
||||
{cb.isOpen && items.map((item, i) => (
|
||||
<li
|
||||
key={item}
|
||||
{...cb.getItemProps({ item, index: i })}
|
||||
style={{ background: cb.highlightedIndex === i ? "#def" : "white" }}
|
||||
>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Async items (debounced)
|
||||
```tsx
|
||||
import { useCombobox } from "downshift";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useDebouncedValue } from "./hooks";
|
||||
|
||||
export function UserPicker() {
|
||||
const [query, setQuery] = useState("");
|
||||
const debounced = useDebouncedValue(query, 250);
|
||||
const [items, setItems] = useState<User[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!debounced) return;
|
||||
fetch(`/api/users?q=${debounced}`).then(r => r.json()).then(setItems);
|
||||
}, [debounced]);
|
||||
|
||||
const cb = useCombobox({
|
||||
items,
|
||||
itemToString: u => u?.name ?? "",
|
||||
onInputValueChange: ({ inputValue }) => setQuery(inputValue ?? ""),
|
||||
});
|
||||
// 매 render UI 동일 패턴.
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 3: Multi-select with chips
|
||||
```tsx
|
||||
import { useCombobox, useMultipleSelection } from "downshift";
|
||||
|
||||
function TagInput({ all }: { all: string[] }) {
|
||||
const ms = useMultipleSelection<string>({});
|
||||
const remaining = all.filter(t => !ms.selectedItems.includes(t));
|
||||
const cb = useCombobox({
|
||||
items: remaining,
|
||||
onSelectedItemChange: ({ selectedItem }) => {
|
||||
if (selectedItem) ms.addSelectedItem(selectedItem);
|
||||
},
|
||||
selectedItem: null,
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
{ms.selectedItems.map((t, i) => (
|
||||
<span key={t} {...ms.getSelectedItemProps({ selectedItem: t, index: i })}>
|
||||
{t} <button onClick={() => ms.removeSelectedItem(t)}>×</button>
|
||||
</span>
|
||||
))}
|
||||
<input {...cb.getInputProps(ms.getDropdownProps())} />
|
||||
{/* menu... */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 4: useSelect (no typing)
|
||||
```tsx
|
||||
import { useSelect } from "downshift";
|
||||
|
||||
function SizeSelect() {
|
||||
const items = ["S","M","L","XL"];
|
||||
const sel = useSelect({ items });
|
||||
return (
|
||||
<>
|
||||
<button {...sel.getToggleButtonProps()}>
|
||||
{sel.selectedItem ?? "Pick size"}
|
||||
</button>
|
||||
<ul {...sel.getMenuProps()}>
|
||||
{sel.isOpen && items.map((s, i) => (
|
||||
<li key={s} {...sel.getItemProps({ item: s, index: i })}>{s}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 5: Controlled state (Redux / Zustand)
|
||||
```tsx
|
||||
const cb = useCombobox({
|
||||
items,
|
||||
selectedItem: store.selected,
|
||||
onSelectedItemChange: ({ selectedItem }) => store.set(selectedItem),
|
||||
inputValue: store.input,
|
||||
onInputValueChange: ({ inputValue }) => store.setInput(inputValue ?? ""),
|
||||
});
|
||||
```
|
||||
|
||||
### Pattern 6: stateReducer for custom keyboard
|
||||
```tsx
|
||||
import { useCombobox } from "downshift";
|
||||
const stateReducer = (state, { type, changes }) => {
|
||||
switch (type) {
|
||||
case useCombobox.stateChangeTypes.InputKeyDownEnter:
|
||||
case useCombobox.stateChangeTypes.ItemClick:
|
||||
// 매 Enter 시 dropdown 의 close 하지 X
|
||||
return { ...changes, isOpen: state.isOpen, highlightedIndex: state.highlightedIndex };
|
||||
default: return changes;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| React + ARIA combobox | downshift |
|
||||
| Full design system (Modal, Tabs, etc.) | Radix UI / Ariakit |
|
||||
| Adobe quality + i18n | React Aria |
|
||||
| Tailwind + simple primitives | Headless UI |
|
||||
| Vue / Svelte | downshift X — find native |
|
||||
|
||||
**기본값**: 매 React combobox 만 필요 시 downshift, 매 design system 전체 시 Radix.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[React]] · [[Headless UI]] · [[Accessibility (A11y)|Accessibility]]
|
||||
- 변형: [[Radix UI]] · [[Ariakit]]
|
||||
- 응용: [[Autocomplete]]
|
||||
- Adjacent: [[Render_Props|Render Props]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 React typeahead / select 의 build, 매 a11y 의 mandatory.
|
||||
**언제 X**: 매 native select 의 충분, 매 React 외 framework.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Custom keyboard 의 reinvent**: 매 stateReducer 로 override 의 의도된 path.
|
||||
- **State sync mistakes**: 매 controlled / uncontrolled 의 mix — 매 하나의 choose.
|
||||
- **getXxxProps 의 spread 의 omit**: 매 ARIA attrs 의 lost — 매 a11y 의 break.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (downshift v9 docs, ARIA 1.2 combobox pattern, GitHub 11k+ stars).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — useCombobox + useSelect + multi-select |
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
---
|
||||
id: wiki-2026-0508-dynamic-systems-development-meth
|
||||
title: Dynamic Systems Development Method (DSDM)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [DSDM, Atern, AgilePF]
|
||||
duplicate_of: none
|
||||
source_trust_level: B
|
||||
confidence_score: 0.85
|
||||
verification_status: applied
|
||||
tags: [agile, methodology, project-management, architecture]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: methodology
|
||||
framework: agile
|
||||
---
|
||||
|
||||
# Dynamic Systems Development Method (DSDM)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 fix the time/cost, flex the features"**. 1994년 UK consortium 의 RAD 의 evolution 의 develop, 매 원조 agile method (Agile Manifesto 2001 보다 7년 앞섬). 매 2026 의 매 niche — 매 SAFe / Scrum / Kanban 의 dominate, 매 DSDM 의 enterprise governance 의 좋은 reference.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 8 Principles
|
||||
1. Focus on the business need.
|
||||
2. Deliver on time.
|
||||
3. Collaborate.
|
||||
4. Never compromise quality.
|
||||
5. Build incrementally from firm foundations.
|
||||
6. Develop iteratively.
|
||||
7. Communicate continuously and clearly.
|
||||
8. Demonstrate control.
|
||||
|
||||
### 매 MoSCoW Prioritization
|
||||
- **Must** have — 매 minimum viable.
|
||||
- **Should** have — 매 important 이지만 매 not vital.
|
||||
- **Could** have — 매 nice-to-have.
|
||||
- **Won't** have (this time) — 매 explicit out-of-scope.
|
||||
|
||||
### 매 Phases
|
||||
1. **Pre-project** — feasibility approval.
|
||||
2. **Feasibility** — high-level scope.
|
||||
3. **Foundations** — business / solution / management approach.
|
||||
4. **Evolutionary Development** — timeboxed iterations (2-6 weeks).
|
||||
5. **Deployment** — release.
|
||||
6. **Post-project** — benefits realization.
|
||||
|
||||
### 매 Roles
|
||||
- **Business Sponsor / Visionary / Ambassador** — 매 business side.
|
||||
- **Technical Coordinator / Solution Developer / Tester** — 매 dev side.
|
||||
- **Project Manager / Team Leader** — 매 facilitation.
|
||||
|
||||
### 매 응용
|
||||
1. Government / regulated projects — 매 audit trail required.
|
||||
2. Fixed-deadline launches — 매 features 의 flex.
|
||||
3. Hybrid waterfall→agile transitions.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Pattern 1: MoSCoW backlog (JSON)
|
||||
```json
|
||||
{
|
||||
"must": [
|
||||
{"id": "AUTH-1", "title": "User login", "effort": 5},
|
||||
{"id": "PAY-1", "title": "Stripe checkout", "effort": 8}
|
||||
],
|
||||
"should": [
|
||||
{"id": "PAY-2", "title": "Saved payment methods", "effort": 5}
|
||||
],
|
||||
"could": [
|
||||
{"id": "UI-1", "title": "Dark mode", "effort": 3}
|
||||
],
|
||||
"wont": [
|
||||
{"id": "PAY-3", "title": "Cryptocurrency", "reason": "out of scope v1"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Timebox tracking
|
||||
```typescript
|
||||
interface Timebox {
|
||||
id: string;
|
||||
name: string;
|
||||
startDate: Date;
|
||||
endDate: Date; // 매 fixed
|
||||
must: Story[]; // 매 100% required
|
||||
should: Story[]; // 매 80% target
|
||||
could: Story[]; // 매 20-60% expected
|
||||
}
|
||||
|
||||
function timeboxHealth(tb: Timebox): "green"|"amber"|"red" {
|
||||
const mustDone = tb.must.filter(s => s.status === "done").length / tb.must.length;
|
||||
if (mustDone < 0.5) return "red";
|
||||
if (mustDone < 1.0) return "amber";
|
||||
return "green";
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 3: Facilitated Workshop agenda
|
||||
```markdown
|
||||
# Foundations Workshop (Day 1)
|
||||
09:00 — Business vision (Sponsor)
|
||||
10:00 — Solution architecture sketch (Tech Coord)
|
||||
11:00 — MoSCoW first pass (all)
|
||||
13:00 — Risk / dependency mapping
|
||||
15:00 — Timebox plan
|
||||
16:30 — Commitment / sign-off
|
||||
```
|
||||
|
||||
### Pattern 4: Modeling — high level only
|
||||
```mermaid
|
||||
flowchart LR
|
||||
User[(User)] --> API
|
||||
API --> Auth
|
||||
API --> Order
|
||||
Order --> Payment[(Stripe)]
|
||||
Order --> DB[(Postgres)]
|
||||
```
|
||||
|
||||
### Pattern 5: Daily Stand-up (DSDM flavor)
|
||||
```
|
||||
Yesterday: what advanced Must/Should items
|
||||
Today: which Must/Should items
|
||||
Blockers: escalate to Project Manager same day
|
||||
Timebox burn: x days remaining / y stories left
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Fixed deadline (regulatory) | DSDM (timebox + MoSCoW) |
|
||||
| Feature-driven product | Scrum |
|
||||
| Continuous flow ops | Kanban |
|
||||
| Large enterprise (>100 devs) | SAFe + DSDM principles |
|
||||
| Modern startup | Scrum-ish or Shape Up |
|
||||
|
||||
**기본값**: 매 Scrum 으로 시작, 매 fixed-deadline 시 DSDM MoSCoW 의 borrow.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Agile]]
|
||||
- 변형: [[Scrum]] · [[Extreme Programming (XP)]]
|
||||
- 응용: [[Timeboxing]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 enterprise / regulated agile 의 reference, 매 MoSCoW 의 prioritization.
|
||||
**언제 X**: 매 modern product team — 매 Scrum / Shape Up 의 simpler.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **MoSCoW 의 abuse**: 매 모두 Must — 매 prioritization 의 lost.
|
||||
- **Timebox 의 extend**: 매 DSDM core 의 violate.
|
||||
- **Heavy documentation**: 매 RAD 의 origin 의 betray.
|
||||
- **No business presence**: 매 Ambassador role 의 essential.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (DSDM Consortium / Agile Business Consortium handbook, ISO/IEC TR 29110).
|
||||
- 신뢰도 B (매 niche method, 매 modern usage 의 limited).
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — 8 principles + MoSCoW + timebox |
|
||||
@@ -0,0 +1,190 @@
|
||||
---
|
||||
id: wiki-2026-0508-e-commerce-platforms
|
||||
title: E-commerce Platforms
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [ecommerce, online-store-platform, headless-commerce]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [ecommerce, shopify, stripe, headless]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: shopify-hydrogen/medusa/stripe
|
||||
---
|
||||
|
||||
# E-commerce Platforms
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 e-commerce 의 매 catalog × cart × checkout × fulfillment × payments × tax + 매 every regional edge case"**. 2026 의 매 SaaS (Shopify, BigCommerce, commercetools), 매 headless (Hydrogen, Medusa.js, Saleor, Vendure), 매 PSP (Stripe, Adyen) 가 dominant — 매 buy-vs-build 의 매 default 의 buy.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 platform tiers
|
||||
1. **All-in-one SaaS** — Shopify, BigCommerce, Wix. 매 lowest TCO.
|
||||
2. **Headless / composable** — Shopify Hydrogen, commercetools, Saleor, Medusa.js, Vendure. 매 custom UX 필요.
|
||||
3. **Self-hosted / OSS** — Medusa, Vendure, Saleor (Apache 2 / MIT).
|
||||
4. **Marketplace SaaS** — Mirakl, Sharetribe.
|
||||
5. **B2B-specific** — Spryker, BigCommerce B2B, commercetools.
|
||||
|
||||
### 매 PSP / payments
|
||||
- **Stripe** — global default, broad APM coverage.
|
||||
- **Adyen** — enterprise / omnichannel.
|
||||
- **PayPal/Braintree, Klarna, Afterpay, Apple/Google Pay** — APMs.
|
||||
- **Crypto / stablecoin** — Coinbase Commerce (niche).
|
||||
|
||||
### 매 cross-cutting concerns
|
||||
- **Tax** — Stripe Tax, Avalara, TaxJar (US sales tax post-Wayfair, EU VAT IOSS, UK VAT).
|
||||
- **Shipping** — ShipStation, Shippo, EasyPost.
|
||||
- **Search** — Algolia, Typesense, Meilisearch.
|
||||
- **CMS** — Sanity, Contentful, Storyblok (PIM/CMS hybrid).
|
||||
- **Fraud** — Stripe Radar, Riskified, Signifyd.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Stripe Checkout (PCI-light)
|
||||
```ts
|
||||
import Stripe from 'stripe';
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET!);
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
mode: 'payment',
|
||||
line_items: [{ price: 'price_123', quantity: 2 }],
|
||||
automatic_tax: { enabled: true },
|
||||
shipping_address_collection: { allowed_countries: ['US', 'GB', 'KR'] },
|
||||
success_url: 'https://shop.example.com/success?cs={CHECKOUT_SESSION_ID}',
|
||||
cancel_url: 'https://shop.example.com/cart',
|
||||
});
|
||||
return Response.redirect(session.url!, 303);
|
||||
```
|
||||
|
||||
### Stripe webhook (idempotent)
|
||||
```ts
|
||||
import { headers } from 'next/headers';
|
||||
export async function POST(req: Request) {
|
||||
const sig = (await headers()).get('stripe-signature')!;
|
||||
const body = await req.text();
|
||||
const event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
|
||||
// 매 idempotency: 매 event.id 의 record, 매 already-processed skip
|
||||
if (await db.processedEvents.has(event.id)) return new Response(null, { status: 200 });
|
||||
switch (event.type) {
|
||||
case 'checkout.session.completed':
|
||||
await fulfillOrder(event.data.object as Stripe.Checkout.Session);
|
||||
break;
|
||||
}
|
||||
await db.processedEvents.insert(event.id);
|
||||
return new Response(null, { status: 200 });
|
||||
}
|
||||
```
|
||||
|
||||
### Shopify Hydrogen (Storefront API)
|
||||
```ts
|
||||
import { storefrontClient } from '~/lib/shopify';
|
||||
const PRODUCT = `#graphql
|
||||
query Product($handle: String!) {
|
||||
product(handle: $handle) {
|
||||
id title descriptionHtml
|
||||
variants(first: 10) { nodes { id price { amount currencyCode } availableForSale } }
|
||||
images(first: 5) { nodes { url altText } }
|
||||
}
|
||||
}`;
|
||||
const { data } = await storefrontClient.request(PRODUCT, { variables: { handle } });
|
||||
```
|
||||
|
||||
### Medusa.js custom backend
|
||||
```ts
|
||||
// backend 의 custom plugin
|
||||
import { OrderService } from '@medusajs/medusa';
|
||||
class FraudGuardService {
|
||||
constructor(private orderService: OrderService) {}
|
||||
async beforeCapture(orderId: string) {
|
||||
const score = await fetch('https://radar/api', { /* … */ }).then(r => r.json());
|
||||
if (score.risk > 0.8) await this.orderService.cancel(orderId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cart state (Server Component + Cookie)
|
||||
```ts
|
||||
import { cookies } from 'next/headers';
|
||||
export async function getCart(): Promise<Cart> {
|
||||
const id = (await cookies()).get('cart_id')?.value;
|
||||
return id ? storefront.cart.get(id) : storefront.cart.create();
|
||||
}
|
||||
```
|
||||
|
||||
### Algolia product search index
|
||||
```ts
|
||||
import { algoliasearch } from 'algoliasearch';
|
||||
const client = algoliasearch(APP_ID, ADMIN_KEY);
|
||||
await client.saveObjects({
|
||||
indexName: 'products',
|
||||
objects: products.map(p => ({
|
||||
objectID: p.id, title: p.title, brand: p.brand, price: p.price.amount,
|
||||
inStock: p.inventory > 0, _tags: p.collections,
|
||||
})),
|
||||
});
|
||||
```
|
||||
|
||||
### Tax (Stripe Tax inline)
|
||||
```ts
|
||||
const tax = await stripe.tax.calculations.create({
|
||||
currency: 'usd',
|
||||
line_items: [{ amount: 5000, reference: 'sku_1', tax_behavior: 'exclusive' }],
|
||||
customer_details: {
|
||||
address: { line1: '...', city: 'Seattle', state: 'WA', postal_code: '98101', country: 'US' },
|
||||
address_source: 'shipping',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### B2B quote → order
|
||||
```ts
|
||||
// commercetools / Vendure pattern
|
||||
const quote = await ct.quotes.create({ customer, lineItems, validUntil: addDays(new Date(), 14) });
|
||||
// 매 buyer accept → order 의 transition
|
||||
const order = await ct.orders.fromQuote(quote.id);
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Lean DTC start | Shopify (basic) + Stripe |
|
||||
| Custom UX, brand priority | Shopify Hydrogen or Saleor + headless CMS |
|
||||
| Self-host / OSS / cost control | Medusa.js or Vendure |
|
||||
| Enterprise composable | commercetools + Algolia + Contentful + Adyen |
|
||||
| Marketplace | Mirakl or Sharetribe |
|
||||
| B2B with quotes/contracts | commercetools / Spryker |
|
||||
|
||||
**기본값**: Shopify for SMB; Hydrogen for premium DTC; commercetools for enterprise composable; Stripe as PSP unless enterprise (then Adyen).
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Web-Architecture]]
|
||||
- 변형: [[Headless-Commerce]]
|
||||
- Adjacent: [[Algolia]] · [[Sanity]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 platform comparison, 매 webhook handler 의 scaffold, 매 GraphQL Storefront query 의 작성, 매 tax/shipping flow explanation, 매 catalog data model design.
|
||||
**언제 X**: 매 PCI-DSS / SCA / regional tax 의 final compliance review (legal + Stripe/Adyen docs). 매 fraud rules in production (data scientist + risk team).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Roll-your-own checkout**: 매 PCI scope explode + tax + APM hell — Stripe/Adyen Checkout 사용.
|
||||
- **No webhook idempotency**: 매 duplicate fulfillment / charge — event.id dedupe.
|
||||
- **Cart in localStorage only**: 매 multi-device 가 broken — server-side cart.
|
||||
- **No automatic_tax**: 매 EU VAT / US Wayfair 의 manual mess.
|
||||
- **Storefront API token in client**: 매 secret leak — 매 server-only token.
|
||||
- **Sync inventory in serverless cold path**: 매 oversell — event-driven inventory + reservation.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Shopify Hydrogen docs, Medusa.js docs, commercetools docs, Stripe docs, Adyen docs).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — e-commerce platform landscape, Shopify/Stripe/headless patterns |
|
||||
@@ -0,0 +1,201 @@
|
||||
---
|
||||
id: wiki-2026-0508-eve-온라인-eve-online
|
||||
title: EVE 온라인(EVE Online)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [EVE Online, EVE, CCP Games]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [game-design, mmo, sandbox, virtual-economy, distributed-systems]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: stackless-python
|
||||
framework: tranquility
|
||||
---
|
||||
|
||||
# EVE 온라인(EVE Online)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 single-shard MMO sandbox 의 player-driven economy 의 22-year proof"**. EVE Online (CCP Games, 2003-) 매 single persistent universe 매 모든 players 의 same world 의 share — 매 player-built corporations, wars, markets, espionage 의 ground truth. Architecture (Stackless Python + StacklessIO + cluster), economy, sandbox design 매 industry case study.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Architecture (Tranquility)
|
||||
- **Single shard**: 매 모든 players 매 one universe — 매 7,800+ star systems.
|
||||
- **Per-system simulation node**: 매 system 매 one node 의 simulate, 매 nodes 매 cluster 의 distribute.
|
||||
- **Stackless Python**: 매 lightweight tasklets 매 thousands 의 ships 의 concurrent simulation.
|
||||
- **Time Dilation (TiDi)**: 매 fleet fight 의 thousands 의 ships 시 매 time slow-down (10%까지) — 매 graceful degradation.
|
||||
|
||||
### 매 Economy
|
||||
- **Player-driven 99%**: 매 모든 ships, modules, structures 매 player-mined / built.
|
||||
- **PLEX**: 매 real-money item 의 in-game ISK 의 trade — 매 secondary market.
|
||||
- **Real economist**: 매 CCP 매 in-house economist (Eyjólfur Guðmundsson) 매 quarterly reports 의 publish — 매 inflation, sink/faucet balance.
|
||||
|
||||
### 매 응용
|
||||
1. Sandbox MMO design reference.
|
||||
2. Distributed simulation scaling case.
|
||||
3. Virtual economy research (papers).
|
||||
4. Emergent player politics — Goonswarm, Test Alliance.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### TiDi-style adaptive tickrate
|
||||
```python
|
||||
class AdaptiveSimulation:
|
||||
def __init__(self, target_fps=10):
|
||||
self.target_dt = 1.0 / target_fps
|
||||
self.tidi = 1.0 # 1.0 = real-time, 0.1 = 10% speed
|
||||
|
||||
def step(self, entities):
|
||||
start = time.monotonic()
|
||||
for e in entities:
|
||||
e.update(self.target_dt * self.tidi)
|
||||
elapsed = time.monotonic() - start
|
||||
# If frame took longer than budget, dilate
|
||||
if elapsed > self.target_dt:
|
||||
self.tidi = max(0.1, self.target_dt / elapsed)
|
||||
else:
|
||||
self.tidi = min(1.0, self.tidi + 0.05)
|
||||
```
|
||||
|
||||
### Per-system node assignment
|
||||
```python
|
||||
SYSTEM_NODES = {} # system_id -> node_id
|
||||
|
||||
def route_player(player, system_id):
|
||||
node_id = SYSTEM_NODES.get(system_id)
|
||||
if node_id is None:
|
||||
node_id = pick_least_loaded_node()
|
||||
SYSTEM_NODES[system_id] = node_id
|
||||
forward_to_node(node_id, player)
|
||||
|
||||
def reinforce_system(system_id, expected_players):
|
||||
"""
|
||||
Pre-fight reinforcement: 매 large fleet 의 expected 시
|
||||
매 system 의 dedicated high-spec node 의 move
|
||||
"""
|
||||
if expected_players > 500:
|
||||
move_system_to_node(system_id, get_dedicated_node())
|
||||
```
|
||||
|
||||
### ISK economy faucet/sink tracking
|
||||
```python
|
||||
@dataclass
|
||||
class EconomicEvent:
|
||||
type: Literal['faucet', 'sink']
|
||||
source: str # 'mission_reward', 'market_fee', 'jump_clone_cost'
|
||||
amount: int # ISK
|
||||
actor_id: str
|
||||
ts: datetime
|
||||
|
||||
def monthly_economic_report(events: list[EconomicEvent]):
|
||||
by_source = defaultdict(lambda: {'in': 0, 'out': 0})
|
||||
for e in events:
|
||||
bucket = 'in' if e.type == 'faucet' else 'out'
|
||||
by_source[e.source][bucket] += e.amount
|
||||
total_in = sum(b['in'] for b in by_source.values())
|
||||
total_out = sum(b['out'] for b in by_source.values())
|
||||
return {
|
||||
'net': total_in - total_out,
|
||||
'inflation_signal': (total_in - total_out) / max(total_out, 1),
|
||||
'top_faucets': sorted(by_source.items(), key=lambda x: -x[1]['in'])[:5],
|
||||
'top_sinks': sorted(by_source.items(), key=lambda x: -x[1]['out'])[:5],
|
||||
}
|
||||
```
|
||||
|
||||
### Market order matching (regional)
|
||||
```python
|
||||
class MarketEngine:
|
||||
def __init__(self):
|
||||
self.buy_orders = defaultdict(list) # item_id -> [orders]
|
||||
self.sell_orders = defaultdict(list)
|
||||
|
||||
def place_buy(self, order):
|
||||
sells = sorted(self.sell_orders[order.item_id], key=lambda o: o.price)
|
||||
remaining = order.quantity
|
||||
for s in sells:
|
||||
if s.price > order.max_price: break
|
||||
qty = min(remaining, s.quantity)
|
||||
self.execute_trade(order.buyer, s.seller, order.item_id, qty, s.price)
|
||||
s.quantity -= qty
|
||||
remaining -= qty
|
||||
if remaining == 0: break
|
||||
if remaining > 0:
|
||||
order.quantity = remaining
|
||||
heapq.heappush(self.buy_orders[order.item_id], order)
|
||||
```
|
||||
|
||||
### Killmail event sourcing
|
||||
```python
|
||||
@dataclass
|
||||
class Killmail:
|
||||
victim_id: str; attackers: list[str]
|
||||
ship_type: str; system_id: str
|
||||
isk_destroyed: int
|
||||
ts: datetime
|
||||
final_blow: str
|
||||
|
||||
# 매 immutable log — 매 zKillboard / EveWho 의 derive
|
||||
def replay_corp_history(killmails: list[Killmail], corp_id: str):
|
||||
losses = sum(k.isk_destroyed for k in killmails if member_of(k.victim_id, corp_id))
|
||||
kills = sum(k.isk_destroyed for k in killmails
|
||||
if any(member_of(a, corp_id) for a in k.attackers))
|
||||
return {'isk_efficiency': kills / max(kills + losses, 1)}
|
||||
```
|
||||
|
||||
### Sovereignty / structure timer
|
||||
```python
|
||||
class StructureTimer:
|
||||
"""매 attacked structure 매 reinforce mode 의 enter — 매 24h timer."""
|
||||
def __init__(self, structure_id):
|
||||
self.id = structure_id
|
||||
self.state = 'anchored'
|
||||
self.timer_end = None
|
||||
|
||||
def take_damage(self, dmg):
|
||||
if self.state == 'anchored' and self.hp_below(50):
|
||||
self.state = 'reinforced'
|
||||
self.timer_end = now() + timedelta(hours=24, minutes=randint(0, 360))
|
||||
elif self.state == 'reinforced' and now() >= self.timer_end:
|
||||
self.state = 'vulnerable'
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Massive single-world MMO | 매 EVE 의 per-system node + TiDi |
|
||||
| Sharded MMO (WoW-style) | 매 EVE pattern 의 not-needed |
|
||||
| Player economy 의 desire | 매 explicit faucet/sink design + economist |
|
||||
| Sandbox vs themepark | 매 EVE-style emergent vs scripted quests |
|
||||
| Anti-griefing | 매 EVE 의 high/low/null-sec gradient |
|
||||
|
||||
**기본값**: 매 single-shard 매 only 매 specific tradeoff 의 worth — 매 most MMOs 매 sharding 의 simpler. Economy 매 EVE pattern 의 broad applicability.
|
||||
|
||||
## 🔗 Graph
|
||||
- 응용: [[Virtual-Economy]]
|
||||
- Adjacent: [[CCP-Games]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 player-economy game design reference. Distributed simulation 매 graceful degradation case. Virtual economy academic study.
|
||||
**언제 X**: 매 non-MMO context. 매 small game 매 EVE complexity 의 over-apply.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **No sinks**: 매 only faucets — 매 hyperinflation. EVE 매 broker fees, jump clone, structure costs 의 sinks.
|
||||
- **Hard server cap**: 매 no TiDi 매 fleet fight 의 disconnect storm. Graceful slow-down 의 prefer.
|
||||
- **Dev intervention 의 economy**: 매 CCP 매 historically 의 hands-off — 매 player corp scams 매 allowed. 매 too much intervention 매 sandbox 의 break.
|
||||
- **Single-shard 의 cargo-cult**: 매 most games 매 single-shard 의 not-need — 매 sharding cost 의 cheaper.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (CCP Games dev blogs; "EVE Online: How to Build a Single-Shard MMO" GDC; Eyjólfur Guðmundsson MER reports).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — EVE architecture + economy + 6 patterns |
|
||||
@@ -0,0 +1,187 @@
|
||||
---
|
||||
id: wiki-2026-0508-enabling-point-활성화-지점
|
||||
title: Enabling Point (활성화 지점)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Activation Point, Enabling Constraint, Trigger Point]
|
||||
duplicate_of: none
|
||||
source_trust_level: B
|
||||
confidence_score: 0.8
|
||||
verification_status: applied
|
||||
tags: [architecture, design, leverage-point, evolutionary-architecture]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: methodology
|
||||
framework: architecture
|
||||
---
|
||||
|
||||
# Enabling Point (활성화 지점)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 작은 architectural seam 의 future change 의 unlock"**. 매 enabling point 의 매 system 의 specific spot — 매 intentionally placed extension hook / abstraction / boundary 의 매 later capability 의 enable. 매 Donella Meadows의 "leverage point" + Neal Ford 의 "evolutionary architecture" 의 fitness function 의 cousin.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 무엇 / 무엇 X
|
||||
- **무엇**: 매 explicit 한 future-flexibility hook (interface / event / config flag / plugin).
|
||||
- **무엇 X**: 매 speculative generality (YAGNI 위반) — 매 enabling point 의 cheap / minimal.
|
||||
|
||||
### 매 indicator
|
||||
- 매 small abstraction 의 매 large optionality 의 unlock.
|
||||
- 매 reversible decision 의 keep — 매 매 one-way door 의 avoid.
|
||||
- 매 cost-of-change 의 grow before it's added.
|
||||
|
||||
### 매 종류
|
||||
1. **Interface seam** — 매 abstraction 의 swap.
|
||||
2. **Event hook** — 매 publish / subscribe.
|
||||
3. **Feature flag** — 매 runtime toggle.
|
||||
4. **Plugin point** — 매 third-party extension.
|
||||
5. **Schema evolution slot** — 매 versioning / optional field.
|
||||
6. **Configuration point** — 매 env / DI binding.
|
||||
|
||||
### 매 응용
|
||||
1. Strangler fig migration — 매 facade 의 enabling point.
|
||||
2. Plugin systems (VSCode, Obsidian, Backstage).
|
||||
3. Multi-tenant SaaS — 매 tenant-scoped extension.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Pattern 1: Interface seam (TypeScript)
|
||||
```typescript
|
||||
// 매 enabling point — payment provider 의 swap
|
||||
interface PaymentProvider {
|
||||
charge(amount: number, token: string): Promise<ChargeResult>;
|
||||
refund(chargeId: string): Promise<void>;
|
||||
}
|
||||
|
||||
class StripeProvider implements PaymentProvider { /* ... */ }
|
||||
class TossProvider implements PaymentProvider { /* ... */ }
|
||||
|
||||
// service 의 매 PaymentProvider 만 의 know
|
||||
class CheckoutService {
|
||||
constructor(private payments: PaymentProvider) {}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Event hook
|
||||
```typescript
|
||||
type Hook<T> = (ctx: T) => Promise<void>;
|
||||
|
||||
class HookRegistry<T> {
|
||||
private hooks: Hook<T>[] = [];
|
||||
register(h: Hook<T>) { this.hooks.push(h); }
|
||||
async fire(ctx: T) { for (const h of this.hooks) await h(ctx); }
|
||||
}
|
||||
|
||||
// 매 enabling point — order placed 시 매 plugin 의 react
|
||||
const orderPlaced = new HookRegistry<{ orderId: string }>();
|
||||
orderPlaced.register(async ({ orderId }) => emailService.confirmation(orderId));
|
||||
orderPlaced.register(async ({ orderId }) => analytics.track("order", orderId));
|
||||
```
|
||||
|
||||
### Pattern 3: Feature flag (LaunchDarkly-style)
|
||||
```typescript
|
||||
import { flags } from "./flags";
|
||||
|
||||
async function checkout(req: CheckoutReq) {
|
||||
if (await flags.isOn("new-checkout-flow", req.userId)) {
|
||||
return newFlow(req);
|
||||
}
|
||||
return legacyFlow(req);
|
||||
}
|
||||
// 매 enabling point — 매 percentage rollout / kill switch / A/B.
|
||||
```
|
||||
|
||||
### Pattern 4: Plugin point
|
||||
```typescript
|
||||
// VSCode-style contribution
|
||||
interface CommandContribution {
|
||||
id: string;
|
||||
title: string;
|
||||
handler(args: unknown): Promise<void>;
|
||||
}
|
||||
|
||||
class PluginHost {
|
||||
private commands = new Map<string, CommandContribution>();
|
||||
register(c: CommandContribution) { this.commands.set(c.id, c); }
|
||||
async execute(id: string, args: unknown) {
|
||||
return this.commands.get(id)?.handler(args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 5: Schema evolution
|
||||
```typescript
|
||||
// 매 v1 → v2 의 enabling point — optional field
|
||||
interface OrderV1 { id: string; items: Item[]; }
|
||||
interface OrderV2 extends OrderV1 {
|
||||
giftMessage?: string; // 매 backward-compatible add
|
||||
shippingMethod?: "standard" | "express";
|
||||
}
|
||||
// 매 readers 의 매 둘 다 의 handle.
|
||||
```
|
||||
|
||||
### Pattern 6: Strangler fig facade
|
||||
```typescript
|
||||
// 매 legacy / new 의 enabling point — facade 의 routing
|
||||
async function getUser(id: string): Promise<User> {
|
||||
if (await isUserMigrated(id)) {
|
||||
return newService.getUser(id);
|
||||
}
|
||||
return legacyService.getUser(id);
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 7: Fitness function (architecture test)
|
||||
```typescript
|
||||
// 매 enabling point 의 의도된 boundary 의 verify
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { extractImports } from "./arch-test";
|
||||
|
||||
describe("architecture", () => {
|
||||
it("domain layer must not import infrastructure", () => {
|
||||
const imports = extractImports("src/domain/**/*.ts");
|
||||
const violations = imports.filter(i => i.includes("/infrastructure/"));
|
||||
expect(violations).toEqual([]);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| 매 evidence 의 future change | enabling point 의 add |
|
||||
| 매 speculative | YAGNI — skip |
|
||||
| One-way door decision | enabling point 의 mandatory |
|
||||
| Reversible decision | 매 simple 의 ship, refactor later |
|
||||
| Plugin ecosystem 의 plan | plugin point 의 first-class |
|
||||
|
||||
**기본값**: 매 evidence-based — 매 2nd 번 같은 change 의 demand 시 enabling point 의 introduce ("Rule of three").
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Software Architecture]]
|
||||
- 응용: [[Feature Flags]]
|
||||
- Adjacent: [[Hexagonal Architecture]] · [[YAGNI]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 architecture review, 매 future change 의 anticipate, 매 refactor planning.
|
||||
**언제 X**: 매 prototype, 매 evidence 의 absent — 매 over-engineering.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Speculative enabling**: 매 "혹시 모를" abstraction — YAGNI.
|
||||
- **Too many hooks**: 매 plugin point 의 abuse — 매 cognitive load.
|
||||
- **Hidden coupling**: 매 enabling point 의 facade 만, 매 internal coupling 의 그대로.
|
||||
- **No fitness function**: 매 enabling point 의 verify X — 매 시간 erode.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Neal Ford "Building Evolutionary Architectures" 2nd ed, M. Feathers "Working Effectively with Legacy Code", Meadows "Thinking in Systems").
|
||||
- 신뢰도 B (매 term 의 community variance).
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — enabling point 종류 + fitness function |
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
category: Architecture
|
||||
tags: [auto-wikified, technical-documentation, architecture]
|
||||
title: Entity (엔티티)
|
||||
description: "엔티티(Entity)는 애플리케이션에서 데이터베이스 모델이나 순수한 비즈니스 규칙을 표현하는 핵심 데이터 구조이다 [1-3]."
|
||||
last_updated: 2026-05-04
|
||||
---
|
||||
|
||||
# Entity (엔티티)
|
||||
|
||||
## 📌 Brief Summary
|
||||
엔티티(Entity)는 애플리케이션에서 데이터베이스 모델이나 순수한 비즈니스 규칙을 표현하는 핵심 데이터 구조이다 [1-3]. 현대 소프트웨어 개발 및 실전 아키텍처에서는 엔티티를 API 입출력을 담당하는 DTO(Data Transfer Object)와 엄격하게 분리하여 관리하는 것을 핵심 패턴으로 삼는다 [1-3]. 이를 통해 데이터베이스 스키마와 외부 API 스펙 간의 결합도를 낮추고 대규모 시스템의 유지보수성을 확보할 수 있다 [2-4].
|
||||
|
||||
## 📖 Core Content
|
||||
* **역할 및 위치:**
|
||||
실무 아키텍처에서 엔티티는 주로 TypeORM, Prisma, Drizzle(NestJS 환경) 또는 JPA(Spring Boot 환경)와 같은 도구의 데코레이터/어노테이션을 통해 데이터베이스 모델로 정의되며, 서비스(Service) 계층에서 사용된다 [2, 5, 6]. 특히 헥사고날 아키텍처(Hexagonal Architecture)에서는 도메인(Domain) 계층에 위치하여 어떠한 외부 프레임워크나 라이브러리에도 의존하지 않는 순수한 형태로 존재해야 한다 [3].
|
||||
* **DTO와의 명확한 분리:**
|
||||
프로젝트 초기에는 엔티티와 DTO의 구조가 매우 유사해 보일 수 있으나, 시간이 지나고 애플리케이션이 확장됨에 따라 두 객체의 구조는 필연적으로 달라지게 된다 [1, 2]. 따라서 엔티티는 데이터베이스 스키마 및 도메인 표현에만 집중하고, DTO는 API를 넘나드는 데이터의 형태를 정의하는 용도로 역할을 분리해야 한다 [1, 2].
|
||||
* **데이터 변환 및 계약 유지:**
|
||||
엔티티를 애플리케이션의 다른 계층이나 외부로 전달할 때는 매퍼(Mapper)를 통해 DTO로 데이터를 변환하는 과정을 거쳐야 한다 [3]. 소비 계층(Consuming layer)에는 인터페이스 계약에 기반한 DTO만 반환함으로써 핵심 엔티티를 격리 및 보호할 수 있다 [4].
|
||||
|
||||
## ⚖️ Trade-offs & Caveats
|
||||
* **직접 노출 시의 위험성 (안티 패턴):**
|
||||
컨트롤러(Controller)에서 엔티티를 클라이언트에게 직접 노출하는 것은 심각한 안티 패턴이다 [2]. 엔티티를 직접 반환하면 내부 데이터베이스 필드 구조가 외부에 그대로 유출될 뿐만 아니라, 애플리케이션이 DB 스키마에 완전히 종속(Lock-in)되게 된다 [2]. 이로 인해 추후 데이터 모델이 변경될 때 외부 API 스펙까지 파괴될 위험이 있으며, API 변경에 막대한 비용이 발생하게 된다 [2, 3].
|
||||
* **보일러플레이트 코드와 복잡성 증가:**
|
||||
엔티티와 DTO를 분리하고 변환하는 패턴은 아키텍처를 견고하게 만들지만, 개발자가 작성하고 유지보수해야 할 보일러플레이트 코드(예: 중복되어 보이는 DTO 클래스 및 매핑 로직)를 증가시킨다는 단점이 있다 [7]. 소규모 팀이나 단순한 프로젝트에서는 이러한 엄격한 분리 구조가 오히려 비즈니스 가치 창출보다 프레임워크 배선(wiring) 작업에 더 많은 비용을 소모하게 만들 수 있다 [7, 8].
|
||||
|
||||
---
|
||||
*Last updated: 2026-05-03*
|
||||
@@ -0,0 +1,196 @@
|
||||
---
|
||||
id: wiki-2026-0508-event-mediator
|
||||
title: Event Mediator
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Mediator Pattern, Event Bus]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [architecture, design-pattern, event-driven, decoupling]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: nodejs
|
||||
---
|
||||
|
||||
# Event Mediator
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 N×N component coupling 의 N×1 mediator hub 의 collapse"**. Event Mediator 매 multiple components 의 direct coupling 의 elimination — 매 components 매 mediator 를 publish/subscribe, 매 mediator 매 routing/orchestration 의 own. GoF Mediator pattern 의 event-driven evolution.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Mediator vs Observer
|
||||
- **Observer**: 매 1 subject → N observers, 매 unidirectional broadcast.
|
||||
- **Mediator**: 매 N senders ↔ N receivers, 매 hub 의 bidirectional routing.
|
||||
- **Event Bus**: 매 Mediator 의 generic implementation — 매 string-keyed event types.
|
||||
|
||||
### 매 동기 vs 비동기
|
||||
- **Sync mediator**: 매 in-process method dispatch — 매 EventEmitter, MediatR.
|
||||
- **Async mediator**: 매 message queue 의 backed — 매 RabbitMQ, Kafka, Redis Streams.
|
||||
- **Hybrid**: 매 in-process sync + cross-service async (e.g., NestJS CQRS).
|
||||
|
||||
### 매 응용
|
||||
1. UI components decoupling — chat rooms, form fields.
|
||||
2. Microservices orchestration — saga coordinator.
|
||||
3. CQRS command/query bus — MediatR.
|
||||
4. Game event systems — unit death, achievement triggers.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Typed Event Bus (TypeScript)
|
||||
```typescript
|
||||
type EventMap = {
|
||||
'user.signup': { userId: string; email: string };
|
||||
'order.placed': { orderId: string; total: number };
|
||||
'cache.invalidate': { key: string };
|
||||
};
|
||||
|
||||
class EventMediator<M> {
|
||||
private handlers = new Map<keyof M, Set<(p: any) => void | Promise<void>>>();
|
||||
|
||||
on<K extends keyof M>(event: K, handler: (payload: M[K]) => void | Promise<void>) {
|
||||
if (!this.handlers.has(event)) this.handlers.set(event, new Set());
|
||||
this.handlers.get(event)!.add(handler);
|
||||
return () => this.handlers.get(event)!.delete(handler);
|
||||
}
|
||||
|
||||
async emit<K extends keyof M>(event: K, payload: M[K]) {
|
||||
const hs = this.handlers.get(event);
|
||||
if (!hs) return;
|
||||
await Promise.all([...hs].map(h => h(payload)));
|
||||
}
|
||||
}
|
||||
|
||||
const bus = new EventMediator<EventMap>();
|
||||
bus.on('user.signup', async ({ userId, email }) => {
|
||||
await sendWelcomeEmail(email);
|
||||
});
|
||||
```
|
||||
|
||||
### Mediator with Request/Response (MediatR-style)
|
||||
```typescript
|
||||
interface IRequest<TResponse> { __response?: TResponse }
|
||||
interface IHandler<TReq extends IRequest<TRes>, TRes> {
|
||||
handle(req: TReq): Promise<TRes>;
|
||||
}
|
||||
|
||||
class Mediator {
|
||||
private handlers = new Map<Function, IHandler<any, any>>();
|
||||
register<T extends IRequest<R>, R>(reqCtor: new (...a: any[]) => T, h: IHandler<T, R>) {
|
||||
this.handlers.set(reqCtor, h);
|
||||
}
|
||||
async send<R>(req: IRequest<R>): Promise<R> {
|
||||
const h = this.handlers.get(req.constructor);
|
||||
if (!h) throw new Error(`No handler for ${req.constructor.name}`);
|
||||
return h.handle(req);
|
||||
}
|
||||
}
|
||||
|
||||
class GetUserQuery implements IRequest<User> { constructor(public id: string) {} }
|
||||
class GetUserHandler implements IHandler<GetUserQuery, User> {
|
||||
async handle(q: GetUserQuery) { return db.users.findById(q.id); }
|
||||
}
|
||||
```
|
||||
|
||||
### Saga Mediator (orchestrated)
|
||||
```typescript
|
||||
class OrderSaga {
|
||||
constructor(private bus: EventMediator<OrderEvents>) {
|
||||
bus.on('order.created', this.onCreated.bind(this));
|
||||
bus.on('payment.failed', this.onPaymentFailed.bind(this));
|
||||
}
|
||||
async onCreated({ orderId }: { orderId: string }) {
|
||||
await this.bus.emit('payment.requested', { orderId });
|
||||
}
|
||||
async onPaymentFailed({ orderId, reason }: any) {
|
||||
await this.bus.emit('order.cancelled', { orderId, reason });
|
||||
await this.bus.emit('inventory.released', { orderId });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Middleware Pipeline
|
||||
```typescript
|
||||
type Middleware<E> = (event: E, next: () => Promise<void>) => Promise<void>;
|
||||
|
||||
class PipelineMediator<E> {
|
||||
private middlewares: Middleware<E>[] = [];
|
||||
use(m: Middleware<E>) { this.middlewares.push(m); }
|
||||
async dispatch(event: E) {
|
||||
let i = -1;
|
||||
const run = async (idx: number): Promise<void> => {
|
||||
if (idx <= i) throw new Error('next() called twice');
|
||||
i = idx;
|
||||
const fn = this.middlewares[idx];
|
||||
if (fn) await fn(event, () => run(idx + 1));
|
||||
};
|
||||
await run(0);
|
||||
}
|
||||
}
|
||||
// usage: logging, validation, retry, dead-letter
|
||||
```
|
||||
|
||||
### Cross-Service Mediator (Kafka)
|
||||
```typescript
|
||||
import { Kafka } from 'kafkajs';
|
||||
|
||||
const kafka = new Kafka({ brokers: ['localhost:9092'] });
|
||||
const producer = kafka.producer();
|
||||
const consumer = kafka.consumer({ groupId: 'order-svc' });
|
||||
|
||||
await consumer.subscribe({ topic: 'orders', fromBeginning: false });
|
||||
await consumer.run({
|
||||
eachMessage: async ({ message }) => {
|
||||
const evt = JSON.parse(message.value!.toString());
|
||||
await handler[evt.type]?.(evt.payload);
|
||||
},
|
||||
});
|
||||
|
||||
await producer.send({
|
||||
topic: 'orders',
|
||||
messages: [{ key: orderId, value: JSON.stringify({ type: 'order.placed', payload }) }],
|
||||
});
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| 2-3 components 의 direct call | 매 mediator skip — 매 over-engineering |
|
||||
| 5+ components, fan-out broadcast | 매 in-process EventEmitter |
|
||||
| CQRS / clean architecture | 매 MediatR-style request bus |
|
||||
| Cross-service, durability | 매 Kafka / RabbitMQ |
|
||||
| Long-running workflow | 매 saga orchestrator (Temporal, Inngest) |
|
||||
|
||||
**기본값**: 매 typed in-process EventEmitter (Node) 또는 MediatR (.NET) — 매 cross-service 의 Kafka.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Design-Patterns]] · [[Event-Driven-Architecture]]
|
||||
- 변형: [[Observer-Pattern]] · [[Pub-Sub]]
|
||||
- 응용: [[CQRS]] · [[Microservices]]
|
||||
- Adjacent: [[Event Sourcing]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 N×N coupling 의 emerge — 매 5+ components 매 mutual callbacks. CQRS / saga implementation.
|
||||
**언제 X**: 매 simple 2-component dependency — 매 direct injection 매 sufficient. Hot-path latency-critical (mediator dispatch overhead).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **God Mediator**: 매 mediator 매 business logic 의 absorb — 매 anemic handlers, 매 mediator 의 1000+ lines. Mediator 의 routing only.
|
||||
- **Event soup**: 매 untyped string events — 매 'user_signup' vs 'userSignup' typo 의 silent failure. Typed map 의 use.
|
||||
- **Sync chain pretending async**: 매 emit() 매 await chain 의 200ms latency — 매 async queue 의 use.
|
||||
- **Lost events**: 매 in-memory bus 매 crash 의 lose — 매 durability 매 required 의 persistent queue.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (GoF Design Patterns; MediatR docs; NestJS CQRS).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Event Mediator pattern 의 typed/saga/Kafka 의 5 patterns |
|
||||
@@ -0,0 +1,195 @@
|
||||
---
|
||||
id: wiki-2026-0508-event-stream-processing
|
||||
title: Event Stream Processing
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Stream Processing, ESP, Streaming Analytics]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.93
|
||||
verification_status: applied
|
||||
tags: [streaming, kafka, flink, real-time, data-engineering]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: polyglot
|
||||
framework: kafka-flink
|
||||
---
|
||||
|
||||
# Event Stream Processing
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 unbounded data 의 매 record-by-record 의 transform"**. 매 batch 의 ETL 의 opposite — 매 event 의 arrive 시점 에 immediately compute. 매 2026 의 매 dominant stack 의 Kafka + Flink (or Kafka Streams), 매 emerging 의 RisingWave / Materialize (streaming SQL DB), 매 cloud-native 의 Confluent Cloud / AWS Kinesis / GCP Dataflow.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 batch vs streaming
|
||||
- **Batch**: 매 hourly/daily, 매 high latency, 매 reprocess easy.
|
||||
- **Streaming**: 매 sub-second, 매 always-on, 매 stateful.
|
||||
- **Lambda architecture**: batch + streaming hybrid (매 deprecated 2026).
|
||||
- **Kappa architecture**: streaming-only, 매 replay 의 reprocess.
|
||||
|
||||
### 매 핵심 개념
|
||||
- **Event time vs processing time** — 매 event 의 produce 시점 vs broker 의 receive.
|
||||
- **Watermark** — 매 "event time T 까지의 모든 event 의 도착 의 expect" 의 signal.
|
||||
- **Window** — tumbling / sliding / session.
|
||||
- **Exactly-once semantics** — Kafka transactions + Flink checkpoints.
|
||||
- **Stateful operator** — 매 RocksDB / state backend.
|
||||
|
||||
### 매 frameworks (2026)
|
||||
- **Apache Flink 1.20** — 매 most powerful, 매 unified batch+streaming, 매 exactly-once.
|
||||
- **Kafka Streams 3.7** — 매 JVM 만, 매 library (no cluster).
|
||||
- **Apache Beam** — 매 portable runner (Flink/Dataflow/Spark).
|
||||
- **RisingWave / Materialize** — 매 streaming SQL DB, 매 incremental view maintenance.
|
||||
- **Bytewax** — 매 Python-native, 매 Rust core.
|
||||
|
||||
### 매 응용
|
||||
1. Real-time fraud detection.
|
||||
2. IoT telemetry aggregation.
|
||||
3. Live dashboards (clickstream).
|
||||
4. CDC → search index sync.
|
||||
5. AI feature stores (real-time).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Pattern 1: Kafka producer (TS)
|
||||
```typescript
|
||||
import { Kafka } from "kafkajs";
|
||||
const kafka = new Kafka({ brokers: ["broker:9092"] });
|
||||
const producer = kafka.producer({ idempotent: true, maxInFlightRequests: 5 });
|
||||
|
||||
await producer.connect();
|
||||
await producer.send({
|
||||
topic: "orders",
|
||||
messages: [{
|
||||
key: order.id,
|
||||
value: JSON.stringify(order),
|
||||
headers: { "event-time": Date.now().toString() },
|
||||
}],
|
||||
});
|
||||
```
|
||||
|
||||
### Pattern 2: Flink DataStream (Java)
|
||||
```java
|
||||
DataStream<Order> orders = env.fromSource(
|
||||
KafkaSource.<Order>builder()
|
||||
.setBootstrapServers("broker:9092")
|
||||
.setTopics("orders")
|
||||
.setDeserializer(new OrderDeserializer())
|
||||
.build(),
|
||||
WatermarkStrategy
|
||||
.<Order>forBoundedOutOfOrderness(Duration.ofSeconds(5))
|
||||
.withTimestampAssigner((o, ts) -> o.eventTime),
|
||||
"kafka-source");
|
||||
|
||||
orders
|
||||
.keyBy(o -> o.userId)
|
||||
.window(TumblingEventTimeWindows.of(Time.minutes(1)))
|
||||
.aggregate(new OrderCountAgg())
|
||||
.sinkTo(new MetricsSink());
|
||||
```
|
||||
|
||||
### Pattern 3: Kafka Streams aggregation
|
||||
```java
|
||||
StreamsBuilder builder = new StreamsBuilder();
|
||||
KStream<String, Order> orders = builder.stream("orders");
|
||||
orders
|
||||
.groupBy((k, v) -> v.userId)
|
||||
.windowedBy(TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(5)))
|
||||
.count(Materialized.as("user-order-count"))
|
||||
.toStream()
|
||||
.to("user-order-counts");
|
||||
```
|
||||
|
||||
### Pattern 4: Streaming SQL (Flink SQL / RisingWave)
|
||||
```sql
|
||||
-- 매 RisingWave / Flink SQL 동일
|
||||
CREATE SOURCE orders (
|
||||
order_id VARCHAR, user_id VARCHAR, amount DOUBLE,
|
||||
event_time TIMESTAMP, WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND
|
||||
) WITH (connector='kafka', topic='orders', properties.bootstrap.servers='broker:9092') FORMAT JSON;
|
||||
|
||||
CREATE MATERIALIZED VIEW user_revenue_5m AS
|
||||
SELECT user_id,
|
||||
window_start, window_end,
|
||||
SUM(amount) AS revenue
|
||||
FROM TUMBLE(orders, event_time, INTERVAL '5' MINUTES)
|
||||
GROUP BY user_id, window_start, window_end;
|
||||
```
|
||||
|
||||
### Pattern 5: Bytewax (Python streaming)
|
||||
```python
|
||||
from bytewax.dataflow import Dataflow
|
||||
from bytewax.connectors.kafka import KafkaSource, KafkaSink
|
||||
from bytewax import operators as op
|
||||
|
||||
flow = Dataflow("fraud-detect")
|
||||
src = op.input("kafka-in", flow, KafkaSource(brokers=["broker:9092"], topics=["tx"]))
|
||||
parsed = op.map("parse", src, lambda kv: json.loads(kv.value))
|
||||
flagged = op.filter("suspicious", parsed, lambda tx: tx["amount"] > 10_000)
|
||||
op.output("kafka-out", flagged, KafkaSink(brokers=["broker:9092"], topic="alerts"))
|
||||
```
|
||||
|
||||
### Pattern 6: CDC stream (Debezium → Kafka → Flink)
|
||||
```yaml
|
||||
# Debezium connector config
|
||||
connector.class: io.debezium.connector.postgresql.PostgresConnector
|
||||
database.hostname: pg
|
||||
database.dbname: app
|
||||
plugin.name: pgoutput
|
||||
table.include.list: public.orders
|
||||
topic.prefix: cdc
|
||||
```
|
||||
|
||||
### Pattern 7: Exactly-once sink (Flink)
|
||||
```java
|
||||
KafkaSink<Order> sink = KafkaSink.<Order>builder()
|
||||
.setBootstrapServers("broker:9092")
|
||||
.setRecordSerializer(KafkaRecordSerializationSchema.builder()
|
||||
.setTopic("processed-orders")
|
||||
.setValueSerializationSchema(new OrderSerializer())
|
||||
.build())
|
||||
.setDeliveryGuarantee(DeliveryGuarantee.EXACTLY_ONCE)
|
||||
.setTransactionalIdPrefix("orders-")
|
||||
.build();
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Sub-second latency, JVM team | Flink |
|
||||
| Lightweight, library-only | Kafka Streams |
|
||||
| Python team, simple jobs | Bytewax |
|
||||
| SQL-only, fast iteration | RisingWave / Materialize |
|
||||
| Multi-runtime portability | Apache Beam |
|
||||
| Cloud-managed | Confluent / Dataflow / Kinesis |
|
||||
|
||||
**기본값**: 매 Kafka + Flink (전통 stack), 매 SQL-only 시 RisingWave.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Data Engineering]] · [[Distributed Systems]]
|
||||
- 응용: [[Feature Store]]
|
||||
- Adjacent: [[Apache Flink]] · [[Event Sourcing]] · [[CQRS]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 real-time pipeline 설계, 매 streaming SQL 의 generate.
|
||||
**언제 X**: 매 daily batch 의 충분, 매 small data 의 cron job.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **No watermark**: 매 late event 의 silently drop or wrong window.
|
||||
- **At-least-once + idempotent missing**: 매 duplicate 의 downstream impact.
|
||||
- **Unbounded state**: 매 keyed state 의 TTL 의 missing — 매 OOM.
|
||||
- **Reorder reliance**: 매 partition 별 only 의 ordering — 매 cross-partition 의 X.
|
||||
- **Synchronous external call inside operator**: 매 backpressure / slow.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Flink docs, Kafka docs, "Streaming Systems" Akidau et al., RisingWave docs).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Flink/Kafka Streams/RisingWave + windowing |
|
||||
@@ -0,0 +1,174 @@
|
||||
---
|
||||
id: wiki-2026-0508-event-storming
|
||||
title: Event Storming
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [EventStorming, DDD discovery workshop]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.92
|
||||
verification_status: applied
|
||||
tags: [ddd, modeling, workshop, architecture, discovery]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: methodology
|
||||
framework: ddd
|
||||
---
|
||||
|
||||
# Event Storming
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 sticky-note 의 도메인 의 explosion"**. Alberto Brandolini 의 2013 invent, 매 domain experts + devs 의 한 방 (혹은 Miro/FigJam) 에 모여 매 orange sticky note (domain event) 의 timeline 의 plot. 매 2026 의 매 distributed workshop tool (Miro AI, FigJam AI) 의 매 LLM-assisted aggregation 의 standard.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 sticky note color convention
|
||||
- 🟧 **Orange** — Domain Event (past tense — "OrderPlaced", "PaymentReceived").
|
||||
- 🟦 **Blue** — Command (intent — "PlaceOrder", "RefundPayment").
|
||||
- 🟨 **Yellow** — Actor / Persona.
|
||||
- 🟪 **Purple** — Policy / Reactive logic ("when X then Y").
|
||||
- 🟩 **Green** — Read Model / View.
|
||||
- 🟥 **Red / Pink** — Hotspot / Issue (매 unclear / disagreement).
|
||||
- ⬜ **White** — Aggregate (매 consistency boundary).
|
||||
- 🟫 **Brown** — External system.
|
||||
|
||||
### 매 3 levels
|
||||
1. **Big Picture** — 매 entire business — 매 chaos exploration, 매 hours.
|
||||
2. **Process Level** — 매 한 process flow — 매 commands / policies / read models.
|
||||
3. **Design Level** — 매 aggregate / bounded context — 매 implementation 의 input.
|
||||
|
||||
### 매 step-by-step (Big Picture)
|
||||
1. **Chaotic exploration** — 매 모두 orange events 의 plaster.
|
||||
2. **Timeline** — 매 left → right 의 sort.
|
||||
3. **Pivotal events** — 매 phase boundary 의 mark.
|
||||
4. **Hotspot identification** — 매 red sticky 의 disagreement.
|
||||
5. **Bounded context** — 매 swimlane 의 split.
|
||||
|
||||
### 매 응용
|
||||
1. Greenfield DDD design — 매 aggregate / bounded context discovery.
|
||||
2. Legacy understanding — 매 domain knowledge 의 surface.
|
||||
3. Microservice decomposition — 매 service boundary 의 inform.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Pattern 1: Miro-export → JSON event log
|
||||
```typescript
|
||||
interface DomainEvent {
|
||||
id: string;
|
||||
name: string; // PascalCase past tense
|
||||
timestamp: number; // 매 column index
|
||||
aggregate?: string;
|
||||
triggeredBy?: string; // command id
|
||||
hotspots: string[];
|
||||
}
|
||||
|
||||
const events: DomainEvent[] = [
|
||||
{ id: "e1", name: "OrderPlaced", timestamp: 1, aggregate: "Order",
|
||||
triggeredBy: "c1", hotspots: [] },
|
||||
{ id: "e2", name: "PaymentReceived", timestamp: 2, aggregate: "Payment",
|
||||
triggeredBy: "c2", hotspots: ["partial-payment-policy"] },
|
||||
];
|
||||
```
|
||||
|
||||
### Pattern 2: Event → TypeScript event type
|
||||
```typescript
|
||||
// 매 sticky 의 code 의 transition
|
||||
export type OrderEvent =
|
||||
| { type: "OrderPlaced"; orderId: string; items: Item[]; placedAt: Date }
|
||||
| { type: "OrderPaid"; orderId: string; paymentId: string }
|
||||
| { type: "OrderShipped"; orderId: string; trackingNo: string }
|
||||
| { type: "OrderCancelled"; orderId: string; reason: string };
|
||||
```
|
||||
|
||||
### Pattern 3: Policy as code
|
||||
```typescript
|
||||
// Purple sticky: "When OrderPaid then schedule shipment"
|
||||
function onOrderPaid(e: Extract<OrderEvent, {type:"OrderPaid"}>) {
|
||||
shipmentService.schedule({ orderId: e.orderId });
|
||||
}
|
||||
eventBus.on("OrderPaid", onOrderPaid);
|
||||
```
|
||||
|
||||
### Pattern 4: Aggregate boundary check
|
||||
```typescript
|
||||
// 매 white sticky 의 invariant
|
||||
class OrderAggregate {
|
||||
private events: OrderEvent[] = [];
|
||||
place(items: Item[]) {
|
||||
if (items.length === 0) throw new Error("empty order");
|
||||
this.events.push({ type: "OrderPlaced", orderId: this.id, items, placedAt: new Date() });
|
||||
}
|
||||
// 매 모든 mutation 의 매 event 의 emit.
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 5: Bounded context map (Mermaid)
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Sales
|
||||
Order
|
||||
Cart
|
||||
end
|
||||
subgraph Billing
|
||||
Payment
|
||||
Invoice
|
||||
end
|
||||
subgraph Logistics
|
||||
Shipment
|
||||
end
|
||||
Order -- "OrderPlaced" --> Payment
|
||||
Payment -- "OrderPaid" --> Shipment
|
||||
```
|
||||
|
||||
### Pattern 6: AI-assisted event extraction (2026)
|
||||
```typescript
|
||||
// 매 transcript / Miro export → event suggestions
|
||||
const prompt = `From this user interview, extract domain events (PascalCase past tense),
|
||||
commands, and hotspots. Output JSON matching: { events:[], commands:[], hotspots:[] }.
|
||||
|
||||
Interview: ${transcript}`;
|
||||
const result = await claude.messages.create({
|
||||
model: "claude-opus-4-7",
|
||||
max_tokens: 4000,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
});
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Greenfield complex domain | Big Picture → Process → Design |
|
||||
| Legacy reverse engineering | Big Picture only |
|
||||
| Microservice split | Process Level + bounded context |
|
||||
| Small CRUD app | Skip — overkill |
|
||||
| Distributed team | Miro / FigJam + AI summarizer |
|
||||
|
||||
**기본값**: 매 complex domain 시 Big Picture (4 hours), 매 implementation 직전 Design Level.
|
||||
|
||||
## 🔗 Graph
|
||||
- 응용: [[Bounded Context]] · [[CQRS]]
|
||||
- Adjacent: [[Event Sourcing]] · [[User-Story-Mapping]] · [[C4 Model (Architecture Documentation)]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 domain discovery, 매 microservice boundary 의 find, 매 onboarding 의 understanding.
|
||||
**언제 X**: 매 trivial CRUD, 매 well-known domain (e.g., todo app).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Tech-first sticky**: 매 "INSERT INTO orders" — 매 domain event 의 X.
|
||||
- **Present tense**: 매 "PlaceOrder" 의 event 의 X — 매 command.
|
||||
- **No business expert**: 매 dev-only — 매 EventStorming purpose 의 lost.
|
||||
- **Skip hotspot**: 매 red sticky 의 ignore — 매 가장 valuable disagreement.
|
||||
- **Premature aggregate**: 매 Big Picture 에서 white sticky 의 too early.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Brandolini "Introducing EventStorming" book 2021, DDD Europe talks).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — sticky color + 3 levels + AI-assisted |
|
||||
@@ -0,0 +1,187 @@
|
||||
---
|
||||
id: wiki-2026-0508-eventual-consistency
|
||||
title: Eventual Consistency
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Eventual Consistency, BASE, AP System]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [distributed-systems, database, consistency, cap]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: sql
|
||||
framework: cassandra
|
||||
---
|
||||
|
||||
# Eventual Consistency
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 update 후 충분한 시간이 지나면 매 모든 replica 가 같은 값에 수렴한다"**. 매 Eventual Consistency는 CAP theorem 의 AP 선택 — strong consistency 의 latency/availability cost 대신 매 staleness 허용. DynamoDB, Cassandra, Riak 의 default model. 2026 globally-distributed system 의 매 default trade-off.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 CAP & PACELC
|
||||
- **CAP**: partition 시 Consistency vs Availability 택 1
|
||||
- **PACELC** (Abadi): partition 없을 때도 Latency vs Consistency
|
||||
- 매 Eventual = AP + EL (Else Latency)
|
||||
- 매 strong consistency 는 quorum write/read latency cost
|
||||
|
||||
### 매 BASE vs ACID
|
||||
- **B**asically **A**vailable: 매 partial failure 시 partial response
|
||||
- **S**oft state: 매 state 는 변할 수 있음 (replica sync)
|
||||
- **E**ventual consistency: 매 시간이 지나면 수렴
|
||||
- 매 ACID와 trade-off — choose per use case
|
||||
|
||||
### 매 응용
|
||||
1. DNS (TTL-based eventual).
|
||||
2. CDN cache invalidation.
|
||||
3. Social media feed (read-your-writes는 보장).
|
||||
4. Shopping cart (Amazon Dynamo paper).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Cassandra tunable consistency
|
||||
```python
|
||||
from cassandra.cluster import Cluster
|
||||
from cassandra import ConsistencyLevel
|
||||
from cassandra.query import SimpleStatement
|
||||
|
||||
cluster = Cluster(['node1', 'node2', 'node3'])
|
||||
session = cluster.connect('myapp')
|
||||
|
||||
# 매 write: ONE = fast, eventual; QUORUM = stronger
|
||||
write = SimpleStatement(
|
||||
"INSERT INTO users (id, name) VALUES (%s, %s)",
|
||||
consistency_level=ConsistencyLevel.QUORUM
|
||||
)
|
||||
session.execute(write, (user_id, name))
|
||||
|
||||
# 매 R + W > N → strong consistency
|
||||
# 매 R=1, W=1, N=3 → eventual
|
||||
```
|
||||
|
||||
### CRDT (G-Counter, conflict-free)
|
||||
```python
|
||||
class GCounter:
|
||||
def __init__(self, node_id: str):
|
||||
self.node_id = node_id
|
||||
self.counts = {node_id: 0}
|
||||
|
||||
def increment(self):
|
||||
self.counts[self.node_id] += 1
|
||||
|
||||
def value(self) -> int:
|
||||
return sum(self.counts.values())
|
||||
|
||||
def merge(self, other: 'GCounter'):
|
||||
# 매 idempotent + commutative + associative
|
||||
for nid, count in other.counts.items():
|
||||
self.counts[nid] = max(self.counts.get(nid, 0), count)
|
||||
```
|
||||
|
||||
### Vector Clock (causal ordering)
|
||||
```python
|
||||
class VectorClock:
|
||||
def __init__(self, node_id):
|
||||
self.node_id = node_id
|
||||
self.clock = {}
|
||||
|
||||
def tick(self):
|
||||
self.clock[self.node_id] = self.clock.get(self.node_id, 0) + 1
|
||||
|
||||
def update(self, other_clock):
|
||||
for nid, ts in other_clock.items():
|
||||
self.clock[nid] = max(self.clock.get(nid, 0), ts)
|
||||
self.tick()
|
||||
|
||||
def happens_before(self, other) -> bool:
|
||||
return all(self.clock.get(k, 0) <= other.clock.get(k, 0)
|
||||
for k in self.clock) and self.clock != other.clock
|
||||
```
|
||||
|
||||
### Read-your-writes (sticky session)
|
||||
```nginx
|
||||
upstream backend {
|
||||
ip_hash; # 매 same client → same backend → reads see own writes
|
||||
server backend1;
|
||||
server backend2;
|
||||
server backend3;
|
||||
}
|
||||
```
|
||||
|
||||
### Last-Write-Wins (DynamoDB style)
|
||||
```python
|
||||
def lww_merge(local: dict, remote: dict) -> dict:
|
||||
if remote['updated_at'] > local['updated_at']:
|
||||
return remote
|
||||
elif remote['updated_at'] < local['updated_at']:
|
||||
return local
|
||||
else:
|
||||
# 매 tie-break by node_id
|
||||
return remote if remote['node_id'] > local['node_id'] else local
|
||||
```
|
||||
|
||||
### Hinted Handoff (Cassandra)
|
||||
```yaml
|
||||
# cassandra.yaml
|
||||
hinted_handoff_enabled: true
|
||||
max_hint_window_in_ms: 10800000 # 매 3 hours
|
||||
# 매 down node 회복 시 hint replay → eventual consistency 보장
|
||||
```
|
||||
|
||||
### Anti-Entropy (Merkle tree sync)
|
||||
```python
|
||||
def merkle_sync(local_tree, remote_tree, path=""):
|
||||
if local_tree.hash == remote_tree.hash:
|
||||
return # 매 subtree identical, skip
|
||||
if local_tree.is_leaf:
|
||||
sync_data(path)
|
||||
return
|
||||
for i, (l, r) in enumerate(zip(local_tree.children, remote_tree.children)):
|
||||
merkle_sync(l, r, path + f"/{i}")
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Consistency |
|
||||
|---|---|
|
||||
| Bank transfer | Strong (linearizable) |
|
||||
| Social feed | Eventual |
|
||||
| Shopping cart | Eventual + LWW |
|
||||
| Counter (likes, views) | Eventual + CRDT |
|
||||
| Configuration / leader election | Strong (Raft, etcd) |
|
||||
| User profile | Read-your-writes |
|
||||
|
||||
**기본값**: 매 eventual + CRDT (counter, set, register). 매 money / lock / unique-id 는 strong.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Distributed Systems]] · [[CAP Theorem & PACELC]]
|
||||
- 변형: [[Strong Consistency]] · [[Read-Your-Writes]]
|
||||
- 응용: [[Cassandra]] · [[CRDT]]
|
||||
- Adjacent: [[Vector Clock]] · [[Quorum]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 system design interview, distributed DB 선택, conflict resolution strategy.
|
||||
**언제 X**: 매 financial transaction, inventory deduction — strong consistency 필요.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **모든 곳에 eventual**: 매 money/lock 도 eventual → 매 double-spend, race.
|
||||
- **Conflict ignore**: 매 LWW만 쓰고 user-visible conflict 무시 → 매 silent data loss.
|
||||
- **No bounded staleness**: 매 sync 영원히 안 됨 → "eventual" 의미 무.
|
||||
- **Vector clock 무한 성장**: 매 GC/pruning 없음 → 매 metadata explosion.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (DeCandia et al., "Dynamo: Amazon's Highly Available Key-value Store", 2007).
|
||||
- Verified (Brewer, CAP Theorem, PODC 2000).
|
||||
- Verified (Vogels, "Eventually Consistent", CACM 2009).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — CAP/BASE + CRDT + Dynamo patterns |
|
||||
@@ -0,0 +1,157 @@
|
||||
---
|
||||
id: wiki-2026-0508-excess-property-checking
|
||||
title: Excess Property Checking
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [TypeScript Excess Property, Strict Object Literal]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [typescript, type-system, structural-typing]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: none
|
||||
---
|
||||
|
||||
# Excess Property Checking
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 fresh object literal 의 only 의 strict property check"**. TypeScript 매 structural typing 매 normally 의 extra properties 의 allow, 매 but 의 fresh object literal 의 directly 의 typed slot 의 assign 시 매 extra properties 의 error 의 raise. Typo 방지 + intent clarity 의 design choice.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Why exists
|
||||
- 매 structural typing 매 `{a:1, b:2}` 의 `{a:number}` 의 assignable.
|
||||
- 매 그러나 매 literal 매 typo 의 high risk — `{ colour: 'red' }` 매 `{ color?: string }` 매 silent ignore.
|
||||
- 매 TS 매 fresh literal 의 special-case — 매 `Object literal may only specify known properties` 의 error.
|
||||
|
||||
### 매 Fresh-ness 매 lost
|
||||
- 매 variable 의 assign 시 매 widened — `const x = { extra: 1 }; fn(x)` 매 ok.
|
||||
- 매 spread 매 fresh-ness 의 keep (TS 4.0+).
|
||||
- 매 type assertion `as T` 매 bypass.
|
||||
- 매 index signature `[k: string]: ...` 매 disable.
|
||||
|
||||
### 매 응용
|
||||
1. React props typo detection.
|
||||
2. Config object validation.
|
||||
3. API request body shape enforcement.
|
||||
4. Discriminated union narrowing aid.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Basic excess error
|
||||
```typescript
|
||||
interface Point { x: number; y: number }
|
||||
const p: Point = { x: 1, y: 2, z: 3 };
|
||||
// ^^^^ Object literal may only specify known properties
|
||||
```
|
||||
|
||||
### Bypass via intermediate variable
|
||||
```typescript
|
||||
const tmp = { x: 1, y: 2, z: 3 };
|
||||
const p: Point = tmp; // OK — fresh-ness lost
|
||||
// Use this only when extra properties are intentional
|
||||
```
|
||||
|
||||
### Index signature 의 opt-out
|
||||
```typescript
|
||||
interface Config {
|
||||
name: string;
|
||||
[key: string]: unknown; // 매 extra props 의 allow
|
||||
}
|
||||
const c: Config = { name: 'x', debug: true, port: 3000 }; // OK
|
||||
```
|
||||
|
||||
### React props 의 typo guard
|
||||
```typescript
|
||||
type ButtonProps = { label: string; onClick: () => void };
|
||||
function Button(props: ButtonProps) { /* ... */ }
|
||||
|
||||
<Button label="Save" onCick={save} />
|
||||
// ^^^^^ Property 'onCick' does not exist
|
||||
// Excess property check catches typo at JSX call site
|
||||
```
|
||||
|
||||
### Discriminated union with strict checks
|
||||
```typescript
|
||||
type Shape =
|
||||
| { kind: 'circle'; radius: number }
|
||||
| { kind: 'square'; side: number };
|
||||
|
||||
const s: Shape = { kind: 'circle', radius: 5, side: 3 };
|
||||
// ^^^^ excess
|
||||
// 매 kind 의 narrow 의 'circle', 매 side 의 not allowed
|
||||
```
|
||||
|
||||
### Spread 의 preserve fresh-ness (TS 4.0+)
|
||||
```typescript
|
||||
type T = { a: number };
|
||||
const base = { a: 1 };
|
||||
const x: T = { ...base, b: 2 }; // 매 error — b 의 excess
|
||||
```
|
||||
|
||||
### Optional excess via Exact type emulation
|
||||
```typescript
|
||||
type Exact<T, U extends T> = T & {
|
||||
[K in Exclude<keyof U, keyof T>]: never;
|
||||
};
|
||||
|
||||
function strict<T>() {
|
||||
return <U extends T>(x: Exact<T, U>): T => x as T;
|
||||
}
|
||||
|
||||
const point = strict<Point>()({ x: 1, y: 2, z: 3 });
|
||||
// ^^^^ Type 'number' is not assignable to 'never'
|
||||
// 매 variable assign path 의 also 의 strict
|
||||
```
|
||||
|
||||
### Util: 매 satisfies operator (TS 4.9+)
|
||||
```typescript
|
||||
const config = {
|
||||
endpoint: 'https://api.example.com',
|
||||
timeout: 5000,
|
||||
retries: 3,
|
||||
} satisfies { endpoint: string; timeout: number };
|
||||
// ^^^^ 매 retries 의 excess error
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Config / DTO literal | 매 default check 의 leverage |
|
||||
| Plugin system 의 unknown extras | 매 index signature 의 add |
|
||||
| Test fixture 의 extra debug fields | 매 intermediate var 또는 `as` |
|
||||
| 매 strict 의 want 의 variable path | 매 `Exact` helper 또는 `satisfies` |
|
||||
| Library author 의 strict API | 매 `satisfies` 또는 nominal brand |
|
||||
|
||||
**기본값**: 매 default behavior 의 leverage — 매 typo 매 catch. 매 escape 매 minimize, 매 `satisfies` 의 prefer.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[TypeScript]] · [[TypeScript 타입 시스템 (TypeScript Type System)|Type-System]]
|
||||
- 변형: [[Structural Typing|Structural-Typing]] · [[Satisfies Operator]]
|
||||
- Adjacent: [[API Response & State Modeling|Discriminated-Unions]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 typo-prone literal — config, props, API body. Schema-bound inputs.
|
||||
**언제 X**: 매 plugin / metadata 의 extra props 매 intentional — 매 index signature 의 add.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **`as T` 의 bypass habit**: 매 error 의 mask, 매 typo 의 ship — 매 fix 의 root cause.
|
||||
- **Index signature 의 over-permissive**: 매 모든 class 매 `[k: string]: any` 의 add — 매 type safety 의 destroy.
|
||||
- **Intermediate var 의 escape**: 매 `const x = {...}; fn(x)` 매 intent 의 obscure — 매 명시적 cast 의 prefer.
|
||||
- **`satisfies` 의 ignore**: 매 TS 4.9+ 매 widening 없는 strict literal 의 best tool — 매 underused.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (TS handbook "Object Types"; TS 4.9 release notes).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — excess property check + satisfies/Exact patterns |
|
||||
@@ -0,0 +1,222 @@
|
||||
---
|
||||
id: wiki-2026-0508-executable-documentation
|
||||
title: Executable Documentation
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Living Documentation, Runnable Docs, Doc Tests]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.92
|
||||
verification_status: applied
|
||||
tags: [documentation, bdd, doctest, testing, ai]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: polyglot
|
||||
framework: docs-as-code
|
||||
---
|
||||
|
||||
# Executable Documentation
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 docs 의 CI 의 run — 매 stale 의 fail"**. 매 docs 의 prose 의 not — 매 examples / scenarios / API specs 의 매 actually-execute. 매 1990s Donald Knuth literate programming + Python doctest 의 origin, 매 2020s Cucumber/BDD 의 popularize, 매 2026 의 매 LLM-assisted doc generation + verification 의 standard practice.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 종류
|
||||
1. **Doctest** — 매 docstring 안의 example 의 run (Python doctest, Rust doctests).
|
||||
2. **BDD** — Gherkin (`Given/When/Then`) 의 step 의 bind (Cucumber, Behave).
|
||||
3. **API docs from spec** — OpenAPI → contract test (Schemathesis, Dredd).
|
||||
4. **Notebook docs** — Jupyter / Quarto / Marimo 의 매 cell 의 run.
|
||||
5. **Markdown code-fence test** — `mdsh`, `mdocc`, `markdown-doctest`.
|
||||
6. **AI-verified docs** — 매 LLM 의 docs 의 read + code 의 inspect + drift 의 detect.
|
||||
|
||||
### 매 왜 valuable
|
||||
- 매 docs 의 truth = code 의 truth.
|
||||
- 매 onboarding example 의 매 always working.
|
||||
- 매 API contract 의 single source of truth.
|
||||
- 매 refactor 시 docs 의 break — 매 catch.
|
||||
|
||||
### 매 응용
|
||||
1. SDK / library docs (매 examples 의 always-correct).
|
||||
2. API contract testing (OpenAPI + Schemathesis).
|
||||
3. BDD acceptance tests (매 PM-readable).
|
||||
4. Tutorial CI (매 step-by-step 의 verify).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Pattern 1: Python doctest
|
||||
```python
|
||||
def fibonacci(n: int) -> int:
|
||||
"""Return the n-th Fibonacci number.
|
||||
|
||||
>>> fibonacci(0)
|
||||
0
|
||||
>>> fibonacci(10)
|
||||
55
|
||||
>>> [fibonacci(i) for i in range(7)]
|
||||
[0, 1, 1, 2, 3, 5, 8]
|
||||
"""
|
||||
a, b = 0, 1
|
||||
for _ in range(n): a, b = b, a + b
|
||||
return a
|
||||
|
||||
# pytest --doctest-modules
|
||||
```
|
||||
|
||||
### Pattern 2: Rust doctest
|
||||
```rust
|
||||
/// Adds two numbers.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use mycrate::add;
|
||||
/// assert_eq!(add(2, 3), 5);
|
||||
/// ```
|
||||
pub fn add(a: i32, b: i32) -> i32 { a + b }
|
||||
|
||||
// cargo test --doc
|
||||
```
|
||||
|
||||
### Pattern 3: Cucumber BDD (TS)
|
||||
```gherkin
|
||||
# features/checkout.feature
|
||||
Feature: Checkout
|
||||
Scenario: Successful purchase
|
||||
Given a cart with 2 items totaling $50
|
||||
And a valid Stripe token "tok_visa"
|
||||
When the user submits checkout
|
||||
Then the order status should be "paid"
|
||||
And a confirmation email is queued
|
||||
```
|
||||
|
||||
```typescript
|
||||
// step defs
|
||||
import { Given, When, Then } from "@cucumber/cucumber";
|
||||
Given("a cart with {int} items totaling ${int}", function (n, total) {
|
||||
this.cart = makeCart(n, total);
|
||||
});
|
||||
When("the user submits checkout", async function () {
|
||||
this.result = await checkout(this.cart, this.token);
|
||||
});
|
||||
Then("the order status should be {string}", function (status) {
|
||||
expect(this.result.status).toBe(status);
|
||||
});
|
||||
```
|
||||
|
||||
### Pattern 4: OpenAPI contract test (Schemathesis)
|
||||
```bash
|
||||
schemathesis run https://api.example.com/openapi.json \
|
||||
--checks all \
|
||||
--hypothesis-deadline 5000
|
||||
# 매 매 endpoint 의 spec 의 conform 의 verify (property-based).
|
||||
```
|
||||
|
||||
### Pattern 5: Markdown code-fence test
|
||||
```typescript
|
||||
// scripts/test-readme.ts
|
||||
import { readFileSync } from "fs";
|
||||
import { execSync } from "child_process";
|
||||
|
||||
const md = readFileSync("README.md", "utf8");
|
||||
const blocks = [...md.matchAll(/```typescript\n([\s\S]*?)\n```/g)];
|
||||
|
||||
for (const [, code] of blocks) {
|
||||
const tmp = `/tmp/snippet-${Date.now()}.ts`;
|
||||
require("fs").writeFileSync(tmp, code);
|
||||
execSync(`tsx ${tmp}`, { stdio: "inherit" });
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 6: Quarto / Marimo notebook
|
||||
```python
|
||||
# 매 marimo notebook — 매 reactive, 매 git-friendly
|
||||
import marimo as mo
|
||||
|
||||
@mo.cell
|
||||
def fetch():
|
||||
import requests
|
||||
return requests.get("https://api.example.com/users").json()
|
||||
|
||||
@mo.cell
|
||||
def show(fetch):
|
||||
return mo.ui.table(fetch)
|
||||
|
||||
# 매 marimo run notebook.py — 매 docs + working app.
|
||||
```
|
||||
|
||||
### Pattern 7: AI-verified doc drift (2026)
|
||||
```typescript
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
const ai = new Anthropic();
|
||||
|
||||
async function checkDocDrift(docPath: string, codeGlob: string) {
|
||||
const docs = readFileSync(docPath, "utf8");
|
||||
const code = await loadFiles(codeGlob);
|
||||
|
||||
const res = await ai.messages.create({
|
||||
model: "claude-opus-4-7",
|
||||
max_tokens: 2000,
|
||||
system: "You verify docs match code. Output JSON: {drifts:[{location, doc_says, code_actually}]}",
|
||||
messages: [{
|
||||
role: "user",
|
||||
content: `DOCS:\n${docs}\n\nCODE:\n${code}\n\nFind drift.`,
|
||||
}],
|
||||
});
|
||||
return JSON.parse(res.content[0].text);
|
||||
}
|
||||
// CI 의 매 fail 의 drift 의 found 시.
|
||||
```
|
||||
|
||||
### Pattern 8: Architecture decision record (ADR) with assertion
|
||||
```markdown
|
||||
# ADR-0042: Use Postgres for primary store
|
||||
|
||||
## Decision
|
||||
PostgreSQL 16 의 single-tenant DB.
|
||||
|
||||
## Verification (run in CI)
|
||||
\`\`\`bash
|
||||
# 매 docs 의 claim 의 verify
|
||||
psql $DATABASE_URL -c "SELECT version();" | grep -q "PostgreSQL 16"
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Library / SDK | doctest (Python/Rust) |
|
||||
| REST API | OpenAPI + Schemathesis |
|
||||
| Acceptance test (PM) | Cucumber BDD |
|
||||
| Tutorial / book | Markdown code-fence test |
|
||||
| Data analysis | Quarto / Marimo |
|
||||
| Architecture docs | AI drift detector |
|
||||
|
||||
**기본값**: 매 README 예제 의 매 CI 의 run, 매 API 의 OpenAPI 의 contract test.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Docs as Code]]
|
||||
- 응용: [[OpenAPI]]
|
||||
- Adjacent: [[Property-Based Testing]] · [[Contract Testing]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 docs drift detection, 매 example generation 의 verify, 매 BDD step 의 generate.
|
||||
**언제 X**: 매 internal scratchpad notes — 매 over-engineering.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Doctest 의 too many**: 매 docstring 의 cluttered — 매 separate test 가 better.
|
||||
- **BDD 의 implementation detail**: 매 Given/When/Then 이 SQL 의 mention — 매 wrong abstraction.
|
||||
- **Spec drift**: 매 OpenAPI 의 stale — 매 generate-from-code or test 의 mandatory.
|
||||
- **No CI**: 매 executable 의 X — 매 prose docs 의 same.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Knuth literate programming, Python doctest stdlib, Cucumber.io, Schemathesis docs, Marimo docs).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — doctest/BDD/OpenAPI/Marimo/AI-drift |
|
||||
@@ -0,0 +1,217 @@
|
||||
---
|
||||
id: wiki-2026-0508-exergaming
|
||||
title: Exergaming
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Exergaming, Exergame, Fitness Gaming, Active Gaming]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [game-design, fitness, vr, ar, health]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: design
|
||||
framework: vr-fitness
|
||||
---
|
||||
|
||||
# Exergaming
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 game mechanic 을 physical exercise 와 결합한다"**. 매 Exergaming은 DDR(1998) 의 dance arcade에서 시작 → Wii Fit(2007) mass-market 진입 → Pokemon Go(2016) AR walking → 매 2026 Quest 3/Vision Pro VR fitness 의 mainstream 시대. 매 sedentary lifestyle 대응 + intrinsic motivation 결합.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Why it works
|
||||
- **Intrinsic motivation**: 매 fun → exercise (vs treadmill 의 외재 동기)
|
||||
- **Flow**: 매 difficulty/skill match — 매 exercise 인지 인식 안 됨
|
||||
- **Social**: 매 multiplayer 가 적정한 peer pressure
|
||||
- **Telemetry feedback**: 매 calorie, heart rate 즉시 가시화
|
||||
|
||||
### 매 Technology stack
|
||||
- **Camera-based** (Kinect, smartphone pose): 매 markerless tracking
|
||||
- **Motion controller** (Wii, Quest, PSVR2): 매 6DOF tracking
|
||||
- **Wearable** (HRM, Apple Watch): 매 heart rate / step
|
||||
- **GPS-based** (Pokemon Go, Zwift): 매 outdoor / indoor cycling
|
||||
|
||||
### 매 응용
|
||||
1. VR fitness (Beat Saber, Supernatural, Les Mills Bodycombat).
|
||||
2. Outdoor AR (Pokemon Go, Zombies Run!).
|
||||
3. Console fitness (Ring Fit Adventure, Just Dance).
|
||||
4. Indoor cycling (Zwift, Peloton + game).
|
||||
5. Rehab exergames (stroke recovery, balance training).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Pose tracking (MediaPipe + Python)
|
||||
```python
|
||||
import cv2
|
||||
import mediapipe as mp
|
||||
|
||||
mp_pose = mp.solutions.pose
|
||||
pose = mp_pose.Pose(min_detection_confidence=0.7)
|
||||
|
||||
cap = cv2.VideoCapture(0)
|
||||
squat_count = 0
|
||||
in_squat = False
|
||||
|
||||
while cap.isOpened():
|
||||
ret, frame = cap.read()
|
||||
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
results = pose.process(rgb)
|
||||
|
||||
if results.pose_landmarks:
|
||||
hip_y = results.pose_landmarks.landmark[24].y
|
||||
knee_y = results.pose_landmarks.landmark[26].y
|
||||
# 매 hip drops below knee = squat
|
||||
if hip_y > knee_y - 0.05 and not in_squat:
|
||||
in_squat = True
|
||||
elif hip_y < knee_y - 0.15 and in_squat:
|
||||
squat_count += 1
|
||||
in_squat = False
|
||||
print(f"매 squat #{squat_count}")
|
||||
```
|
||||
|
||||
### Calorie estimation (METS + HR)
|
||||
```python
|
||||
def estimate_calories(weight_kg: float, duration_min: float,
|
||||
avg_hr: float, age: int, is_male: bool) -> float:
|
||||
# 매 Keytel formula
|
||||
if is_male:
|
||||
cal_per_min = (-55.0969 + 0.6309*avg_hr + 0.1988*weight_kg + 0.2017*age) / 4.184
|
||||
else:
|
||||
cal_per_min = (-20.4022 + 0.4472*avg_hr - 0.1263*weight_kg + 0.074*age) / 4.184
|
||||
return max(0, cal_per_min * duration_min)
|
||||
```
|
||||
|
||||
### Beat Saber-style hit detection (Unity)
|
||||
```csharp
|
||||
public class Saber : MonoBehaviour
|
||||
{
|
||||
public Vector3 prevPos;
|
||||
public float minSpeedToCut = 2f;
|
||||
|
||||
void OnTriggerEnter(Collider block)
|
||||
{
|
||||
var velocity = (transform.position - prevPos) / Time.deltaTime;
|
||||
if (velocity.magnitude < minSpeedToCut) return;
|
||||
|
||||
var blockNormal = block.transform.up;
|
||||
var dot = Vector3.Dot(velocity.normalized, blockNormal);
|
||||
if (dot < -0.7f) { // 매 cutting in correct direction
|
||||
block.GetComponent<Block>().Cut();
|
||||
ScoreSystem.Add(100);
|
||||
}
|
||||
}
|
||||
|
||||
void LateUpdate() { prevPos = transform.position; }
|
||||
}
|
||||
```
|
||||
|
||||
### Heart rate zone (Apple Watch / WatchOS)
|
||||
```swift
|
||||
import HealthKit
|
||||
|
||||
let store = HKHealthStore()
|
||||
let hrType = HKQuantityType.quantityType(forIdentifier: .heartRate)!
|
||||
|
||||
let query = HKAnchoredObjectQuery(type: hrType, predicate: nil,
|
||||
anchor: nil, limit: HKObjectQueryNoLimit) {
|
||||
_, samples, _, _, _ in
|
||||
for sample in samples as? [HKQuantitySample] ?? [] {
|
||||
let bpm = sample.quantity.doubleValue(for: HKUnit(from: "count/min"))
|
||||
let zone = hrZone(bpm: bpm, age: 30)
|
||||
// 매 game difficulty 조정 — zone 4 너무 길면 reduce
|
||||
GameDifficulty.adjust(zone: zone)
|
||||
}
|
||||
}
|
||||
store.execute(query)
|
||||
|
||||
func hrZone(bpm: Double, age: Int) -> Int {
|
||||
let max_hr = 220.0 - Double(age)
|
||||
let pct = bpm / max_hr
|
||||
switch pct {
|
||||
case ..<0.6: return 1
|
||||
case ..<0.7: return 2
|
||||
case ..<0.8: return 3
|
||||
case ..<0.9: return 4
|
||||
default: return 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GPS-based step (React Native)
|
||||
```typescript
|
||||
import Geolocation from '@react-native-community/geolocation';
|
||||
|
||||
let totalDistance = 0;
|
||||
let prev: GeolocationPosition | null = null;
|
||||
|
||||
Geolocation.watchPosition((position) => {
|
||||
if (prev) {
|
||||
const dist = haversine(
|
||||
prev.coords.latitude, prev.coords.longitude,
|
||||
position.coords.latitude, position.coords.longitude
|
||||
);
|
||||
if (dist < 50 && position.coords.speed < 5) { // 매 walking
|
||||
totalDistance += dist;
|
||||
spawnPokemonIfDistanceThreshold(totalDistance);
|
||||
}
|
||||
}
|
||||
prev = position;
|
||||
}, null, { enableHighAccuracy: true, distanceFilter: 5 });
|
||||
```
|
||||
|
||||
### Adaptive difficulty by HR
|
||||
```python
|
||||
def adjust_intensity(current_hr: float, target_zone: tuple,
|
||||
game_difficulty: float) -> float:
|
||||
low, high = target_zone # 매 e.g. (140, 160) for zone 3
|
||||
if current_hr < low:
|
||||
return min(1.0, game_difficulty + 0.1) # 매 ramp up
|
||||
elif current_hr > high:
|
||||
return max(0.1, game_difficulty - 0.15) # 매 cool down
|
||||
return game_difficulty
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Platform |
|
||||
|---|---|
|
||||
| High-intensity cardio | VR (Beat Saber, Supernatural) |
|
||||
| Outdoor walking | AR mobile (Pokemon Go) |
|
||||
| Family casual | Console (Just Dance, Ring Fit) |
|
||||
| Strength training | Wearable + companion app |
|
||||
| Rehab / elderly | Camera-based (low barrier) |
|
||||
| Cycling | Smart trainer + Zwift |
|
||||
|
||||
**기본값**: 매 target heart rate zone 3-4, session 20-45min, gamified streak + progression.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Game Design]]
|
||||
- 변형: [[Active Gaming]]
|
||||
- 응용: [[Beat Saber]]
|
||||
- Adjacent: [[Gamification]] · [[Pose Estimation]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 fitness app design, exercise game mechanic 설계, HR-based difficulty algorithm.
|
||||
**언제 X**: 매 medical-grade rehab — 매 clinical validation 필요, LLM scope 외.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **No safety check**: 매 elderly user 에게 high-intensity 강제 → 매 injury risk.
|
||||
- **Overgamification**: 매 streak 압박이 매 overtraining 유발.
|
||||
- **No rest day**: 매 daily quest 만 있고 recovery 무시.
|
||||
- **Inaccurate calorie**: 매 inflated number → user trust 상실.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Peng et al., "Is playing exergames really exercising?", 2013, systematic review).
|
||||
- Verified (Quest 3 fitness API, Meta Health, 2024).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — VR fitness + Pokemon Go + pose tracking |
|
||||
@@ -0,0 +1,210 @@
|
||||
---
|
||||
id: wiki-2026-0508-experience-sampling-method
|
||||
title: Experience Sampling Method
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [ESM, EMA, Ecological Momentary Assessment, Diary Studies]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [research-methodology, psychology, ux-research, mobile]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: react-native
|
||||
---
|
||||
|
||||
# Experience Sampling Method
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 retrospection bias 의 in-the-moment self-report 의 replace"**. Experience Sampling Method (ESM, Csikszentmihalyi & Larson 1987) 매 participants 의 day 의 multiple times 의 random/scheduled prompt, 매 current activity/affect/context 의 record. Mobile-era 매 EMA (Ecological Momentary Assessment) 의 generalize — 매 mental health, UX, productivity research 의 gold standard.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Why ESM
|
||||
- **Retrospection bias**: 매 "지난주 어땠나" 매 peak-end bias, mood-congruent recall 의 distort.
|
||||
- **Ecological validity**: 매 in-context 매 lab 의 not-replicate.
|
||||
- **Within-subject variance**: 매 person × situation 의 interaction 의 capture.
|
||||
|
||||
### 매 Sampling schedules
|
||||
- **Signal-contingent**: 매 random beep 매 day 매 6-8 prompts.
|
||||
- **Interval-contingent**: 매 fixed times (9am/12pm/3pm/6pm).
|
||||
- **Event-contingent**: 매 specific event (meal, exercise) 의 trigger.
|
||||
- **Hybrid**: 매 baseline random + event triggers.
|
||||
|
||||
### 매 응용
|
||||
1. Mood / affect tracking (depression, bipolar).
|
||||
2. Pain studies (chronic pain).
|
||||
3. UX product research — feature use in-context.
|
||||
4. Flow state research (Csikszentmihalyi original).
|
||||
5. LLM agent behavior tracking — analog 매 process.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Mobile prompt scheduler (React Native)
|
||||
```typescript
|
||||
import * as Notifications from 'expo-notifications';
|
||||
|
||||
interface ESMConfig {
|
||||
startHour: number; endHour: number;
|
||||
promptsPerDay: number;
|
||||
minIntervalMinutes: number;
|
||||
}
|
||||
|
||||
async function schedulePrompts(cfg: ESMConfig, days = 7) {
|
||||
const slots = generateRandomSlots(cfg, days);
|
||||
for (const slot of slots) {
|
||||
await Notifications.scheduleNotificationAsync({
|
||||
content: {
|
||||
title: 'Quick check-in (30s)',
|
||||
body: 'How are you feeling right now?',
|
||||
data: { promptId: slot.id, scheduledFor: slot.time.toISOString() },
|
||||
},
|
||||
trigger: { date: slot.time },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function generateRandomSlots(cfg: ESMConfig, days: number) {
|
||||
const slots = [];
|
||||
for (let d = 0; d < days; d++) {
|
||||
const dayStart = new Date();
|
||||
dayStart.setDate(dayStart.getDate() + d);
|
||||
const windowMs = (cfg.endHour - cfg.startHour) * 3600_000;
|
||||
const minGap = cfg.minIntervalMinutes * 60_000;
|
||||
const times: number[] = [];
|
||||
while (times.length < cfg.promptsPerDay) {
|
||||
const candidate = Math.random() * windowMs;
|
||||
if (times.every(t => Math.abs(t - candidate) >= minGap)) {
|
||||
times.push(candidate);
|
||||
}
|
||||
}
|
||||
times.sort((a, b) => a - b).forEach((offset, i) => {
|
||||
const t = new Date(dayStart);
|
||||
t.setHours(cfg.startHour, 0, 0, 0);
|
||||
t.setTime(t.getTime() + offset);
|
||||
slots.push({ id: `${d}-${i}`, time: t });
|
||||
});
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
```
|
||||
|
||||
### Brief response form (PANAS-short, 30s budget)
|
||||
```typescript
|
||||
interface ESMResponse {
|
||||
promptId: string;
|
||||
respondedAt: Date;
|
||||
latencyMs: number;
|
||||
affect: {
|
||||
valence: number; // -3..+3
|
||||
arousal: number; // -3..+3
|
||||
};
|
||||
activity: string; // dropdown: work | social | rest | exercise | other
|
||||
social: 'alone' | 'with_others';
|
||||
freeText?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Compliance tracking
|
||||
```typescript
|
||||
function complianceMetrics(responses: ESMResponse[], scheduled: number) {
|
||||
const completed = responses.length;
|
||||
const onTime = responses.filter(r => r.latencyMs < 15 * 60_000).length;
|
||||
const meanLatency = responses.reduce((s, r) => s + r.latencyMs, 0) / completed;
|
||||
return {
|
||||
completionRate: completed / scheduled, // target > 0.7
|
||||
onTimeRate: onTime / scheduled, // target > 0.5
|
||||
meanLatencyMin: meanLatency / 60_000,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Multilevel analysis (within vs between)
|
||||
```python
|
||||
import statsmodels.formula.api as smf
|
||||
|
||||
# 매 each row 매 prompt response, 매 participant_id 매 grouping
|
||||
model = smf.mixedlm(
|
||||
'valence ~ activity + social + time_of_day',
|
||||
data=df,
|
||||
groups=df['participant_id'],
|
||||
re_formula='~time_of_day',
|
||||
).fit()
|
||||
print(model.summary())
|
||||
# 매 within-person variance (situation) 매 between-person (trait) 의 separate
|
||||
```
|
||||
|
||||
### Sliding-window mood detection
|
||||
```typescript
|
||||
function detectMoodEpisode(responses: ESMResponse[], windowDays = 7, threshold = -1.5) {
|
||||
const sorted = [...responses].sort((a, b) =>
|
||||
a.respondedAt.getTime() - b.respondedAt.getTime());
|
||||
const episodes = [];
|
||||
for (let i = 0; i < sorted.length; i++) {
|
||||
const start = sorted[i].respondedAt;
|
||||
const end = new Date(start.getTime() + windowDays * 86400_000);
|
||||
const window = sorted.filter(r =>
|
||||
r.respondedAt >= start && r.respondedAt <= end);
|
||||
if (window.length < 5) continue;
|
||||
const meanV = window.reduce((s, r) => s + r.affect.valence, 0) / window.length;
|
||||
if (meanV < threshold) episodes.push({ start, end, meanV, n: window.length });
|
||||
}
|
||||
return mergeOverlapping(episodes);
|
||||
}
|
||||
```
|
||||
|
||||
### Privacy: 매 on-device aggregation
|
||||
```typescript
|
||||
// 매 raw responses 매 device 의 stay, 매 weekly summary 만 의 server 의 send
|
||||
async function uploadWeeklySummary(responses: ESMResponse[]) {
|
||||
const summary = {
|
||||
week: getCurrentWeek(),
|
||||
n: responses.length,
|
||||
valenceMean: mean(responses.map(r => r.affect.valence)),
|
||||
valenceStd: std(responses.map(r => r.affect.valence)),
|
||||
activityHistogram: histogram(responses.map(r => r.activity)),
|
||||
};
|
||||
await api.post('/esm/summary', summary);
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Trait measurement (depression baseline) | 매 ESM unnecessary — 매 single questionnaire 매 fine |
|
||||
| Within-day variation 의 question | 매 ESM signal-contingent |
|
||||
| Specific event 매 rare | 매 event-contingent |
|
||||
| Compliance fragile | 매 prompt count 의 reduce, 매 incentive |
|
||||
| Privacy-sensitive (clinical) | 매 on-device aggregation 또는 federated |
|
||||
|
||||
**기본값**: 매 6-8 prompts/day, 매 14 days, 매 30s response — 매 compliance > 70% 의 target.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Research-Methodology]]
|
||||
- 변형: [[Ecological-Momentary-Assessment]]
|
||||
- 응용: [[Flow_State|Flow-State]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 in-the-moment subjective state 의 measure. Within-person variance 의 study. Retrospective bias 의 likely.
|
||||
**언제 X**: 매 stable trait. 매 single-shot decision study. 매 intrusive sampling 매 acceptable 의 X.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Too many prompts**: 매 12+/day 매 fatigue → compliance crash.
|
||||
- **Long forms**: 매 5min response 매 ecological 의 break.
|
||||
- **Ignoring missing-not-at-random**: 매 prompts during depressive episode 매 skipped — 매 selection bias.
|
||||
- **Cross-sectional analysis 의 hierarchical data**: 매 multilevel model 의 use, 매 OLS 의 std error 의 underestimate.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Csikszentmihalyi & Larson 1987 JNMD; Shiffman et al. 2008 Ann Rev Clin Psych).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — ESM scheduler + analysis + privacy patterns |
|
||||
@@ -0,0 +1,201 @@
|
||||
---
|
||||
id: wiki-2026-0508-exploration-vs-exploitation
|
||||
title: Exploration vs Exploitation
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Explore-Exploit, Multi-Armed Bandit Tradeoff, RL Tradeoff]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [reinforcement-learning, bandits, decision-theory, optimization]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: python
|
||||
framework: numpy
|
||||
---
|
||||
|
||||
# Exploration vs Exploitation
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 known-best 의 exploit 의 unknown 의 explore 의 fundamental tradeoff"**. Exploration-exploitation dilemma 매 RL · bandits · A/B testing 의 core — 매 current best action 의 only 의 take 시 매 better unknown 의 miss, 매 too much explore 시 매 reward 의 burn. Optimal balance 매 horizon, prior, regret budget 의 function.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Spectrum
|
||||
- **Pure exploit (greedy)**: 매 always 매 argmax Q(a) — 매 local optimum trap.
|
||||
- **Pure explore (random)**: 매 always uniform — 매 expected regret O(T).
|
||||
- **ε-greedy**: 매 prob ε 매 explore, 매 prob 1−ε 매 exploit.
|
||||
- **UCB**: 매 confidence-bounded 매 deterministic explore.
|
||||
- **Thompson Sampling**: 매 posterior sampling 매 Bayesian optimal.
|
||||
|
||||
### 매 Regret bounds
|
||||
- 매 ε-greedy(static): O(T).
|
||||
- 매 ε-greedy(decaying 1/t): O(log T).
|
||||
- 매 UCB1: O(log T) — provably tight for stochastic bandit.
|
||||
- 매 Thompson Sampling: matches Lai-Robbins lower bound.
|
||||
|
||||
### 매 응용
|
||||
1. A/B/n testing — adaptive traffic allocation.
|
||||
2. Recommender systems — cold start.
|
||||
3. Hyperparameter tuning (Optuna, Vizier).
|
||||
4. RL games — Atari, AlphaGo MCTS.
|
||||
5. LLM 매 sampling temperature, top-p.
|
||||
6. Drug trials — bandit-style adaptive design.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### ε-greedy bandit
|
||||
```python
|
||||
import numpy as np
|
||||
|
||||
class EpsilonGreedy:
|
||||
def __init__(self, k, eps=0.1):
|
||||
self.k = k
|
||||
self.eps = eps
|
||||
self.Q = np.zeros(k)
|
||||
self.N = np.zeros(k)
|
||||
|
||||
def select(self):
|
||||
if np.random.rand() < self.eps:
|
||||
return np.random.randint(self.k)
|
||||
return int(np.argmax(self.Q))
|
||||
|
||||
def update(self, a, r):
|
||||
self.N[a] += 1
|
||||
self.Q[a] += (r - self.Q[a]) / self.N[a]
|
||||
```
|
||||
|
||||
### UCB1
|
||||
```python
|
||||
class UCB1:
|
||||
def __init__(self, k):
|
||||
self.k, self.t = k, 0
|
||||
self.Q = np.zeros(k)
|
||||
self.N = np.zeros(k)
|
||||
|
||||
def select(self):
|
||||
self.t += 1
|
||||
for a in range(self.k):
|
||||
if self.N[a] == 0:
|
||||
return a # cold-start each arm once
|
||||
ucb = self.Q + np.sqrt(2 * np.log(self.t) / self.N)
|
||||
return int(np.argmax(ucb))
|
||||
|
||||
def update(self, a, r):
|
||||
self.N[a] += 1
|
||||
self.Q[a] += (r - self.Q[a]) / self.N[a]
|
||||
```
|
||||
|
||||
### Thompson Sampling (Bernoulli)
|
||||
```python
|
||||
class ThompsonBernoulli:
|
||||
def __init__(self, k):
|
||||
self.alpha = np.ones(k) # successes + 1
|
||||
self.beta = np.ones(k) # failures + 1
|
||||
|
||||
def select(self):
|
||||
samples = np.random.beta(self.alpha, self.beta)
|
||||
return int(np.argmax(samples))
|
||||
|
||||
def update(self, a, r):
|
||||
if r > 0: self.alpha[a] += 1
|
||||
else: self.beta[a] += 1
|
||||
```
|
||||
|
||||
### Decaying ε schedule
|
||||
```python
|
||||
def epsilon(t, start=1.0, end=0.05, decay=10000):
|
||||
return end + (start - end) * np.exp(-t / decay)
|
||||
|
||||
# DQN-style: 매 early episodes 의 explore-heavy, 매 late 의 exploit
|
||||
```
|
||||
|
||||
### Boltzmann (softmax) exploration
|
||||
```python
|
||||
def softmax_select(Q, tau=1.0):
|
||||
p = np.exp(Q / tau)
|
||||
p /= p.sum()
|
||||
return np.random.choice(len(Q), p=p)
|
||||
# tau→0 매 greedy, tau→∞ 매 uniform
|
||||
```
|
||||
|
||||
### Contextual bandit (LinUCB)
|
||||
```python
|
||||
class LinUCB:
|
||||
def __init__(self, k, d, alpha=1.0):
|
||||
self.A = [np.eye(d) for _ in range(k)]
|
||||
self.b = [np.zeros(d) for _ in range(k)]
|
||||
self.alpha = alpha
|
||||
|
||||
def select(self, x): # context vector
|
||||
ucb = []
|
||||
for a in range(len(self.A)):
|
||||
Ainv = np.linalg.inv(self.A[a])
|
||||
theta = Ainv @ self.b[a]
|
||||
mean = theta @ x
|
||||
bonus = self.alpha * np.sqrt(x @ Ainv @ x)
|
||||
ucb.append(mean + bonus)
|
||||
return int(np.argmax(ucb))
|
||||
|
||||
def update(self, a, x, r):
|
||||
self.A[a] += np.outer(x, x)
|
||||
self.b[a] += r * x
|
||||
```
|
||||
|
||||
### LLM sampling 의 explore-exploit
|
||||
```python
|
||||
# temperature=0 → exploit (deterministic argmax)
|
||||
# temperature=1 → explore (full distribution)
|
||||
# top-p=0.9 → constrained explore (nucleus)
|
||||
def sample_token(logits, temperature=0.7, top_p=0.9):
|
||||
logits = logits / temperature
|
||||
probs = softmax(logits)
|
||||
sorted_idx = np.argsort(probs)[::-1]
|
||||
cum = np.cumsum(probs[sorted_idx])
|
||||
cutoff = np.searchsorted(cum, top_p) + 1
|
||||
keep = sorted_idx[:cutoff]
|
||||
p = probs[keep] / probs[keep].sum()
|
||||
return np.random.choice(keep, p=p)
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Stationary stochastic bandit | 매 UCB1 또는 Thompson |
|
||||
| Bernoulli reward | 매 Thompson Beta-binomial |
|
||||
| Contextual features 의 available | 매 LinUCB / NeuralBandit |
|
||||
| Non-stationary (drift) | 매 sliding-window UCB / discounted TS |
|
||||
| Deep RL | 매 ε-greedy decay 또는 noisy nets |
|
||||
| LLM creative generation | 매 temperature 0.7-1.0 + top-p 0.9 |
|
||||
|
||||
**기본값**: 매 Thompson Sampling — 매 strong empirical 의 winner, 매 simple implementation.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Reinforcement-Learning]] · [[Decision Theory]]
|
||||
- 변형: [[Multi-Armed-Bandit]]
|
||||
- 응용: [[Recommender-Systems]] · [[Hyperparameters|Hyperparameter-Tuning]] · [[MCTS]]
|
||||
- Adjacent: [[Bayesian-Optimization]] · [[Active Learning]] · [[LLM-Sampling]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 sequential decision 매 reward feedback. Cold-start recommender. A/B 의 multi-arm 의 generalize.
|
||||
**언제 X**: 매 known reward distribution + horizon→∞ — 매 closed-form optimal. Single-shot decision.
|
||||
|
||||
## 어려운 점 (안티패턴)
|
||||
- **Static ε too high**: 매 ε=0.5 forever — 매 final 50% traffic 의 random arm 의 burn. Decay 의 use.
|
||||
- **No cold-start arms**: 매 UCB 의 N[a]=0 의 not-handled — 매 inf 의 produce, 매 each arm 의 1 초기 pull 의 require.
|
||||
- **Non-stationarity ignored**: 매 reward drift 의 discount 없이 의 stale Q value 의 trust.
|
||||
- **Reward leakage**: 매 future info 매 leak — 매 fake "exploit" 매 actually 의 cheat.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Sutton & Barto Ch. 2; Lai-Robbins 1985; Russo et al. "Tutorial on Thompson Sampling" 2018).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — explore-exploit + 7 algorithm patterns |
|
||||
@@ -0,0 +1,206 @@
|
||||
---
|
||||
id: wiki-2026-0508-extract-class-클래스-추출하기
|
||||
title: Extract Class (클래스 추출하기)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Extract Class, 클래스 추출, Refactoring]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [refactoring, fowler, srp, oop]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: none
|
||||
---
|
||||
|
||||
# Extract Class (클래스 추출하기)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 too-much-doing class 의 cohesive subset 의 split"**. Extract Class 매 Fowler refactoring catalog 의 core move — 매 single class 매 multiple responsibilities 의 carry, 매 data + methods 의 cohesive cluster 의 new class 의 extract. SRP 의 enforcement.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Smell signals
|
||||
- 매 class 매 200+ lines 의 grow.
|
||||
- 매 fields 의 subset 매 always 의 used together (e.g., `street/city/zip` 의 always together).
|
||||
- 매 methods 매 disjoint groups 의 form — 매 group A methods 매 group A fields only 의 use.
|
||||
- 매 class name 매 vague — `Manager`, `Helper`, `Util`.
|
||||
|
||||
### 매 Mechanics (Fowler)
|
||||
1. 매 responsibility 의 identify — 매 split point.
|
||||
2. 매 new child class 의 create.
|
||||
3. 매 parent → child reference 의 establish.
|
||||
4. 매 fields 의 move (Move Field).
|
||||
5. 매 methods 의 move (Move Method) — 매 leaves first.
|
||||
6. 매 child interface 의 review — 매 internal exposure 의 minimize.
|
||||
7. 매 expose: 매 child 의 public 의 또는 parent-only 의 decision.
|
||||
|
||||
### 매 응용
|
||||
1. Person → Person + TelephoneNumber.
|
||||
2. Order → Order + ShippingAddress + BillingAddress.
|
||||
3. UserService → UserService + AuthService + ProfileService.
|
||||
4. GameEntity → Position + Velocity + Health (ECS).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Before — God class
|
||||
```typescript
|
||||
class Person {
|
||||
name: string;
|
||||
officeAreaCode: string;
|
||||
officeNumber: string;
|
||||
|
||||
getTelephoneNumber(): string {
|
||||
return `(${this.officeAreaCode}) ${this.officeNumber}`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### After — Extract Class
|
||||
```typescript
|
||||
class TelephoneNumber {
|
||||
constructor(public areaCode: string, public number: string) {}
|
||||
toString(): string { return `(${this.areaCode}) ${this.number}`; }
|
||||
}
|
||||
|
||||
class Person {
|
||||
constructor(
|
||||
public name: string,
|
||||
private officeTelephone: TelephoneNumber,
|
||||
) {}
|
||||
get telephoneNumber(): string { return this.officeTelephone.toString(); }
|
||||
// delegate accessors during transition
|
||||
get officeAreaCode() { return this.officeTelephone.areaCode; }
|
||||
set officeAreaCode(v: string) { this.officeTelephone.areaCode = v; }
|
||||
}
|
||||
```
|
||||
|
||||
### Address extraction
|
||||
```typescript
|
||||
// Before
|
||||
class Order {
|
||||
customerName: string;
|
||||
street: string; city: string; zipCode: string; country: string;
|
||||
shippingCost: number;
|
||||
}
|
||||
|
||||
// After
|
||||
class Address {
|
||||
constructor(
|
||||
public street: string,
|
||||
public city: string,
|
||||
public zipCode: string,
|
||||
public country: string,
|
||||
) {}
|
||||
format(): string { return `${this.street}, ${this.city} ${this.zipCode}, ${this.country}`; }
|
||||
isInternational(home: string) { return this.country !== home; }
|
||||
}
|
||||
|
||||
class Order {
|
||||
constructor(
|
||||
public customerName: string,
|
||||
public shippingAddress: Address,
|
||||
public shippingCost: number,
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
### Service split (SRP)
|
||||
```typescript
|
||||
// Before — 600 LOC
|
||||
class UserService {
|
||||
signup() { /* ... */ }
|
||||
login() { /* ... */ }
|
||||
resetPassword() { /* ... */ }
|
||||
updateProfile() { /* ... */ }
|
||||
uploadAvatar() { /* ... */ }
|
||||
getPreferences() { /* ... */ }
|
||||
}
|
||||
|
||||
// After
|
||||
class AuthService {
|
||||
signup() {} login() {} resetPassword() {}
|
||||
}
|
||||
class ProfileService {
|
||||
updateProfile() {} uploadAvatar() {} getPreferences() {}
|
||||
}
|
||||
// UserService 의 orchestration only 의 keep
|
||||
```
|
||||
|
||||
### ECS-style field group extraction
|
||||
```typescript
|
||||
// Before — game entity 의 carries everything
|
||||
class Enemy {
|
||||
x: number; y: number; vx: number; vy: number;
|
||||
hp: number; maxHp: number; armor: number;
|
||||
spriteId: string; animFrame: number;
|
||||
}
|
||||
|
||||
// After — components
|
||||
class Position { constructor(public x: number, public y: number) {} }
|
||||
class Velocity { constructor(public vx: number, public vy: number) {} }
|
||||
class Health { constructor(public hp: number, public maxHp: number, public armor = 0) {} }
|
||||
class Sprite { constructor(public id: string, public frame = 0) {} }
|
||||
|
||||
class Enemy {
|
||||
constructor(
|
||||
public position: Position,
|
||||
public velocity: Velocity,
|
||||
public health: Health,
|
||||
public sprite: Sprite,
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
### Incremental migration via delegation
|
||||
```typescript
|
||||
class Person {
|
||||
private _phone = new TelephoneNumber('', '');
|
||||
// Old API 의 keep working
|
||||
get officeNumber() { return this._phone.number; }
|
||||
set officeNumber(v: string) { this._phone.number = v; }
|
||||
// New API 의 prefer
|
||||
get phone() { return this._phone; }
|
||||
}
|
||||
// Phase 1: delegate. Phase 2: callers migrate to .phone. Phase 3: remove old getters.
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Class < 100 LOC, single concept | 매 extract 매 X — 매 premature |
|
||||
| Field subset always 의 together | 매 Extract Class |
|
||||
| Two distinct responsibilities | 매 Extract Class + Move Method |
|
||||
| Component reuse 의 needed (game) | 매 ECS-style components 의 extract |
|
||||
| Public API 의 break 의 dangerous | 매 delegating accessors 의 transition |
|
||||
|
||||
**기본값**: 매 class 매 2 의 distinct responsibility 의 carry 의 extract — 매 cohesion > 1 class.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Refactoring_Best_Practices|Refactoring]] · [[SOLID]]
|
||||
- 응용: [[Single Responsibility Principle (SRP)|Single-Responsibility-Principle]] · [[Entity-Component-System]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 class 매 200+ LOC, 매 multiple distinct concerns. Field cluster 의 always 의 co-occur. Test setup 매 unrelated mocks 의 require.
|
||||
**언제 X**: 매 small DTO. 매 abstraction 의 cost > readability gain (매 over-extraction).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Anemic extraction**: 매 fields only 의 move, 매 methods 매 parent 의 stay — 매 just data bag 의 result. Methods 의 follow 의 must.
|
||||
- **Reverse coupling**: 매 child 매 parent 의 reference back — 매 cyclic dependency. One-way reference 의 keep.
|
||||
- **Premature extract**: 매 50 LOC class 의 split — 매 navigation overhead > clarity.
|
||||
- **Public sprawl**: 매 child 의 immediately public — 매 hide 매 first, 매 expose 의 demand 의 occur 시.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Fowler "Refactoring" 2nd ed., Ch. 7).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Extract Class refactoring 의 mechanics + 5 patterns |
|
||||
@@ -0,0 +1,163 @@
|
||||
---
|
||||
id: wiki-2026-0508-extract-method-함수-추출하기
|
||||
title: Extract Method (함수 추출하기)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Extract Function, 함수 추출]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [refactoring, clean-code, method]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: refactoring
|
||||
---
|
||||
|
||||
# Extract Method (함수 추출하기)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 code fragment 가 의도를 가질 때, 매 그 의도를 이름으로 만든다"**. 매 Fowler *Refactoring* (1999, 2018) 의 매 most-used refactoring — 매 long method 를 작은 well-named function 으로 쪼개 매 readability + reusability 의 동시 확보. 2026 IDE (IntelliJ, VS Code, Cursor) 의 자동 refactor 로 매 cost 가 사실상 0.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 trigger
|
||||
- 매 method 가 한 화면을 넘는다.
|
||||
- 매 comment 로 "이 부분은 X 한다" 라고 설명하고 있다 → 매 그 X 가 method name.
|
||||
- 매 동일 logic 의 duplication.
|
||||
- 매 nested conditional 의 가독성 hurt.
|
||||
|
||||
### 매 mechanic (Fowler)
|
||||
1. 매 새 함수 의 create — intent-revealing name.
|
||||
2. 매 fragment 를 새 함수로 copy.
|
||||
3. 매 local variable 의 scope 분석 → parameter / return value 결정.
|
||||
4. 매 원래 자리에 call 로 replace.
|
||||
5. 매 test 실행 (매 step 마다).
|
||||
|
||||
### 매 응용
|
||||
1. Long Method 의 fix.
|
||||
2. Duplicate Code 의 removal.
|
||||
3. Replace Temp with Query 의 stepping stone.
|
||||
4. 매 LLM-assisted refactor 의 safest primitive.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Before (long method)
|
||||
```ts
|
||||
function printOwing(invoice: Invoice) {
|
||||
let outstanding = 0;
|
||||
console.log("***********************");
|
||||
console.log("**** Customer Owes ****");
|
||||
console.log("***********************");
|
||||
|
||||
for (const o of invoice.orders) outstanding += o.amount;
|
||||
|
||||
const today = new Date();
|
||||
invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
|
||||
|
||||
console.log(`name: ${invoice.customer}`);
|
||||
console.log(`amount: ${outstanding}`);
|
||||
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
|
||||
}
|
||||
```
|
||||
|
||||
### After (extracted)
|
||||
```ts
|
||||
function printOwing(invoice: Invoice) {
|
||||
printBanner();
|
||||
const outstanding = calculateOutstanding(invoice);
|
||||
recordDueDate(invoice);
|
||||
printDetails(invoice, outstanding);
|
||||
}
|
||||
|
||||
function printBanner() {
|
||||
console.log("***********************");
|
||||
console.log("**** Customer Owes ****");
|
||||
console.log("***********************");
|
||||
}
|
||||
|
||||
function calculateOutstanding(invoice: Invoice): number {
|
||||
return invoice.orders.reduce((sum, o) => sum + o.amount, 0);
|
||||
}
|
||||
|
||||
function recordDueDate(invoice: Invoice) {
|
||||
const today = new Date();
|
||||
invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
|
||||
}
|
||||
|
||||
function printDetails(invoice: Invoice, outstanding: number) {
|
||||
console.log(`name: ${invoice.customer}`);
|
||||
console.log(`amount: ${outstanding}`);
|
||||
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Extract with multiple return (TS tuple)
|
||||
```ts
|
||||
function summarize(xs: number[]): { sum: number; avg: number } {
|
||||
return { sum: total(xs), avg: total(xs) / xs.length };
|
||||
}
|
||||
function total(xs: number[]) { return xs.reduce((a, b) => a + b, 0); }
|
||||
```
|
||||
|
||||
### Extract from conditional (Decompose Conditional)
|
||||
```ts
|
||||
// before
|
||||
if (date.before(SUMMER_START) || date.after(SUMMER_END)) charge = qty * winterRate + winterFee;
|
||||
else charge = qty * summerRate;
|
||||
|
||||
// after
|
||||
charge = isSummer(date) ? summerCharge(qty) : winterCharge(qty);
|
||||
function isSummer(d: Date) { return !d.before(SUMMER_START) && !d.after(SUMMER_END); }
|
||||
```
|
||||
|
||||
### IDE shortcut (VS Code / Cursor)
|
||||
```
|
||||
Select code → Cmd+. → "Extract to function in module scope"
|
||||
# 매 zero-cost — 매 Fowler 가 1999 에 손으로 하던 것
|
||||
```
|
||||
|
||||
### LLM-assisted (Claude Code)
|
||||
```
|
||||
"이 함수에서 outstanding 계산 부분을 calculateOutstanding 으로 extract"
|
||||
→ 매 LLM 이 mechanic 의 1-5 를 자동 수행 + test 보존 검증
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Fragment 에 의도 있음 | Extract Method |
|
||||
| Single expression with confusing name | Extract Variable |
|
||||
| Class 가 너무 비대 | Extract Class (after multiple Extract Method) |
|
||||
| 매 한 번만 쓰이고 trivial | 매 inline 유지 — 매 over-extraction 회피 |
|
||||
|
||||
**기본값**: 매 의심되면 extract — 매 IDE 가 자동, 매 inline 으로 되돌리기도 trivial.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Refactoring_Best_Practices|Refactoring]] · [[Clean Code]]
|
||||
- 변형: [[Extract Class]]
|
||||
- Adjacent: [[Inline Method]] (매 inverse) · [[Single Responsibility]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 long method / duplicated block 발견 시 즉시 propose.
|
||||
**언제 X**: 매 hot path 의 micro-optimization (function call overhead) — 매 측정 기반.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Over-extraction**: 매 1-2 line trivial 도 extract → 매 indirection 폭발.
|
||||
- **Bad name**: `helper1`, `doStuff` — 매 의도 의 표현 실패.
|
||||
- **Hidden side effect**: 매 pure name 으로 보이는데 매 mutation 수행.
|
||||
- **Premature extraction without test**: 매 mechanic step 5 건너뛰기 → silent regression.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Fowler *Refactoring* 2nd ed. 2018 Ch. 6, Beck *Tidy First?* 2024).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Fowler mechanic + modern IDE/LLM workflow 정리 |
|
||||
@@ -0,0 +1,180 @@
|
||||
---
|
||||
id: wiki-2026-0508-fomo-fear-of-missing-out
|
||||
title: FOMO (Fear of Missing Out)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [FOMO, Fear of Missing Out, 포모]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [game-design, monetization, psychology, behavioral]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: design
|
||||
framework: live-ops
|
||||
---
|
||||
|
||||
# FOMO (Fear of Missing Out)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 놓칠지 모른다는 불안이 행동을 유발한다"**. 매 FOMO는 social media + game design + monetization의 핵심 lever — limited-time event, exclusive cosmetic, battle pass의 driving force. 2026 mobile gaming revenue의 60%+가 매 FOMO mechanic 기반.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 심리학 base
|
||||
- **Loss aversion** (Kahneman): 매 loss는 gain보다 2x painful
|
||||
- **Social comparison** (Festinger): 매 peer가 가진 것 → 매 desire
|
||||
- **Scarcity heuristic**: 매 rare = valuable 의 자동 추론
|
||||
- 매 FOMO는 매 evolved survival instinct (놓치면 자원 부족)의 hijack
|
||||
|
||||
### 매 Game Design lever
|
||||
- **Time-gated content**: 매 event 7일, 매 daily login bonus
|
||||
- **Limited cosmetics**: 매 "이 skin은 다시는 안 나옴" claim
|
||||
- **Battle pass FOMO**: 매 시즌 끝나면 보상 영원히 lost
|
||||
- **Push notification**: 매 "친구가 1위!" — direct social FOMO
|
||||
|
||||
### 매 응용
|
||||
1. Fortnite item shop (24h rotation).
|
||||
2. Genshin Impact limited banner (5-star character 일정 기간만).
|
||||
3. Duolingo streak (매 끊기면 손실감).
|
||||
4. Instagram/TikTok stories (24h disappearance).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Limited-Time Event Logic (Unity C#)
|
||||
```csharp
|
||||
public class LimitedEvent : MonoBehaviour
|
||||
{
|
||||
public DateTime startUtc;
|
||||
public DateTime endUtc;
|
||||
public Reward exclusiveReward;
|
||||
|
||||
void Update()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if (now < startUtc) ShowCountdown(startUtc - now, "starts in");
|
||||
else if (now < endUtc) ShowCountdown(endUtc - now, "ends in");
|
||||
else HideEvent();
|
||||
}
|
||||
|
||||
void ShowCountdown(TimeSpan ts, string prefix)
|
||||
{
|
||||
// 매 urgency UI: red text, ticking sound
|
||||
countdownText.text = $"{prefix} {ts.Hours}h {ts.Minutes}m";
|
||||
countdownText.color = ts.TotalHours < 1 ? Color.red : Color.white;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Battle Pass Tier (TypeScript)
|
||||
```typescript
|
||||
interface BattlePass {
|
||||
seasonEndUtc: Date;
|
||||
tiers: Tier[];
|
||||
userXP: number;
|
||||
}
|
||||
|
||||
function unclaimedRewards(bp: BattlePass): number {
|
||||
const currentTier = Math.floor(bp.userXP / 1000);
|
||||
return bp.tiers
|
||||
.slice(0, currentTier + 1)
|
||||
.filter(t => !t.claimed && !t.locked)
|
||||
.length;
|
||||
}
|
||||
|
||||
// 매 push notification trigger
|
||||
if (unclaimedRewards(bp) > 0 && hoursUntilEnd(bp) < 48) {
|
||||
sendPush(`${unclaimedRewards(bp)} 보상이 곧 사라집니다!`);
|
||||
}
|
||||
```
|
||||
|
||||
### Streak Reset Warning (Python)
|
||||
```python
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
def check_streak_at_risk(user):
|
||||
last_active = user.last_login
|
||||
now = datetime.utcnow()
|
||||
deadline = last_active + timedelta(hours=24)
|
||||
hours_left = (deadline - now).total_seconds() / 3600
|
||||
|
||||
if 0 < hours_left < 4 and user.streak_days >= 7:
|
||||
send_notification(
|
||||
user,
|
||||
f"매 {user.streak_days}일 연속 기록이 {int(hours_left)}시간 후 끊깁니다"
|
||||
)
|
||||
```
|
||||
|
||||
### Gacha Pity System (showing scarcity)
|
||||
```python
|
||||
class GachaBanner:
|
||||
def __init__(self):
|
||||
self.pity_counter = 0
|
||||
self.featured_5star_rate = 0.006
|
||||
self.hard_pity = 90
|
||||
|
||||
def pull(self) -> Item:
|
||||
self.pity_counter += 1
|
||||
if self.pity_counter >= self.hard_pity:
|
||||
self.pity_counter = 0
|
||||
return self.featured_5star
|
||||
if random.random() < self.featured_5star_rate:
|
||||
self.pity_counter = 0
|
||||
return self.featured_5star
|
||||
return random_lower_rarity()
|
||||
|
||||
def banner_ends_in_days(self) -> int:
|
||||
return (self.end_date - datetime.now()).days
|
||||
```
|
||||
|
||||
### Social FOMO Push (FCM)
|
||||
```python
|
||||
def notify_friend_milestone(user_id: int, friend_id: int, achievement: str):
|
||||
user = get_user(user_id)
|
||||
friend = get_user(friend_id)
|
||||
fcm.send(
|
||||
token=user.fcm_token,
|
||||
title=f"{friend.name}님이 {achievement} 달성!",
|
||||
body="매 친구를 따라잡으세요",
|
||||
data={"deeplink": f"profile/{friend_id}"}
|
||||
)
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | FOMO 강도 |
|
||||
|---|---|
|
||||
| Casual mobile game | Low — soft daily login bonus |
|
||||
| Live-service multiplayer | Medium — battle pass, seasonal events |
|
||||
| Gacha / collection RPG | High — limited banners, time-gated chars |
|
||||
| Premium single-player | None — ethical: 매 player time respect |
|
||||
|
||||
**기본값**: 매 ethical FOMO (notify, don't manipulate). 매 dark pattern 회피 — regulatory risk (EU, Belgium loot box bans).
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Game Design]]
|
||||
- 변형: [[Loss Aversion]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 retention strategy 설계, event scheduling, push notification copy 생성.
|
||||
**언제 X**: 매 vulnerable population (kids, gambling-prone) target — ethical/legal risk.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Predatory FOMO**: 매 minor / addiction-prone 대상 dark pattern → 매 lawsuit, regulation.
|
||||
- **Notification spam**: 매 매일 5+ FOMO push → 매 uninstall.
|
||||
- **Fake scarcity**: 매 "limited" 인데 매번 재출시 → 매 trust erosion.
|
||||
- **No off-ramp**: 매 streak 끊고 싶을 때 vacation mode 없음.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Przybylski et al., "Motivational, emotional, and behavioral correlates of fear of missing out", 2013).
|
||||
- Verified (Belgium Gambling Commission loot box ruling, 2018).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — game design + monetization mechanics |
|
||||
@@ -0,0 +1,210 @@
|
||||
---
|
||||
id: wiki-2026-0508-fault-tolerance
|
||||
title: Fault Tolerance
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Fault Tolerance, 장애 내성, Resilience Engineering]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [architecture, distributed-systems, resilience, erlang]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: erlang
|
||||
framework: otp
|
||||
---
|
||||
|
||||
# Fault Tolerance
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 system은 fail한다 — 매 question은 'when'이지 'if' 아님"**. 매 fault tolerance는 component failure에도 system이 계속 동작하도록 design — Erlang/OTP의 "let it crash" philosophy에서 modern Kubernetes self-healing까지 evolution. 2026 cloud-native에서는 chaos engineering, circuit breaker, bulkhead가 default.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Fault vs Error vs Failure
|
||||
- **Fault**: 매 root cause (bug, hardware glitch, network partition)
|
||||
- **Error**: 매 fault의 manifestation (incorrect state)
|
||||
- **Failure**: 매 service가 contract 위반 (user-visible)
|
||||
- 매 goal: fault → error containment, error → failure prevention
|
||||
|
||||
### 매 Erlang Philosophy
|
||||
- **Let it crash**: 매 defensive coding 대신 supervisor가 restart
|
||||
- **Process isolation**: 매 lightweight process per actor, shared-nothing
|
||||
- **Hot code reload**: 매 zero-downtime upgrade
|
||||
- 매 WhatsApp이 2 billion users를 50 engineers로 운영한 비결
|
||||
|
||||
### 매 응용
|
||||
1. Erlang/OTP supervisor tree (telecom, WhatsApp, Discord).
|
||||
2. Kubernetes pod restart + liveness probes.
|
||||
3. Circuit breaker (Hystrix, resilience4j).
|
||||
4. Distributed databases (Cassandra hinted handoff, Spanner).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Erlang Supervisor Tree
|
||||
```erlang
|
||||
-module(my_sup).
|
||||
-behaviour(supervisor).
|
||||
-export([start_link/0, init/1]).
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
init([]) ->
|
||||
SupFlags = #{strategy => one_for_one,
|
||||
intensity => 5,
|
||||
period => 10},
|
||||
Children = [
|
||||
#{id => worker1,
|
||||
start => {worker, start_link, []},
|
||||
restart => permanent,
|
||||
shutdown => 5000,
|
||||
type => worker}
|
||||
],
|
||||
{ok, {SupFlags, Children}}.
|
||||
```
|
||||
|
||||
### Circuit Breaker (Python)
|
||||
```python
|
||||
from pybreaker import CircuitBreaker
|
||||
|
||||
db_breaker = CircuitBreaker(fail_max=5, reset_timeout=60)
|
||||
|
||||
@db_breaker
|
||||
def query_db(sql: str):
|
||||
return db.execute(sql)
|
||||
|
||||
try:
|
||||
result = query_db("SELECT * FROM users")
|
||||
except CircuitBreakerError:
|
||||
return cached_response() # fallback
|
||||
```
|
||||
|
||||
### Retry with Exponential Backoff
|
||||
```python
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
async def retry_with_backoff(fn, max_retries=5, base=1.0):
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
return await fn()
|
||||
except Exception as e:
|
||||
if attempt == max_retries - 1:
|
||||
raise
|
||||
delay = base * (2 ** attempt) + random.uniform(0, 1)
|
||||
await asyncio.sleep(delay)
|
||||
```
|
||||
|
||||
### Bulkhead Pattern (Go)
|
||||
```go
|
||||
import "golang.org/x/sync/semaphore"
|
||||
|
||||
type Service struct {
|
||||
dbSem *semaphore.Weighted // 10 concurrent DB calls
|
||||
apiSem *semaphore.Weighted // 50 concurrent API calls
|
||||
}
|
||||
|
||||
func (s *Service) CallDB(ctx context.Context) error {
|
||||
if err := s.dbSem.Acquire(ctx, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.dbSem.Release(1)
|
||||
return doDBWork()
|
||||
}
|
||||
```
|
||||
|
||||
### Kubernetes Liveness/Readiness
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8080
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: 8080
|
||||
periodSeconds: 5
|
||||
```
|
||||
|
||||
### Chaos Engineering (Litmus)
|
||||
```yaml
|
||||
apiVersion: litmuschaos.io/v1alpha1
|
||||
kind: ChaosEngine
|
||||
spec:
|
||||
experiments:
|
||||
- name: pod-delete
|
||||
spec:
|
||||
components:
|
||||
env:
|
||||
- name: TOTAL_CHAOS_DURATION
|
||||
value: '60'
|
||||
- name: PODS_AFFECTED_PERC
|
||||
value: '50'
|
||||
```
|
||||
|
||||
### Saga Pattern (Compensation)
|
||||
```python
|
||||
class OrderSaga:
|
||||
async def execute(self, order):
|
||||
steps = []
|
||||
try:
|
||||
payment = await charge_card(order)
|
||||
steps.append(("refund", payment.id))
|
||||
inventory = await reserve_stock(order)
|
||||
steps.append(("release", inventory.id))
|
||||
await ship_order(order)
|
||||
except Exception:
|
||||
for action, ref in reversed(steps):
|
||||
await compensate(action, ref)
|
||||
raise
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Telecom-grade uptime (5 nines) | Erlang/OTP supervisor tree |
|
||||
| Microservices REST | Circuit breaker + retry + timeout |
|
||||
| Stateful distributed DB | Quorum + hinted handoff |
|
||||
| Container orchestration | K8s liveness/readiness + PodDisruptionBudget |
|
||||
| Cross-service transactions | Saga + compensation |
|
||||
|
||||
**기본값**: 매 timeout + retry + circuit breaker 3종 세트 + chaos testing.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Distributed Systems]]
|
||||
- 변형: [[Circuit Breaker]]
|
||||
- 응용: [[Kubernetes]] · [[Microservices]]
|
||||
- Adjacent: [[Chaos Engineering]] · [[Eventual Consistency]] · [[CAP Theorem & PACELC]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 distributed system design 시 failure mode enumeration, supervisor tree 설계, retry strategy 추천.
|
||||
**언제 X**: 매 single-process script — fault tolerance overhead 가 value 보다 큼.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Catch-all exception swallow**: 매 error를 log만 하고 무시 → 매 silent corruption.
|
||||
- **Infinite retry**: 매 backoff 없는 retry → 매 thundering herd, cascading failure.
|
||||
- **Shared fate**: 매 단일 DB 의존 모든 service → 매 single point of failure.
|
||||
- **No timeout**: 매 hang된 dependency가 매 caller exhaust.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Joe Armstrong, "Making Reliable Distributed Systems in the Presence of Software Errors", 2003).
|
||||
- Verified (Netflix Chaos Engineering principles, principlesofchaos.org).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Erlang/OTP + modern resilience patterns |
|
||||
@@ -0,0 +1,198 @@
|
||||
---
|
||||
id: wiki-2026-0508-feature-driven-architecture
|
||||
title: Feature-Driven Architecture
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [FDD, Feature Slices, Vertical Slice Architecture]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [architecture, feature-slices, modularity, frontend]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: nextjs
|
||||
---
|
||||
|
||||
# Feature-Driven Architecture
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 layer-by-type 의 feature-by-vertical-slice 의 invert"**. Feature-Driven Architecture 매 codebase 의 organization unit 매 feature/use-case — 매 each feature 매 own UI + state + API + tests 의 own. Frontend 매 FSD (Feature-Sliced Design), Backend 매 vertical slice / modular monolith 의 manifest.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Why
|
||||
- **Layer-by-type problem**: 매 `controllers/`, `services/`, `models/` 매 small project 의 fine, 매 100+ features 시 매 cross-cutting changes 의 5 directories 의 touch.
|
||||
- **Feature 의 lifecycle**: 매 feature 의 add/remove/own 의 single folder 의 happen.
|
||||
- **Team scaling**: 매 vertical squad 의 own 매 single feature folder, 매 conflicts 의 minimize.
|
||||
|
||||
### 매 FSD layers (frontend)
|
||||
1. `app/` — global setup, routing, providers.
|
||||
2. `pages/` — route compositions.
|
||||
3. `widgets/` — composite UI blocks.
|
||||
4. `features/` — user actions (login, addToCart).
|
||||
5. `entities/` — business objects (User, Product).
|
||||
6. `shared/` — UI kit, utils, API client.
|
||||
- 매 import rule: 매 upper layer → lower layer only.
|
||||
|
||||
### 매 응용
|
||||
1. Next.js / Remix apps with FSD.
|
||||
2. Modular monolith — Java/.NET vertical slices.
|
||||
3. Mobile (RN/Flutter) feature modules.
|
||||
4. Microfrontend per-feature deployment.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### FSD folder layout
|
||||
```
|
||||
src/
|
||||
├── app/ # providers, router, global styles
|
||||
├── pages/
|
||||
│ └── checkout/
|
||||
│ └── ui/Checkout.tsx
|
||||
├── widgets/
|
||||
│ └── header/ui/Header.tsx
|
||||
├── features/
|
||||
│ ├── auth-login/
|
||||
│ │ ├── ui/LoginForm.tsx
|
||||
│ │ ├── model/store.ts
|
||||
│ │ ├── api/login.ts
|
||||
│ │ └── index.ts # public api
|
||||
│ └── cart-add-item/
|
||||
├── entities/
|
||||
│ ├── user/{ui, model, api}/
|
||||
│ └── product/{ui, model, api}/
|
||||
└── shared/
|
||||
├── ui/Button.tsx
|
||||
└── api/baseQuery.ts
|
||||
```
|
||||
|
||||
### Public API (index.ts barrel)
|
||||
```typescript
|
||||
// features/auth-login/index.ts
|
||||
export { LoginForm } from './ui/LoginForm';
|
||||
export { useLoginMutation } from './api/login';
|
||||
// 매 internal model/store 의 not exported — encapsulation
|
||||
```
|
||||
|
||||
### ESLint 의 enforce layer rules
|
||||
```javascript
|
||||
// .eslintrc — eslint-plugin-boundaries
|
||||
module.exports = {
|
||||
plugins: ['boundaries'],
|
||||
settings: {
|
||||
'boundaries/elements': [
|
||||
{ type: 'app', pattern: 'src/app/*' },
|
||||
{ type: 'pages', pattern: 'src/pages/*' },
|
||||
{ type: 'features', pattern: 'src/features/*' },
|
||||
{ type: 'entities', pattern: 'src/entities/*' },
|
||||
{ type: 'shared', pattern: 'src/shared/*' },
|
||||
],
|
||||
},
|
||||
rules: {
|
||||
'boundaries/element-types': ['error', {
|
||||
default: 'disallow',
|
||||
rules: [
|
||||
{ from: 'pages', allow: ['features', 'entities', 'shared'] },
|
||||
{ from: 'features', allow: ['entities', 'shared'] },
|
||||
{ from: 'entities', allow: ['shared'] },
|
||||
],
|
||||
}],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Vertical slice (.NET / Node)
|
||||
```typescript
|
||||
// features/place-order/handler.ts
|
||||
export class PlaceOrderCommand {
|
||||
constructor(public userId: string, public items: Item[]) {}
|
||||
}
|
||||
|
||||
export async function placeOrder(cmd: PlaceOrderCommand, deps: Deps) {
|
||||
const user = await deps.users.findById(cmd.userId);
|
||||
if (!user) throw new NotFoundError();
|
||||
const order = Order.create(user, cmd.items);
|
||||
await deps.orders.save(order);
|
||||
await deps.bus.emit('order.placed', { orderId: order.id });
|
||||
return { orderId: order.id };
|
||||
}
|
||||
|
||||
// features/place-order/route.ts
|
||||
router.post('/orders', async (req, res) => {
|
||||
const result = await placeOrder(req.body, deps);
|
||||
res.json(result);
|
||||
});
|
||||
// 매 single folder 매 use-case 의 entire handle
|
||||
```
|
||||
|
||||
### Cross-feature communication via events
|
||||
```typescript
|
||||
// features/cart-checkout — 매 features/inventory-update 의 NOT 매 import
|
||||
// 매 instead: emit event, 매 inventory feature 의 subscribe
|
||||
import { bus } from '@/shared/event-bus';
|
||||
|
||||
async function checkout(cart: Cart) {
|
||||
await bus.emit('checkout.completed', { items: cart.items });
|
||||
}
|
||||
|
||||
// features/inventory-update/index.ts
|
||||
bus.on('checkout.completed', async ({ items }) => {
|
||||
await decrementStock(items);
|
||||
});
|
||||
```
|
||||
|
||||
### Feature flag boundary
|
||||
```typescript
|
||||
// features/new-search-v2/index.ts
|
||||
import { useFlag } from '@/shared/feature-flags';
|
||||
import { SearchV1 } from '@/features/search-v1';
|
||||
import { SearchV2 } from './ui/SearchV2';
|
||||
|
||||
export function Search() {
|
||||
const v2 = useFlag('search-v2');
|
||||
return v2 ? <SearchV2 /> : <SearchV1 />;
|
||||
}
|
||||
// Removal 의 single folder delete
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Solo / < 10 features | 매 layer-by-type 매 fine |
|
||||
| Frontend, growing team | 매 FSD |
|
||||
| Backend, modular monolith | 매 vertical slice (CQRS-style) |
|
||||
| Microservices | 매 service-per-feature 매 already |
|
||||
| Strict isolation 의 needed | 매 ESLint boundaries + barrel exports |
|
||||
|
||||
**기본값**: 매 frontend 매 FSD, 매 backend 매 vertical slice — 매 cross-feature 의 events.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Software-Architecture]] · [[Modular Monolith]]
|
||||
- 변형: [[Feature-Sliced-Design]] · [[Vertical-Slice-Architecture]] · [[Hexagonal Architecture]]
|
||||
- 응용: [[Micro frontends]] · [[CQRS]] · [[Bounded Context]]
|
||||
- Adjacent: [[Domain-Driven-Design]] · [[Clean-Architecture]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 10+ features, 매 multi-team. Frontend 매 page-driven 의 outgrow. Modular monolith 매 service split 의 prepare.
|
||||
**언제 X**: 매 small app (<10 screens). 매 prototype phase — 매 over-structure cost > benefit.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Cross-feature direct import**: 매 `features/cart` 매 `features/checkout/internal` 의 import — 매 coupling 의 reintroduce. Public API 만 의 use.
|
||||
- **God shared/**: 매 모든 utility 의 `shared/utils/` 의 dump — 매 entities/features 의 leak 의 should.
|
||||
- **Premature feature split**: 매 single-screen app 의 7 features 의 carve — 매 navigation cost.
|
||||
- **Layer skipping**: 매 `entities` 매 `features` 의 import — 매 dependency rule violation.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (feature-sliced.design official; Jimmy Bogard "Vertical Slice Architecture").
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — FSD layout + ESLint boundaries + vertical slice patterns |
|
||||
@@ -0,0 +1,166 @@
|
||||
---
|
||||
id: wiki-2026-0508-fiber-architecture
|
||||
title: Fiber Architecture
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [React Fiber, Reconciler]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [react, frontend, reconciliation]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: react
|
||||
---
|
||||
|
||||
# Fiber Architecture
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 reconciliation 을 interruptible 한 unit 으로 쪼갠다"**. 매 React 16 (2017) 에서 stack reconciler 를 대체 — 매 work loop 가 매 fiber node 단위로 yield 가능하므로 매 concurrent rendering, Suspense, transitions 의 토대. 2026 React 19 의 Server Components, Actions, `use` hook 모두 매 fiber tree 위에서 동작.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 fiber node
|
||||
- 매 React element 1:1 의 mutable bookkeeping object.
|
||||
- 매 `child / sibling / return` pointer 로 tree linkage (매 array 가 아닌 linked list).
|
||||
- `pendingProps`, `memoizedProps`, `memoizedState`, `effectTag`, `lanes`.
|
||||
- 매 두 tree: **current** (committed) + **workInProgress** (next render) — 매 double buffering.
|
||||
|
||||
### 매 work loop
|
||||
1. **Render phase** (interruptible) — 매 beginWork → completeWork DFS, 매 frame budget 만료 시 yield.
|
||||
2. **Commit phase** (synchronous) — 매 DOM mutation, ref attach, layout effect.
|
||||
3. **Lanes** — 매 priority bitmask (Sync, Default, Transition, Idle).
|
||||
|
||||
### 매 응용
|
||||
1. `useTransition` / `useDeferredValue` — 매 low-priority lane.
|
||||
2. Suspense boundary — 매 throw promise → fallback render.
|
||||
3. Server Components (RSC) — 매 server fiber 의 serialize.
|
||||
4. Concurrent rendering / time slicing.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Fiber node 의 shape (개념)
|
||||
```ts
|
||||
type Fiber = {
|
||||
type: any; key: string | null;
|
||||
child: Fiber | null; sibling: Fiber | null; return: Fiber | null;
|
||||
alternate: Fiber | null; // current ↔ workInProgress
|
||||
pendingProps: any; memoizedProps: any; memoizedState: any;
|
||||
flags: number; // Placement | Update | Deletion
|
||||
lanes: number; childLanes: number;
|
||||
stateNode: any; // DOM node | class instance
|
||||
};
|
||||
```
|
||||
|
||||
### Work loop (개념)
|
||||
```ts
|
||||
function workLoopConcurrent() {
|
||||
while (workInProgress !== null && !shouldYield()) {
|
||||
workInProgress = performUnitOfWork(workInProgress);
|
||||
}
|
||||
}
|
||||
function performUnitOfWork(fiber: Fiber): Fiber | null {
|
||||
const next = beginWork(fiber.alternate, fiber, renderLanes);
|
||||
if (next === null) return completeUnitOfWork(fiber);
|
||||
return next;
|
||||
}
|
||||
```
|
||||
|
||||
### useTransition (React 19)
|
||||
```tsx
|
||||
import { useTransition, useState } from "react";
|
||||
|
||||
export function Search() {
|
||||
const [query, setQuery] = useState("");
|
||||
const [results, setResults] = useState<string[]>([]);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
return (
|
||||
<>
|
||||
<input value={query} onChange={e => {
|
||||
setQuery(e.target.value); // sync lane
|
||||
startTransition(() => setResults(filter(e.target.value))); // transition lane
|
||||
}}/>
|
||||
{isPending ? <Spinner/> : <List items={results}/>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Suspense + use (React 19)
|
||||
```tsx
|
||||
import { Suspense, use } from "react";
|
||||
|
||||
function Profile({ promise }: { promise: Promise<User> }) {
|
||||
const user = use(promise); // 매 throw 시 Suspense fallback
|
||||
return <h1>{user.name}</h1>;
|
||||
}
|
||||
|
||||
export default () => (
|
||||
<Suspense fallback={<Skeleton/>}>
|
||||
<Profile promise={fetchUser()}/>
|
||||
</Suspense>
|
||||
);
|
||||
```
|
||||
|
||||
### Server Component (RSC)
|
||||
```tsx
|
||||
// app/page.tsx — 매 server fiber, payload 로 serialize
|
||||
export default async function Page() {
|
||||
const posts = await db.posts.findMany();
|
||||
return <PostList posts={posts}/>; // 매 client 로는 RSC payload 전송
|
||||
}
|
||||
```
|
||||
|
||||
### useDeferredValue
|
||||
```tsx
|
||||
const deferred = useDeferredValue(query); // 매 stale value 로 render, urgent update 우선
|
||||
```
|
||||
|
||||
### Lane priority debug (React DevTools)
|
||||
```
|
||||
Profiler tab → Highlight transitions → 매 어느 lane 에서 commit 됐는지 확인
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| 즉시 반영 input | 직접 setState (sync lane) |
|
||||
| 무거운 list filter | startTransition (transition lane) |
|
||||
| Async data 의 render | Suspense + use |
|
||||
| Server-only data fetch | RSC (`async function Page`) |
|
||||
| Stale UI 허용 + responsive | useDeferredValue |
|
||||
|
||||
**기본값**: React 19 + RSC (Next.js App Router), client side 는 transition + Suspense.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[React]] · [[Reconciliation]]
|
||||
- 변형: [[React Server Components — 경계 의식]] · [[Concurrent Features|Concurrent Rendering]]
|
||||
- 응용: [[Suspense]] · [[useTransition]] · [[useDeferredValue]] · [[Streaming SSR]]
|
||||
- Adjacent: [[Virtual DOM과 Reconciliation|Virtual DOM]] · [[Hydration]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 jank 진단, transition vs sync 결정, Suspense boundary 위치 reasoning.
|
||||
**언제 X**: 매 non-React framework — 매 Vue / Svelte / Solid 는 매 다른 reconciler.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Sync setState in event for heavy work**: 매 main thread block.
|
||||
- **Suspense without boundary**: 매 root crash — 매 ErrorBoundary + Suspense pair.
|
||||
- **useTransition for urgent input**: 매 typing latency 발생.
|
||||
- **Mutating fiber internals**: 매 React 의 internal — 매 forward-compat 보장 X.
|
||||
- **Effect 의 setState loop**: 매 무한 render — 매 dependency 정확히.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (React 19 release notes, Andrew Clark *fiber* RFC, React docs 2026, Vercel RSC docs).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — fiber + lanes + React 19 (RSC, use, transition) 정리 |
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
id: wiki-2026-0508-flow-state
|
||||
title: Flow State
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Flow, Flow State, 몰입, Csikszentmihalyi Flow]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [psychology, game-design, productivity, ux]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: design
|
||||
framework: ux
|
||||
---
|
||||
|
||||
# Flow State
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 challenge와 skill이 정확히 균형된 순간 시간이 사라진다"**. 매 Flow는 Csikszentmihalyi(1975)가 정의한 optimal experience — 매 자아 의식 사라짐, immediate feedback, intrinsic reward. 2026 game design, productivity tool, learning platform의 매 design target.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 8 Conditions (Csikszentmihalyi)
|
||||
1. **Clear goals**: 매 매 순간 무엇을 해야 하는지 명확
|
||||
2. **Immediate feedback**: 매 action → result 즉시
|
||||
3. **Skill-challenge balance**: 매 너무 쉬우면 boredom, 너무 어려우면 anxiety
|
||||
4. **Action-awareness merge**: 매 doing = thinking
|
||||
5. **Concentration**: 매 task에 완전 집중
|
||||
6. **Sense of control**: 매 outcome 지배감
|
||||
7. **Loss of self-consciousness**: 매 ego 사라짐
|
||||
8. **Time distortion**: 매 5시간이 30분처럼
|
||||
|
||||
### 매 Flow Channel
|
||||
- **Anxiety zone**: 매 challenge ≫ skill
|
||||
- **Boredom zone**: 매 challenge ≪ skill
|
||||
- **Flow channel**: 매 둘이 동반 상승 (스킬 ↑ → 도전 ↑)
|
||||
|
||||
### 매 응용
|
||||
1. Game difficulty curves (Souls-like, rhythm games).
|
||||
2. Coding flow (Pomodoro, focus mode).
|
||||
3. Dynamic difficulty adjustment (Resident Evil 4 director AI).
|
||||
4. Educational scaffolding (Duolingo adaptive).
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Dynamic Difficulty Adjustment (Unity C#)
|
||||
```csharp
|
||||
public class FlowDifficulty : MonoBehaviour
|
||||
{
|
||||
float playerSkill = 0.5f; // 매 EMA estimate
|
||||
float currentDifficulty = 0.5f;
|
||||
|
||||
void OnPlayerSuccess()
|
||||
{
|
||||
playerSkill = 0.9f * playerSkill + 0.1f * 1.0f;
|
||||
AdjustDifficulty();
|
||||
}
|
||||
|
||||
void OnPlayerFail()
|
||||
{
|
||||
playerSkill = 0.9f * playerSkill + 0.1f * 0.0f;
|
||||
AdjustDifficulty();
|
||||
}
|
||||
|
||||
void AdjustDifficulty()
|
||||
{
|
||||
// 매 keep difficulty slightly above skill (flow channel)
|
||||
currentDifficulty = Mathf.Clamp(playerSkill + 0.1f, 0.1f, 0.95f);
|
||||
ApplyDifficulty(currentDifficulty);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Focus Mode (VS Code extension)
|
||||
```typescript
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function enterFlowMode() {
|
||||
vscode.workspace.getConfiguration().update('zenMode.fullScreen', true);
|
||||
vscode.workspace.getConfiguration().update('workbench.activityBar.visible', false);
|
||||
vscode.workspace.getConfiguration().update('editor.minimap.enabled', false);
|
||||
|
||||
// 매 disable notifications
|
||||
vscode.commands.executeCommand('notifications.toggleDoNotDisturbMode');
|
||||
|
||||
// 매 25min Pomodoro
|
||||
setTimeout(() => {
|
||||
vscode.window.showInformationMessage('매 flow check: 잠시 휴식?');
|
||||
}, 25 * 60 * 1000);
|
||||
}
|
||||
```
|
||||
|
||||
### Skill Tracking (Python EMA)
|
||||
```python
|
||||
class SkillTracker:
|
||||
def __init__(self, alpha=0.1):
|
||||
self.alpha = alpha
|
||||
self.skill = 0.5
|
||||
|
||||
def update(self, success: bool):
|
||||
target = 1.0 if success else 0.0
|
||||
self.skill = (1 - self.alpha) * self.skill + self.alpha * target
|
||||
|
||||
def in_flow_zone(self, difficulty: float) -> bool:
|
||||
# 매 difficulty 가 skill 의 ±15% 안에 있어야 flow
|
||||
return abs(difficulty - self.skill) < 0.15
|
||||
```
|
||||
|
||||
### Immediate Feedback (Web)
|
||||
```javascript
|
||||
// 매 keystroke마다 syntax check (flow-friendly)
|
||||
let timeoutId;
|
||||
editor.on('change', () => {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => {
|
||||
const errors = lint(editor.getValue());
|
||||
showInlineErrors(errors); // 매 즉시 visual feedback
|
||||
}, 100); // 매 debounce 100ms — 매 fast enough for flow
|
||||
});
|
||||
```
|
||||
|
||||
### Distraction Blocking (macOS Focus)
|
||||
```bash
|
||||
# 매 macOS Shortcuts CLI
|
||||
shortcuts run "Focus: Do Not Disturb On"
|
||||
|
||||
# 매 hosts file block social
|
||||
echo "0.0.0.0 twitter.com" | sudo tee -a /etc/hosts
|
||||
echo "0.0.0.0 reddit.com" | sudo tee -a /etc/hosts
|
||||
```
|
||||
|
||||
### Flow Telemetry (game)
|
||||
```python
|
||||
def log_session_flow(session):
|
||||
duration = session.end - session.start
|
||||
deaths = session.deaths
|
||||
completions = session.completions
|
||||
|
||||
# 매 high engagement + moderate failure = flow
|
||||
if duration > timedelta(minutes=20) \
|
||||
and 0.3 < deaths / (deaths + completions) < 0.6:
|
||||
analytics.track("flow_session", session.user_id)
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Design priority |
|
||||
|---|---|
|
||||
| Action game | Tight feedback loop, DDA |
|
||||
| Strategy game | Clear goal, skill ceiling exposure |
|
||||
| Learning app | Adaptive difficulty, immediate hint |
|
||||
| Productivity tool | Distraction-free mode, focus timer |
|
||||
| Casual game | Low anxiety, high accessibility |
|
||||
|
||||
**기본값**: 매 difficulty = skill + 10%, feedback < 200ms, distraction = 0.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Game Design]]
|
||||
- 변형: [[Deep Work]] (Cal Newport)
|
||||
- 응용: [[DDA]]
|
||||
- Adjacent: [[Intrinsic Motivation]] · [[Self-Determination Theory]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 game design review, learning UX critique, productivity workflow 진단.
|
||||
**언제 X**: 매 medical/clinical attention disorder context — flow state는 self-help framework, not therapy.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Forced flow**: 매 user를 trap (매 dark UX) → 매 burnout, churn.
|
||||
- **Notification interrupt**: 매 push 매 5분 → 매 flow 진입 불가능.
|
||||
- **Difficulty spike**: 매 sudden challenge → 매 anxiety zone 추락.
|
||||
- **Reward without challenge**: 매 free progress → 매 boredom.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Csikszentmihalyi, "Flow: The Psychology of Optimal Experience", 1990).
|
||||
- Verified (Sweetser & Wyeth, "GameFlow: A Model for Evaluating Player Enjoyment in Games", 2005).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Csikszentmihalyi flow + game design |
|
||||
@@ -0,0 +1,184 @@
|
||||
---
|
||||
id: wiki-2026-0508-fluid-typography
|
||||
title: Fluid Typography
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [Fluid Typography, Responsive Typography, Clamp Typography]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [css, web, design, responsive]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: css
|
||||
framework: web
|
||||
---
|
||||
|
||||
# Fluid Typography
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 viewport 에 따라 font-size 가 부드럽게 scale 된다"**. 매 Fluid Typography는 fixed breakpoint 의 jumpy 변화 대신 매 linear interpolation 으로 매 자연스러운 size 변화 — `clamp()` + `vw` 가 매 modern standard. 2026 모든 design system 의 default.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Why fluid?
|
||||
- **Breakpoint jumps**: 매 320px → 768px 에서 갑자기 font 12px → 18px (jarring)
|
||||
- **Fluid interp**: 매 320px = 12px, 1280px = 24px, 매 사이는 linear
|
||||
- **Single source of truth**: 매 media query × N 대신 매 1 line CSS
|
||||
|
||||
### 매 clamp(min, preferred, max)
|
||||
- `min`: 매 최소 (small viewport)
|
||||
- `preferred`: 매 vw 기반 fluid value
|
||||
- `max`: 매 최대 (large viewport cap)
|
||||
- 매 browser가 자동 clamp
|
||||
|
||||
### 매 응용
|
||||
1. Modern design systems (Tailwind v4 fluid presets, Open Props).
|
||||
2. Editorial sites (NYT, Medium-style readers).
|
||||
3. Marketing landing pages.
|
||||
4. CSS-in-JS theme tokens.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Basic clamp() pattern
|
||||
```css
|
||||
:root {
|
||||
/* 매 min 1rem (16px), max 1.5rem (24px), fluid 사이 */
|
||||
--fs-body: clamp(1rem, 0.875rem + 0.625vw, 1.5rem);
|
||||
--fs-h1: clamp(2rem, 1rem + 5vw, 5rem);
|
||||
--fs-h2: clamp(1.5rem, 1rem + 2.5vw, 3rem);
|
||||
}
|
||||
|
||||
body { font-size: var(--fs-body); }
|
||||
h1 { font-size: var(--fs-h1); line-height: 1.1; }
|
||||
h2 { font-size: var(--fs-h2); line-height: 1.2; }
|
||||
```
|
||||
|
||||
### Linear interp formula (Utopia.fyi)
|
||||
```javascript
|
||||
// 매 min 320px viewport: 16px font
|
||||
// 매 max 1280px viewport: 24px font
|
||||
function fluidClamp(minPx, maxPx, minVwPx = 320, maxVwPx = 1280) {
|
||||
const slope = (maxPx - minPx) / (maxVwPx - minVwPx);
|
||||
const intercept = minPx - slope * minVwPx;
|
||||
const slopeVw = slope * 100;
|
||||
return `clamp(${minPx / 16}rem, ${intercept / 16}rem + ${slopeVw}vw, ${maxPx / 16}rem)`;
|
||||
}
|
||||
|
||||
console.log(fluidClamp(16, 24));
|
||||
// "clamp(1rem, 0.833rem + 0.833vw, 1.5rem)"
|
||||
```
|
||||
|
||||
### Type scale (modular)
|
||||
```css
|
||||
:root {
|
||||
--ratio: 1.25; /* 매 major third scale */
|
||||
--fs-0: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
|
||||
--fs-1: calc(var(--fs-0) * var(--ratio));
|
||||
--fs-2: calc(var(--fs-1) * var(--ratio));
|
||||
--fs-3: calc(var(--fs-2) * var(--ratio));
|
||||
--fs--1: calc(var(--fs-0) / var(--ratio));
|
||||
}
|
||||
```
|
||||
|
||||
### Tailwind v4 fluid (CSS)
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--text-fluid-sm: clamp(0.875rem, 0.8rem + 0.4vw, 1rem);
|
||||
--text-fluid-base: clamp(1rem, 0.875rem + 0.625vw, 1.25rem);
|
||||
--text-fluid-lg: clamp(1.125rem, 0.95rem + 0.9vw, 1.5rem);
|
||||
--text-fluid-xl: clamp(1.5rem, 1rem + 2.5vw, 2.5rem);
|
||||
}
|
||||
```
|
||||
|
||||
```html
|
||||
<h1 class="text-fluid-xl">매 fluid heading</h1>
|
||||
<p class="text-fluid-base">매 body text fluid</p>
|
||||
```
|
||||
|
||||
### Container query fluid (modern)
|
||||
```css
|
||||
/* 매 viewport 대신 container 기반 — true component fluidity */
|
||||
.card {
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: clamp(1rem, 4cqi, 2rem); /* 매 cqi = container query inline */
|
||||
}
|
||||
```
|
||||
|
||||
### Accessibility-safe (rem floor)
|
||||
```css
|
||||
/* 매 user font-size preference 존중 — rem 사용 */
|
||||
:root {
|
||||
--fs-readable: clamp(1rem, 0.9rem + 0.5vw, 1.25rem);
|
||||
/* 매 NOT: clamp(16px, 1vw, 20px) — px ignores user setting */
|
||||
}
|
||||
```
|
||||
|
||||
### Line-height 도 fluid
|
||||
```css
|
||||
.prose {
|
||||
font-size: clamp(1rem, 0.9rem + 0.5vw, 1.25rem);
|
||||
/* 매 작은 size 일수록 line-height 더 큼 (readability) */
|
||||
line-height: clamp(1.4, 1.6 - 0.1vw, 1.6);
|
||||
/* 매 ch unit으로 measure 제한 */
|
||||
max-width: 65ch;
|
||||
}
|
||||
```
|
||||
|
||||
### Spacing 도 fluid (consistency)
|
||||
```css
|
||||
:root {
|
||||
--space-xs: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem);
|
||||
--space-sm: clamp(1rem, 0.8rem + 1vw, 1.5rem);
|
||||
--space-md: clamp(1.5rem, 1.2rem + 1.5vw, 2.25rem);
|
||||
--space-lg: clamp(2.25rem, 1.8rem + 2.25vw, 3.375rem);
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Marketing site | Fluid type + spacing |
|
||||
| App UI (dense) | Discrete sizes (consistent UX) |
|
||||
| Editorial (long-form) | Fluid + max-width 65-75ch |
|
||||
| Component library | Container query fluid |
|
||||
| Email | Static (limited CSS) |
|
||||
|
||||
**기본값**: 매 clamp() + rem + Utopia.fyi calculator. 매 viewport range 320-1280px 기준.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Responsive Design]]
|
||||
- 변형: [[컨테이너 쿼리 (Container Queries)|Container Queries]]
|
||||
- 응용: [[Design Systems]] · [[CSS_Architecture_and_Styling|Tailwind CSS]] · [[CSS_Architecture_and_Styling|CSS Variables]]
|
||||
- Adjacent: [[Type Scale]] · [[Accessibility (A11y)|Accessibility]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 design system token 생성, clamp() formula 계산, breakpoint refactor.
|
||||
**언제 X**: 매 email HTML, legacy IE — clamp() unsupported.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **px in clamp()**: 매 user font preference 무시 → 매 a11y 실패.
|
||||
- **No max cap**: 매 4K monitor 에서 매 font 너무 큼.
|
||||
- **No min floor**: 매 320px 미만 viewport 에서 매 unreadable.
|
||||
- **vw alone**: 매 `font-size: 2vw` — 매 zoom 시 깨짐 (no rem fallback).
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Utopia.fyi, "Fluid responsive design", 2020).
|
||||
- Verified (CSS Values L4 spec, clamp() function).
|
||||
- Verified (Caniuse: clamp() 96%+ global support).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — clamp() + container queries + Tailwind v4 |
|
||||
@@ -0,0 +1,229 @@
|
||||
---
|
||||
id: wiki-2026-0508-fragment-bound
|
||||
title: Fragment-bound
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [React Fragment, Fragment-bound Component, Multi-root Component]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
verification_status: applied
|
||||
tags: [react, frontend, jsx, dom]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: typescript
|
||||
framework: react
|
||||
---
|
||||
|
||||
# Fragment-bound
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 component 의 root 의 wrapper div 의 elimination"**. React Fragment (`<>...</>` 또는 `<Fragment>`) 매 component 매 multiple sibling roots 의 return 의 enable — 매 unnecessary `<div>` wrapper 의 avoid. "Fragment-bound" component 매 single DOM root 의 not-have, 매 layout (Grid, Table, Flex) 매 fragile 의 implication 의 carry.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 Why Fragment
|
||||
- **DOM cleanliness**: 매 wrapper div 의 CSS Grid/Flex 의 break — 매 child 매 grid item 의 directly 의 must be.
|
||||
- **No semantic noise**: 매 `<table>` 매 `<tr>` 매 `<td>` 의 nest 의 wrapper div 의 invalid HTML.
|
||||
- **Performance (marginal)**: 매 fewer DOM nodes — 매 hot lists 의 measurable.
|
||||
|
||||
### 매 Forms
|
||||
- `<></>` — short syntax, 매 no key/props.
|
||||
- `<Fragment key={...}>` — 매 list iteration 매 key 의 needed 시.
|
||||
- `<React.Fragment>` — explicit import, 매 build tooling 의 short syntax 의 not-support 시.
|
||||
|
||||
### 매 Fragment-bound implications
|
||||
- 매 ref 의 attach 의 not-possible (매 single DOM node 의 not-have).
|
||||
- 매 parent 매 child layout 의 control 의 must — 매 child 매 own root 의 not-have.
|
||||
- 매 portals 매 separate concern.
|
||||
|
||||
### 매 응용
|
||||
1. Table rows / cells (`<TableRow>` returning `<td>...</td><td>...</td>`).
|
||||
2. Grid items in CSS Grid layout.
|
||||
3. Component library — wrapper-less primitives.
|
||||
4. Conditional sibling rendering.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Basic Fragment
|
||||
```tsx
|
||||
function Greeting() {
|
||||
return (
|
||||
<>
|
||||
<h1>Hello</h1>
|
||||
<p>World</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
// 매 DOM 의 <h1>+<p> 의 sibling, 매 no wrapper
|
||||
```
|
||||
|
||||
### Fragment with key (list)
|
||||
```tsx
|
||||
import { Fragment } from 'react';
|
||||
|
||||
function Glossary({ items }: { items: { term: string; def: string }[] }) {
|
||||
return (
|
||||
<dl>
|
||||
{items.map(it => (
|
||||
<Fragment key={it.term}>
|
||||
<dt>{it.term}</dt>
|
||||
<dd>{it.def}</dd>
|
||||
</Fragment>
|
||||
))}
|
||||
</dl>
|
||||
);
|
||||
}
|
||||
// 매 short syntax 매 key prop 의 not-accept — 매 explicit Fragment 의 use
|
||||
```
|
||||
|
||||
### Table row composition
|
||||
```tsx
|
||||
function ProductRow({ product }: { product: Product }) {
|
||||
return (
|
||||
<>
|
||||
<td>{product.name}</td>
|
||||
<td>{product.price}</td>
|
||||
<td>{product.stock}</td>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ProductTable({ products }: { products: Product[] }) {
|
||||
return (
|
||||
<table>
|
||||
<tbody>
|
||||
{products.map(p => (
|
||||
<tr key={p.id}><ProductRow product={p} /></tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
// 매 wrapper div 매 tr 안 의 invalid HTML — 매 Fragment 의 only correct
|
||||
```
|
||||
|
||||
### CSS Grid items
|
||||
```tsx
|
||||
function GridGroup() {
|
||||
return (
|
||||
<>
|
||||
<div className="grid-item">A</div>
|
||||
<div className="grid-item">B</div>
|
||||
<div className="grid-item">C</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Layout() {
|
||||
return (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)' }}>
|
||||
<GridGroup /> {/* 매 3 children 의 grid items 의 directly */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// 매 wrapper div 의 add 시 매 single grid cell 의 collapse
|
||||
```
|
||||
|
||||
### Conditional sibling rendering
|
||||
```tsx
|
||||
function Notification({ user }: { user?: User }) {
|
||||
if (!user) return null;
|
||||
return (
|
||||
<>
|
||||
{user.unreadCount > 0 && (
|
||||
<span className="badge">{user.unreadCount}</span>
|
||||
)}
|
||||
<span className="name">{user.name}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Ref forwarding 매 NOT possible
|
||||
```tsx
|
||||
// 매 X — Fragment 매 ref 의 not-attach 의
|
||||
const Bad = forwardRef<HTMLDivElement>((props, ref) => (
|
||||
<>
|
||||
<h1 ref={ref}>Title</h1> {/* 매 child element 의 ref 의 forward 의 must */}
|
||||
<p>Body</p>
|
||||
</>
|
||||
));
|
||||
|
||||
// 매 O — 매 explicit child 의 ref 의 forward
|
||||
const Card = forwardRef<HTMLHeadingElement, { title: string; body: string }>(
|
||||
({ title, body }, ref) => (
|
||||
<>
|
||||
<h1 ref={ref}>{title}</h1>
|
||||
<p>{body}</p>
|
||||
</>
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
### Suspense / ErrorBoundary 매 Fragment children
|
||||
```tsx
|
||||
function App() {
|
||||
return (
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<>
|
||||
<Header />
|
||||
<Main />
|
||||
<Footer />
|
||||
</>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
// 매 Suspense 매 single child 의 not-require — 매 Fragment 의 N children 의 all 의 wait
|
||||
```
|
||||
|
||||
### Slot pattern 의 fragment-aware
|
||||
```tsx
|
||||
type SlotProps = { children: ReactNode };
|
||||
function Slot({ children }: SlotProps) {
|
||||
// 매 children 매 Fragment 매 single 매 multiple 매 unwrap 의 logic
|
||||
if (isValidElement(children) && children.type === Fragment) {
|
||||
return <>{children.props.children}</>;
|
||||
}
|
||||
return <>{children}</>;
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Component 매 단일 root 의 natural | 매 `<div>` 의 use |
|
||||
| Wrapper div 매 layout 의 break | 매 Fragment |
|
||||
| Table / dl / select children | 매 Fragment 의 mandatory |
|
||||
| List item with multiple roots | 매 Fragment with key |
|
||||
| Ref / styling 의 root needed | 매 div / specific element 의 use |
|
||||
|
||||
**기본값**: 매 wrapper 매 semantic value 의 carry 의 div, 매 그렇지 않으면 매 Fragment.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[React]] · [[JSX]]
|
||||
- 변형: [[Slot-Pattern]]
|
||||
- 응용: [[CSS Grid]] · [[Component-Composition]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 multi-root component. Table/Grid layout 의 wrapper 의 break 의 시. List item 매 multiple sibling 의 render.
|
||||
**언제 X**: 매 single root + ref/styling needed — 매 div 의 use. Wrapper 의 styling target 의 expected 시.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Wrapper div habit**: 매 모든 component 매 `<div>` 의 wrap — 매 div soup, 매 layout 의 fragile.
|
||||
- **Fragment without key in list**: 매 `<>` 매 `.map` 안 의 use — 매 React warning + reconciliation 의 broken.
|
||||
- **Trying to ref a Fragment**: 매 Fragment 의 DOM node 의 not-have — 매 forwardRef 의 specific child 의 forward 의 must.
|
||||
- **Fragment inside single-child API**: 매 some libs (older) 매 single child 의 expect — 매 Fragment 의 expand, 매 break.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (React docs "Fragments"; React 16.2 release notes).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — Fragment patterns + table/grid/key examples |
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user