fix: 하네스 제거 및 keycloak 문서 보강
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/ca-skeleton-frontend/architecture-deployment-2026-07-18.drawio
|
||||
@@ -0,0 +1,51 @@
|
||||
<mxfile host="Codex" agent="wiki-workflow + diagram-standards v2 minimalist" version="24.0.0" type="device">
|
||||
<diagram name="ca-skeleton-frontend-deployment" id="frontend-deployment">
|
||||
<mxGraphModel dx="1000" dy="680" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1000" pageHeight="680" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<mxCell id="title" value="<b>ca-skeleton frontend — static delivery</b> How does the browser receive immutable assets and mutable /config.json?" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=18;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#57606A;" vertex="1" parent="1">
|
||||
<mxGeometry x="50" y="20" width="900" height="58" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="browser" value="<b>User Browser</b> External client" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#F6F8FA;strokeColor=#D0D7DE;strokeWidth=1.5;dashed=1;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#57606A;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="60" y="240" width="190" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="cdn" value="<b>CDN / Static Host</b> Serves both artifacts" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=2.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#57606A;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="390" y="240" width="220" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="build-artifact" value="<b>Build Artifact</b> Immutable hashed assets" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#57606A;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="700" y="170" width="200" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="config-artifact" value="<b>/config.json</b> Mutable · no-store" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#57606A;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="700" y="350" width="200" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="publish-build" value="publish assets" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;entryX=1;entryY=0.3;strokeColor=#57606A;strokeWidth=1.5;endArrow=open;endFill=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#57606A;" edge="1" parent="1" source="build-artifact" target="cdn">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="publish-config" value="publish config" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;entryX=1;entryY=0.7;strokeColor=#57606A;strokeWidth=1.5;endArrow=open;endFill=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#57606A;" edge="1" parent="1" source="config-artifact" target="cdn">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="serve-assets" value="serve immutable assets" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.3;entryX=1;entryY=0.3;strokeColor=#57606A;strokeWidth=2.5;endArrow=block;endFill=1;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#57606A;" edge="1" parent="1" source="cdn" target="browser">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="serve-config" value="serve /config.json" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.7;entryX=1;entryY=0.7;strokeColor=#57606A;strokeWidth=2.5;endArrow=block;endFill=1;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#57606A;" edge="1" parent="1" source="cdn" target="browser">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="legend" value="Open arrow = artifact publish Filled arrow = browser delivery" style="text;html=1;strokeColor=none;fillColor=none;align=right;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#57606A;" vertex="1" parent="1">
|
||||
<mxGeometry x="650" y="570" width="300" height="42" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/ca-skeleton-frontend/architecture-overview-2026-07-18.drawio
|
||||
@@ -0,0 +1,59 @@
|
||||
<mxfile host="Codex" agent="wiki-workflow + diagram-standards v2 minimalist" version="24.0.0" type="device">
|
||||
<diagram name="ca-skeleton-frontend-overview" id="frontend-overview">
|
||||
<mxGraphModel dx="1000" dy="560" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1000" pageHeight="560" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<mxCell id="title" value="<b>ca-skeleton frontend — compile-time dependency ownership</b> Which source layer may depend on which owner?" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=18;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#57606A;" vertex="1" parent="1">
|
||||
<mxGeometry x="50" y="20" width="900" height="58" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="presentation" value="<b>Presentation</b> Routes · UI state" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#57606A;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="100" y="180" width="210" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="application" value="<b>Application</b> Use cases · owned ports" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#57606A;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="370" y="180" width="210" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="domain" value="<b>Domain</b> Business policy" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#EFF6FF;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="640" y="180" width="210" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="composition-root" value="<b>Composition Root</b> Bootstrap · wiring" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#57606A;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="370" y="400" width="210" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="adapters" value="<b>Adapters</b> HTTP · telemetry" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#57606A;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="640" y="400" width="210" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="dep-presentation-application" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;entryX=0;entryY=0.5;strokeColor=#57606A;strokeWidth=1.5;endArrow=open;endFill=0;" edge="1" parent="1" source="presentation" target="application">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="dep-application-domain" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;entryX=0;entryY=0.5;strokeColor=#57606A;strokeWidth=1.5;endArrow=open;endFill=0;" edge="1" parent="1" source="application" target="domain">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="dep-adapters-application" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.2;exitY=0;entryX=0.8;entryY=1;strokeColor=#57606A;strokeWidth=1.5;endArrow=open;endFill=0;" edge="1" parent="1" source="adapters" target="application">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="dep-root-presentation" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;entryX=0.5;entryY=1;strokeColor=#57606A;strokeWidth=1.5;endArrow=open;endFill=0;" edge="1" parent="1" source="composition-root" target="presentation">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="dep-root-application" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;entryX=0.5;entryY=1;strokeColor=#57606A;strokeWidth=1.5;endArrow=open;endFill=0;" edge="1" parent="1" source="composition-root" target="application">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="dep-root-adapters" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;entryX=0;entryY=0.5;strokeColor=#57606A;strokeWidth=1.5;endArrow=open;endFill=0;" edge="1" parent="1" source="composition-root" target="adapters">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/ca-skeleton/architecture-modules-2026-05-26.drawio
|
||||
@@ -0,0 +1,97 @@
|
||||
<mxfile host="Claude Code" agent="ca-superpowers + diagram-standards v2 minimalist" version="24.0.0" type="device">
|
||||
<diagram name="ca-skeleton-modules" id="modules">
|
||||
<mxGraphModel dx="1200" dy="700" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1200" pageHeight="800" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<mxCell id="title" value="ca-skeleton — Gradle 모듈 의존성 (Clean Architecture)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F2937;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="20" width="900" height="32" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="subtitle" value="cmd → presentation → service → domain + cmd → infra → domain. domain 은 어디에도 의존하지 않는다." style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="52" width="1000" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- cmd (composition root) -->
|
||||
<mxCell id="cmd" value="<b>cmd</b> composition root" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="500" y="110" width="200" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Middle row: presentation, service, infra -->
|
||||
<mxCell id="presentation" value="<b>presentation</b> HTTP · DTO · Filter" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="160" y="260" width="200" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="service" value="<b>service</b> UseCase · Command" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="500" y="260" width="200" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="infra" value="<b>infra</b> JPA · Adapter" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="840" y="260" width="200" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- domain (protagonist: pure) -->
|
||||
<mxCell id="domain" value="<b>domain</b> Java stdlib only" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#EFF6FF;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="500" y="420" width="200" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- EDGES (allowed dependencies) -->
|
||||
<mxCell id="e-cmd-pres" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;" edge="1" parent="1" source="cmd" target="presentation">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e-cmd-svc" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;" edge="1" parent="1" source="cmd" target="service">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e-cmd-infra" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;" edge="1" parent="1" source="cmd" target="infra">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e-pres-svc" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;" edge="1" parent="1" source="presentation" target="service">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e-pres-dom" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.2;entryY=0;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=2.5;" edge="1" parent="1" source="presentation" target="domain">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e-svc-dom" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=2.5;" edge="1" parent="1" source="service" target="domain">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e-infra-dom" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.8;entryY=0;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=2.5;" edge="1" parent="1" source="infra" target="domain">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- CALLOUT -->
|
||||
<mxCell id="callout" value="⚠️ <b>HARD-STOP — 금지된 의존</b> ✗ domain → Spring / JPA / HTTP / cloud SDK ✗ service → infra 또는 presentation ✗ presentation → infra (또는 JPA Entity 직접 사용) ✗ infra → presentation 또는 controller DTO ✅ ArchUnit + verifyCleanArchitectureDependencies 로 컴파일 타임 강제" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FEF2F2;strokeColor=#DC2626;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#7F1D1D;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="560" width="600" height="140" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- LEGEND -->
|
||||
<mxCell id="leg-title" value="Legend" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="760" y="560" width="100" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-domain" value="파란 박스 = domain (CA 의 심장 — 순수 Java)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="760" y="585" width="420" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-blue-arrow" value="파란 굵은 화살 = → domain (CA 의 본질적 의존 방향)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="760" y="605" width="420" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-gray" value="회색 가는 화살 = cmd / presentation→service (조립 의존)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#57606A;" vertex="1" parent="1">
|
||||
<mxGeometry x="760" y="625" width="420" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="footer" value="v1 (minimalist) · 2026-05-26" style="text;html=1;strokeColor=none;fillColor=none;align=right;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=2;fontFamily=Pretendard,Helvetica;fontColor=#9CA3AF;" vertex="1" parent="1">
|
||||
<mxGeometry x="900" y="750" width="200" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/ca-skeleton/architecture-runtime-topology-2026-05-26.drawio
|
||||
@@ -0,0 +1,100 @@
|
||||
<mxfile host="Claude Code" agent="ca-superpowers + diagram-standards v2 minimalist" version="24.0.0" type="device">
|
||||
<diagram name="ca-skeleton-runtime-topology" id="runtime">
|
||||
<mxGraphModel dx="1200" dy="700" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1200" pageHeight="780" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<mxCell id="title" value="ca-skeleton — 런타임 토폴로지 (mandatory vs optional)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F2937;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="20" width="900" height="32" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="subtitle" value="앱이 기동하기 위해 반드시 있어야 하는 것 vs APP_ADAPTER_*_ENABLED=false 로 끌 수 있는 것" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="52" width="1000" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Application boundary (주인공) -->
|
||||
<mxCell id="grp-app" value="Application (cmd composition root)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#EFF6FF;strokeColor=#1F6FEB;strokeWidth=2.5;dashed=0;fontSize=12;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=left;verticalAlign=top;spacingLeft=10;spacingTop=6;" vertex="1" parent="1">
|
||||
<mxGeometry x="300" y="100" width="260" height="200" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- External zone (점선 회색) -->
|
||||
<mxCell id="grp-external" value="External (Internet)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#F6F8FA;strokeColor=#D0D7DE;strokeWidth=1.5;dashed=1;fontSize=12;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;align=left;verticalAlign=top;spacingLeft=10;spacingTop=6;" vertex="1" parent="1">
|
||||
<mxGeometry x="880" y="100" width="240" height="200" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="client" value="<b>Client</b> HTTP" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="100" y="180" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- App (CA 내부 collapse: 단일 박스로) -->
|
||||
<mxCell id="app" value="<b>App</b> Spring Boot" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#1F6FEB;strokeWidth=2;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="340" y="170" width="180" height="90" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Mandatory: PostgreSQL -->
|
||||
<mxCell id="db" value="<b>PostgreSQL</b> mandatory" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#FFFFFF;strokeColor=#1F6FEB;strokeWidth=2;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=center;" vertex="1" parent="1">
|
||||
<mxGeometry x="620" y="140" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Optional: Redis + Kafka (점선 + 주황) -->
|
||||
<mxCell id="redis" value="<b>Redis</b> optional" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#FFFFFF;strokeColor=#FB923C;strokeWidth=1.5;dashed=1;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;align=center;" vertex="1" parent="1">
|
||||
<mxGeometry x="620" y="220" width="120" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="kafka" value="<b>Kafka</b> optional" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#FFFFFF;strokeColor=#FB923C;strokeWidth=1.5;dashed=1;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;align=center;" vertex="1" parent="1">
|
||||
<mxGeometry x="760" y="220" width="120" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- External APIs (집합 — Email · Slack · Google) -->
|
||||
<mxCell id="external-apis" value="<b>External APIs</b> Email · Slack · Google" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#D0D7DE;strokeWidth=1.5;dashed=1;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="920" y="180" width="200" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- EDGES -->
|
||||
<mxCell id="e1" value="HTTPS REST" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" edge="1" parent="1" source="client" target="app">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e2" value="JDBC" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.3;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" edge="1" parent="1" source="app" target="db">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e3" value="RESP" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.7;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#FB923C;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;dashed=1;" edge="1" parent="1" source="app" target="redis">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e4" value="Kafka" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.9;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#FB923C;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;dashed=1;" edge="1" parent="1" source="app" target="kafka">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e5" value="HTTPS" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#6B7280;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;dashed=1;" edge="1" parent="1" source="app" target="external-apis">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- CALLOUT (실제 trap) -->
|
||||
<mxCell id="callout" value="⚠️ <b>optional 어댑터가 mandatory 처럼 동작</b> service 에서 RedisCachePort 를 null 검사 없이 의존 → Redis OFF 시 NPE 로 앱 자체가 죽음. §11 위반. ✅ optional adapter 는 fallback path 또는 Optional<Port> 로." style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FEF2F2;strokeColor=#DC2626;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#7F1D1D;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="100" y="380" width="660" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- LEGEND (3 항목 — §9 표준 컨벤션 외만) -->
|
||||
<mxCell id="leg-title" value="Legend" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="100" y="540" width="100" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-mand" value="파란 굵은 실선 = mandatory (없으면 기동 실패)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="100" y="565" width="700" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-opt" value="주황 점선 = optional internal adapter (Redis · Kafka — APP_ADAPTER_*_ENABLED=false 로 OFF 가능)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;" vertex="1" parent="1">
|
||||
<mxGeometry x="100" y="585" width="900" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="footer" value="v2 (minimalist) · 2026-05-26" style="text;html=1;strokeColor=none;fillColor=none;align=right;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=2;fontFamily=Pretendard,Helvetica;fontColor=#9CA3AF;" vertex="1" parent="1">
|
||||
<mxGeometry x="900" y="730" width="200" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/clean-architecture-topic/architecture-clean-concentric-2026-07-04.drawio
|
||||
@@ -0,0 +1,52 @@
|
||||
<mxfile host="Claude Code" agent="wiki-superpowers + diagram-standards v2 minimalist" version="24.0.0" type="device">
|
||||
<diagram name="clean-architecture-concentric" id="clean">
|
||||
<mxGraphModel dx="1100" dy="720" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1000" pageHeight="760" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<mxCell id="title" value="Clean Architecture — 4개 동심원과 The Dependency Rule" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F2937;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="20" width="920" height="32" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="subtitle" value="의존성은 바깥 → 안으로만 향한다. 안쪽 원은 바깥 원의 이름·타입·함수를 전혀 모른다." style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="52" width="920" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="ring4" value="<b>Frameworks & Drivers</b> DB · Web · UI · 외부 기기" style="ellipse;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#57606A;verticalAlign=top;align=center;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="330" y="110" width="540" height="470" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="ring3" value="<b>Interface Adapters</b> Controller · Gateway · Presenter" style="ellipse;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#57606A;verticalAlign=top;align=center;spacingTop=6;" vertex="1" parent="1">
|
||||
<mxGeometry x="400" y="165" width="400" height="360" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="ring2" value="<b>Use Cases</b> application 규칙" style="ellipse;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#57606A;verticalAlign=top;align=center;spacingTop=6;" vertex="1" parent="1">
|
||||
<mxGeometry x="465" y="220" width="270" height="250" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="ring1" value="<b>Entities</b> 핵심 규칙" style="ellipse;whiteSpace=wrap;html=1;fillColor=#EFF6FF;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=13;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;verticalAlign=middle;align=center;" vertex="1" parent="1">
|
||||
<mxGeometry x="530" y="290" width="140" height="110" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="dep-arrow" value="" style="endArrow=classic;html=1;strokeColor=#1F6FEB;strokeWidth=3;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" edge="1" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="150" y="345" as="sourcePoint" />
|
||||
<mxPoint x="330" y="345" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="dep-label" value="<b>The Dependency Rule</b> 바깥 → 안으로만" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="90" y="285" width="180" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="callout" value="<b>안쪽일수록 순수, 바깥일수록 기술</b> • 제어 흐름(호출)은 바깥→안, 소스 의존성도 바깥→안 (일치) • 흐름과 반대인 곳은 인터페이스(DIP)로 방향을 뒤집는다 • 경계를 넘는 데이터는 단순 구조(DTO 등)로 전달 • Hexagonal 과 규칙은 같고, 링을 4개로 더 세분화한 표현" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#F6F8FA;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#374151;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="600" width="580" height="130" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg1" value="파란 원 = Entities (가장 안쪽 · 순수 규칙)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="730" y="610" width="250" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="footer" value="v1 (minimalist) · 2026-07-04" style="text;html=1;strokeColor=none;fillColor=none;align=right;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=2;fontFamily=Pretendard,Helvetica;fontColor=#9CA3AF;" vertex="1" parent="1">
|
||||
<mxGeometry x="760" y="720" width="220" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/clean-architecture-topic/architecture-clean-concentric-2026-07-04.svg
|
||||
|
Before Width: | Height: | Size: 105 B After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,48 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="760" viewBox="0 0 1000 760" font-family="Pretendard, Helvetica, Arial, sans-serif">
|
||||
<defs>
|
||||
<marker id="arrowBlue" markerWidth="12" markerHeight="12" refX="8" refY="5" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path d="M0,0 L9,5 L0,10 z" fill="#1F6FEB"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="1000" height="760" fill="#FFFFFF"/>
|
||||
|
||||
<text x="40" y="42" font-size="20" font-weight="bold" fill="#1F2937">Clean Architecture — 4개 동심원과 The Dependency Rule</text>
|
||||
<text x="40" y="66" font-size="12" fill="#6B7280">의존성은 바깥 → 안으로만 향한다. 안쪽 원은 바깥 원의 이름·타입·함수를 전혀 모른다.</text>
|
||||
|
||||
<!-- Concentric rings -->
|
||||
<ellipse cx="600" cy="345" rx="270" ry="235" fill="none" stroke="#57606A" stroke-width="1.5"/>
|
||||
<ellipse cx="600" cy="345" rx="200" ry="180" fill="none" stroke="#57606A" stroke-width="1.5"/>
|
||||
<ellipse cx="600" cy="345" rx="135" ry="125" fill="none" stroke="#57606A" stroke-width="1.5"/>
|
||||
<ellipse cx="600" cy="345" rx="70" ry="55" fill="#EFF6FF" stroke="#1F6FEB" stroke-width="2.5"/>
|
||||
|
||||
<!-- Ring labels (top of each ring) -->
|
||||
<text x="600" y="132" text-anchor="middle" font-size="12" font-weight="bold" fill="#57606A">Frameworks & Drivers</text>
|
||||
<text x="600" y="149" text-anchor="middle" font-size="11" fill="#57606A">DB · Web · UI · 외부 기기</text>
|
||||
|
||||
<text x="600" y="187" text-anchor="middle" font-size="12" font-weight="bold" fill="#57606A">Interface Adapters</text>
|
||||
<text x="600" y="204" text-anchor="middle" font-size="11" fill="#57606A">Controller · Gateway · Presenter</text>
|
||||
|
||||
<text x="600" y="242" text-anchor="middle" font-size="12" font-weight="bold" fill="#57606A">Use Cases</text>
|
||||
<text x="600" y="259" text-anchor="middle" font-size="11" fill="#57606A">application 규칙</text>
|
||||
|
||||
<text x="600" y="341" text-anchor="middle" font-size="13" font-weight="bold" fill="#1F6FEB">Entities</text>
|
||||
<text x="600" y="359" text-anchor="middle" font-size="11" fill="#1F6FEB">핵심 규칙</text>
|
||||
|
||||
<!-- Dependency Rule arrow -->
|
||||
<line x1="150" y1="345" x2="322" y2="345" stroke="#1F6FEB" stroke-width="3" marker-end="url(#arrowBlue)"/>
|
||||
<text x="180" y="303" text-anchor="middle" font-size="11" font-weight="bold" fill="#1F6FEB">The Dependency Rule</text>
|
||||
<text x="180" y="321" text-anchor="middle" font-size="11" fill="#1F6FEB">바깥 → 안으로만</text>
|
||||
|
||||
<!-- Callout (key insight) -->
|
||||
<rect x="120" y="600" width="580" height="130" rx="8" fill="#F6F8FA" stroke="#57606A" stroke-width="1.5"/>
|
||||
<text x="136" y="626" font-size="12" font-weight="bold" fill="#374151">안쪽일수록 순수, 바깥일수록 기술</text>
|
||||
<text x="136" y="652" font-size="11" fill="#374151">• 제어 흐름(호출)은 바깥→안, 소스 의존성도 바깥→안 (일치)</text>
|
||||
<text x="136" y="674" font-size="11" fill="#374151">• 흐름과 반대인 곳은 인터페이스(DIP)로 방향을 뒤집는다</text>
|
||||
<text x="136" y="696" font-size="11" fill="#374151">• 경계를 넘는 데이터는 단순 구조(DTO 등)로 전달</text>
|
||||
<text x="136" y="718" font-size="11" fill="#374151">• Hexagonal 과 규칙은 같고, 링을 4개로 더 세분화한 표현</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<text x="730" y="614" font-size="11" fill="#1F6FEB">● 파란 원 = Entities (가장 안쪽 · 순수 규칙)</text>
|
||||
|
||||
<text x="960" y="732" text-anchor="end" font-size="10" font-style="italic" fill="#9CA3AF">v1 · 2026-07-04</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 105 B After Width: | Height: | Size: 3.5 KiB |
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/clean-architecture-topic/architecture-hexagonal-2026-07-04.drawio
|
||||
@@ -0,0 +1,62 @@
|
||||
<mxfile host="Claude Code" agent="wiki-superpowers + diagram-standards v2 minimalist" version="24.0.0" type="device">
|
||||
<diagram name="hexagonal-architecture" id="hexagonal">
|
||||
<mxGraphModel dx="1100" dy="700" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1040" pageHeight="720" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<mxCell id="title" value="Hexagonal (Ports & Adapters) — 도메인을 바깥에서 격리한다" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F2937;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="20" width="960" height="32" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="subtitle" value="모든 adapter가 core에 의존(안쪽). core는 port(interface)만 정의하고 외부(web·DB)를 모른다." style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="52" width="960" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="core" value="<b>Application Core</b> domain + use case" style="shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#EFF6FF;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=13;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="410" y="270" width="240" height="150" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="web" value="<b>Web Adapter</b> REST · inbound" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="230" width="200" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="test" value="<b>Test / Batch</b> inbound" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="390" width="200" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="persist" value="<b>Persistence Adapter</b> JPA · outbound" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="780" y="230" width="200" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="db" value="<b>DB</b>" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=14;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="820" y="400" width="120" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e-web" value="inbound port" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.4;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" edge="1" parent="1" source="web" target="core">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="e-test" value="inbound port" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.6;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" edge="1" parent="1" source="test" target="core">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="e-persist" value="outbound port 구현" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" edge="1" parent="1" source="persist" target="core">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="e-db" value="JDBC" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" edge="1" parent="1" source="persist" target="db">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="callout" value="<b>핵심 — 의존성 역전(DIP)</b> • core 는 port(interface)만 정의, 구현은 바깥 adapter 가 담당 • 그래서 core(도메인)는 web·DB 의 존재를 모른다 • DB·web 교체 자유 + 실제 장비 없이 격리 테스트 가능 • 대가: port/adapter 보일러플레이트, 모델 매핑 비용" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#F6F8FA;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#374151;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="340" y="500" width="560" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg1" value="파란 육각형 = Application Core (격리 대상)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="60" y="510" width="260" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg2" value="파란 화살 = adapter → core 의존(안쪽)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="60" y="532" width="260" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="footer" value="v1 (minimalist) · 2026-07-04" style="text;html=1;strokeColor=none;fillColor=none;align=right;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=2;fontFamily=Pretendard,Helvetica;fontColor=#9CA3AF;" vertex="1" parent="1">
|
||||
<mxGeometry x="780" y="670" width="220" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/clean-architecture-topic/architecture-hexagonal-2026-07-04.svg
|
||||
|
Before Width: | Height: | Size: 98 B After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,65 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1040" height="720" viewBox="0 0 1040 720" font-family="Pretendard, Helvetica, Arial, sans-serif">
|
||||
<defs>
|
||||
<marker id="arrowBlue" markerWidth="10" markerHeight="10" refX="7" refY="5" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path d="M0,0 L8,5 L0,10 z" fill="#1F6FEB"/>
|
||||
</marker>
|
||||
<marker id="arrowGray" markerWidth="10" markerHeight="10" refX="7" refY="5" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path d="M0,0 L8,5 L0,10 z" fill="#57606A"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="1040" height="720" fill="#FFFFFF"/>
|
||||
|
||||
<text x="40" y="42" font-size="20" font-weight="bold" fill="#1F2937">Hexagonal (Ports & Adapters) — 도메인을 바깥에서 격리한다</text>
|
||||
<text x="40" y="66" font-size="12" fill="#6B7280">모든 adapter가 core에 의존(안쪽). core는 port(interface)만 정의하고 외부(web·DB)를 모른다.</text>
|
||||
|
||||
<!-- Application Core (hexagon) -->
|
||||
<polygon points="410,345 450,270 610,270 650,345 610,420 450,420" fill="#EFF6FF" stroke="#1F6FEB" stroke-width="2.5"/>
|
||||
<text x="530" y="340" text-anchor="middle" font-size="13" font-weight="bold" fill="#1F6FEB">Application Core</text>
|
||||
<text x="530" y="360" text-anchor="middle" font-size="12" fill="#1F6FEB">domain + use case</text>
|
||||
|
||||
<!-- Web Adapter (inbound) -->
|
||||
<rect x="80" y="230" width="200" height="70" fill="#FFFFFF" stroke="#57606A" stroke-width="1.5"/>
|
||||
<text x="180" y="260" text-anchor="middle" font-size="12" font-weight="bold" fill="#24292F">Web Adapter</text>
|
||||
<text x="180" y="280" text-anchor="middle" font-size="12" fill="#24292F">REST · inbound</text>
|
||||
|
||||
<!-- Test / Batch (inbound) -->
|
||||
<rect x="80" y="390" width="200" height="70" fill="#FFFFFF" stroke="#57606A" stroke-width="1.5"/>
|
||||
<text x="180" y="420" text-anchor="middle" font-size="12" font-weight="bold" fill="#24292F">Test / Batch</text>
|
||||
<text x="180" y="440" text-anchor="middle" font-size="12" fill="#24292F">inbound</text>
|
||||
|
||||
<!-- Persistence Adapter (outbound) -->
|
||||
<rect x="780" y="230" width="200" height="70" fill="#FFFFFF" stroke="#57606A" stroke-width="1.5"/>
|
||||
<text x="880" y="260" text-anchor="middle" font-size="12" font-weight="bold" fill="#24292F">Persistence Adapter</text>
|
||||
<text x="880" y="280" text-anchor="middle" font-size="12" fill="#24292F">JPA · outbound</text>
|
||||
|
||||
<!-- DB cylinder -->
|
||||
<path d="M820,412 L820,470 A60,10 0 0 0 940,470 L940,412" fill="#FFFFFF" stroke="#57606A" stroke-width="1.5"/>
|
||||
<ellipse cx="880" cy="412" rx="60" ry="10" fill="#FFFFFF" stroke="#57606A" stroke-width="1.5"/>
|
||||
<text x="880" y="448" text-anchor="middle" font-size="12" font-weight="bold" fill="#24292F">DB</text>
|
||||
|
||||
<!-- Edges: adapters -> core (inward, blue) -->
|
||||
<line x1="280" y1="265" x2="444" y2="306" stroke="#1F6FEB" stroke-width="2.5" marker-end="url(#arrowBlue)"/>
|
||||
<text x="352" y="276" font-size="11" fill="#1F6FEB">inbound port</text>
|
||||
<line x1="280" y1="425" x2="446" y2="386" stroke="#1F6FEB" stroke-width="2.5" marker-end="url(#arrowBlue)"/>
|
||||
<text x="352" y="420" font-size="11" fill="#1F6FEB">inbound port</text>
|
||||
<line x1="780" y1="265" x2="616" y2="306" stroke="#1F6FEB" stroke-width="2.5" marker-end="url(#arrowBlue)"/>
|
||||
<text x="648" y="276" font-size="11" fill="#1F6FEB">outbound port 구현</text>
|
||||
|
||||
<!-- Edge: persistence -> DB (gray) -->
|
||||
<line x1="880" y1="300" x2="880" y2="410" stroke="#57606A" stroke-width="1.5" marker-end="url(#arrowGray)"/>
|
||||
<text x="890" y="360" font-size="11" fill="#6B7280">JDBC</text>
|
||||
|
||||
<!-- Callout (key insight) -->
|
||||
<rect x="340" y="500" width="560" height="120" rx="8" fill="#F6F8FA" stroke="#57606A" stroke-width="1.5"/>
|
||||
<text x="356" y="526" font-size="12" font-weight="bold" fill="#374151">핵심 — 의존성 역전(DIP)</text>
|
||||
<text x="356" y="552" font-size="11" fill="#374151">• core 는 port(interface)만 정의, 구현은 바깥 adapter 가 담당</text>
|
||||
<text x="356" y="574" font-size="11" fill="#374151">• 그래서 core(도메인)는 web·DB 의 존재를 모른다</text>
|
||||
<text x="356" y="596" font-size="11" fill="#374151">• DB·web 교체 자유 + 실제 장비 없이 격리 테스트 가능</text>
|
||||
<text x="356" y="618" font-size="11" fill="#374151">• 대가: port/adapter 보일러플레이트, 모델 매핑 비용</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<text x="60" y="516" font-size="11" fill="#1F6FEB">⬡ 파란 육각형 = Application Core (격리 대상)</text>
|
||||
<text x="60" y="538" font-size="11" fill="#1F6FEB">→ 파란 화살 = adapter → core 의존(안쪽)</text>
|
||||
|
||||
<text x="1000" y="690" text-anchor="end" font-size="10" font-style="italic" fill="#9CA3AF">v1 · 2026-07-04</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 98 B After Width: | Height: | Size: 4.6 KiB |
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/clean-architecture-topic/architecture-layered-2026-07-04.drawio
|
||||
@@ -0,0 +1,48 @@
|
||||
<mxfile host="Claude Code" agent="wiki-superpowers + diagram-standards v2 minimalist" version="24.0.0" type="device">
|
||||
<diagram name="layered-architecture" id="layered">
|
||||
<mxGraphModel dx="1000" dy="700" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="900" pageHeight="780" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<mxCell id="title" value="전통적 Layered 아키텍처 — 의존성이 DB로 향한다" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F2937;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="20" width="820" height="32" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="subtitle" value="Presentation → Business → Data Access → DB. 위에서 아래로만 의존한다 (질문: 무엇이 문제인가?)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="52" width="820" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="pres" value="<b>Presentation</b> Controller · View" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="100" width="280" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="biz" value="<b>Business Logic</b> Service · 도메인 규칙" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="215" width="280" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="dao" value="<b>Data Access</b> Repository · DAO" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="330" width="280" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="db" value="<b>Database</b>" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=14;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="445" width="200" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e1" value="depends on" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" edge="1" parent="1" source="pres" target="biz">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="e2" value="depends on" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" edge="1" parent="1" source="biz" target="dao">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="e3" value="depends on" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" edge="1" parent="1" source="dao" target="db">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="callout" value="⚠️ <b>문제 — 도메인이 기술에 묶인다</b> • 비즈니스 규칙이 아래(DB·기술)에 의존 → DB·프레임워크를 바꾸면 도메인까지 영향 • 도메인 테스트에 DB가 필요 → 느리고 깨지기 쉬움 • (package-by-layer) 한 도메인이 controller/service/repository로 흩어져 저응집" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FEF2F2;strokeColor=#DC2626;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#7F1D1D;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="180" y="575" width="560" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="footer" value="v1 (minimalist) · 2026-07-04" style="text;html=1;strokeColor=none;fillColor=none;align=right;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=2;fontFamily=Pretendard,Helvetica;fontColor=#9CA3AF;" vertex="1" parent="1">
|
||||
<mxGeometry x="640" y="730" width="220" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/clean-architecture-topic/architecture-layered-2026-07-04.svg
|
||||
|
Before Width: | Height: | Size: 96 B After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,48 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="780" viewBox="0 0 900 780" font-family="Pretendard, Helvetica, Arial, sans-serif">
|
||||
<defs>
|
||||
<marker id="arrowGray" markerWidth="10" markerHeight="10" refX="7" refY="5" orient="auto" markerUnits="userSpaceOnUse">
|
||||
<path d="M0,0 L8,5 L0,10 z" fill="#57606A"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="900" height="780" fill="#FFFFFF"/>
|
||||
|
||||
<text x="40" y="42" font-size="20" font-weight="bold" fill="#1F2937">전통적 Layered 아키텍처 — 의존성이 DB로 향한다</text>
|
||||
<text x="40" y="66" font-size="12" fill="#6B7280">Presentation → Business → Data Access → DB. 위에서 아래로만 의존한다 (질문: 무엇이 문제인가?)</text>
|
||||
|
||||
<!-- Presentation -->
|
||||
<rect x="320" y="100" width="280" height="70" fill="#FFFFFF" stroke="#57606A" stroke-width="1.5"/>
|
||||
<text x="460" y="130" text-anchor="middle" font-size="12" font-weight="bold" fill="#24292F">Presentation</text>
|
||||
<text x="460" y="150" text-anchor="middle" font-size="12" fill="#24292F">Controller · View</text>
|
||||
|
||||
<!-- Business -->
|
||||
<rect x="320" y="215" width="280" height="70" fill="#FFFFFF" stroke="#57606A" stroke-width="1.5"/>
|
||||
<text x="460" y="245" text-anchor="middle" font-size="12" font-weight="bold" fill="#24292F">Business Logic</text>
|
||||
<text x="460" y="265" text-anchor="middle" font-size="12" fill="#24292F">Service · 도메인 규칙</text>
|
||||
|
||||
<!-- Data Access -->
|
||||
<rect x="320" y="330" width="280" height="70" fill="#FFFFFF" stroke="#57606A" stroke-width="1.5"/>
|
||||
<text x="460" y="360" text-anchor="middle" font-size="12" font-weight="bold" fill="#24292F">Data Access</text>
|
||||
<text x="460" y="380" text-anchor="middle" font-size="12" fill="#24292F">Repository · DAO</text>
|
||||
|
||||
<!-- DB cylinder -->
|
||||
<path d="M360,457 L360,523 A100,12 0 0 0 560,523 L560,457" fill="#FFFFFF" stroke="#57606A" stroke-width="1.5"/>
|
||||
<ellipse cx="460" cy="457" rx="100" ry="12" fill="#FFFFFF" stroke="#57606A" stroke-width="1.5"/>
|
||||
<text x="460" y="497" text-anchor="middle" font-size="12" font-weight="bold" fill="#24292F">Database</text>
|
||||
|
||||
<!-- Edges -->
|
||||
<line x1="460" y1="170" x2="460" y2="213" stroke="#57606A" stroke-width="1.5" marker-end="url(#arrowGray)"/>
|
||||
<text x="470" y="196" font-size="11" fill="#6B7280">depends on</text>
|
||||
<line x1="460" y1="285" x2="460" y2="328" stroke="#57606A" stroke-width="1.5" marker-end="url(#arrowGray)"/>
|
||||
<text x="470" y="311" font-size="11" fill="#6B7280">depends on</text>
|
||||
<line x1="460" y1="400" x2="460" y2="443" stroke="#57606A" stroke-width="1.5" marker-end="url(#arrowGray)"/>
|
||||
<text x="470" y="426" font-size="11" fill="#6B7280">depends on</text>
|
||||
|
||||
<!-- Callout (problem) -->
|
||||
<rect x="180" y="575" width="560" height="120" rx="8" fill="#FEF2F2" stroke="#DC2626" stroke-width="1.5"/>
|
||||
<text x="196" y="601" font-size="12" font-weight="bold" fill="#7F1D1D">⚠️ 문제 — 도메인이 기술에 묶인다</text>
|
||||
<text x="196" y="629" font-size="11" fill="#7F1D1D">• 비즈니스 규칙이 아래(DB·기술)에 의존 → DB·프레임워크를 바꾸면 도메인까지 영향</text>
|
||||
<text x="196" y="653" font-size="11" fill="#7F1D1D">• 도메인 테스트에 DB가 필요 → 느리고 깨지기 쉬움</text>
|
||||
<text x="196" y="677" font-size="11" fill="#7F1D1D">• (package-by-layer) 한 도메인이 controller/service/repository로 흩어져 저응집</text>
|
||||
|
||||
<text x="860" y="745" text-anchor="end" font-size="10" font-style="italic" fill="#9CA3AF">v1 · 2026-07-04</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 96 B After Width: | Height: | Size: 3.5 KiB |
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/keycloak-patterns/architecture-p1a-edge-no-google-2026-05-26.drawio
|
||||
@@ -0,0 +1,89 @@
|
||||
<mxfile host="Claude Code" agent="ca-superpowers + diagram-standards v2 minimalist" version="24.0.0" type="device">
|
||||
<diagram name="P1A-Edge-no-Google" id="p1a">
|
||||
<mxGraphModel dx="1200" dy="700" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1200" pageHeight="780" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<mxCell id="title" value="P1A — Edge ForwardAuth (no Google)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F2937;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="20" width="700" height="32" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="subtitle" value="Edge proxy 가 인증 게이트일 때, 백엔드는 어떻게 인증 코드 0줄로 동작하는가?" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="52" width="900" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Edge zone (강조: 주인공이 oauth2-proxy) -->
|
||||
<mxCell id="grp-edge" value="Edge zone" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFF7ED;strokeColor=#FB923C;strokeWidth=2;dashed=0;fontSize=12;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;align=left;verticalAlign=top;spacingLeft=10;spacingTop=6;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="100" width="260" height="240" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Internal zone -->
|
||||
<mxCell id="grp-internal" value="Internal (post-auth)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FBFCFD;strokeColor=#1F6FEB;strokeWidth=2;dashed=0;fontSize=12;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=left;verticalAlign=top;spacingLeft=10;spacingTop=6;" vertex="1" parent="1">
|
||||
<mxGeometry x="640" y="100" width="460" height="240" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="user" value="<b>User</b> Browser" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="190" width="140" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- oauth2-proxy: 주인공 -->
|
||||
<mxCell id="proxy" value="<b>oauth2-proxy</b> forward-auth gateway" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFEDD5;strokeColor=#FB923C;strokeWidth=2.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="190" width="200" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="backend" value="<b>Backend API</b> 인증 코드 0줄" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="680" y="140" width="200" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="keycloak" value="<b>Keycloak</b> OIDC AS" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="680" y="240" width="200" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- EDGES -->
|
||||
<mxCell id="e1" value="① HTTPS" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#FB923C;strokeWidth=2.5;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;" edge="1" parent="1" source="user" target="proxy">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e2" value="② OIDC redirect" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.7;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;" edge="1" parent="1" source="proxy" target="keycloak">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e3" value="③ 로그인 + token" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.3;exitDx=0;exitDy=0;entryX=0.8;entryY=1;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;dashed=1;" edge="1" parent="1" source="keycloak" target="user">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e4" value="④ X-Forwarded-User" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.3;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#FB923C;strokeWidth=2.5;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;" edge="1" parent="1" source="proxy" target="backend">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- CALLOUT -->
|
||||
<mxCell id="callout" value="⚠️ <b>헤더 spoofing 위험</b> backend 가 X-Forwarded-User 헤더만으로 사용자 식별 → ingress 우회 경로 (NetworkPolicy / SG 미설정) 시 위조 가능. ✅ Network 격리 + mTLS 로 proxy 만 backend 호출 가능하게." style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FEF2F2;strokeColor=#DC2626;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#7F1D1D;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="400" width="500" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- LEGEND -->
|
||||
<mxCell id="leg-title" value="Legend" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="560" width="100" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-edge" value="주황 zone = Edge (proxy 가 인증 게이트, 주인공)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="585" width="500" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-internal" value="파란 zone = Internal (인증 완료 후 영역)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="605" width="500" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-arrow" value="주황 굵은 화살표 = 인증 핵심 경로" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="625" width="500" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="footer" value="v1 (minimalist) · 2026-05-26" style="text;html=1;strokeColor=none;fillColor=none;align=right;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=2;fontFamily=Pretendard,Helvetica;fontColor=#9CA3AF;" vertex="1" parent="1">
|
||||
<mxGeometry x="900" y="730" width="200" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/keycloak-patterns/architecture-p1b-edge-google-2026-05-26.drawio
|
||||
@@ -0,0 +1,92 @@
|
||||
<mxfile host="Claude Code" agent="ca-superpowers + diagram-standards v2 minimalist" version="24.0.0" type="device">
|
||||
<diagram name="P1B-Edge-Google" id="p1b">
|
||||
<mxGraphModel dx="1200" dy="700" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1200" pageHeight="780" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<mxCell id="title" value="P1B — Edge ForwardAuth + Google federation" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F2937;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="20" width="800" height="32" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="subtitle" value="P1A 에 Google 로그인을 붙이면 Keycloak IdP brokering 만으로 SPA/Backend 변경 없이 가능한가?" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="52" width="1000" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="grp-edge" value="Edge zone" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFF7ED;strokeColor=#FB923C;strokeWidth=2;dashed=0;fontSize=12;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;align=left;verticalAlign=top;spacingLeft=10;spacingTop=6;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="100" width="260" height="240" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="grp-internal" value="Internal" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FBFCFD;strokeColor=#1F6FEB;strokeWidth=2;dashed=0;fontSize=12;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=left;verticalAlign=top;spacingLeft=10;spacingTop=6;" vertex="1" parent="1">
|
||||
<mxGeometry x="640" y="100" width="240" height="240" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="user" value="<b>User</b> Browser" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="190" width="140" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="proxy" value="<b>oauth2-proxy</b>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="140" width="200" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="backend" value="<b>Backend API</b>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="680" y="140" width="180" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Keycloak: 주인공 (brokering의 핵심) -->
|
||||
<mxCell id="keycloak" value="<b>Keycloak</b> + Google IdP brokering" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#EFF6FF;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="680" y="240" width="180" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Google (외부, 점선) -->
|
||||
<mxCell id="google" value="<b>Google OIDC</b> (External IdP)" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#F6F8FA;strokeColor=#D0D7DE;strokeWidth=1.5;dashed=1;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="940" y="240" width="180" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- EDGES -->
|
||||
<mxCell id="e1" value="① HTTPS" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;" edge="1" parent="1" source="user" target="proxy">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e2" value="② OIDC redirect" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.7;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;" edge="1" parent="1" source="proxy" target="keycloak">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e3" value="③ Google 로그인" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" edge="1" parent="1" source="keycloak" target="google">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e4" value="④ id_token (email_verified)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.8;exitDx=0;exitDy=0;entryX=1;entryY=0.8;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;dashed=1;" edge="1" parent="1" source="google" target="keycloak">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e5" value="⑤ X-Forwarded-User" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;" edge="1" parent="1" source="proxy" target="backend">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- CALLOUT -->
|
||||
<mxCell id="callout" value="⚠️ <b>First Broker Login Flow — email-match 자동 linking</b> Keycloak 기본 옵션이 email 기반 자동 linking 제공. Google email_verified=false 시 본인 외 사용자의 기존 계정 탈취 가능. ✅ &quot;Confirm Link Existing Account&quot; + email_verified=true 강제." style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FEF2F2;strokeColor=#DC2626;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#7F1D1D;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="400" width="600" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- LEGEND -->
|
||||
<mxCell id="leg-title" value="Legend" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="560" width="100" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-kc" value="파란 박스 + 파란 굵은 화살 = Google brokering 핵심 (주인공)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="585" width="600" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-ext" value="회색 점선 박스 = External (Google OIDC)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="605" width="600" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="footer" value="v1 (minimalist) · 2026-05-26" style="text;html=1;strokeColor=none;fillColor=none;align=right;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=2;fontFamily=Pretendard,Helvetica;fontColor=#9CA3AF;" vertex="1" parent="1">
|
||||
<mxGeometry x="900" y="730" width="200" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/keycloak-patterns/architecture-p2a-cluster-internal-no-google-2026-05-26.drawio
|
||||
@@ -0,0 +1,80 @@
|
||||
<mxfile host="Claude Code" agent="ca-superpowers + diagram-standards v2 minimalist" version="24.0.0" type="device">
|
||||
<diagram name="P2A-Cluster-Internal-no-Google" id="p2a">
|
||||
<mxGraphModel dx="1200" dy="700" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1200" pageHeight="780" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<mxCell id="title" value="P2A — Cluster-internal SPA-direct (no Google)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F2937;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="20" width="800" height="32" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="subtitle" value="Edge proxy 없이 SPA가 직접 OIDC. Backend는 JWT Resource Server. JWKS 가 어떻게 신뢰를 닫는가?" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="52" width="1000" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Cluster Internal zone -->
|
||||
<mxCell id="grp-internal" value="Cluster Internal" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FBFCFD;strokeColor=#1F6FEB;strokeWidth=2;dashed=0;fontSize=12;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=left;verticalAlign=top;spacingLeft=10;spacingTop=6;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="100" width="760" height="260" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="user" value="<b>User</b> Browser + SPA" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="190" width="140" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- SPA: 주인공 (토큰 보유자) -->
|
||||
<mxCell id="spa" value="<b>vanilla JS SPA</b> nginx · PKCE" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#EFF6FF;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="180" width="200" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="backend" value="<b>Backend RS</b> Spring Security 6.x" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="640" y="140" width="200" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="keycloak" value="<b>Keycloak</b> OIDC AS + JWKS" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="900" y="240" width="160" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- EDGES -->
|
||||
<mxCell id="e1" value="① HTTPS GET (SPA)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;" edge="1" parent="1" source="user" target="spa">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e2" value="② OIDC + PKCE" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.7;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" edge="1" parent="1" source="spa" target="keycloak">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e3" value="③ Bearer access_token" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.3;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" edge="1" parent="1" source="spa" target="backend">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e4" value="④ JWKS (검증 키)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.8;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" edge="1" parent="1" source="backend" target="keycloak">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- CALLOUT -->
|
||||
<mxCell id="callout" value="⚠️ <b>SPA 토큰 보유 → XSS surface 확대</b> access/refresh token 이 브라우저 메모리/스토리지에 노출 가능. XSS 1건 = 토큰 탈취 = 사용자 세션 전체 탈취. ✅ refresh token 보호 필요 시 BFF(P1) 또는 httpOnly cookie 전략 검토." style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FEF2F2;strokeColor=#DC2626;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#7F1D1D;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="420" width="600" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- LEGEND -->
|
||||
<mxCell id="leg-title" value="Legend" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="580" width="100" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-spa" value="파란 박스 + 파란 굵은 화살 = SPA-direct OIDC 핵심 (주인공: SPA + 토큰 흐름)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="605" width="700" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-zone" value="파란 zone = Cluster Internal (Edge proxy 없음, P1 과의 결정적 차이)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="625" width="700" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="footer" value="v1 (minimalist) · 2026-05-26" style="text;html=1;strokeColor=none;fillColor=none;align=right;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=2;fontFamily=Pretendard,Helvetica;fontColor=#9CA3AF;" vertex="1" parent="1">
|
||||
<mxGeometry x="900" y="730" width="200" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/keycloak-patterns/architecture-p2b-cluster-internal-google-2026-05-26.drawio
|
||||
@@ -0,0 +1,88 @@
|
||||
<mxfile host="Claude Code" agent="ca-superpowers + diagram-standards v2 minimalist" version="24.0.0" type="device">
|
||||
<diagram name="P2B-Cluster-Internal-Google" id="p2b">
|
||||
<mxGraphModel dx="1200" dy="700" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1200" pageHeight="780" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<mxCell id="title" value="P2B — Cluster-internal + Google federation" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F2937;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="20" width="800" height="32" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="subtitle" value="P2A 에 Google brokering 추가. SPA/Backend 코드 변경 없이 Keycloak Realm 설정만으로 Google SSO 가능한가?" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="52" width="1000" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="grp-internal" value="Cluster Internal" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FBFCFD;strokeColor=#1F6FEB;strokeWidth=2;dashed=0;fontSize=12;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=left;verticalAlign=top;spacingLeft=10;spacingTop=6;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="100" width="600" height="260" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="user" value="<b>User</b> Browser + SPA" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="190" width="140" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="spa" value="<b>vanilla JS SPA</b> nginx + PKCE" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="180" width="180" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="backend" value="<b>Backend RS</b> Spring Security 6.x" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="580" y="140" width="200" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Keycloak: 주인공 (brokering의 핵심) -->
|
||||
<mxCell id="keycloak" value="<b>Keycloak</b> + Google IdP brokering" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#EFF6FF;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="580" y="240" width="200" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Google (외부, 점선) -->
|
||||
<mxCell id="google" value="<b>Google OIDC</b> (External IdP)" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#F6F8FA;strokeColor=#D0D7DE;strokeWidth=1.5;dashed=1;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="960" y="240" width="180" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- EDGES -->
|
||||
<mxCell id="e1" value="① HTTPS GET" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;" edge="1" parent="1" source="user" target="spa">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e2" value="② OIDC + PKCE" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.7;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;" edge="1" parent="1" source="spa" target="keycloak">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e3" value="③ Google 로그인" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" edge="1" parent="1" source="keycloak" target="google">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e4" value="④ id_token" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.8;exitDx=0;exitDy=0;entryX=1;entryY=0.8;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;dashed=1;" edge="1" parent="1" source="google" target="keycloak">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e5" value="⑤ Bearer + JWKS" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.3;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;" edge="1" parent="1" source="spa" target="backend">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- CALLOUT -->
|
||||
<mxCell id="callout" value="⚠️ <b>같은 함정 — First Broker Login Flow auto-linking + SPA XSS</b> P1B 의 email-match 함정 + P2A 의 XSS 함정이 모두 적용됨. ✅ Confirm Link Existing Account 강제 + email_verified=true ✅ XSS 방지 (CSP / sanitize) + BFF 필요 시 P1 으로 이주." style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FEF2F2;strokeColor=#DC2626;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#7F1D1D;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="420" width="640" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- LEGEND -->
|
||||
<mxCell id="leg-title" value="Legend" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="580" width="100" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-kc" value="파란 박스 + 파란 굵은 화살 = Google brokering 핵심 (주인공)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="605" width="700" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-ext" value="회색 점선 박스 = External (Google OIDC)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="625" width="700" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="footer" value="v1 (minimalist) · 2026-05-26" style="text;html=1;strokeColor=none;fillColor=none;align=right;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=2;fontFamily=Pretendard,Helvetica;fontColor=#9CA3AF;" vertex="1" parent="1">
|
||||
<mxGeometry x="900" y="730" width="200" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/keycloak-patterns/architecture-p3a-single-ec2-no-google-2026-05-26.drawio
|
||||
@@ -0,0 +1,105 @@
|
||||
<mxfile host="Claude Code" agent="ca-superpowers + diagram-standards v2 minimalist" version="24.0.0" type="device">
|
||||
<diagram name="P3A-Single-EC2" id="p3a-v3">
|
||||
<mxGraphModel dx="1200" dy="700" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1200" pageHeight="780" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<!-- HEADER -->
|
||||
<mxCell id="title" value="P3A — Single EC2 (no Google)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F2937;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="20" width="700" height="32" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="subtitle" value="사용자 요청이 어떤 컴포넌트를 어떤 순서로 거치며 인증되는가?" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="52" width="900" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- TRUST BOUNDARY: Single EC2 -->
|
||||
<mxCell id="grp-ec2" value="Single EC2 (localhost)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FBFCFD;strokeColor=#1F6FEB;strokeWidth=2;dashed=0;fontSize=12;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=left;verticalAlign=top;spacingLeft=10;spacingTop=6;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="100" width="780" height="500" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- USER (외부, 회색 무채색) -->
|
||||
<mxCell id="user" value="<b>User</b> Browser" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="310" width="140" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- NGINX (기본 회색) -->
|
||||
<mxCell id="nginx" value="<b>nginx</b> SPA + reverse proxy" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="380" y="160" width="200" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- BACKEND (기본 회색) -->
|
||||
<mxCell id="backend" value="<b>Spring Boot RS</b> JWT validator · :8080" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="840" y="160" width="220" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- KEYCLOAK (주인공 — 강조색 파랑) -->
|
||||
<mxCell id="keycloak" value="<b>Keycloak</b> OIDC · :8180" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#EFF6FF;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="540" y="380" width="220" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- POSTGRES (cylinder, 기본 회색) -->
|
||||
<mxCell id="postgres" value="<b>PostgreSQL</b>" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="870" y="500" width="160" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ================= EDGES (5개, 단순화) ================= -->
|
||||
|
||||
<!-- ① User → nginx (HTTPS) -->
|
||||
<mxCell id="e1" value="① HTTPS GET /" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.3;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;" edge="1" parent="1" source="user" target="nginx">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ② User → Keycloak (OIDC) — 강조 (critical path) -->
|
||||
<mxCell id="e2" value="② OIDC + PKCE" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.7;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=2.5;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" edge="1" parent="1" source="user" target="keycloak">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ③ User → nginx (API call) — 두 번째 호출 (단순화: 단일 라벨로) -->
|
||||
<mxCell id="e3" value="③ Bearer token + /api" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.8;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;" edge="1" parent="1" source="user" target="nginx">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ④ nginx → Backend -->
|
||||
<mxCell id="e4" value="④ proxy_pass" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;" edge="1" parent="1" source="nginx" target="backend">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ⑤ Backend → Keycloak (JWKS) — 강조 (관계가 중요) -->
|
||||
<mxCell id="e5" value="⑤ JWKS" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.3;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.3;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=2;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" edge="1" parent="1" source="backend" target="keycloak">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Keycloak → PostgreSQL (보조, 번호 없음) -->
|
||||
<mxCell id="e-kc-pg" value="JDBC" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.8;exitY=1;exitDx=0;exitDy=0;entryX=0.3;entryY=0;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;dashed=1;" edge="1" parent="1" source="keycloak" target="postgres">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ================= CALLOUT (1개만 — 핵심 함정) ================= -->
|
||||
<mxCell id="callout" value="⚠️ <b>KC_HOSTNAME 함정</b> Browser는 public host, backend는 localhost로 Keycloak 호출 → JWT iss claim mismatch. ✅ KC_HOSTNAME=&lt;public-host&gt; 명시." style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FEF2F2;strokeColor=#DC2626;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#7F1D1D;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="450" width="380" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ================= LEGEND (2 항목) ================= -->
|
||||
<mxCell id="leg-title" value="Legend" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="60" y="650" width="100" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-bnd" value="파란 실선 박스 = Trust Boundary (localhost containers)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="60" y="680" width="500" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-critical" value="파란 굵은 화살표 / 파란 박스 = OIDC 핵심 경로 (주인공)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="60" y="700" width="500" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<mxCell id="footer" value="v3 (minimalist) · 2026-05-26" style="text;html=1;strokeColor=none;fillColor=none;align=right;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=2;fontFamily=Pretendard,Helvetica;fontColor=#9CA3AF;" vertex="1" parent="1">
|
||||
<mxGeometry x="900" y="750" width="200" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/keycloak-patterns/architecture-p3b-single-ec2-google-2026-05-26.drawio
|
||||
@@ -0,0 +1,106 @@
|
||||
<mxfile host="Claude Code" agent="ca-superpowers + diagram-standards v2 minimalist" version="24.0.0" type="device">
|
||||
<diagram name="P3B-Single-EC2-Google" id="p3b">
|
||||
<mxGraphModel dx="1200" dy="700" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1200" pageHeight="800" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<mxCell id="title" value="P3B — Single EC2 + Google federation" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F2937;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="20" width="800" height="32" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="subtitle" value="P3A 에 Google 을 붙이려면 왜 EC2 외부 HTTPS endpoint(tunnel/RP)가 강제되는가?" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="52" width="1000" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Public HTTPS zone (강조 — Google 요구 사항) -->
|
||||
<mxCell id="grp-public" value="Public HTTPS (tunnel / RP — Google 요구)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFF7ED;strokeColor=#FB923C;strokeWidth=2;dashed=0;fontSize=12;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;align=left;verticalAlign=top;spacingLeft=10;spacingTop=6;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="100" width="240" height="280" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- EC2 zone -->
|
||||
<mxCell id="grp-ec2" value="Single EC2 (localhost)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FBFCFD;strokeColor=#1F6FEB;strokeWidth=2;dashed=0;fontSize=12;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=left;verticalAlign=top;spacingLeft=10;spacingTop=6;" vertex="1" parent="1">
|
||||
<mxGeometry x="560" y="100" width="380" height="280" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="user" value="<b>User</b> Browser" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="100" y="220" width="140" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Tunnel: 주인공 (public HTTPS 강제) -->
|
||||
<mxCell id="tunnel" value="<b>HTTPS tunnel</b> cloudflared / ngrok / Caddy" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFEDD5;strokeColor=#FB923C;strokeWidth=2.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="220" width="180" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="nginx" value="<b>nginx</b> SPA + reverse proxy" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="600" y="140" width="160" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="backend" value="<b>Backend</b> Spring Boot RS" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="780" y="140" width="140" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="keycloak" value="<b>Keycloak</b> + Google IdP brokering" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="600" y="240" width="320" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Google (외부, 점선) -->
|
||||
<mxCell id="google" value="<b>Google OIDC</b> (External IdP)" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#F6F8FA;strokeColor=#D0D7DE;strokeWidth=1.5;dashed=1;fontSize=12;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="980" y="240" width="160" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- EDGES -->
|
||||
<mxCell id="e1" value="① HTTPS" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#FB923C;strokeWidth=2.5;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;" edge="1" parent="1" source="user" target="tunnel">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e2" value="② localhost (HTTP)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.3;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#FB923C;strokeWidth=2.5;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;" edge="1" parent="1" source="tunnel" target="nginx">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e3" value="③ proxy_pass /api" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;" edge="1" parent="1" source="nginx" target="backend">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e4" value="④ JWKS" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.2;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;" edge="1" parent="1" source="backend" target="keycloak">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e5" value="⑤ Google 로그인 (공개 HTTPS)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#FB923C;strokeWidth=2.5;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;" edge="1" parent="1" source="keycloak" target="google">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e6" value="⑥ id_token" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.8;exitDx=0;exitDy=0;entryX=1;entryY=0.8;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#24292F;dashed=1;" edge="1" parent="1" source="google" target="keycloak">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- CALLOUT -->
|
||||
<mxCell id="callout" value="⚠️ <b>KC_HOSTNAME = 공개 hostname (P3A 와 결정적 차이)</b> Google 이 검증하는 redirect_uri 와 Keycloak issuer 가 모두 공개 HTTPS host 여야 함. P3A 처럼 localhost 로 설정 시 Google 흐름 실패 / issuer 불일치 401. ✅ KC_HOSTNAME=<public> + Keycloak Realm Client redirect_uri = public URL." style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FEF2F2;strokeColor=#DC2626;strokeWidth=1.5;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#7F1D1D;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="100" y="430" width="660" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- LEGEND -->
|
||||
<mxCell id="leg-title" value="Legend" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="100" y="590" width="100" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-tunnel" value="주황 zone + 주황 굵은 화살 = Public HTTPS 경로 (Google이 강제, P3A 와의 결정적 차이)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;" vertex="1" parent="1">
|
||||
<mxGeometry x="100" y="615" width="800" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-ec2" value="파란 zone = Single EC2 localhost (P3A 와 동일 신뢰 모델)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="100" y="635" width="800" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-warn" value="빨간 박스 = 함정 (KC_HOSTNAME 을 localhost 로 두면 Google 흐름 실패)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontFamily=Pretendard,Helvetica;fontColor=#DC2626;" vertex="1" parent="1">
|
||||
<mxGeometry x="100" y="655" width="800" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="footer" value="v1 (minimalist) · 2026-05-26" style="text;html=1;strokeColor=none;fillColor=none;align=right;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=2;fontFamily=Pretendard,Helvetica;fontColor=#9CA3AF;" vertex="1" parent="1">
|
||||
<mxGeometry x="900" y="750" width="200" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1 +0,0 @@
|
||||
../../../../vault/10-projects/diagrams/keycloak-patterns/archived/architecture-p3a-single-ec2-no-google-2026-05-26-v1.drawio
|
||||
@@ -0,0 +1,113 @@
|
||||
<mxfile host="Claude Code" agent="ca-superpowers" version="24.0.0" type="device">
|
||||
<diagram name="P3A-Single-EC2-no-Google" id="p3a-overall">
|
||||
<mxGraphModel dx="1422" dy="800" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="900" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<!-- TITLE -->
|
||||
<mxCell id="title" value="P3A — Single EC2 (no Google) vanilla JS SPA + Spring Boot Resource Server + Keycloak (localhost)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="20" width="900" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- EXTERNAL: User -->
|
||||
<mxCell id="grp-external" value="클러스터 외부 (Internet)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#F6F8FA;strokeColor=#D0D7DE;strokeWidth=1;dashed=1;fontSize=12;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#57606A;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="90" width="240" height="700" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="user" value="User Browser (SPA 호출 + OIDC redirect)" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1;fontSize=12;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="380" width="160" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- TRUST BOUNDARY: Single EC2 -->
|
||||
<mxCell id="grp-ec2" value="Trust Boundary: Single EC2 (localhost containers)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FBFCFD;strokeColor=#1F6FEB;strokeWidth=2;dashed=0;fontSize=13;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="90" width="1000" height="700" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- nginx -->
|
||||
<mxCell id="nginx" value="<b>nginx</b> static SPA serving + reverse proxy :443 (or :80)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFF7ED;strokeColor=#FB923C;strokeWidth=1;fontSize=11;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#C2410C;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="380" y="140" width="220" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Backend -->
|
||||
<mxCell id="backend" value="<b>Spring Boot</b> Resource Server Spring Security 6.x JWT validator (iss, aud, exp) localhost:8080" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#D4EDDA;strokeColor=#155724;strokeWidth=1;fontSize=11;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#155724;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="850" y="140" width="240" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Keycloak -->
|
||||
<mxCell id="keycloak" value="<b>Keycloak 25.x</b> Authorization Server KC_HOSTNAME=&lt;public-host&gt; localhost:8180" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#CFE2FF;strokeColor=#0D6EFD;strokeWidth=1;fontSize=11;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#0D6EFD;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="380" y="400" width="260" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- PostgreSQL -->
|
||||
<mxCell id="postgres" value="<b>PostgreSQL 16</b> Keycloak 백엔드 DB localhost:5432" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#F8D7DA;strokeColor=#842029;strokeWidth=1;fontSize=11;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#842029;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="900" y="420" width="180" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- EDGES -->
|
||||
<!-- User <-> nginx (HTTPS) -->
|
||||
<mxCell id="e-user-nginx" value="HTTPS :443 GET / + GET /api/v1/* Bearer access_token" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.3;exitDx=0;exitDy=0;entryX=0;entryY=0.4;entryDx=0;entryDy=0;strokeColor=#0969DA;strokeWidth=2;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#0969DA;" edge="1" parent="1" source="user" target="nginx">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e-nginx-user" value="static SPA (HTML/JS/CSS)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.7;exitDx=0;exitDy=0;entryX=1;entryY=0.6;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#57606A;dashed=1;" edge="1" parent="1" source="nginx" target="user">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- User <-> Keycloak (OIDC browser redirect) -->
|
||||
<mxCell id="e-user-kc" value="OIDC Authorization Code + PKCE (browser redirect)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.7;exitDx=0;exitDy=0;entryX=0;entryY=0.3;entryDx=0;entryDy=0;strokeColor=#0D6EFD;strokeWidth=2;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#0D6EFD;" edge="1" parent="1" source="user" target="keycloak">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="e-kc-user" value="{access_token, refresh_token, id_token}" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.7;exitDx=0;exitDy=0;entryX=1;entryY=0.85;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#57606A;dashed=1;" edge="1" parent="1" source="keycloak" target="user">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- nginx -> Backend (reverse proxy) -->
|
||||
<mxCell id="e-nginx-backend" value="reverse proxy localhost:8080" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#155724;strokeWidth=2;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#155724;" edge="1" parent="1" source="nginx" target="backend">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Backend -> Keycloak (JWKS) -->
|
||||
<mxCell id="e-backend-kc" value="JWKS (localhost:8180/realms/.../certs)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.3;exitY=1;exitDx=0;exitDy=0;entryX=0.7;entryY=0;entryDx=0;entryDy=0;strokeColor=#155724;strokeWidth=1;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#155724;dashed=1;" edge="1" parent="1" source="backend" target="keycloak">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Keycloak -> PostgreSQL (JDBC) -->
|
||||
<mxCell id="e-kc-pg" value="JDBC (localhost:5432)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#842029;strokeWidth=2;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#842029;" edge="1" parent="1" source="keycloak" target="postgres">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- LEGEND -->
|
||||
<mxCell id="grp-legend" value="Legend / 범례" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#D0D7DE;strokeWidth=1;dashed=0;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=left;verticalAlign=top;spacingLeft=10;spacingTop=6;" vertex="1" parent="1">
|
||||
<mxGeometry x="900" y="620" width="400" height="160" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="legend-ext" value="외부 (Internet) — 점선 테두리" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#F6F8FA;strokeColor=#D0D7DE;dashed=1;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#57606A;" vertex="1" parent="1">
|
||||
<mxGeometry x="920" y="650" width="180" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="legend-tb" value="Trust Boundary — 파란 실선" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FBFCFD;strokeColor=#1F6FEB;strokeWidth=2;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="920" y="680" width="180" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="legend-proxy" value="Edge proxy (nginx) — 주황" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFF7ED;strokeColor=#FB923C;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#C2410C;" vertex="1" parent="1">
|
||||
<mxGeometry x="920" y="710" width="180" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="legend-backend" value="Backend (Spring Boot) — 초록" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#D4EDDA;strokeColor=#155724;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#155724;" vertex="1" parent="1">
|
||||
<mxGeometry x="1110" y="650" width="180" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="legend-kc" value="Auth Server (Keycloak) — 파랑" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#CFE2FF;strokeColor=#0D6EFD;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#0D6EFD;" vertex="1" parent="1">
|
||||
<mxGeometry x="1110" y="680" width="180" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="legend-db" value="DB (PostgreSQL) — 빨강" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#F8D7DA;strokeColor=#842029;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#842029;" vertex="1" parent="1">
|
||||
<mxGeometry x="1110" y="710" width="180" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1 +0,0 @@
|
||||
../../../../vault/10-projects/diagrams/keycloak-patterns/archived/architecture-p3a-single-ec2-no-google-2026-05-26-v2.drawio
|
||||
@@ -0,0 +1,231 @@
|
||||
<mxfile host="Claude Code" agent="ca-superpowers + diagram-standards v1" version="24.0.0" type="device">
|
||||
<diagram name="P3A-Single-EC2-no-Google-Conference" id="p3a-conf">
|
||||
<mxGraphModel dx="2000" dy="1100" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="2000" pageHeight="1200" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<!-- ================= HEADER ================= -->
|
||||
<mxCell id="header-title" value="P3A — Single EC2 (no Google federation)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=22;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F2937;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="20" width="1000" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="header-question" value="❓ 질문: 단일 EC2 호스트(localhost)에 SPA + Spring Boot + Keycloak 이 동거할 때, 사용자 요청이 어떤 컴포넌트를 어떤 순서로 거치며 인증·인가·서비스되는가? KC_HOSTNAME 함정은 어디에서 발생하는가?" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#4B5563;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="58" width="1500" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="header-project" value="📁 Project: keycloak-patterns | branch: develop-keycloak-single-ec2-no-google | status: documented-only" style="text;html=1;strokeColor=none;fillColor=none;align=right;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=2;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="1050" y="20" width="900" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ================= NETWORK BOUNDARY: Public Internet ================= -->
|
||||
<mxCell id="grp-internet" value="🌐 Public Internet (untrusted)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#F6F8FA;strokeColor=#D0D7DE;strokeWidth=1;dashed=1;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="120" width="320" height="880" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- USER -->
|
||||
<mxCell id="user" value="<b style='font-size:13px'>User Browser</b> Role: SPA 호출 + OIDC redirect Stack: Chromium/Firefox/Safari Endpoint: HTTPS :443 Owner: End user" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontSize=11;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="480" width="240" height="140" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ================= NETWORK BOUNDARY: Public-facing edge ================= -->
|
||||
<mxCell id="grp-edge-net" value="🔓 Public-facing edge (HTTPS termination)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FEF9C3;strokeColor=#EAB308;strokeWidth=1;dashed=1;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#854D0E;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="400" y="120" width="380" height="880" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ================= TRUST BOUNDARY: Single EC2 ================= -->
|
||||
<mxCell id="grp-ec2" value="🔒 Trust Boundary: Single EC2 host (localhost docker-compose)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FBFCFD;strokeColor=#1F6FEB;strokeWidth=2.5;dashed=0;fontSize=12;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="820" y="120" width="1140" height="880" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- NGINX -->
|
||||
<mxCell id="nginx" value="<b style='font-size:13px'>nginx 1.27</b> Role: SPA static serving + reverse proxy → backend Stack: nginx (alpine docker) Endpoint: :443 (TLS) / :80 (HTTP) Capacity: ~5000 conn (default) Owner: 본인 (vanilla JS 호스팅)" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FFF7ED;strokeColor=#FB923C;strokeWidth=2;fontSize=11;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="450" y="180" width="280" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- BACKEND -->
|
||||
<mxCell id="backend" value="<b style='font-size:13px'>Spring Boot Resource Server</b> Role: 비즈니스 API + JWT validation (iss / aud / exp / signature) Stack: Spring Boot 3.4 / Java 21 + Spring Security 6.x Endpoint: localhost:8080 Capacity: 1 instance (single-host) Owner: 본인 (백엔드)" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#D4EDDA;strokeColor=#155724;strokeWidth=2.5;fontSize=11;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#155724;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="1620" y="180" width="320" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- KEYCLOAK -->
|
||||
<mxCell id="keycloak" value="<b style='font-size:13px'>Keycloak 25.x</b> Role: OIDC Authorization Server (realm + client + user DB) Stack: Keycloak 25.x (docker) Endpoint: localhost:8180 Config: KC_HOSTNAME=&lt;public-host&gt; KC_HTTP_ENABLED=true Capacity: 1 instance Owner: 본인 (인증)" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#E0E7FF;strokeColor=#3730A3;strokeWidth=2.5;fontSize=11;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#3730A3;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="980" y="500" width="320" height="200" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- POSTGRES -->
|
||||
<mxCell id="postgres" value="<b style='font-size:13px'>PostgreSQL 16</b> Role: Keycloak 사용자/realm DB Stack: postgres:16-alpine Endpoint: localhost:5432 Capacity: 1 instance (no replica) Owner: 본인 (Keycloak 백엔드)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#F8D7DA;strokeColor=#842029;strokeWidth=2;fontSize=11;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#842029;align=center;verticalAlign=middle;" vertex="1" parent="1">
|
||||
<mxGeometry x="1620" y="510" width="280" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ================= EDGES ================= -->
|
||||
|
||||
<!-- ① User → nginx: HTTPS SPA request -->
|
||||
<mxCell id="e1" value="① HTTPS GET / (HTML/JS) payload: ~50KB (initial SPA bundle) p99: ~80ms (cold) / ~10ms (cache)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.2;exitDx=0;exitDy=0;entryX=0;entryY=0.3;entryDx=0;entryDy=0;strokeColor=#1F6FEB;strokeWidth=3;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;fontStyle=1;" edge="1" parent="1" source="user" target="nginx">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ② nginx → User: SPA bundle (response) -->
|
||||
<mxCell id="e2" value="② static bundle HTML / JS / CSS" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.6;exitDx=0;exitDy=0;entryX=1;entryY=0.7;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#57606A;dashed=1;" edge="1" parent="1" source="nginx" target="user">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ③ User → Keycloak: OIDC redirect (browser) -->
|
||||
<mxCell id="e3" value="③ HTTPS GET /realms/&lt;r&gt;/protocol/openid-connect/auth OIDC Authorization Code + PKCE query: ?client_id=spa&code_challenge=...&redirect_uri=... (browser redirect, full URL navigation)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.8;exitDx=0;exitDy=0;entryX=0;entryY=0.3;entryDx=0;entryDy=0;strokeColor=#3730A3;strokeWidth=3;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#3730A3;fontStyle=1;" edge="1" parent="1" source="user" target="keycloak">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ④ Keycloak → User: token (after login) -->
|
||||
<mxCell id="e4" value="④ POST /token (auth code → tokens) { access_token, refresh_token, id_token } p99 ~150ms (PBKDF2 password hash + JWT sign)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.9;entryDx=0;entryDy=0;strokeColor=#57606A;strokeWidth=1.5;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#57606A;dashed=1;" edge="1" parent="1" source="keycloak" target="user">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ⑤ User → nginx (Bearer token, API call) -->
|
||||
<mxCell id="e5" value="⑤ HTTPS GET /api/v1/&lt;resource&gt; Authorization: Bearer &lt;access_token&gt; (XHR/fetch, ~1000 QPS @ peak) p99 &lt; 100ms (target)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.4;exitDx=0;exitDy=0;entryX=0;entryY=0.6;entryDx=0;entryDy=0;strokeColor=#0969DA;strokeWidth=3;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#0969DA;fontStyle=1;" edge="1" parent="1" source="user" target="nginx">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ⑥ nginx → Backend (reverse proxy) -->
|
||||
<mxCell id="e6" value="⑥ proxy_pass http://localhost:8080 Authorization header forward (timeout: 30s, keepalive: 60s)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#155724;strokeWidth=3;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#155724;fontStyle=1;" edge="1" parent="1" source="nginx" target="backend">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ⑦ Backend → Keycloak (JWKS) -->
|
||||
<mxCell id="e7" value="⑦ GET /realms/&lt;r&gt;/protocol/openid-connect/certs (JWKS — JWT signature 검증용 키) 캐시: 5분 (NimbusJwtDecoder default)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.3;exitY=1;exitDx=0;exitDy=0;entryX=0.7;entryY=0;entryDx=0;entryDy=0;strokeColor=#155724;strokeWidth=1.5;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#155724;dashed=1;" edge="1" parent="1" source="backend" target="keycloak">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ⑧ Keycloak → PostgreSQL -->
|
||||
<mxCell id="e8" value="⑧ JDBC SELECT realm/user/client (Hikari pool: 100 conn default)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#842029;strokeWidth=3;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#842029;fontStyle=1;" edge="1" parent="1" source="keycloak" target="postgres">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ❌ ERROR PATH: JWT iss mismatch -->
|
||||
<mxCell id="e-err1" value="❌ ⓔ JWT iss mismatch → 401 Unauthorized 원인: KC_HOSTNAME 미설정 시 발생" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.4;exitDx=0;exitDy=0;entryX=1;entryY=0.3;entryDx=0;entryDy=0;strokeColor=#DC2626;strokeWidth=2;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#DC2626;dashed=1;fontStyle=1;" edge="1" parent="1" source="backend" target="user">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ================= CALLOUTS (gotchas) ================= -->
|
||||
|
||||
<!-- Callout 1: KC_HOSTNAME 함정 -->
|
||||
<mxCell id="callout-kc-hostname" value="⚠️ <b>핵심 함정 #1 — KC_HOSTNAME</b> Browser는 EC2 public hostname 으로 Keycloak 호출 → JWT 의 iss claim = 그 hostname. Backend는 localhost:8180 로 JWKS 조회 → iss 비교 시 mismatch → ⓔ 401 발생. ✅ 해결: docker-compose env 에 KC_HOSTNAME=&lt;public-host&gt; KC_HTTP_ENABLED=true 📎 [[raw/branch-notes/develop-keycloak-iss-claim-hostname-mismatch]]" style="rounded=12;whiteSpace=wrap;html=1;fillColor=#FEF2F2;strokeColor=#DC2626;strokeWidth=2;dashed=0;fontSize=10;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#7F1D1D;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="980" y="740" width="320" height="200" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Callout 2: redirect_uri 함정 -->
|
||||
<mxCell id="callout-redirect-uri" value="⚠️ <b>핵심 함정 #2 — redirect_uri</b> Keycloak client 의 Valid Redirect URIs 등록 시 localhost 만 등록 / browser 는 127.0.0.1 접근 → OIDC redirect 실패. ✅ 해결: 등록과 접근 hostname 1:1 일치 또는 둘 다 등록. 📎 [[raw/branch-notes/develop-keycloak-docker-compose-stack]]" style="rounded=12;whiteSpace=wrap;html=1;fillColor=#FEF2F2;strokeColor=#DC2626;strokeWidth=2;dashed=0;fontSize=10;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#7F1D1D;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="1340" y="740" width="280" height="200" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Callout 3: SPoF -->
|
||||
<mxCell id="callout-spof" value="⚠️ <b>SPoF (단일 실패 지점)</b> EC2 1대에 모든 컴포넌트 동거 → EC2 다운 = 전체 시스템 정지. P3A 는 학습 / 개발 환경 한정. 운영급은 P2A (cluster-internal) + Keycloak HA cluster 검토. 📎 [[raw/project-notes/keycloak-patterns-overview]] §10 Phase" style="rounded=12;whiteSpace=wrap;html=1;fillColor=#FEF2F2;strokeColor=#DC2626;strokeWidth=2;dashed=0;fontSize=10;fontStyle=0;fontFamily=Pretendard,Helvetica;fontColor=#7F1D1D;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="1660" y="740" width="280" height="200" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ================= LEGEND ================= -->
|
||||
<mxCell id="grp-legend" value="📖 Legend" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#D0D7DE;strokeWidth=1;dashed=0;fontSize=12;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#24292F;align=left;verticalAlign=top;spacingLeft=12;spacingTop=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="1010" width="1920" height="160" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Legend: Boundaries -->
|
||||
<mxCell id="leg-title-bnd" value="경계 (Boundaries)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="60" y="1040" width="200" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-tb" value="Trust Boundary (파란 실선)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FBFCFD;strokeColor=#1F6FEB;strokeWidth=2;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="60" y="1065" width="200" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-net-pub" value="Public Internet (회색 점선)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#F6F8FA;strokeColor=#D0D7DE;dashed=1;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="60" y="1090" width="200" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-net-edge" value="Public edge zone (노란 점선)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FEF9C3;strokeColor=#EAB308;dashed=1;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#854D0E;" vertex="1" parent="1">
|
||||
<mxGeometry x="60" y="1115" width="200" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-warn" value="Critical / Warning (빨간 점선)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FEF2F2;strokeColor=#DC2626;dashed=1;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#7F1D1D;" vertex="1" parent="1">
|
||||
<mxGeometry x="60" y="1140" width="200" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Legend: Component colors -->
|
||||
<mxCell id="leg-title-cmp" value="컴포넌트 (§A.7 색상 시맨틱)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="290" y="1040" width="200" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-user" value="User / 외부 (흰색)" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#24292F;" vertex="1" parent="1">
|
||||
<mxGeometry x="290" y="1065" width="190" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-proxy" value="Edge / Proxy / GW (주황)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#FFF7ED;strokeColor=#FB923C;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#9A3412;" vertex="1" parent="1">
|
||||
<mxGeometry x="290" y="1090" width="190" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-backend" value="App / Backend (초록)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#D4EDDA;strokeColor=#155724;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#155724;" vertex="1" parent="1">
|
||||
<mxGeometry x="290" y="1115" width="190" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-auth" value="Auth / Identity (남색)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#E0E7FF;strokeColor=#3730A3;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#3730A3;" vertex="1" parent="1">
|
||||
<mxGeometry x="290" y="1140" width="190" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-db" value="Data store (빨강)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#F8D7DA;strokeColor=#842029;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#842029;" vertex="1" parent="1">
|
||||
<mxGeometry x="490" y="1065" width="190" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-external" value="External / 3rd-party (회색 점선)" style="rounded=8;whiteSpace=wrap;html=1;fillColor=#F6F8FA;strokeColor=#D0D7DE;dashed=1;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="490" y="1090" width="190" height="22" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Legend: Edge semantics -->
|
||||
<mxCell id="leg-title-edge" value="화살표 (Edges)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="710" y="1040" width="200" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-edge-hot" value="굵은 실선 (3px) — Critical / Hot path (sync request)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#1F6FEB;" vertex="1" parent="1">
|
||||
<mxGeometry x="710" y="1065" width="400" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-edge-thin" value="점선 (1.5px, 회색) — Response / passive return" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#57606A;" vertex="1" parent="1">
|
||||
<mxGeometry x="710" y="1090" width="400" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-edge-aux" value="얇은 점선 — Auxiliary (JWKS, cache lookup, etc.)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#155724;" vertex="1" parent="1">
|
||||
<mxGeometry x="710" y="1115" width="400" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-edge-err" value="빨간 점선 — Error path (Failure mode 표시)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#DC2626;" vertex="1" parent="1">
|
||||
<mxGeometry x="710" y="1140" width="400" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Legend: Symbols -->
|
||||
<mxCell id="leg-title-sym" value="기호 (Symbols)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="1130" y="1040" width="200" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="leg-sym-step" value="① ② ③ ... — Numbered flow (읽기 순서)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="1130" y="1065" width="400" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-sym-err" value="❌ ⓔ — Error step (라벨 시작에 표시)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#DC2626;" vertex="1" parent="1">
|
||||
<mxGeometry x="1130" y="1090" width="400" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-sym-warn" value="⚠️ — Callout: 비자명한 함정 / 결정 / 위협" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#7F1D1D;" vertex="1" parent="1">
|
||||
<mxGeometry x="1130" y="1115" width="400" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-sym-src" value="📎 — Source wikilink (사실 출처 / 검증 가능)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#4B5563;" vertex="1" parent="1">
|
||||
<mxGeometry x="1130" y="1140" width="400" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Legend: Scale annotation explanation -->
|
||||
<mxCell id="leg-title-scale" value="스케일 표기" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;fontStyle=1;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="1530" y="1040" width="200" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-scale-qps" value="QPS — Queries per second" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="1530" y="1065" width="400" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-scale-p99" value="p99 — 99th percentile latency" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontFamily=Pretendard,Helvetica;fontColor=#374151;" vertex="1" parent="1">
|
||||
<mxGeometry x="1530" y="1090" width="400" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="leg-scale-ec2" value="P3A 는 학습/개발 한정 — 운영 SLA / SLO 미정" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=2;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="1530" y="1115" width="400" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- ================= FOOTER ================= -->
|
||||
<mxCell id="footer-meta" value="Version 2.0 (v1 → archived/) · 2026-05-26 · @donghyeon · Source: [[raw/branch-notes/develop-keycloak-single-ec2-no-google]] · Standard: [[templates/diagram-standards]] §A" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;fontStyle=2;fontFamily=Pretendard,Helvetica;fontColor=#6B7280;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="1180" width="1920" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1 +0,0 @@
|
||||
../../../vault/10-projects/diagrams/nplus1-presentation-prep/architecture-lab-loop-2026-07-20.drawio
|
||||
@@ -0,0 +1,52 @@
|
||||
<mxfile host="app.diagrams.net" modified="2026-07-20T00:00:00.000Z" agent="Codex" version="24.7.17">
|
||||
<diagram id="nplus1-lab-loop" name="N+1 Lab Loop">
|
||||
<mxGraphModel dx="1200" dy="720" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="title" value="N+1 Evidence Loop" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=24;fontStyle=1;fontColor=#24292F;" vertex="1" parent="1">
|
||||
<mxGeometry x="60" y="40" width="300" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="learner" value="<b>Learner</b><br>checkout + observe" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontColor=#24292F;" vertex="1" parent="1">
|
||||
<mxGeometry x="60" y="180" width="160" height="70" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="lab" value="<b>Lab Checkpoint</b><br>profile-isolated API" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#1F6FEB;strokeWidth=2.5;fontColor=#24292F;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="180" width="180" height="70" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="feed" value="<b>Feed Module</b><br>query strategy" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#1F6FEB;strokeWidth=2.5;fontColor=#24292F;" vertex="1" parent="1">
|
||||
<mxGeometry x="520" y="180" width="170" height="70" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="postgres" value="<b>PostgreSQL</b><br>rows + plans" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontColor=#24292F;size=15;" vertex="1" parent="1">
|
||||
<mxGeometry x="760" y="170" width="160" height="90" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="evidence" value="<b>Evidence Record</b><br>metrics + grade" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#1F6FEB;strokeWidth=2.5;fontColor=#24292F;" vertex="1" parent="1">
|
||||
<mxGeometry x="520" y="370" width="170" height="70" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="presentation" value="<b>Presentation</b><br>reviewed claims" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=#57606A;strokeWidth=1.5;fontColor=#24292F;" vertex="1" parent="1">
|
||||
<mxGeometry x="760" y="370" width="160" height="70" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e1" value="① Checkout stage" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#1F6FEB;strokeWidth=2;" edge="1" parent="1" source="learner" target="lab">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e2" value="② Run scenario" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#1F6FEB;strokeWidth=2;" edge="1" parent="1" source="lab" target="feed">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e3" value="③ Execute SQL" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#57606A;strokeWidth=1.5;" edge="1" parent="1" source="feed" target="postgres">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e4" value="④ Return rows" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#57606A;strokeWidth=1.5;" edge="1" parent="1" source="postgres" target="feed">
|
||||
<mxGeometry relative="1" as="geometry"><Array as="points"><mxPoint x="840" y="120"/><mxPoint x="605" y="120"/></Array></mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e5" value="⑤ Capture metrics" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#1F6FEB;strokeWidth=2;" edge="1" parent="1" source="feed" target="evidence">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="e6" value="⑥ Compare result" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#57606A;strokeWidth=1.5;" edge="1" parent="1" source="evidence" target="learner">
|
||||
<mxGeometry relative="1" as="geometry"><Array as="points"><mxPoint x="370" y="405"/><mxPoint x="140" y="405"/></Array></mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="e7" value="⑦ Promote reviewed" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#57606A;strokeWidth=1.5;" edge="1" parent="1" source="evidence" target="presentation">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||