Files
gh-cubical6-melly/skills/c4model-c3/dependency-analysis.md
2025-11-29 18:17:07 +08:00

4.1 KiB

Dependency Analysis

This guide provides comprehensive methodology for analyzing component dependencies at the C3 level.


Dependency Analysis

Dependency Types

1. Internal Dependencies (Within Container)

// UserService.ts
import { UserRepository } from './UserRepository';     // Same feature
import { EmailService } from '../email/EmailService';  // Different feature
import { ValidationUtil } from '../utils/validation';  // Utility

// Dependencies:
// - UserRepository (direct dependency)
// - EmailService (cross-feature dependency)
// - ValidationUtil (utility dependency)

2. External Dependencies (Outside Container)

// PaymentService.ts
import Stripe from 'stripe';                    // External library
import { Logger } from '@nestjs/common';        // Framework
import axios from 'axios';                      // HTTP client

// External dependencies:
// - Stripe SDK
// - NestJS framework
// - Axios library

3. Framework Dependencies

// UserController.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';

// Framework dependencies:
// - NestJS decorators
// - Swagger decorators

Dependency Direction

Analyze the flow of dependencies:

Recommended flow (Dependency Inversion):

Controllers → Services → Repositories → Models
     ↓            ↓            ↓
  (HTTP)      (Business)    (Data)

Anti-pattern (Skip layers):

Controllers → Repositories  ❌  (skips business logic layer)

Example: Good Dependency Direction

// Good: Controller depends on Service
@Controller('users')
export class UserController {
  constructor(private userService: UserService) {}

  @Post()
  createUser(@Body() dto: CreateUserDto) {
    return this.userService.createUser(dto);
  }
}

// Good: Service depends on Repository
export class UserService {
  constructor(private userRepo: UserRepository) {}

  async createUser(dto: CreateUserDto) {
    // Business logic here
    return this.userRepo.save(user);
  }
}

// Good: Repository depends on Model
export class UserRepository {
  async save(user: User) {
    // Data access here
  }
}

Coupling Analysis

Types of coupling:

1. Tight Coupling (Bad)

// Bad: Direct instantiation
export class UserService {
  private emailService = new EmailService();  // ❌ Tightly coupled

  async createUser(data: CreateUserDto) {
    const user = await this.saveUser(data);
    this.emailService.sendWelcome(user);  // Can't mock in tests
  }
}

2. Loose Coupling (Good)

// Good: Dependency injection
export class UserService {
  constructor(private emailService: EmailService) {}  // ✅ Loosely coupled

  async createUser(data: CreateUserDto) {
    const user = await this.saveUser(data);
    this.emailService.sendWelcome(user);  // Easy to mock
  }
}

Coupling metrics:

  • Afferent Coupling (Ca): Number of components that depend on this component
  • Efferent Coupling (Ce): Number of components this component depends on
  • Instability (I): Ce / (Ca + Ce)
    • I = 0: Very stable (many dependents, no dependencies)
    • I = 1: Very unstable (no dependents, many dependencies)

Detection:

# Find components with many imports (high efferent coupling)
while IFS= read -r -d '' file; do
  count=$(grep -c '^import' "$file" 2>/dev/null || echo 0)
  printf "%d %s\n" "$count" "$file"
done < <(find src -name "*.ts" -print0) | sort -rn | head -20

# Find components imported by many others (high afferent coupling)
grep -rE "from ['\"](\.\./)+[^'\"]+['\"]" src/ | cut -d: -f2 | cut -d"'" -d'"' -f2 | sort | uniq -c | sort -rn

Circular Dependency Detection

Anti-pattern: Circular dependencies

UserService → OrderService → UserService  ❌

Detection:

# Use madge for Node.js projects
npx madge --circular src/

# Use dependency-cruiser
npx depcruise --validate .dependency-cruiser.js src/

Solution:

  • Extract shared logic to third component
  • Use events instead of direct calls
  • Refactor to remove circular reference