Skip to content

Latest commit

 

History

History
336 lines (262 loc) · 10.3 KB

File metadata and controls

336 lines (262 loc) · 10.3 KB

Architecture Documentation

Clean Architecture Principles

This project follows Clean Architecture principles, ensuring separation of concerns, testability, and maintainability.

Layer Structure

┌─────────────────────────────────────────────────┐
│                  Domain Layer                   │
│  (Entities, Interfaces, Business Logic)         │
│  - product.entity.ts                            │
│  - product.repository.ts                        │
│  └─ No external dependencies                    │
└─────────────────────────────────────────────────┘
                        ▲
                        │
                        │ depends on
                        │
┌─────────────────────────────────────────────────┐
│               Use Cases Layer                   │
│  (Application Business Rules)                   │
│  - scrapeProduct.usecase.ts                     │
│  └─ Depends only on Domain                      │
└─────────────────────────────────────────────────┘
                        ▲
                        │
                        │ depends on
                        │
┌─────────────────────────────────────────────────┐
│            Infrastructure Layer                 │
│  (External Concerns, Implementations)           │
│  - httpClient.ts                                │
│  - paniniScraper.service.ts                     │
│  └─ Implements Domain interfaces                │
└─────────────────────────────────────────────────┘

Dependency Rule

Dependencies point inward: Outer layers depend on inner layers, never the reverse.

  • Domain has no dependencies (pure business logic)
  • Use Cases depend only on Domain
  • Infrastructure implements Domain interfaces

Layer Responsibilities

1. Domain Layer (src/domain/)

Purpose: Core business logic and entities

Components:

  • Product interface: Data structure definition
  • ProductEntity class: Business entity with validation and computed properties
  • ProductRepository interface: Data access contract
  • HttpConfig interface: Configuration contract
  • Custom error types: ProductScrapingError, ProductNotFoundError, InvalidUrlError

Principles Applied:

  • Single Responsibility: Each entity has one clear purpose
  • Open/Closed: Extensible without modification
  • Liskov Substitution: ProductEntity implements Product correctly
  • Interface Segregation: Small, focused interfaces
  • Dependency Inversion: Depends on abstractions, not implementations

No External Dependencies: Pure TypeScript, no imports from infrastructure

2. Use Cases Layer (src/usecases/)

Purpose: Application-specific business rules

Components:

  • ScrapeProductUseCase: Orchestrates product scraping flow

Responsibilities:

  • Input validation
  • URL normalization
  • Coordination between layers
  • Error handling delegation

Principles Applied:

  • Single Responsibility: One use case per operation
  • Dependency Injection: Repository injected via constructor
  • Clean interfaces: Simple, focused methods

3. Infrastructure Layer (src/infrastructure/)

Purpose: External concerns and implementations

Components:

  • HttpClient: HTTP request wrapper (abstracts Axios)
  • PaniniScraperService: Implements ProductRepository interface

Responsibilities:

  • HTTP communication
  • HTML parsing (Cheerio)
  • Error transformation
  • External library integration

Principles Applied:

  • Adapter Pattern: HttpClient adapts Axios to our needs
  • Repository Pattern: PaniniScraperService implements data access
  • Dependency Inversion: Implements domain interfaces
  • Error Handling: Transforms external errors to domain errors

SOLID Principles

✅ Single Responsibility Principle (SRP)

Each class has one reason to change:

  • ProductEntity: Business validation and computed properties
  • HttpClient: HTTP communication
  • PaniniScraperService: Web scraping logic
  • ScrapeProductUseCase: Use case orchestration

✅ Open/Closed Principle (OCP)

Open for extension, closed for modification:

  • New scrapers can be added by implementing ProductRepository
  • Product entity can be extended without modifying existing code
  • New HTTP clients can be created by injecting different configurations

✅ Liskov Substitution Principle (LSP)

Subtypes are substitutable:

  • ProductEntity implements Product correctly
  • PaniniScraperService can be replaced by any ProductRepository implementation
  • Custom errors extend base error properly

✅ Interface Segregation Principle (ISP)

Small, focused interfaces:

  • ProductRepository: Single method for scraping
  • HttpConfig: Optional, granular configuration
  • Product: Only essential product data

✅ Dependency Inversion Principle (DIP)

