설계 개편

This commit is contained in:
DongHyeonka
2026-07-24 16:31:12 +09:00
parent f43e909162
commit 8daa568746
70 changed files with 6554 additions and 956 deletions
+331
View File
@@ -0,0 +1,331 @@
from __future__ import annotations
import json
import shutil
import subprocess
from pathlib import Path
from techviz.layout import build_layout
from techviz.renderers.svg import render_svg
from techviz.spec import VizSpec
from techviz.validate import has_errors, validate_spec
ROOT = Path(__file__).resolve().parent
OUT = ROOT / "runtime-profiles"
E = [{"start_line": 1, "end_line": 1}]
SOURCE = {
"document": "examples/runtime-profiles/fixture.md",
"document_sha256": "0" * 64,
"anchor": {"kind": "line", "value": 1, "line": 1},
}
def node(node_id: str, label: str, kind: str, role: str, **extra: object) -> dict[str, object]:
payload: dict[str, object] = {
"id": node_id,
"label": label,
"kind": kind,
"role": role,
"evidence": E,
"assumption": False,
}
payload.update(extra)
return payload
def edge(edge_id: str, source: str, target: str, label: str, kind: str = "request", **extra: object) -> dict[str, object]:
payload: dict[str, object] = {
"id": edge_id,
"from": source,
"to": target,
"label": label,
"kind": kind,
"evidence": E,
"assumption": False,
}
payload.update(extra)
return payload
def spec(profile: str, ref: str, diagram_type: str, direction: str, nodes: list[dict[str, object]], edges: list[dict[str, object]], *, groups: list[dict[str, object]] | None = None, focus: str | None = None) -> dict[str, object]:
composition: dict[str, object] = {
"profile": profile,
"diagram_only": True,
"reference_ids": [ref],
"rationale": f"Runtime regression fixture for {profile}.",
}
if focus:
composition["focus_node"] = focus
return {
"version": "1.1",
"id": profile,
"title": f"{profile} runtime fixture",
"question": f"Does the {profile} logical composition render correctly?",
"type": diagram_type,
"direction": direction,
"audience": ["renderer maintainer"],
"summary": f"Executable {profile} fixture.",
"alt": f"Executable technical diagram fixture for the {profile} composition profile.",
"long_description": f"This fixture validates the node roles, edges, and layout rules of the {profile} profile.",
"source_context": SOURCE,
"composition": composition,
"groups": groups or [],
"nodes": nodes,
"edges": edges,
"legend": [],
"metadata": {"fixture": True},
}
FIXTURES: dict[str, dict[str, object]] = {
"01-component-flow": spec(
"component-flow",
"payment-event-flow",
"data-flow",
"LR",
[
node("client", "클라이언트", "actor", "source", shape="actor"),
node("gateway", "인증 게이트웨이", "security", "service"),
node("api", "체크아웃 API", "service", "service", emphasis="primary"),
node("db", "주문 저장소", "database", "store", shape="database"),
node("provider", "결제 제공자", "service", "service"),
node("bus", "이벤트 버스", "queue", "sink", shape="queue"),
],
[
edge("request", "client", "gateway", "HTTPS 결제 요청"),
edge("validated", "gateway", "api", "검증된 요청"),
edge("pending", "api", "db", "PENDING 기록", "data"),
edge("authorize", "api", "provider", "승인 요청"),
edge("approved", "provider", "api", "승인 응답", "response", style="dashed"),
edge("paid", "api", "db", "PAID 갱신", "data"),
edge("event", "api", "bus", "payment.approved", "publish", style="dashed", emphasis="primary"),
],
focus="api",
),
"02-orchestrator-workers": spec(
"orchestrator-workers",
"mission-workers",
"architecture",
"TB",
[
node("main", "MAIN SESSION", "service", "orchestrator", shape="controller", emphasis="primary", details=["context + logic"]),
node("agents", "SUBAGENTS × N", "service", "worker", details=["client RPS · latency", "server RPS · latency", "JVM heap · GC"]),
node("stream", "BACKGROUND BASH", "process", "subprocess", details=["tail -f ..."]),
node("poll", "POLLING BASH", "process", "subprocess", details=["until <cond>; sleep N"]),
node("monitor", "MONITOR", "service", "monitor", shape="controller", details=["stdout 수신", "이벤트 알림"]),
],
[
edge("dispatch", "main", "agents", "dispatch"),
edge("spawn-stream", "main", "stream", "spawn ×2"),
edge("spawn-poll", "main", "poll", "spawn"),
edge("subscribe", "main", "monitor", "subscribe"),
edge("stdout-stream", "stream", "monitor", "stdout"),
edge("stdout-poll", "poll", "monitor", "stdout"),
edge("notify", "monitor", "main", "notification", "notification", style="dashed"),
],
focus="main",
),
"03-query-fanout": spec(
"query-fanout",
"metrics-query-fanout",
"data-flow",
"LR",
[
node("user", "사용자", "actor", "actor", shape="actor"),
node("query", "rate(http_requests_total[5m])", "request", "query", details=["time range: 15:0015:10"]),
node("parser", "query parser", "component", "parser", shape="port"),
node("selector", "vmselect", "service", "router", emphasis="primary"),
node("a", "vmstorage A", "database", "shard", shape="database", details=["IndexDB → TSID → Data"]),
node("b", "vmstorage B", "database", "shard", shape="database", details=["IndexDB → TSID → Data"]),
node("c", "vmstorage C", "database", "shard", shape="database", details=["IndexDB → TSID → Data"]),
],
[
edge("submit", "user", "query", "PromQL"),
edge("parse", "query", "parser", "parse"),
edge("select", "parser", "selector", "filters + time range"),
edge("to-a", "selector", "a", "range query"),
edge("to-b", "selector", "b", "range query"),
edge("to-c", "selector", "c", "range query"),
],
focus="selector",
),
"04-timeline": spec(
"timeline",
"retention-cycle",
"concept",
"LR",
[
node("t0", "T = 0", "milestone", "milestone", position=1, details=["1970-01-01", "Unix Epoch"]),
node("d372", "+372d", "milestone", "milestone", position=2, details=["1971-01-08"]),
node("d744", "+744d", "milestone", "milestone", position=3, details=["1972-01-15"]),
node("n55", "n=55", "milestone", "milestone", position=4, emphasis="warning", details=["2026-01-07", "직전 로테이션"]),
node("today", "현재", "milestone", "milestone", position=5, emphasis="primary", details=["오늘"]),
node("n56", "n=56", "milestone", "milestone", position=6, details=["2027-01-14", "다음 로테이션"]),
],
[],
focus="today",
),
"05-reconciliation-loop": spec(
"reconciliation-loop",
"declarative-vm",
"architecture",
"LR",
[
node("desired", "VM Custom Resource", "resource", "desired-state", shape="document", details=["vCPU: 2", "memory: 8GB", "process: dbaas-agent"]),
node("controller", "VM Operator", "controller", "controller", shape="controller", emphasis="primary"),
node("actual", "VM", "runtime", "actual-state", details=["dbaas-agent"]),
node("status", "status", "state", "status", shape="port", details=["dbaas-agent: ok"]),
],
[
edge("watch", "desired", "controller", "watch"),
edge("create", "controller", "actual", "create VM", emphasis="primary"),
edge("status-read", "actual", "status", "report status", "response", style="dashed"),
edge("status-write", "status", "desired", "update status", "response", style="dashed"),
],
focus="controller",
),
"06-resource-controller": spec(
"resource-controller",
"dbaas-controller",
"architecture",
"LR",
[
node("user", "User", "actor", "actor", shape="actor"),
node("db-service", "DB Service", "resource", "resource-spec", shape="document", details=["vmCount: 3", "vCPU: 2", "memory: 8GB", "maxclient: 3000"]),
node("manager", "DBaaS Manager", "controller", "controller", shape="controller", emphasis="primary"),
node("vm-cr", "VM Custom Resource ×3", "resource", "custom-resource", shape="document", details=["2 vCPU", "8GB RAM"]),
node("db-cr", "DB Instance CR ×3", "resource", "custom-resource", shape="document", details=["maxclient: 3000", "timeout: 5s"]),
node("vm", "VM + dbaas-agent", "runtime", "runtime-resource", details=["create VM"]),
node("db", "DB", "database", "runtime-resource", shape="database", details=["DB command"]),
],
[
edge("create-spec", "user", "db-service", "create"),
edge("watch-spec", "db-service", "manager", "watch"),
edge("create-vm-cr", "manager", "vm-cr", "create ×3"),
edge("create-db-cr", "manager", "db-cr", "create ×3"),
edge("materialize-vm", "vm-cr", "vm", "reconcile"),
edge("materialize-db", "db-cr", "db", "reconcile"),
edge("agent-command", "vm", "db", "DB command"),
],
focus="manager",
),
"07-two-zone-pipeline": spec(
"two-zone-pipeline",
"localization-pipeline",
"data-flow",
"LR",
[
node("mobile", "배민 앱", "actor", "source", group="request-zone", shape="actor"),
node("list", "목록/상세 BFF", "service", "service", group="request-zone", details=["lang param"]),
node("cart", "장바구니/주문 BFF", "service", "service", group="request-zone", details=["lang param"]),
node("fdh", "FDH", "service", "service", group="request-zone", emphasis="primary"),
node("source-data", "가게/메뉴", "database", "store", group="translation-zone", shape="database"),
node("worker", "FDH Worker", "service", "service", group="translation-zone"),
node("queue", "Queue", "queue", "queue", group="translation-zone", shape="queue"),
node("translator", "LLM Translator", "service", "service", group="translation-zone"),
],
[
edge("app-list", "mobile", "list", "목록/상세 요청"),
edge("app-cart", "mobile", "cart", "주문 요청"),
edge("list-fdh", "list", "fdh", "다국어 조회"),
edge("cart-fdh", "cart", "fdh", "다국어 조회"),
edge("data-worker", "source-data", "worker", "이벤트"),
edge("worker-queue", "worker", "queue", "적재 작업"),
edge("queue-translator", "queue", "translator", "번역 요청"),
edge("translator-worker", "translator", "worker", "번역 결과", "response", style="dashed"),
edge("worker-fdh", "worker", "fdh", "다국어 적재", "data"),
],
groups=[
{"id": "request-zone", "label": "사용자 요청 및 서비스 레이어", "kind": "system", "role": "zone", "evidence": E, "assumption": False},
{"id": "translation-zone", "label": "데이터 번역 및 적재 레이어", "kind": "system", "role": "zone", "evidence": E, "assumption": False},
],
focus="fdh",
),
"08-sequence": spec(
"sequence",
"payment-approval-sequence",
"sequence",
"LR",
[
node("client", "Client", "participant", "participant"),
node("api", "Checkout API", "participant", "participant", emphasis="primary"),
node("provider", "Payment Provider", "participant", "participant"),
node("db", "Orders DB", "participant", "participant"),
node("bus", "Event Bus", "participant", "participant"),
],
[
edge("m1", "client", "api", "결제 요청", order=1),
edge("m2", "api", "db", "PENDING 기록", "data", order=2),
edge("m3", "api", "provider", "승인 요청", order=3),
edge("m4", "provider", "api", "승인 응답", "response", order=4, style="dashed"),
edge("m5", "api", "db", "PAID 갱신", "data", order=5),
edge("m6", "api", "bus", "payment.approved", "publish", order=6, style="dashed", emphasis="primary"),
],
focus="api",
),
"09-ports-adapters": spec(
"ports-adapters",
"order-ports-adapters",
"architecture",
"LR",
[
node("web", "Web Adapter", "adapter", "inbound-adapter", details=["REST · inbound"]),
node("batch", "Test / Batch", "adapter", "inbound-adapter", details=["inbound"]),
node("in-port", "Input Port", "interface", "port", shape="port"),
node("core", "Application Core", "domain", "core", shape="hexagon", emphasis="primary", details=["domain + use cases"]),
node("out-port", "Output Port", "interface", "port", shape="port"),
node("persistence", "Persistence Adapter", "adapter", "outbound-adapter", details=["JPA · outbound"]),
node("db", "DB", "database", "external-system", shape="database"),
],
[
edge("web-port", "web", "in-port", "call"),
edge("batch-port", "batch", "in-port", "call"),
edge("port-core", "in-port", "core", "invoke use case", emphasis="primary"),
edge("core-out", "core", "out-port", "requires"),
edge("adapter-out", "persistence", "out-port", "implements", "dependency"),
edge("adapter-db", "persistence", "db", "JDBC"),
],
focus="core",
),
"10-comparison": spec(
"comparison",
"contract-comparison",
"concept",
"LR",
[
node("transaction", "TransactionPort", "interface", "contract", details=["세션 모델: Spring 구현", "책임: commit / rollback", "현재: 독립 계약"], emphasis="primary"),
node("lock", "DistributedLockPort", "interface", "contract", details=["세션 모델: 호출자 0", "책임: acquire / release", "현재: 독립 계약"], emphasis="primary"),
node("future", "Future lifecycle", "concept", "option", details=["acquire → commit → release", "통합 시 순서 명시 필요"], emphasis="warning"),
],
[],
),
}
def main() -> int:
OUT.mkdir(parents=True, exist_ok=True)
inkscape = shutil.which("inkscape")
for directory, raw in FIXTURES.items():
target = OUT / directory
target.mkdir(parents=True, exist_ok=True)
spec_obj = VizSpec.from_dict(raw)
issues = validate_spec(spec_obj)
if has_errors(issues):
raise RuntimeError(f"{directory}: {issues}")
(target / "spec.json").write_text(json.dumps(raw, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
svg_path = target / f"{spec_obj.id}.svg"
svg_path.write_text(render_svg(spec_obj, build_layout(spec_obj)), encoding="utf-8")
if inkscape:
subprocess.run(
[inkscape, str(svg_path), "--export-type=png", f"--export-filename={target / (spec_obj.id + '.preview.png')}"],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
print(f"WROTE {svg_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())