13 KiB
Pattern Detection
This guide provides comprehensive methodology for detecting design patterns and architectural patterns at the C3 level.
Pattern Detection
Design Patterns
Pattern 1: Singleton Pattern
Description: Ensures a class has only one instance
Detection:
// Singleton pattern indicators
export class DatabaseConnection {
private static instance: DatabaseConnection;
private constructor() {} // Private constructor
static getInstance(): DatabaseConnection {
if (!this.instance) {
this.instance = new DatabaseConnection();
}
return this.instance;
}
}
Grep detection:
grep -r "private static.*instance" src/
grep -r "private constructor" src/
grep -r "getInstance()" src/
Observation:
{
"id": "obs-singleton-database",
"title": "Singleton pattern for database connection",
"category": "design-patterns",
"severity": "info",
"pattern": "singleton",
"description": "DatabaseConnection uses Singleton pattern to ensure single instance across application"
}
Pattern 2: Factory Pattern
Description: Creates objects without specifying exact class
Detection:
// Factory pattern indicators
export class UserFactory {
static createUser(type: string, data: any): User {
switch (type) {
case 'admin':
return new AdminUser(data);
case 'customer':
return new CustomerUser(data);
default:
return new GuestUser(data);
}
}
}
Grep detection:
grep -r "Factory" src/
grep -r "static create" src/
Pattern 3: Repository Pattern
Description: Abstracts data access logic
Detection:
// Repository pattern indicators
export class UserRepository {
async findById(id: string): Promise<User> { ... }
async findAll(): Promise<User[]> { ... }
async save(user: User): Promise<User> { ... }
async delete(id: string): Promise<void> { ... }
}
Grep detection:
grep -r "Repository" src/
grep -rE "findById|findAll|save.*Promise" src/
Pattern 4: Dependency Injection
Description: Dependencies provided externally
Detection in NestJS:
@Injectable()
export class UserService {
constructor(
@Inject(UserRepository) private userRepo: UserRepository,
@Inject(EmailService) private emailService: EmailService
) {}
}
Detection in Spring (Java):
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private EmailService emailService;
}
Grep detection:
# NestJS
grep -rE "@Injectable|@Inject" src/
# Spring
grep -rE "@Service|@Autowired|@Component" src/
# Constructor injection
grep -r "constructor(" src/ | grep "private"
Pattern 5: Observer Pattern (Event-Driven)
Description: Publish-subscribe mechanism
Detection:
// Observer pattern indicators
export class OrderService {
async createOrder(data: CreateOrderDto) {
const order = await this.orderRepo.save(data);
// Emit event
this.eventEmitter.emit('order.created', order);
return order;
}
}
// Observers
export class EmailService {
@OnEvent('order.created')
handleOrderCreated(order: Order) {
this.sendOrderConfirmation(order);
}
}
Grep detection:
grep -rE "\.emit|\.on|@OnEvent|subscribe|publish" src/
Pattern 6: Strategy Pattern
Description: Select algorithm at runtime
Detection:
// Strategy pattern
interface PaymentStrategy {
pay(amount: number): Promise<PaymentResult>;
}
class CreditCardPayment implements PaymentStrategy {
async pay(amount: number) { ... }
}
class PayPalPayment implements PaymentStrategy {
async pay(amount: number) { ... }
}
class PaymentContext {
constructor(private strategy: PaymentStrategy) {}
async executePayment(amount: number) {
return this.strategy.pay(amount);
}
}
Grep detection:
grep -rE "implements.*Strategy|Strategy.*interface" src/
Pattern 7: Decorator Pattern
Description: Add behavior dynamically
Detection in TypeScript:
// Decorator pattern (using TypeScript decorators)
@Controller('users')
@UseGuards(AuthGuard) // Decorator
@UseInterceptors(LoggingInterceptor) // Decorator
export class UserController {
@Get()
@Roles('admin') // Decorator
findAll() { ... }
}
Grep detection:
grep -rE "^@\w+\(" src/
Pattern 8: Adapter Pattern
Description: Converts interface to another interface
Detection:
// Adapter pattern
export class StripeAdapter {
constructor(private stripeClient: Stripe) {}
// Adapt Stripe API to internal interface
async processPayment(payment: PaymentDto): Promise<PaymentResult> {
const stripePayment = this.convertToStripeFormat(payment);
const result = await this.stripeClient.charges.create(stripePayment);
return this.convertFromStripeFormat(result);
}
}
Grep detection:
grep -r "Adapter" src/
grep -r "convert.*Format" src/
Common Architecture Patterns
Pattern 1: MVC (Model-View-Controller)
Description: Separates concerns into Model, View, Controller
Component structure:
src/
├── models/ # Models (Data)
│ ├── User.ts
│ ├── Order.ts
│ └── Product.ts
├── views/ # Views (Presentation) - if server-rendered
│ ├── user-list.ejs
│ └── order-detail.ejs
└── controllers/ # Controllers (Logic)
├── UserController.ts
├── OrderController.ts
└── ProductController.ts
Components identified:
- Controllers: UserController, OrderController, ProductController
- Models: User, Order, Product
- Views: (if applicable)
Observation:
{
"id": "obs-mvc-pattern",
"title": "MVC pattern with controllers and models",
"category": "architectural-pattern",
"pattern": "mvc",
"description": "Application follows MVC pattern with clear separation between controllers (request handling) and models (data representation)"
}
Pattern 2: Layered Architecture (3-Tier)
Description: Separates into Presentation, Business Logic, Data Access layers
Component structure:
src/
├── presentation/ # Presentation Layer
│ ├── controllers/
│ │ ├── UserController.ts
│ │ └── OrderController.ts
│ └── middleware/
│ ├── AuthMiddleware.ts
│ └── ValidationMiddleware.ts
├── business/ # Business Logic Layer
│ ├── services/
│ │ ├── UserService.ts
│ │ └── OrderService.ts
│ └── validators/
│ ├── UserValidator.ts
│ └── OrderValidator.ts
└── data/ # Data Access Layer
├── repositories/
│ ├── UserRepository.ts
│ └── OrderRepository.ts
└── models/
├── User.model.ts
└── Order.model.ts
Layer rules:
- Presentation → Business Logic → Data Access (one direction only)
- No skipping layers (Controller can't call Repository directly)
Components identified:
- Presentation: UserController, OrderController, AuthMiddleware
- Business Logic: UserService, OrderService, UserValidator
- Data Access: UserRepository, OrderRepository, User Model, Order Model
Detection:
# Check for layer violations
grep -r "Repository" src/controllers/ # ❌ Controller calling Repository directly
# Verify proper layering
grep -r "Service" src/controllers/ # ✅ Controller calling Service
grep -r "Repository" src/services/ # ✅ Service calling Repository
Pattern 3: Hexagonal Architecture (Ports & Adapters)
Description: Core business logic isolated from external concerns
Component structure:
src/
├── domain/ # Core Domain (Business Logic)
│ ├── user/
│ │ ├── User.entity.ts
│ │ ├── UserService.ts
│ │ └── ports/
│ │ ├── UserRepository.port.ts # Interface
│ │ └── EmailService.port.ts # Interface
│ └── order/
│ ├── Order.entity.ts
│ └── OrderService.ts
├── application/ # Application Services (Use Cases)
│ ├── CreateUserUseCase.ts
│ ├── PlaceOrderUseCase.ts
│ └── ProcessPaymentUseCase.ts
└── infrastructure/ # Infrastructure (Adapters)
├── database/
│ └── UserRepositoryImpl.ts # Implementation
├── email/
│ └── EmailServiceImpl.ts # Implementation
└── http/
├── UserController.ts # HTTP Adapter
└── OrderController.ts # HTTP Adapter
Key concepts:
- Ports: Interfaces defined by domain (UserRepository.port.ts)
- Adapters: Implementations in infrastructure (UserRepositoryImpl.ts)
- Dependency Inversion: Domain defines interfaces, infrastructure implements
Components identified:
- Domain: User, Order, UserService, OrderService
- Ports: UserRepository (interface), EmailService (interface)
- Adapters: UserRepositoryImpl, EmailServiceImpl, UserController
- Use Cases: CreateUserUseCase, PlaceOrderUseCase
Observation:
{
"id": "obs-hexagonal-arch",
"title": "Hexagonal architecture with ports and adapters",
"category": "architectural-pattern",
"pattern": "hexagonal",
"description": "Application uses hexagonal architecture. Domain defines port interfaces (UserRepository.port.ts), infrastructure provides adapter implementations (UserRepositoryImpl.ts). Business logic isolated from external dependencies."
}
Pattern 4: CQRS (Command Query Responsibility Segregation)
Description: Separate read and write operations
Component structure:
src/
├── commands/ # Write side (Commands)
│ ├── CreateUserCommand.ts
│ ├── UpdateUserCommand.ts
│ └── handlers/
│ ├── CreateUserHandler.ts
│ └── UpdateUserHandler.ts
├── queries/ # Read side (Queries)
│ ├── GetUserQuery.ts
│ ├── ListUsersQuery.ts
│ └── handlers/
│ ├── GetUserHandler.ts
│ └── ListUsersHandler.ts
└── models/
├── write/ # Write models
│ └── User.entity.ts
└── read/ # Read models (often denormalized)
└── UserView.ts
Key concepts:
- Commands: Change state (CreateUser, UpdateUser)
- Queries: Read state (GetUser, ListUsers)
- Separate models: Write model vs Read model
Components identified:
- Commands: CreateUserCommand, UpdateUserCommand
- Command Handlers: CreateUserHandler, UpdateUserHandler
- Queries: GetUserQuery, ListUsersQuery
- Query Handlers: GetUserHandler, ListUsersHandler
- Write Models: User (entity)
- Read Models: UserView
Detection:
grep -rE "Command|Handler" src/
grep -rE "Query|Handler" src/
grep -rE "@CommandHandler|@QueryHandler" src/ # NestJS CQRS
Pattern 5: Microkernel (Plugin Architecture)
Description: Core system with pluggable modules
Component structure:
src/
├── core/ # Core System
│ ├── Application.ts
│ ├── PluginManager.ts
│ └── interfaces/
│ └── Plugin.interface.ts
└── plugins/ # Plugins
├── authentication/
│ ├── AuthPlugin.ts
│ └── AuthService.ts
├── logging/
│ ├── LoggingPlugin.ts
│ └── Logger.ts
└── cache/
├── CachePlugin.ts
└── CacheService.ts
Key concepts:
- Core: Minimal core system
- Plugins: Optional modules that extend core
- Plugin Interface: Contract for plugins
Components identified:
- Core: Application, PluginManager
- Plugins: AuthPlugin, LoggingPlugin, CachePlugin
- Plugin Interface: Plugin.interface.ts
Pattern 6: Event-Driven Architecture
Description: Components communicate via events
Component structure:
src/
├── events/ # Event Definitions
│ ├── UserCreatedEvent.ts
│ ├── OrderPlacedEvent.ts
│ └── PaymentProcessedEvent.ts
├── publishers/ # Event Publishers
│ ├── UserEventPublisher.ts
│ └── OrderEventPublisher.ts
└── subscribers/ # Event Subscribers
├── EmailSubscriber.ts
├── AnalyticsSubscriber.ts
└── NotificationSubscriber.ts
Component flow:
UserService → publishes UserCreatedEvent
↓
[Event Bus / Message Queue]
↓
EmailSubscriber receives event → sends welcome email
AnalyticsSubscriber receives event → tracks user registration
Components identified:
- Event Publishers: UserEventPublisher, OrderEventPublisher
- Event Subscribers: EmailSubscriber, AnalyticsSubscriber, NotificationSubscriber
- Events: UserCreatedEvent, OrderPlacedEvent, PaymentProcessedEvent
- Event Bus: EventEmitter, Message Queue
Detection:
grep -rE "Event|emit|publish|subscribe" src/
grep -rE "@OnEvent|@Subscribe" src/ # Framework decorators