Depend on abstractions:

  • Use cases depend on ProductRepository interface, not concrete implementation
  • Infrastructure implements interfaces defined in domain
  • No direct dependencies on external libraries in domain/use cases

Clean Code Principles

✅ Meaningful Names

// Good: Clear, descriptive names
class ScrapeProductUseCase
async scrapeProduct(url: string): Promise<Product>
const normalizedUrl = this.normalizeUrl(url);

// Bad examples avoided:
// class SPU
// async sp(u: string)
// const nu = this.nu(url);

✅ Functions Do One Thing

// Each function has a single, clear purpose
private normalizeUrl(url: string): string
private extractTitle($: cheerio.CheerioAPI): string
private extractPrices($: cheerio.CheerioAPI): { fullPrice: number; currentPrice: number }

✅ Small Functions

Functions are kept concise and focused:

  • Average function size: 10-30 lines
  • Complex operations broken into smaller methods
  • High cohesion within each function

✅ Don't Repeat Yourself (DRY)

// Price extraction logic centralized
private extractPriceFromText(text: string): number

// URL validation reused
private isValidUrl(url: string): boolean

✅ Error Handling

// Specific error types for different scenarios
throw new InvalidUrlError(url);
throw new ProductNotFoundError(url);
throw new ProductScrapingError(message, url);

// Errors handled at appropriate layers
// Domain errors thrown by entities
// Infrastructure errors transformed to domain errors

✅ Comments and Documentation

  • JSDoc comments for all public APIs
  • Examples in documentation
  • Why, not what: Comments explain reasoning, not obvious code
  • Self-documenting code: Names explain intent

✅ Code Organization

src/
├── domain/           # Business logic (innermost layer)
│   ├── product.entity.ts
│   └── product.repository.ts
├── usecases/         # Application logic
│   └── scrapeProduct.usecase.ts
└── infrastructure/   # External concerns (outermost layer)
    ├── httpClient.ts
    └── paniniScraper.service.ts

✅ Testability

  • 95%+ test coverage
  • 100 passing tests
  • Unit tests for isolated components
  • Integration tests for complete flows
  • Dependency Injection enables mocking

Design Patterns Applied

1. Repository Pattern

interface ProductRepository {
    scrapeProduct(url: string): Promise<Product>;
}

Benefit: Abstracts data access, enabling different implementations

2. Factory Pattern

export function createPaniniScraper(config?: HttpConfig) {
    // Creates configured instance
}

Benefit: Simplifies object creation with configuration

3. Adapter Pattern

class HttpClient {
    // Adapts Axios to our interface
}

Benefit: Isolates external library, easy to swap

4. Dependency Injection

constructor(private readonly productRepository: ProductRepository)

Benefit: Loose coupling, testability, flexibility

Best Practices Implemented

✅ Immutability

export class ProductEntity {
    constructor(
        public readonly title: string,
        public readonly fullPrice: number,
        // ...
    )
}

✅ Type Safety

  • Full TypeScript coverage
  • Strict mode enabled
  • No any types except where necessary
  • Comprehensive interfaces

✅ Error Recovery

  • Graceful degradation (fallback values)
  • Multiple selectors for robustness
  • Detailed error messages

✅ Configuration

  • Flexible HTTP config
  • Proxy support
  • Custom headers
  • Timeout control

✅ Documentation

  • Comprehensive README
  • JSDoc for all public APIs
  • Usage examples
  • Architecture documentation

Maintainability Features

Easy to Extend

  • Add new scrapers by implementing ProductRepository
  • Add new entity properties without breaking existing code
  • Add new use cases independently

Easy to Test

  • All components have unit tests
  • Integration tests for complete flows
  • Mocking enabled by dependency injection
  • 95%+ code coverage

Easy to Understand

  • Clear layer separation
  • Meaningful names
  • Comprehensive documentation
  • Self-documenting code

Easy to Modify

  • Changes isolated to specific layers
  • No ripple effects across layers
  • Interfaces buffer against changes
  • Backward compatibility maintained

Conclusion

This project demonstrates:

  • Clean Architecture with proper layer separation
  • SOLID Principles throughout the codebase
  • Clean Code practices for readability
  • High Test Coverage (95%+)
  • Type Safety with TypeScript
  • Maintainability through good design
  • Professional Documentation
  • Production-Ready quality

The architecture ensures the library is flexible, testable, and maintainable while providing a clean API for users.