--- id: wiki-2026-0508-architectural-violations title: Architectural Violations category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Architecture Erosion, Architecture Drift, Layer Violations, Dependency Violations] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [architecture, violations, erosion, drift, archunit] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: java/kotlin/typescript framework: ArchUnit / Sonargraph / dependency-cruiser --- # Architectural Violations ## 매 한 줄 > **"매 architectural violation 의 intended architecture 와 actual code 사이의 deviation"**. Perry & Wolf 의 *erosion* (decay over time) + *drift* (unauthorized addition) 의 distinguishing — 매 erosion 의 explicit decay, drift 의 silent. 매 modern detection 의 fitness function (ArchUnit, dep-cruiser, Sonargraph) + reflexion model — 매 manual review 의 too-late. ## 매 핵심 ### 매 violation 의 종류 - **Layer violation**: domain → infrastructure (wrong direction). - **Module violation**: one module imports private internals of another. - **Dependency cycle**: A → B → C → A. - **Convention violation**: `*Service` not in service package. - **Forbidden API**: `java.util.Date`, `console.log` in production code. - **Coupling violation**: too many afferent / efferent dependencies. - **Cohesion violation**: feature smeared across many modules. - **Cross-bounded-context leakage**: shared domain model between contexts. ### 매 detection mechanism 1. **Static structural**: ArchUnit, dep-cruiser, Sonargraph (parse + graph). 2. **Reflexion model**: actual graph diff with intended graph. 3. **Runtime**: trace analysis (OpenTelemetry) for unexpected service-to-service calls. 4. **Reviewer-driven**: ADR conformance check in PR. 5. **Visualization**: dependency matrix, sunburst, codecity. ### 매 lifecycle 1. **Detect** (CI fails or alert). 2. **Classify** (true violation vs intentional exception). 3. **Triage** (debt vs immediate fix). 4. **Fix** (refactor or update intended architecture / ADR). 5. **Prevent regression** (add fitness function). ## 💻 패턴 ### Detect layer violation — ArchUnit ```kotlin @AnalyzeClasses(packages = ["com.acme"], importOptions = [DoNotIncludeTests::class]) class LayerViolationTest { @ArchTest val domain_independent: ArchRule = noClasses() .that().resideInAPackage("..domain..") .should().dependOnClassesThat().resideInAnyPackage( "..application..", "..infrastructure..", "..web..") .because("Domain is the innermost layer (Hexagonal)") @ArchTest val no_repo_in_controller: ArchRule = noClasses() .that().resideInAPackage("..web..") .should().dependOnClassesThat().resideInAPackage("..persistence..") } ``` ### Detect cycle — dependency-cruiser (TS) ```bash npx depcruise --validate .dependency-cruiser.cjs src \ --output-type err-html --output-to depgraph.html # Exit 1 if cycles found; html visualizes offending edges ``` ### Reflexion model — intended vs actual (jQAssistant + Cypher) ```cypher // Intended: domain → no-deps; application → domain; infra → application // Detect any class in :Domain depending on :Infra (forbidden) MATCH (a:Class)-[:DEPENDS_ON]->(b:Class) WHERE a.layer = "Domain" AND b.layer = "Infrastructure" RETURN a.fqn AS violator, b.fqn AS forbidden ``` ### Runtime violation — unauthorized service call (OpenTelemetry + Tempo) ```promql # Alert if service A calls service C (intended only A→B, B→C) sum(rate(traces_spanmetrics_calls_total{ client="service-a", server="service-c" }[5m])) > 0 ``` ### Cyclomatic + afferent/efferent metric (Sonargraph) ```xml ``` ### Triage workflow — annotate intentional exception ```kotlin // When violation is *intended* (rare), document explicitly via ADR + suppress @SuppressArchTest( rule = "domain_independent", reason = "ADR-0034: bridge to legacy DAO during 6-month migration", expiresOn = "2026-12-01" ) class LegacyBridgeAdapter { /* ... */ } ``` ```kotlin // Companion test enforces expiry @ArchTest val no_expired_suppressions: ArchRule = noClasses() .should().beAnnotatedWith(SuppressArchTest::class.java) .andShould(haveExpiredSuppression()) // custom condition ``` ### Visualize violations — dependency matrix (D3) ```javascript // scripts/depmatrix.mjs — render violation heatmap import { cruise } from "dependency-cruiser"; import fs from "node:fs"; const r = await cruise(["src"], { ruleSet: require("./.dependency-cruiser.cjs") }); const violations = r.output.summary.violations; const matrix = buildAdjacencyMatrix(r.output.modules, violations); fs.writeFileSync("violations.json", JSON.stringify(matrix)); // Then render with d3-matrix or observable-plot ``` ### CI gating with delta — only new violations fail ```bash # Compare current vs main — fail PR only on new violations (not legacy debt) npx depcruise src --config .dependency-cruiser.cjs --output-type json > head.json git checkout main -- . npx depcruise src --config .dependency-cruiser.cjs --output-type json > main.json node scripts/diff-violations.mjs main.json head.json # exit 1 on new violations ``` ### Erosion KPI dashboard (per service / quarter) ```sql -- violations_history table (populated nightly by CI) SELECT service, DATE_TRUNC('week', detected_at) AS wk, COUNT(*) FILTER (WHERE severity='error') AS errs, COUNT(*) FILTER (WHERE severity='warn') AS warns FROM violations_history WHERE detected_at > NOW() - INTERVAL '12 weeks' GROUP BY service, wk ORDER BY service, wk; ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | New violation detected in PR | Block merge until fixed or ADR exception added | | Legacy debt — many existing violations | Baseline & gate on delta (no new) | | Intentional bridge | Suppress + ADR + expiry date | | Runtime call mismatch | Trace-based alerting + service mesh policy | | Unclear if violation | Run reflexion model — document intended first | **기본값**: ArchUnit/dep-cruiser at PR time + delta gating on legacy code + ADR-tracked suppressions with expiry. ## 🔗 Graph - 부모: [[Software Architecture]] · [[Architectural-Constraint-Enforcement]] - 변형: [[Architecture Erosion]] · [[Architecture Drift]] - 응용: [[Architecture Review (아키텍처 및 설계 리뷰)]] · [[Architecture_Refactor]] · [[Technical Debt]] - Adjacent: [[ArchUnit]] · [[OpenTelemetry]] ## 🤖 LLM 활용 **언제**: classify violation as erosion vs drift vs intentional, draft suppression ADR with expiry, generate refactor plan from violation report, summarize weekly violations dashboard for tech lead. **언제 X**: do not blindly accept LLM "this violation is fine" — every suppression needs ADR + expiry; LLM judgment is suggestion, not authority. ## ❌ 안티패턴 - **Suppression without expiry**: permanent escape hatch — original intent lost. - **No baseline**: blocking PRs on existing legacy violations buries team. - **Documentation-only intended architecture**: cannot detect drift — no ground truth. - **One-shot detection**: run once, never again — violations re-grow. - **Fix-the-symptom**: rename file vs fix actual coupling. - **Auto-suppress**: tool generates suppressions silently — true violations hidden. ## 🧪 검증 / 중복 - Verified (Perry & Wolf "Foundations for the Study of Software Architecture" 1992, Murphy "Reflexion Models" 1995, ArchUnit / Sonargraph / dependency-cruiser docs). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — erosion vs drift, reflexion model, delta gating |