Vai al contenuto principale

Introduzione

Questa guida presenta le migliori pratiche per integrare efficacemente con l’API Lovi, ottimizzando performance, affidabilità e manutenibilità delle tue implementazioni.

🏗️ Architettura e Design

Progettazione dell’Integrazione

Separazione delle Responsabilità:
class LoviService {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://cloud.lovi.ai/functions/v1';
  }

  // Gestione notifiche
  async sendNotification(data) {
    return this.request('/notify', data);
  }

  // Gestione template
  async getTemplates() {
    return this.request('/templates');
  }

  // Metodo privato per richieste HTTP
  async request(endpoint, data = null) {
    const url = `${this.baseUrl}${endpoint}?access_key=${this.apiKey}`;
    // Implementazione richiesta...
  }
}
Configurazione Centrale:
const config = {
  api: {
    baseUrl: 'https://cloud.lovi.ai/functions/v1',
    timeout: 30000,
    retryAttempts: 3
  },
  rateLimit: {
    requestsPerMinute: 100,
    burstLimit: 1000
  }
};

Gestione delle Chiavi API

Sicurezza delle Chiavi:
// ❌ NON fare questo
const apiKey = "sk_live_123456789"; // Chiave hardcoded

// ✅ Fai questo invece
const apiKey = process.env.LOVI_API_KEY; // Variabile d'ambiente

// O usa un gestore di segreti
const apiKey = await secretManager.getSecret('lovi-api-key');
Rotazione delle Chiavi:
class KeyManager {
  async getValidKey() {
    const key = await this.getCurrentKey();
    if (this.isExpiringSoon(key)) {
      await this.rotateKey(key);
    }
    return key;
  }
}

🚀 Ottimizzazione delle Performance

Gestione dei Limiti di Tariffa

Implementazione Bucket Token:
class RateLimiter {
  constructor(requestsPerMinute = 100) {
    this.capacity = requestsPerMinute;
    this.tokens = requestsPerMinute;
    this.lastRefill = Date.now();
    this.refillRate = requestsPerMinute / 60; // token/secondo
  }

  async acquireToken() {
    this.refill();
    if (this.tokens < 1) {
      throw new Error('Rate limit exceeded');
    }
    this.tokens -= 1;
  }

  refill() {
    const now = Date.now();
    const timePassed = (now - this.lastRefill) / 1000;
    const tokensToAdd = timePassed * this.refillRate;
    this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
    this.lastRefill = now;
  }
}
Code di Priorità:
class PriorityQueue {
  constructor() {
    this.queues = {
      high: [],
      normal: [],
      low: []
    };
  }

  async process() {
    while (true) {
      let request = null;
      
      if (this.queues.high.length > 0) {
        request = this.queues.high.shift();
      } else if (this.queues.normal.length > 0) {
        request = this.queues.normal.shift();
      } else if (this.queues.low.length > 0) {
        request = this.queues.low.shift();
      }

      if (request) {
        await this.processRequest(request);
      } else {
        await sleep(100); // Attendi nuove richieste
      }
    }
  }
}

Ottimizzazione delle Richieste

Batch Processing:
async function sendBulkNotifications(notifications) {
  const batches = chunk(notifications, 10); // 10 notifiche per batch
  
  for (const batch of batches) {
    try {
      await Promise.all(
        batch.map(notification => sendNotification(notification))
      );
      await sleep(1000); // Pausa tra batch
    } catch (error) {
      console.error('Batch failed:', error);
      // Gestisci errori parziali
    }
  }
}
Compressione Payload:
const data = {
  contact: { number: '34666033135' },
  template: 'welcome_user',
  variables: { name: 'Mario', company: 'TechCorp' }
};

// Compressione automatica per payload grandi
if (JSON.stringify(data).length > 1000) {
  headers['Content-Encoding'] = 'gzip';
  body = gzipSync(JSON.stringify(data));
}

Caching Intelligente

Cache Template:
class TemplateCache {
  constructor(ttl = 3600000) { // 1 ora
    this.cache = new Map();
    this.ttl = ttl;
  }

