Skip to content

Latest commit

 

History

History
438 lines (372 loc) · 11.2 KB

File metadata and controls

438 lines (372 loc) · 11.2 KB

Dickson's Developer Assessment - Architecture & Design

1. Project Structure & Rationale

Current Structure

fastway-app/
├── api/                    # Business Logic Layer
│   ├── config.php         # Configuration & utilities
│   ├── track.php          # Tracking business logic
│   └── quote.php          # Quote business logic
├── assets/                # Presentation Layer
│   ├── css/              # Styling
│   ├── js/               # Client-side logic
│   └── images/           # Static assets
├── includes/             # Shared UI Components
│   ├── header.php        # Navigation
│   └── footer.php        # Footer
├── index.php             # Front Controller
├── track.php             # Track view
├── quote.php             # Quote view
└── logs/                 # Application logs

Rationale

  • Separation of Concerns: API logic separate from presentation
  • Modularity: Each component has single responsibility
  • Scalability: Easy to add new features without affecting existing code
  • Maintainability: Clear file organization for team collaboration
  • Security: API keys and logic isolated from public access

2. Separation of Concerns

Business Logic (api/)

// api/track.php - Pure business logic
- Validates tracking numbers
- Calls external API
- Parses responses
- Returns structured data
- No HTML/UI code

Responsibilities:

  • Data validation
  • API communication
  • Error handling
  • Data transformation
  • Logging

API Integration (api/config.php)

// Centralized API configuration
- API credentials management
- HTTP client setup (cURL)
- Request/response handling
- Retry logic
- Timeout management

Responsibilities:

  • External service communication
  • Connection pooling
  • Rate limiting
  • Circuit breaker pattern (future)

Presentation/UI (*.php + assets/)

// track.php - Pure presentation
- Displays forms
- Shows results
- No business logic
- Calls API layer via AJAX

Responsibilities:

  • User interface rendering
  • Form validation (client-side)
  • User feedback
  • Responsive design

3. Multi-Provider Architecture

Proposed Design - Strategy Pattern + Factory

// Interface for all courier providers
interface CourierProviderInterface {
    public function track($trackingNumber);
    public function getQuote($params);
    public function validateTrackingNumber($number);
}

// Fastway implementation
class FastwayProvider implements CourierProviderInterface {
    public function track($trackingNumber) {
        // Fastway-specific logic
    }
    
    public function getQuote($params) {
        // Fastway-specific logic
    }
}

// DHL implementation
class DHLProvider implements CourierProviderInterface {
    public function track($trackingNumber) {
        // DHL-specific logic
    }
}

// Provider Factory
class CourierProviderFactory {
    public static function create($provider) {
        switch($provider) {
            case 'fastway':
                return new FastwayProvider();
            case 'dhl':
                return new DHLProvider();
            case 'ups':
                return new UPSProvider();
            default:
                throw new Exception("Unknown provider");
        }
    }
}

// Usage
$provider = CourierProviderFactory::create('fastway');
$result = $provider->track('Z60000983328');

Provider Configuration

// config/providers.php
return [
    'fastway' => [
        'class' => FastwayProvider::class,
        'api_key' => getenv('FASTWAY_API_KEY'),
        'base_url' => 'https://sa.api.fastway.org',
        'timeout' => 30
    ],
    'dhl' => [
        'class' => DHLProvider::class,
        'api_key' => getenv('DHL_API_KEY'),
        'base_url' => 'https://api.dhl.com',
        'timeout' => 30
    ]
];

4. Future API Changes - Adapter Pattern

Version Abstraction

// API Version Adapter
interface APIVersionInterface {
    public function makeRequest($endpoint, $params);
    public function parseResponse($response);
}

// V3 Adapter
class FastwayAPIv3 implements APIVersionInterface {
    public function makeRequest($endpoint, $params) {
        return $this->callAPI("/v3/{$endpoint}", $params);
    }
    
    public function parseResponse($response) {
        // V3-specific parsing
        return [
            'tracking_number' => $response['result']['LabelNumber'],
            'status' => $response['result']['Scans'][0]['StatusDescription']
        ];
    }
}

// V4 Adapter (future)
class FastwayAPIv4 implements APIVersionInterface {
    public function makeRequest($endpoint, $params) {
        return $this->callAPI("/v4/{$endpoint}", $params);
    }
    
    public function parseResponse($response) {
        // V4-specific parsing (different structure)
        return [
            'tracking_number' => $response['data']['label_number'],
            'status' => $response['data']['current_status']
        ];
    }
}

// Configuration-based version selection
class APIVersionManager {
    private $adapter;
    
    public function __construct() {
        $version = getenv('FASTWAY_API_VERSION') ?: 'v3';
        $this->adapter = $this->getAdapter($version);
    }
    
    private function getAdapter($version) {
        $adapters = [
            'v3' => new FastwayAPIv3(),
            'v4' => new FastwayAPIv4()
        ];
        return $adapters[$version];
    }
}

