Chapter 10: Performance Optimization and Best Practices

Haiyue
10min

Chapter 10: Performance Optimization and Best Practices

Learning Objectives

  1. Master request caching mechanisms
  2. Learn data compression and optimization
  3. Implement connection pooling and reuse
  4. Master performance monitoring methods
  5. Understand best practices and standards

10.1 Request Caching

Memory Cache Implementation

class RequestCache {
  constructor(maxSize = 100, ttl = 5 * 60 * 1000) {
    this.cache = new Map();
    this.maxSize = maxSize;
    this.ttl = ttl;
  }

  generateKey(config) {
    const { method, url, params, data } = config;
    return `${method}-${url}-${JSON.stringify(params)}-${JSON.stringify(data)}`;
  }

  get(config) {
    const key = this.generateKey(config);
    const cached = this.cache.get(key);

    if (!cached) return null;

    if (Date.now() - cached.timestamp > this.ttl) {
      this.cache.delete(key);
      return null;
    }

    return cached.data;
  }

  set(config, data) {
    const key = this.generateKey(config);

    // If cache is full, delete the oldest entry
    if (this.cache.size >= this.maxSize) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }

    this.cache.set(key, {
      data,
      timestamp: Date.now()
    });
  }

  clear() {
    this.cache.clear();
  }
}

// Axios instance with caching
const cache = new RequestCache();

const cachedAxios = axios.create();
cachedAxios.interceptors.request.use(config => {
  // Only cache GET requests
  if (config.method === 'get') {
    const cached = cache.get(config);
    if (cached) {
      console.log('Using cached data');
      return Promise.resolve({ data: cached, status: 200, cached: true });
    }
  }
  return config;
});

cachedAxios.interceptors.response.use(response => {
  if (response.config.method === 'get' && !response.cached) {
    cache.set(response.config, response.data);
  }
  return response;
});

10.2 Data Compression

Request Data Compression

// Using compression library for data compression
const pako = require('pako'); // or other compression library

class CompressionHandler {
  static compress(data) {
    const jsonString = JSON.stringify(data);
    const compressed = pako.gzip(jsonString);
    return compressed;
  }

  static decompress(compressed) {
    const decompressed = pako.ungzip(compressed, { to: 'string' });
    return JSON.parse(decompressed);
  }

  static shouldCompress(data, threshold = 1024) {
    const size = JSON.stringify(data).length;
    return size > threshold;
  }
}

// Compression interceptor
axios.interceptors.request.use(config => {
  if (config.data && CompressionHandler.shouldCompress(config.data)) {
    config.data = CompressionHandler.compress(config.data);
    config.headers['Content-Encoding'] = 'gzip';
    config.headers['Content-Type'] = 'application/octet-stream';
  }
  return config;
});

10.3 Connection Pool Optimization

HTTP Agent Configuration (Node.js)

const http = require('http');
const https = require('https');

// Configure connection pool
const httpAgent = new http.Agent({
  keepAlive: true,
  maxSockets: 50,
  maxFreeSockets: 10,
  timeout: 60000,
  freeSocketTimeout: 30000
});

const httpsAgent = new https.Agent({
  keepAlive: true,
  maxSockets: 50,
  maxFreeSockets: 10,
  timeout: 60000,
  freeSocketTimeout: 30000
});

const optimizedAxios = axios.create({
  httpAgent,
  httpsAgent,
  timeout: 30000
});

10.4 Performance Monitoring

Request Performance Monitoring

class PerformanceMonitor {
  constructor() {
    this.metrics = {
      requests: 0,
      responses: 0,
      errors: 0,
      totalTime: 0,
      avgResponseTime: 0
    };
  }

  startTimer(config) {
    config.metadata = {
      startTime: performance.now()
    };
  }

  endTimer(response) {
    const endTime = performance.now();
    const startTime = response.config.metadata?.startTime;

    if (startTime) {
      const duration = endTime - startTime;
      this.recordMetric(duration);
    }
  }

  recordMetric(duration) {
    this.metrics.requests++;
    this.metrics.totalTime += duration;
    this.metrics.avgResponseTime = this.metrics.totalTime / this.metrics.requests;
  }

  recordError() {
    this.metrics.errors++;
  }