  async getTemplate(name, language = 'it_IT') {
    const key = `${name}_${language}`;
    const cached = this.cache.get(key);
    
    if (cached && Date.now() - cached.timestamp < this.ttl) {
      return cached.data;
    }
    
    const template = await this.fetchTemplate(name, language);
    this.cache.set(key, {
      data: template,
      timestamp: Date.now()
    });
    
    return template;
  }
}
Cache Risultati:
const memoize = (fn, ttl = 300000) => { // 5 minuti
  const cache = new Map();
  
  return async (...args) => {
    const key = JSON.stringify(args);
    const cached = cache.get(key);
    
    if (cached && Date.now() - cached.timestamp < ttl) {
      return cached.result;
    }
    
    const result = await fn(...args);
    cache.set(key, {
      result,
      timestamp: Date.now()
    });
    
    return result;
  };
};

🛡️ Affidabilità e Monitoraggio

Gestione degli Errori

Strategie di Retry:
async function withRetry(operation, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await operation();
    } catch (error) {
      if (attempt === maxAttempts || !isRetryable(error)) {
        throw error;
      }
      
      const delay = Math.pow(2, attempt) * 1000; // Backoff esponenziale
      await sleep(delay);
    }
  }
}

function isRetryable(error) {
  return [429, 500, 502, 503].includes(error.statusCode);
}
Circuit Breaker:
class CircuitBreaker {
  constructor(failureThreshold = 5, timeout = 60000) {
    this.failureThreshold = failureThreshold;
    this.timeout = timeout;
    this.failureCount = 0;
    this.lastFailureTime = null;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
  }

  async execute(operation) {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime > this.timeout) {
        this.state = 'HALF_OPEN';
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }

    try {
      const result = await operation();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  onSuccess() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }

  onFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    
    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
    }
  }
}

Monitoraggio e Logging

Struttura Log Completa:
const logRequest = (request) => {
  console.log(JSON.stringify({
    timestamp: new Date().toISOString(),
    request_id: request.id,
    method: request.method,
    endpoint: request.endpoint,
    user_agent: request.userAgent,
    ip_address: request.ip,
    company_id: request.companyId,
    user_id: request.userId,
    request_size: JSON.stringify(request.body).length,
    response_time: Date.now() - request.startTime,
    status_code: request.statusCode,
    error_code: request.error?.code,
    template_name: request.templateName,
    recipient_count: request.recipientCount
  }));
};
Metriche Chiave:
const metrics = {
  requests_total: 0,
  requests_success: 0,
  requests_error: 0,
  response_time_avg: 0,
  rate_limit_hits: 0,
  template_cache_hits: 0,
  template_cache_misses: 0
};

const updateMetrics = (request) => {
  metrics.requests_total++;
  
  if (request.statusCode < 400) {
    metrics.requests_success++;
  } else {
    metrics.requests_error++;
  }
  
  // Aggiorna tempo di risposta medio
  const responseTime = Date.now() - request.startTime;
  metrics.response_time_avg = 
    (metrics.response_time_avg + responseTime) / 2;
};

🔧 Manutenibilità

Testing

Test Unitari:
describe('LoviService', () => {
  let service;
  
  beforeEach(() => {
    service = new LoviService('test_key');
  });
  
  test('sendNotification should handle success', async () => {
    const mockResponse = { success: true, notification_id: '123' };
    // Mock della richiesta HTTP
    
    const result = await service.sendNotification({
      contact: { number: '34666033135' },
      template: 'welcome'
    });
    
    expect(result).toEqual(mockResponse);
  });
  
  test('should handle rate limiting', async () => {
    // Simula errore 429
    await expect(service.sendNotification({}))
      .rejects.toThrow('Rate limit exceeded');
  });
});
Test di Integrazione:
describe('API Integration', () => {
  test('end-to-end notification flow', async () => {
    // 1. Crea template
    const template = await createTemplate(testTemplateData);
    
    // 2. Invia notifica
    const notification = await sendNotification({
      contact: testContact,
      template: template.name
    });
    
    // 3. Verifica consegna (se possibile)
    expect(notification.success).toBe(true);
  });
});

