Section 9/171 menit
9. Module Graph Analysis dan Enforcement
9. Module Graph Analysis dan Enforcement
Visualisasi dengan tuist graph
swift
# Generate dependency graph sebagai gambar
tuist graph
# Output: graph.png di working directory
# Bisa juga output sebagai JSON untuk analisis programmatic
tuist graph --format json > graph.json
# Focus pada subset graph
tuist graph --targets FeatureCheckout,NetworkKit,SharedUI
Memahami Output Graph
swift
graph.png menunjukkan:
App
├── FeatureHome ──→ NetworkKit
│ └→ SharedUI
├── FeatureCheckout ──→ NetworkKit
│ ├→ SharedUI
│ └→ StripeKit (external)
└── FeatureProfile ──→ NetworkKit
└→ SharedUI
Dari graph ini bisa analisis:
- NetworkKit dan SharedUI adalah critical path (semua feature depend ke sana)
- StripeKit hanya digunakan FeatureCheckout (isolated dependency)
- Tidak ada circular dependency (graph adalah DAG)
Implicit Import Detection
swift
# Deteksi module yang di-import dalam source tapi tidak di-declare di Project.swift
tuist inspect implicit-imports
# Contoh output:
# ⚠️ FeatureHome imports 'AnalyticsKit' but it's not declared as a dependency
# ⚠️ FeatureCheckout imports 'Stripe' directly, use .external(name: "Stripe")
# Ini adalah source bug yang umum:
# Developer import module yang kebetulan available karena transitive dependency
# Ketika dependency graph berubah, build tiba-tiba gagal
Architecture Linting via Custom Script
swift
// scripts/lint-architecture.swift
// Jalankan: swift scripts/lint-architecture.swift
import Foundation
// Parse graph.json yang di-generate tuist graph --format json
let graphData = try Data(contentsOf: URL(fileURLWithPath: "graph.json"))
let graph = try JSONDecoder().decode(ModuleGraph.self, from: graphData)
// Rules:
// 1. Core modules tidak boleh depend ke Feature modules
// 2. Shared modules tidak boleh depend ke Feature modules
// 3. Circular dependencies tidak boleh ada
var violations: [String] = []
for (module, deps) in graph.dependencies {
let layer = detectLayer(module)
for dep in deps {
let depLayer = detectLayer(dep)
if layer == .core && depLayer == .feature {
violations.append("❌ VIOLATION: \(module) (core) depends on \(dep) (feature)")
}
if layer == .shared && depLayer == .feature {
violations.append("❌ VIOLATION: \(module) (shared) depends on \(dep) (feature)")
}
}
}
if violations.isEmpty {
print("✅ Architecture rules passed")
} else {
violations.forEach { print($0) }
exit(1)
}
func detectLayer(_ name: String) -> Layer {
if name.hasPrefix("Feature") { return .feature }
if name.hasSuffix("Kit") || name.hasSuffix("Service") { return .core }
if name.hasPrefix("Shared") || name.hasPrefix("Design") { return .shared }
return .unknown
}
enum Layer { case core, shared, feature, app, unknown }
struct ModuleGraph: Decodable { var dependencies: [String: [String]] }