  getStats() {
    return {
      ...this.metrics,
      errorRate: this.metrics.errors / this.metrics.requests,
      successRate: (this.metrics.requests - this.metrics.errors) / this.metrics.requests
    };
  }
}

const monitor = new PerformanceMonitor();

// Performance monitoring interceptors
axios.interceptors.request.use(config => {
  monitor.startTimer(config);
  return config;
});

axios.interceptors.response.use(
  response => {
    monitor.endTimer(response);
    return response;
  },
  error => {
    monitor.recordError();
    if (error.config) {
      monitor.endTimer({ config: error.config });
    }
    return Promise.reject(error);
  }
);

10.5 Best Practices

API Client Design Principles

class BestPracticesClient {
  constructor(config = {}) {
    this.client = axios.create({
      // Basic configuration
      baseURL: config.baseURL,
      timeout: config.timeout || 10000,

      // Security headers
      headers: {
        'X-Requested-With': 'XMLHttpRequest',
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },

      // Status validation
      validateStatus: (status) => status >= 200 && status < 300,

      // Auto-retry configuration
      retry: 3,
      retryDelay: 1000
    });

    this.setupInterceptors();
    this.setupCache();
    this.setupMonitoring();
  }

  setupInterceptors() {
    // Request interceptor
    this.client.interceptors.request.use(
      this.handleRequest.bind(this),
      this.handleRequestError.bind(this)
    );

    // Response interceptor
    this.client.interceptors.response.use(
      this.handleResponse.bind(this),
      this.handleResponseError.bind(this)
    );
  }

  handleRequest(config) {
    // Add request ID
    config.headers['X-Request-ID'] = this.generateRequestId();

    // Add timestamp
    config.metadata = { startTime: Date.now() };

    return config;
  }

  handleResponse(response) {
    // Log response time
    const duration = Date.now() - response.config.metadata.startTime;
    console.log(`Request completed: ${duration}ms`);

    return response;
  }

  handleResponseError(error) {
    // Unified error handling
    const errorInfo = {
      url: error.config?.url,
      method: error.config?.method,
      status: error.response?.status,
      message: error.message
    };

    console.error('Request failed:', errorInfo);

    return Promise.reject(error);
  }

  generateRequestId() {
    return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}

10.6 Code Standards

API Interface Encapsulation Standards

// Good API encapsulation example
class UserService {
  constructor(apiClient) {
    this.api = apiClient;
    this.basePath = '/api/v1/users';
  }

  // Get user list
  async getUsers(params = {}) {
    const response = await this.api.get(this.basePath, { params });
    return response.data;
  }

  // Get single user
  async getUser(id) {
    if (!id) throw new Error('User ID is required');

    const response = await this.api.get(`${this.basePath}/${id}`);
    return response.data;
  }

  // Create user
  async createUser(userData) {
    this.validateUserData(userData);

    const response = await this.api.post(this.basePath, userData);
    return response.data;
  }

  // Update user
  async updateUser(id, userData) {
    if (!id) throw new Error('User ID is required');

    this.validateUserData(userData, false);

    const response = await this.api.put(`${this.basePath}/${id}`, userData);
    return response.data;
  }

  // Delete user
  async deleteUser(id) {
    if (!id) throw new Error('User ID is required');

    await this.api.delete(`${this.basePath}/${id}`);
    return true;
  }

  // Data validation
  validateUserData(data, isCreate = true) {
    if (isCreate && (!data.name || !data.email)) {
      throw new Error('Name and email are required');
    }

    if (data.email && !this.isValidEmail(data.email)) {
      throw new Error('Invalid email format');
    }
  }

  isValidEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }
}

Chapter Summary

  • Caching mechanisms significantly improve performance for repeated requests
  • Data compression reduces network transmission overhead
  • Connection pool optimization improves concurrent processing capability
  • Performance monitoring helps identify and resolve bottlenecks
  • Following best practices improves code quality and maintainability

Key Points

  • Reasonable use of caching balances performance and data freshness
  • Compress large data to reduce transmission time
  • Connection reuse reduces connection overhead
  • Continuous monitoring identifies performance issues
  • Standardized API encapsulation improves development efficiency