14 KiB
Component Identification Methodology
This guide provides comprehensive methodology for identifying components at the C3 (Component) level of the C4 Model.
Component Identification Methodology
Step 1: Understand Container Structure
Start by analyzing the containers from c2-containers.json:
Questions to ask:
- What containers exist in this system?
- What is the technology stack for each container?
- What is the directory structure?
- What is the entry point?
Container-to-Component Mapping:
- Backend API → Controllers, Services, Repositories, Models
- Web Frontend → Pages, Components, Services, State Management
- Mobile App → Screens, ViewModels, Services, Repositories
- Worker Service → Jobs, Handlers, Processors, Queues
- Library → Modules, Utilities, Helpers
Step 2: Apply Component Identification Rules
A component at C3 level is:
✅ A Component IS:
-
A cohesive code module with clear responsibility
- Has one primary purpose (Single Responsibility Principle)
- Encapsulates related functionality
- Example:
UserAuthenticationService,OrderRepository,PaymentController
-
A collection of related classes/functions
- Groups related code together
- Organized in a package, module, or directory
- Example:
authentication/module with auth-related classes
-
An architectural building block
- Represents a significant piece of functionality
- Has well-defined interface/API
- Example:
EmailNotificationService,DatabaseConnection
-
A layer or subsystem
- Distinct architectural layer
- Clear boundary from other layers
- Example:
data-access-layer/,business-logic-layer/
❌ A Component is NOT:
-
Individual functions (too granular - that's C4)
- ❌
validateEmail()function - ❌
calculateTotal()function - ✅
ValidationService(collection of validation functions)
- ❌
-
Single classes unless architecturally significant
- ❌
EmailValidatorclass (too small) - ✅
AuthenticationServiceclass (significant) - Rule: If class > 200 LOC or has major role, it's a component
- ❌
-
Configuration files
- ❌
config.json - ❌
.env - These are not components, but configuration
- ❌
-
Test files
- ❌
UserService.test.ts - Tests verify components but aren't components themselves
- ❌
Step 3: Analyze File Structure
Use file system structure to identify components:
Common patterns:
Pattern 1: Directory-based Components (Recommended)
src/
├── authentication/ # → Component: Authentication
│ ├── AuthService.ts
│ ├── AuthController.ts
│ ├── TokenManager.ts
│ └── index.ts
├── users/ # → Component: User Management
│ ├── UserService.ts
│ ├── UserRepository.ts
│ ├── User.model.ts
│ └── index.ts
└── payments/ # → Component: Payment Processing
├── PaymentService.ts
├── PaymentGateway.ts
└── index.ts
Pattern 2: Layered Components
src/
├── controllers/ # → Component Layer: Controllers
│ ├── UserController.ts
│ ├── OrderController.ts
│ └── ProductController.ts
├── services/ # → Component Layer: Services
│ ├── UserService.ts
│ ├── OrderService.ts
│ └── ProductService.ts
└── repositories/ # → Component Layer: Repositories
├── UserRepository.ts
├── OrderRepository.ts
└── ProductRepository.ts
Pattern 3: Feature-based Components
src/
├── features/
│ ├── user-management/ # → Component: User Management
│ │ ├── components/
│ │ ├── services/
│ │ └── models/
│ ├── order-management/ # → Component: Order Management
│ │ ├── components/
│ │ ├── services/
│ │ └── models/
Detection commands:
# Find directories with code files
find src -type d -not -path "*/node_modules/*" -not -path "*/.git/*"
# List files by directory
ls -la src/*/
# Count files per directory
find src -type f \( -name "*.ts" -o -name "*.js" \) | xargs dirname | sort | uniq -c
Step 4: Detect Component Types
Classify each component by type:
Component Types:
-
controller - HTTP request handlers
- Handles incoming requests
- Routes to services
- Returns responses
- Example:
UserController,OrderController
-
service - Business logic
- Core business logic
- Orchestrates operations
- Encapsulates domain logic
- Example:
UserService,PaymentService
-
repository - Data access
- Database operations
- Data persistence
- Query abstraction
- Example:
UserRepository,OrderRepository
-
model - Data models
- Entity definitions
- Data structures
- Domain objects
- Example:
User,Order,Product
-
middleware - Request/response processing
- Authentication middleware
- Logging middleware
- Validation middleware
- Example:
AuthMiddleware,LoggingMiddleware
-
utility - Helper functions
- Shared utilities
- Common helpers
- Cross-cutting concerns
- Example:
DateUtils,StringUtils
-
dto - Data transfer objects
- Request/response schemas
- API contracts
- Validation schemas
- Example:
CreateUserDto,LoginResponseDto
-
adapter - External integrations
- Third-party API clients
- External service wrappers
- Protocol adapters
- Example:
StripeAdapter,SendGridAdapter
-
factory - Object creation
- Complex object construction
- Dependency creation
- Example:
UserFactory,OrderFactory
-
validator - Validation logic
- Input validation
- Business rule validation
- Example:
EmailValidator,OrderValidator
-
facade - Simplified interfaces
- Simplifies complex subsystems
- Provides unified interface
- Example:
PaymentFacade,NotificationFacade
-
guard - Authorization/authentication
- Access control
- Route protection
- Example:
AuthGuard,RoleGuard
Step 5: Define Component Boundaries
For each component, define:
1. Package/Module Boundary
- What package or module does it belong to?
- What is the namespace?
- What is the import path?
Example:
// Component: Authentication Service
// Package: @app/authentication
// Module: src/authentication/AuthService.ts
// Import: import { AuthService } from '@app/authentication';
2. Layer Boundary
- What architectural layer?
- Presentation, Business Logic, Data Access?
Example:
Presentation Layer: Controllers, Middleware, DTOs
Business Logic Layer: Services, Domain Models, Validators
Data Access Layer: Repositories, Database Models, Adapters
3. Responsibility Boundary
- What is the single responsibility?
- What does it do?
- What does it NOT do?
Example:
{
"id": "user-service",
"name": "User Service",
"responsibility": "Manages user lifecycle operations including registration, profile updates, and account deletion",
"does": [
"Validate user input",
"Create new users",
"Update user profiles",
"Delete user accounts"
],
"does_not": [
"Handle HTTP requests (Controller's job)",
"Access database directly (Repository's job)",
"Send emails (Email Service's job)"
]
}
Step 6: Identify Component Responsibilities
For each component, determine its responsibilities:
Questions to ask:
- What is the primary purpose?
- What operations does it perform?
- What data does it manage?
- What services does it provide?
- What dependencies does it have?
Analysis techniques:
Technique 1: Analyze Public Methods
// UserService.ts
export class UserService {
// Public interface reveals responsibilities:
async createUser(data: CreateUserDto): Promise<User>
async updateUser(id: string, data: UpdateUserDto): Promise<User>
async deleteUser(id: string): Promise<void>
async getUserById(id: string): Promise<User>
async getUserByEmail(email: string): Promise<User>
}
// Responsibilities:
// - User creation
// - User updates
// - User deletion
// - User retrieval by ID
// - User retrieval by email
Technique 2: Analyze Dependencies
// PaymentService.ts depends on:
import { PaymentGateway } from './PaymentGateway';
import { OrderRepository } from '../orders/OrderRepository';
import { EmailService } from '../notifications/EmailService';
// Responsibilities indicated by dependencies:
// - Processes payments (PaymentGateway)
// - Updates order status (OrderRepository)
// - Sends payment confirmations (EmailService)
Technique 3: Analyze File Content
# Count methods in a class (using extended regex)
grep -E '^\s+(public|private|protected|async)\s+\w+\s*\(' UserService.ts
# Find key operations
grep -E '(create|update|delete|get|find|save)' UserService.ts
Code Structure Analysis
Directory Structure Patterns
Pattern 1: Feature-based Structure
src/
├── users/
│ ├── user.controller.ts # Component: User Controller
│ ├── user.service.ts # Component: User Service
│ ├── user.repository.ts # Component: User Repository
│ ├── user.model.ts # Component: User Model
│ ├── dto/
│ │ ├── create-user.dto.ts
│ │ └── update-user.dto.ts
│ └── index.ts
Advantages:
- Clear feature boundaries
- Easy to locate related code
- Good for domain-driven design
Component detection:
# List all feature directories
ls -d src/*/
# Count components per feature
find src \( -name "*.service.ts" -o -name "*.controller.ts" -o -name "*.repository.ts" \) | xargs dirname | sort | uniq -c
Pattern 2: Layer-based Structure
src/
├── controllers/
│ ├── user.controller.ts
│ ├── order.controller.ts
│ └── product.controller.ts
├── services/
│ ├── user.service.ts
│ ├── order.service.ts
│ └── product.service.ts
├── repositories/
│ ├── user.repository.ts
│ ├── order.repository.ts
│ └── product.repository.ts
└── models/
├── user.model.ts
├── order.model.ts
└── product.model.ts
Advantages:
- Clear layer separation
- Easy to enforce layer rules
- Good for traditional MVC
Component detection:
# Count components per layer
wc -l controllers/*.ts services/*.ts repositories/*.ts
Pattern 3: Domain-Driven Design (DDD)
src/
├── domain/
│ ├── user/
│ │ ├── User.entity.ts # Component: User Entity
│ │ ├── UserRepository.ts # Component: User Repository
│ │ └── UserService.ts # Component: User Domain Service
│ └── order/
│ ├── Order.entity.ts
│ ├── OrderRepository.ts
│ └── OrderService.ts
├── application/
│ ├── user/
│ │ ├── CreateUserUseCase.ts # Component: Create User Use Case
│ │ ├── UpdateUserUseCase.ts # Component: Update User Use Case
│ └── order/
│ ├── PlaceOrderUseCase.ts
│ └── CancelOrderUseCase.ts
└── infrastructure/
├── database/
│ ├── UserRepositoryImpl.ts # Component: User Repository Implementation
│ └── OrderRepositoryImpl.ts
└── http/
├── UserController.ts # Component: User Controller
└── OrderController.ts
Advantages:
- Clear domain boundaries
- Separation of concerns
- Testable use cases
Export Pattern Analysis
Analyze how components expose their APIs:
Pattern 1: Named Exports
// authentication/index.ts
export { AuthService } from './AuthService';
export { AuthController } from './AuthController';
export { TokenManager } from './TokenManager';
export * from './dto';
// Component: Authentication Module
// Public API: AuthService, AuthController, TokenManager, DTOs
Pattern 2: Default Exports
// UserService.ts
export default class UserService { ... }
// Component: User Service
// Import: import UserService from './UserService';
Pattern 3: Facade Exports
// payment/index.ts
import { PaymentService } from './PaymentService';
import { PaymentGateway } from './PaymentGateway';
import { PaymentValidator } from './PaymentValidator';
// Facade pattern - single entry point
export class PaymentFacade {
constructor(
private service: PaymentService,
private gateway: PaymentGateway,
private validator: PaymentValidator
) {}
// Simplified API
async processPayment(data: PaymentDto): Promise<PaymentResult> { ... }
}
// Component: Payment Facade
// Hides internal complexity
Code Metrics Analysis
Use metrics to identify significant components:
Metric 1: Lines of Code (LOC)
# Count lines per file
find src -name "*.ts" -print0 | xargs -0 -r wc -l | sort -rn
# Components with > 200 LOC are significant
find src -name "*.ts" -print0 | xargs -0 -r wc -l | awk '$1 > 200 { print $2, $1 }'
Guidelines:
- < 100 LOC: Small component or helper
- 100-300 LOC: Typical component
- 300-500 LOC: Large component (consider splitting)
- > 500 LOC: Very large (definitely a component, likely needs refactoring)
Metric 2: Cyclomatic Complexity
# Use complexity tools
find src -name "*.ts" -print0 | xargs -0 -r npx ts-complexity
# High complexity (>10) indicates important component
Metric 3: Dependency Count
# Count imports per file
find src -name "*.ts" -print0 | xargs -0 -r grep -cH "^import" 2>/dev/null | sort -t: -k2 -rn
# Files with many imports are often important orchestrators (Services)
Metric 4: Export Count
# Count exports per file
find src -name "*.ts" -print0 | xargs -0 -r grep -cH "^export" 2>/dev/null | sort -t: -k2 -rn
# Files with many exports are often facades or utility modules