Caching Strategy:
// CacheService.php
class CacheService {
private $redis;
public function __construct() {
if (class_exists('Redis')) {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
}
}
public function get($key) {
if ($this->redis) {
$data = $this->redis->get($key);
return $data ? json_decode($data, true) : null;
}
// Fallback to file cache
$file = sys_get_temp_dir() . '/' . md5($key) . '.cache';
if (file_exists($file) && (time() - filemtime($file)) < 300) {
return json_decode(file_get_contents($file), true);
}
return null;
}
public function set($key, $value, $ttl = 300) {
if ($this->redis) {
$this->redis->setex($key, $ttl, json_encode($value));
} else {
// File cache fallback
$file = sys_get_temp_dir() . '/' . md5($key) . '.cache';
file_put_contents($file, json_encode($value));
}
}
}
// In api/track.php
$cache = new CacheService();
$cacheKey = "tracking:{$trackingNumber}";
// Check cache first
if ($cached = $cache->get($cacheKey)) {
sendResponse(true, $cached, 'Retrieved from cache');
}
// If not cached, call API
$apiData = makeApiRequest(...);
// Cache for 5 minutes
$cache->set($cacheKey, $trackingInfo, 300);Request Deduplication:
// RequestDeduplicator.php
class RequestDeduplicator {
private static $pendingRequests = [];
public static function execute($key, callable $callback) {
// If request is already pending, wait for it
if (isset(self::$pendingRequests[$key])) {
while (isset(self::$pendingRequests[$key])) {
usleep(100000); // Wait 100ms
}
// Return cached result
return (new CacheService())->get($key);
}
// Mark as pending
self::$pendingRequests[$key] = true;
try {
$result = $callback();
return $result;
} finally {
unset(self::$pendingRequests[$key]);
}
}
}
// Usage
$result = RequestDeduplicator::execute("tracking:{$trackingNumber}", function() {
return makeApiRequest(...);
});Client-Side Caching:
// In track.js
const trackingCache = new Map();
function trackParcel(trackingNumber) {
// Check local cache (5 minutes)
const cached = trackingCache.get(trackingNumber);
if (cached && (Date.now() - cached.timestamp) < 300000) {
displayTrackingResults(cached.data);
return;
}
// Make request
fetch('api/track.php', {...})
.then(data => {
// Cache result
trackingCache.set(trackingNumber, {
data: data.data,
timestamp: Date.now()
});
displayTrackingResults(data.data);
});
}Batch Requests:
// For tracking multiple parcels
function trackMultiple(array $trackingNumbers): array {
// Group into single API call if provider supports it
$results = [];
foreach (array_chunk($trackingNumbers, 10) as $batch) {
$response = makeApiRequest('/tracktrace/batch', [
'tracking_numbers' => implode(',', $batch)
]);
$results = array_merge($results, $response);
}
return $results;
}Multi-Layer Caching Architecture:
Client Browser Cache (60 seconds)
↓
CDN Cache (5 minutes) - Static assets
↓
Application Cache - Redis (5 minutes)
↓
Database Query Cache (if using DB)
↓
API Response Cache (5 minutes)
↓
External API (Fastway)
Implementation:
// 1. HTTP Caching Headers
header('Cache-Control: public, max-age=60');
header('ETag: ' . md5($trackingNumber . $timestamp));
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
$_SERVER['HTTP_IF_NONE_MATCH'] === $etag) {
http_response_code(304);
exit;
}
// 2. Redis Cache
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$cacheKey = "tracking:{$trackingNumber}";
$ttl = 300; // 5 minutes
if ($cached = $redis->get($cacheKey)) {
return json_decode($cached, true);
}
$data = callApi();
$redis->setex($cacheKey, $ttl, json_encode($data));
// 3. Memcached (Alternative)
$memcached = new Memcached();
$memcached->addServer('localhost', 11211);
$data = $memcached->get($cacheKey);
if ($data === false) {
$data = callApi();
$memcached->set($cacheKey, $data, 300);
}
// 4. APCu (In-Memory Cache)
$cacheKey = "tracking_{$trackingNumber}";
$data = apcu_fetch($cacheKey, $success);
if (!$success) {
$data = callApi();
apcu_store($cacheKey, $data, 300);
}
// 5. File-Based Cache (Fallback)
$cacheFile = __DIR__ . "/cache/" . md5($trackingNumber) . ".json";
$cacheAge = file_exists($cacheFile) ? time() - filemtime($cacheFile) : 999999;
if ($cacheAge < 300) {
$data = json_decode(file_get_contents($cacheFile), true);
} else {
$data = callApi();
file_put_contents($cacheFile, json_encode($data));
}Cache Invalidation Strategies:
// 1. Time-Based (TTL)
$cache->setex($key, 300, $value); // Expires after 5 minutes
// 2. Event-Based
class TrackingEventListener {
public function onTrackingUpdated($trackingNumber) {
$cache->delete("tracking:{$trackingNumber}");
}
}
// 3. Tag-Based
$cache->tags(['tracking', 'active'])->put($key, $value, 300);
$cache->tags(['tracking'])->flush(); // Clear all tracking cache
// 4. LRU (Least Recently Used)
$redis->config('SET', 'maxmemory-policy', 'allkeys-lru');100 Users/Day (Current Architecture - Sufficient):
- Single PHP server (Google App Engine F1 instance)
- File-based caching acceptable
- No database needed
- ~4 requests per hour
- Cost: $0-10/month
10,000 Users/Day (Scaled Architecture):
Architecture:
┌─────────────┐
│ Cloud CDN │ ← Static assets (CSS, JS, images)
└──────┬──────┘
↓
┌─────────────┐
│Load Balancer│ ← Distributes traffic
└──────┬──────┘
↓
┌──────┴──────┬──────────┬──────────┐
│ App │ App │ App │
│ Instance 1 │Instance 2│Instance 3│ ← Auto-scaling
└──────┬──────┴────┬─────┴────┬─────┘
↓ ↓ ↓
┌─────────────────────────────────┐
│ Redis Cluster │ ← Shared cache
└─────────────────────────────────┘
↓ ↓ ↓
┌──────────────────────────────────┐
│ Cloud SQL / PostgreSQL │ ← Database (if needed)
└──────────────────────────────────┘
↓
┌──────────────────────────────────┐
│ Cloud Logging & Monitoring │
└──────────────────────────────────┘
Google App Engine Configuration:
# app.yaml for high traffic
runtime: php81
env: standard
automatic_scaling:
min_instances: 2 # Always-on instances
max_instances: 20 # Scale up to 20
target_cpu_utilization: 0.6
target_throughput_utilization: 0.7
instance_class: F2 # More memory (512MB)
# Session affinity for cache consistency
session_affinity: true
vpc_access_connector:
name: projects/PROJECT/locations/REGION/connectors/redis-connector
env_variables:
REDIS_HOST: '10.0.0.3'
REDIS_PORT: '6379'Performance Optimizations:
// 1. Connection Pooling
class DatabasePool {
private static $connections = [];
public static function getConnection() {
if (empty(self::$connections)) {
self::$connections[] = new PDO(...);
}
return self::$connections[0];
}
}
// 2. Async API Calls
use React\EventLoop\Factory;
use React\HttpClient\Client;
$loop = Factory::create();
$client = new Client($loop);
$requests = [];
foreach ($trackingNumbers as $number) {
$requests[] = $client->request('GET', "/api/track/{$number}");
}
Promise\all($requests)->then(function($results) {
// Process all results
});
$loop->run();
// 3. Database Query Optimization
// Use prepaed statements with query caching
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
// Add indexes
CREATE INDEX idx_tracking_number ON tracking (tracking_number);
CREATE INDEX idx_created_at ON tracking (created_at);
// 4. OpCode Caching
// Enable OPcache in php.ini
opcache.enable=1
opcache.memory_consumption=128
opcache.max_accelerated_files=10000Cost Comparison:
| Users/Day | Requests/Hour | Instances | Cost/Month |
|---|---|---|---|
| 100 | 4 | 1 (F1) | $5 |
| 1,000 | 42 | 1-2 (F1) | $20 |
| 10,000 | 417 | 2-5 (F2) | $150 |
| 100,000 | 4,167 | 10-20 (F4) | $1,500 |
Microservices Architecture:
┌──────────────────────────────────────────────────────┐
│ API Gateway (Kong/Nginx) │
│ Route, Auth, Rate Limit │
└────┬──────────┬──────────┬──────────┬────────────────┘
│ │ │ │
↓ ↓ ↓ ↓
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│Track │ │Quote │ │User │ │Notif │
│Service │ │Service │ │Service │ │Service │
└────┬───┘ └────┬───┘ └────┬───┘ └────┬───┘
│ │ │ │
└──────────┴──────────┴──────────┘
↓
┌─────────────────┐
│ Message Queue │ (RabbitMQ/Pub/Sub)
└─────────────────┘
↓
┌─────────────────┐
│ Shared Services │
│ - Cache (Redis) │
│ - Database │
│ - Logging │
└─────────────────┘
Implementation:
fastway-microservices/
├── services/
│ ├── tracking-service/
│ │ ├── src/
│ │ ├── Dockerfile
│ │ ├── composer.json
│ │ └── kubernetes/
│ │ └── deployment.yaml
│ ├── quote-service/
│ │ ├── src/
│ │ ├── Dockerfile
│ │ └── kubernetes/
│ ├── notification-service/
│ │ ├── src/
│ │ └── Dockerfile
│ └── user-service/
│ ├── src/
│ └── Dockerfile
├── api-gateway/
│ ├── kong.yml
│ └── nginx.conf
├── shared/
│ ├── proto/ # gRPC definitions
│ └── libraries/ # Shared code
└── docker-compose.yml
Tracking Microservice (Standalone):
// tracking-service/src/index.php
<?php
require 'vendor/autoload.php';
use FastwayTracking\TrackingController;
use FastwayTracking\FastwayAPIClient;
use FastwayTracking\CacheService;
$app = new \Slim\App();
// Health check
$app->get('/health', function($request, $response) {
return $response->withJson(['status' => 'healthy']);
});
// Track endpoint
$app->post('/track', function($request, $response) {
$data = $request->getParsedBody();
$trackingNumber = $data['tracking_number'];
$cache = new CacheService();
$client = new FastwayAPIClient();
$controller = new TrackingController($client, $cache);
try {
$result = $controller->track($trackingNumber);
return $response->withJson(['success' => true, 'data' => $result]);
} catch (\Exception $e) {
return $response->withStatus(500)
->withJson(['error' => $e->getMessage()]);
}
});
$app->run();Dockerfile:
# tracking-service/Dockerfile
FROM php:8.1-fpm
# Install dependencies
RUN apt-get update && apt-get install -y \
libzip-dev \
&& docker-php-ext-install zip pdo_mysql
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Copy application
WORKDIR /app
COPY . /app
RUN composer install --no-dev --optimize-autoloader
EXPOSE 8080
CMD ["php", "-S", "0.0.0.0:8080", "-t", "src"]Kubernetes Deployment:
# tracking-service/kubernetes/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: tracking-service
spec:
replicas: 3
selector:
matchLabels:
app: tracking-service
template:
metadata:
labels:
app: tracking-service
spec:
containers:
- name: tracking-service
image: gcr.io/PROJECT/tracking-service:latest
ports:
- containerPort: 8080
env:
- name: REDIS_HOST
value: "redis-service"
- name: FASTWAY_API_KEY
valueFrom:
secretKeyRef:
name: fastway-secrets
key: api-key
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: tracking-service
spec:
selector:
app: tracking-service
ports:
- port: 80
targetPort: 8080
type: LoadBalancerInter-Service Communication (gRPC):
// shared/proto/tracking.proto
syntax = "proto3";
package tracking;
service TrackingService {
rpc Track(TrackRequest) returns (TrackResponse);
}
message TrackRequest {
string tracking_number = 1;
}
message TrackResponse {
string tracking_number = 1;
string status = 2;
repeated Event events = 3;
}
message Event {
string date = 1;
string description = 2;
string location = 3;
}Message Queue for Async Processing:
// notification-service/src/NotificationWorker.php
use PhpAmqpLib\Connection\AMQPStreamConnection;
$connection = new AMQPStreamConnection('rabbitmq', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('tracking_notifications', false, true, false, false);
$callback = function($msg) {
$data = json_decode($msg->body, true);
// Send notification
sendEmail($data['email'], "Tracking Update", $data['message']);
$msg->ack();
};
$channel->basic_consume('tracking_notifications', '', false, false, false, false, $callback);
while ($channel->is_consuming()) {
$channel->wait();
}Benefits of Microservices:
- Independent scaling of services
- Technology flexibility (use Node.js for real-time, Python for ML)
- Fault isolation
- Independent deployment
- Team autonomy
Multi-Layer Error Handling:
// 1. Global Exception Handler
set_exception_handler(function($exception) {
logError('Uncaught Exception', [
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $exception->getTraceAsString()
]);
http_response_code(500);
echo json_encode([
'success' => false,
'error' => 'An unexpected error occurred'
]);
});
// 2. Custom Error Handler
set_error_handler(function($errno, $errstr, $errfile, $errline) {
if (!(error_reporting() & $errno)) {
return false;
}
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});
// 3. Try-Catch in Critical Sections
try {
$response = curl_exec($ch);
if ($curlError = curl_error($ch)) {
throw new APIException("cURL Error: $curlError");
}
if ($httpCode !== 200) {
throw new HTTPException("HTTP $httpCode received");
}
$data = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new JSONException("JSON parse error: " . json_last_error_msg());
}
} catch (APIException $e) {
logError('API Error', ['error' => $e->getMessage()]);
sendResponse(false, [], 'Unable to connect to tracking service');
} catch (JSONException $e) {
logError('Data Format Error', ['error' => $e->getMessage()]);
sendResponse(false, [], 'Invalid response from tracking service');
} catch (Exception $e) {
logError('General Error', ['error' => $e->getMessage()]);
sendResponse(false, [], 'An error occurred processing your request');
}
// 4. Input Validation Errors
if (!validateInput($trackingNumber)) {
http_response_code(400);
sendResponse(false, [], 'Invalid tracking number format');
}
// 5. Business Logic Errors
if ($weight > 30) {
sendResponse(false, [], 'Maximum weight is 30kg');
}Custom Exception Classes:
// exceptions/APIException.php
class APIException extends Exception {
protected $apiResponse;
public function __construct($message, $apiResponse = null) {
parent::__construct($message);
$this->apiResponse = $apiResponse;
}
public function getAPIResponse() {
return $this->apiResponse;
}
}
// exceptions/ValidationException.php
class ValidationException extends Exception {
protected $errors;
public function __construct($message, array $errors = []) {
parent::__construct($message);
$this->errors = $errors;
}
public function getErrors() {
return $this->errors;
}
}Centralized Logging Architecture:
// Logger.php OR // in this case you can check the /logs/error.log as well
class Logger {
private static $instance;
private $handlers = [];
private function __construct() {
// Add handlers
$this->addHandler(new FileHandler());
$this->addHandler(new CloudLoggingHandler());
$this->addHandler(new SentryHandler());
}
public static function getInstance() {
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function log($level, $message, array $context = []) {
$entry = [
'timestamp' => date('c'),
'level' => $level,
'message' => $message,
'context' => $context,
'request_id' => $_SERVER['HTTP_X_REQUEST_ID'] ?? uniqid(),
'user_ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
];
foreach ($this->handlers as $handler) {
$handler->handle($entry);
}
}
public function error($message, array $context = []) {
$this->log('ERROR', $message, $context);
}
public function warning($message, array $context = []) {
$this->log('WARNING', $message, $context);
}
public function info($message, array $context = []) {
$this->log('INFO', $message, $context);
}
}
// Handlers/FileHandler.php
class FileHandler {
public function handle(array $entry) {
$logFile = __DIR__ . '/../logs/' . date('Y-m-d') . '.log';
$line = sprintf(
"[%s] %s: %s %s\n",
$entry['timestamp'],
$entry['level'],
$entry['message'],
json_encode($entry['context'])
);
file_put_contents($logFile, $line, FILE_APPEND);
}
}
// Handlers/CloudLoggingHandler.php
class CloudLoggingHandler {
private $client;
public function __construct() {
$this->client = new \Google\Cloud\Logging\LoggingClient();
}
public function handle(array $entry) {
$logger = $this->client->logger('fastway-app');
$logger->write($entry, [
'severity' => $entry['level']
]);
}
}
// Usage
$logger = Logger::getInstance();
$logger->error('API call failed', [
'tracking_number' => $trackingNumber,
'error' => $errorMessage,
'api_response' => $apiResponse
]);Structured Logging Format:
{
"timestamp": "2026-01-24T15:30:00+00:00",
"level": "ERROR",
"message": "Fastway API call failed",
"context": {
"tracking_number": "Z60000983328",
"http_code": 500,
"error": "Connection timeout"
},
"request_id": "req_abc123",
"user_ip": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"trace_id": "trace_xyz789",
"span_id": "span_456"
}Monitoring Stack:
// Monitoring/MetricsCollector.php
class MetricsCollector {
private $statsd;
public function __construct() {
$this->statsd = new \Domnikl\Statsd\Client(
new \Domnikl\Statsd\Connection\UdpSocket('statsd', 8125)
);
}
public function incrementAPICall($endpoint, $success) {
$metric = $success ? 'api.success' : 'api.failure';
$this->statsd->increment($metric, 1, ['endpoint' => $endpoint]);
}
public function recordAPILatency($endpoint, $duration) {
$this->statsd->timing('api.latency', $duration, ['endpoint' => $endpoint]);
}
public function gaugeActiveRequests($count) {
$this->statsd->gauge('api.active_requests', $count);
}
}
// In api/track.php
$metrics = new MetricsCollector();
$startTime = microtime(true);
try {
$response = makeApiRequest(...);
$metrics->incrementAPICall('track', true);
} catch (Exception $e) {
$metrics->incrementAPICall('track', false);
throw $e;
} finally {
$duration = (microtime(true) - $startTime) * 1000; // ms
$metrics->recordAPILatency('track', $duration);
}Health Check Endpoint:
// health-check.php // You can visit https://fastway-webapp.ue.r.appspot.com/health-check.php as well if you are that lazy LOL //
class HealthChecker {
public function check(): array {
return [
'status' => $this->getOverallStatus(),
'checks' => [
'api' => $this->checkAPIAvailability(),
'database' => $this->checkDatabase(),
'redis' => $this->checkRedis(),
'disk' => $this->checkDiskSpace(),
'memory' => $this->checkMemory()
],
'timestamp' => date('c')
];
}
private function checkAPIAvailability(): array {
try {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://sa.api.fastway.org/v3/health');
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'status' => $httpCode === 200 ? 'healthy' : 'unhealthy',
'response_code' => $httpCode
];
} catch (Exception $e) {
return ['status' => 'unhealthy', 'error' => $e->getMessage()];
}
}
}Monitoring Dashboard (Grafana):
# prometheus.yml // learned this from BMW, they are also trying to build a tech flow for the SA market //
scrape_configs:
- job_name: 'fastway-app'
static_configs:
- targets: ['app:8080']
metrics_path: '/metrics'
scrape_interval: 15s
# Metrics exposed
# HELP api_requests_total Total number of API requests
# TYPE api_requests_total counter
api_requests_total{endpoint="track",status="success"} 1523
api_requests_total{endpoint="track",status="failure"} 12
# HELP api_latency_seconds API request latency
# TYPE api_latency_seconds histogram
api_latency_seconds_bucket{endpoint="track",le="0.1"} 1200
api_latency_seconds_bucket{endpoint="track",le="0.5"} 1500
api_latency_seconds_bucket{endpoint="track",le="1.0"} 1523Alert Management System:
// Alerting/AlertManager.php
class AlertManager {
private $channels;
public function __construct() {
$this->channels = [
new EmailChannel(),
new SlackChannel(),
new PagerDutyChannel(),
new SMSChannel()
];
}
public function sendAlert($severity, $message, array $context = []) {
$alert = [
'severity' => $severity,
'message' => $message,
'context' => $context,
'timestamp' => date('c'),
'environment' => getenv('APP_ENV')
];
// Send based on severity
foreach ($this->getChannelsForSeverity($severity) as $channel) {
$channel->send($alert);
}
}
private function getChannelsForSeverity($severity) {
switch ($severity) {
case 'CRITICAL':
return $this->channels; // All channels
case 'WARNING':
return [new EmailChannel(), new SlackChannel()];
case 'INFO':
return [new SlackChannel()];
default:
return [];
}
}
}
// Channels/SlackChannel.php
class SlackChannel {
public function send(array $alert) {
$webhook = getenv('SLACK_WEBHOOK_URL');
$payload = [
'text' => $alert['message'],
'attachments' => [[
'color' => $this->getColor($alert['severity']),
'fields' => [
['title' => 'Severity', 'value' => $alert['severity'], 'short' => true],
['title' => 'Environment', 'value' => $alert['environment'], 'short' => true],
['title' => 'Details', 'value' => json_encode($alert['context'])]
],
'footer' => 'Fastway App',
'ts' => strtotime($alert['timestamp'])
]]
];
$ch = curl_init($webhook);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_exec($ch);
curl_close($ch);
}
}
// Channels/PagerDutyChannel.php
class PagerDutyChannel {
public function send(array $alert) {
if ($alert['severity'] !== 'CRITICAL') {
return; // Only page for critical issues
}
$payload = [
'routing_key' => getenv('PAGERDUTY_KEY'),
'event_action' => 'trigger',
'payload' => [
'summary' => $alert['message'],
'severity' => 'critical',
'source' => 'fastway-app',
'custom_details' => $alert['context']
]
];
$ch = curl_init('https://events.pagerduty.com/v2/enqueue');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_exec($ch);
curl_close($ch);
}
}API Health Monitoring:
// Cron job or scheduled task
// cron/check-api-health.php
class APIHealthMonitor {
private $alertManager;
private $failureCount = 0;
private $maxFailures = 3;
public function __construct() {
$this->alertManager = new AlertManager();
}
public function monitor() {
$isHealthy = $this->checkAPIHealth();
if (!$isHealthy) {
$this->failureCount++;
if ($this->failureCount >= $this->maxFailures) {
$this->alertManager->sendAlert('CRITICAL',
'Fastway API is unavailable',
[
'consecutive_failures' => $this->failureCount,
'last_check' => date('c')
]
);
}
} else {
if ($this->failureCount > 0) {
// API recovered
$this->alertManager->sendAlert('INFO',
'Fastway API has recovered',
['downtime_checks' => $this->failureCount]
);
}
$this->failureCount = 0;
}
}
private function checkAPIHealth(): bool {
try {
$response = file_get_contents(
'https://sa.api.fastway.org/v3/health?api_key=' . FASTWAY_API_KEY,
false,
stream_context_create(['http' => ['timeout' => 5]])
);
return !empty($response);
} catch (Exception $e) {
return false;
}
}
}
// Run every 5 minutes
$monitor = new APIHealthMonitor();
$monitor->monitor();Alerting Rules (Prometheus):
# alert.rules.yml
groups:
- name: fastway-api
interval: 30s
rules:
- alert: FastwayAPIDown
expr: api_requests_total{status="failure"} / api_requests_total > 0.5
for: 5m
labels:
severity: critical
annotations:
summary: "Fastway API failure rate above 50%"
description: "{{ $value }}% of requests are failing"
- alert: FastwayAPIHighLatency
expr: histogram_quantile(0.95, api_latency_seconds) > 2
for: 10m
labels:
severity: warning
annotations:
summary: "Fastway API latency high"
description: "95th percentile latency is {{ $value }}s"Continue in next message with Testing Strategy...