Documentazione

Documentazione Automatica:
/**
 * Invia una notifica WhatsApp usando un template
 * @param {Object} data - Dati della notifica
 * @param {Object} data.contact - Informazioni del contatto
 * @param {string} data.contact.number - Numero di telefono (solo numeri)
 * @param {string} data.template - Nome del template
 * @param {Object} [data.variables] - Variabili del template
 * @param {string} [data.datetime_sending] - Data/ora programmata (ISO 8601)
 * @returns {Promise<Object>} Risultato dell'invio
 */
async function sendNotification(data) {
  // Implementazione...
}

📊 Ottimizzazione per Diversi Casi d’Uso

E-commerce

Carrello Abbandonato:
async function sendAbandonedCartReminder(userId, cartItems) {
  const variables = {
    name: await getUserName(userId),
    items: cartItems.map(item => item.name).join(', '),
    total: calculateTotal(cartItems),
    discount_code: generateDiscountCode()
  };
  
  return await loviService.sendNotification({
    contact: { number: await getUserPhone(userId) },
    template: 'cart_reminder',
    variables,
    datetime_sending: moment().add(1, 'hour').toISOString()
  });
}

Assistenza Clienti

Escalation Automatica:
async function handleCustomerEscalation(ticket) {
  if (ticket.priority === 'high' && !ticket.agent_assigned) {
    await notifyAvailableAgents(ticket);
    
    // Riprova dopo 5 minuti se nessun agente risponde
    setTimeout(async () => {
      if (!ticket.agent_assigned) {
        await escalateToSupervisor(ticket);
      }
    }, 5 * 60 * 1000);
  }
}

Marketing

Campagne Personalizzate:
async function sendPersonalizedCampaign(segment) {
  const users = await getUsersInSegment(segment);
  const batches = chunk(users, 50); // Batch da 50
  
  for (const batch of batches) {
    await Promise.all(
      batch.map(async (user) => {
        const variables = await generatePersonalizedContent(user);
        return loviService.sendNotification({
          contact: { number: user.phone },
          template: 'personalized_offer',
          variables
        });
      })
    );
    
    await sleep(60000); // Pausa 1 minuto tra batch
  }
}

🔒 Sicurezza

Validazione Input

Sanitizzazione:
function sanitizeInput(input) {
  return {
    contact: {
      number: input.contact.number.replace(/\D/g, '') // Solo numeri
    },
    template: input.template.replace(/[^a-zA-Z0-9_]/g, ''), // Solo caratteri sicuri
    variables: Object.keys(input.variables || {}).reduce((acc, key) => {
      acc[key] = String(input.variables[key]).substring(0, 100); // Limita lunghezza
      return acc;
    }, {})
  };
}
Validazione Schema:
const notificationSchema = {
  type: 'object',
  required: ['contact', 'template'],
  properties: {
    contact: {
      type: 'object',
      required: ['number'],
      properties: {
        number: {
          type: 'string',
          pattern: '^[0-9]{8,15}$'
        }
      }
    },
    template: {
      type: 'string',
      minLength: 1,
      maxLength: 100
    },
    variables: {
      type: 'object',
      maxProperties: 20
    }
  }
};

Gestione Segreti

Rotazione Automatica:
class SecretManager {
  async getSecret(name) {
    const secret = await this.fetchSecret(name);
    
    if (this.isExpiring(secret)) {
      await this.rotateSecret(name);
      return this.fetchSecret(name);
    }
    
    return secret;
  }
  
  isExpiring(secret) {
    const expiry = new Date(secret.expiry);
    const now = new Date();
    const daysUntilExpiry = (expiry - now) / (1000 * 60 * 60 * 24);
    return daysUntilExpiry < 30; // Ruota 30 giorni prima
  }
}
Questa guida fornisce una base solida per implementazioni robuste e scalabili con l’API Lovi. Ricorda di monitorare costantemente le performance e adattare le pratiche alle tue esigenze specifiche.