Benefits

  • Minimal Code Changes: Switch API versions via config
  • Backward Compatibility: Support multiple versions simultaneously
  • Easy Testing: Mock different API versions
  • Gradual Migration: Phase out old versions slowly

5. Design Patterns Applied

1. Factory Pattern

// Used in: CourierProviderFactory
// Why: Creates provider instances without exposing instantiation logic

2. Strategy Pattern

// Used in: CourierProviderInterface implementations
// Why: Allows switching between different courier providers at runtime

3. Adapter Pattern

// Used in: API version adapters
// Why: Makes incompatible API versions work together

4. Singleton Pattern (proposed)

// For: Database connections, cache instances
class Database {
    private static $instance = null;
    
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new PDO(...);
        }
        return self::$instance;
    }
}

5. Repository Pattern (proposed)

// For: Data access abstraction
class TrackingRepository {
    public function save($tracking);
    public function findByNumber($number);
    public function findRecent($limit);
}

6. Dependency Injection (proposed)

// For: Testing and flexibility
class TrackingService {
    private $provider;
    private $cache;
    
    public function __construct(
        CourierProviderInterface $provider,
        CacheInterface $cache
    ) {
        $this->provider = $provider;
        $this->cache = $cache;
    }
}

6. Refactoring to MVC Architecture

Proposed MVC Structure

fastway-app/
├── app/
│   ├── Controllers/
│   │   ├── TrackingController.php
│   │   ├── QuoteController.php
│   │   └── HomeController.php
│   ├── Models/
│   │   ├── Tracking.php
│   │   ├── Quote.php
│   │   └── CourierProvider.php
│   ├── Views/
│   │   ├── layouts/
│   │   │   ├── header.php
│   │   │   └── footer.php
│   │   ├── tracking/
│   │   │   ├── index.php
│   │   │   └── results.php
│   │   └── quotes/
│   │       ├── index.php
│   │       └── results.php
│   ├── Services/
│   │   ├── CourierService.php
│   │   ├── CacheService.php
│   │   └── LoggingService.php
│   └── Repositories/
│       ├── TrackingRepository.php
│       └── QuoteRepository.php
├── config/
│   ├── app.php
│   ├── database.php
│   └── providers.php
├── public/
│   ├── index.php (Front Controller)
│   └── assets/
├── routes/
│   └── web.php
└── tests/
    ├── Unit/
    └── Integration/

Controller Example

// app/Controllers/TrackingController.php
class TrackingController {
    private $trackingService;
    
    public function __construct(TrackingService $service) {
        $this->trackingService = $service;
    }
    
    public function index() {
        return view('tracking/index');
    }
    
    public function track(Request $request) {
        $trackingNumber = $request->input('tracking_number');
        
        try {
            $result = $this->trackingService->track($trackingNumber);
            return json(['success' => true, 'data' => $result]);
        } catch (Exception $e) {
            return json(['success' => false, 'message' => $e->getMessage()]);
        }
    }
}

Model Example

// app/Models/Tracking.php
class Tracking {
    private $id;
    private $trackingNumber;
    private $status;
    private $events;
    private $createdAt;
    
    public function save() {
        // Persist to database
    }
    
    public static function findByNumber($number) {
        // Retrieve from database
    }
}

Service Example

// app/Services/TrackingService.php
class TrackingService {
    private $provider;
    private $cache;
    private $repository;
    
    public function track($trackingNumber) {
        // Check cache first
        $cached = $this->cache->get("track_{$trackingNumber}");
        if ($cached) return $cached;
        
        // Call API
        $result = $this->provider->track($trackingNumber);
        
        // Cache result
        $this->cache->set("track_{$trackingNumber}", $result, 300);
        
        // Save to database
        $this->repository->save($result);
        
        return $result;
    }
}

Router Example

// routes/web.php
Route::get('/', 'HomeController@index');
Route::get('/track', 'TrackingController@index');
Route::post('/api/track', 'TrackingController@track');
Route::get('/quote', 'QuoteController@index');
Route::post('/api/quote', 'QuoteController@calculate');

Benefits of MVC Refactoring

  • Clear separation of concerns
  • Testability - Each component can be tested independently
  • Scalability - Easy to add features
  • Team collaboration - Multiple developers can work simultaneously
  • Code reusability - Models and services can be shared
  • Maintainability - Changes isolated to specific layers

Summary

The current implementation follows good practices with clear separation between business logic, API integration, and presentation. However, it can be significantly improved by:

  1. Implementing design patterns (Factory, Strategy, Adapter)
  2. Adopting MVC architecture for better organization
  3. Using dependency injection for flexibility
  4. Creating service layer for complex business logic
  5. Implementing repository pattern for data access

These improvements would make the system more maintainable, testable, and scalable while preparing it for enterprise-level requirements.