Melhores Práticas
Este guia apresenta as melhores práticas para usar a API Speedio de forma eficiente, segura e escalável em produção.Segurança
1. Gerenciamento de Credenciais
- ✅ Faça
- ❌ Não Faça
Copy
# Use variáveis de ambiente
export SPEEDIO_USERNAME="usuario_producao"
export SPEEDIO_PASSWORD="senha_complexa_123!"
# Use serviços de secrets management
aws secretsmanager get-secret-value --secret-id "speedio-api-credentials"
# Configure rotação automática
kubectl create secret generic speedio-credentials \
--from-literal=username="$SPEEDIO_USERNAME" \
--from-literal=password="$SPEEDIO_PASSWORD"
Copy
// NUNCA faça isso!
const auth = 'Basic dGVzdHVzZXI6c3BkdGVzdHVzZXI='; // Hardcoded
// NUNCA commite credenciais
const config = {
username: 'meu_usuario',
password: 'minha_senha_123'
};
// NUNCA exponha em logs
console.log('Auth:', username, password);
2. Rotação de Credenciais
Copy
class CredentialManager {
constructor() {
this.credentials = null;
this.lastRotation = null;
this.rotationInterval = 24 * 60 * 60 * 1000; // 24 horas
}
async getCredentials() {
if (this.needsRotation()) {
await this.rotateCredentials();
}
return this.credentials;
}
needsRotation() {
if (!this.lastRotation) return true;
return Date.now() - this.lastRotation > this.rotationInterval;
}
async rotateCredentials() {
try {
// Buscar novas credenciais do secret manager
this.credentials = await this.fetchFromSecretManager();
this.lastRotation = Date.now();
console.log('🔑 Credenciais rotacionadas com sucesso');
} catch (error) {
console.error('❌ Erro ao rotacionar credenciais:', error);
// Manter credenciais atuais se a rotação falhar
}
}
async fetchFromSecretManager() {
// Implementar busca no AWS Secrets Manager, Azure Key Vault, etc.
return {
username: process.env.SPEEDIO_USERNAME,
password: process.env.SPEEDIO_PASSWORD
};
}
}
3. Validação de Entrada
Copy
class InputValidator {
static validateCNPJ(cnpj) {
if (!cnpj || typeof cnpj !== 'string') {
throw new ValidationError('CNPJ deve ser uma string válida');
}
// Remove formatação
const cleanCNPJ = cnpj.replace(/\D/g, '');
if (cleanCNPJ.length !== 14) {
throw new ValidationError('CNPJ deve ter 14 dígitos');
}
// Validar dígitos verificadores
if (!this.isValidCNPJ(cleanCNPJ)) {
throw new ValidationError('CNPJ inválido');
}
return cleanCNPJ;
}
static validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!email || typeof email !== 'string') {
throw new ValidationError('E-mail deve ser uma string válida');
}
if (!emailRegex.test(email)) {
throw new ValidationError('Formato de e-mail inválido');
}
if (email.length > 320) {
throw new ValidationError('E-mail muito longo');
}
return email.toLowerCase().trim();
}
static validateBatchSize(items, maxSize = 100) {
if (!Array.isArray(items)) {
throw new ValidationError('Deve ser um array');
}
if (items.length === 0) {
throw new ValidationError('Array não pode estar vazio');
}
if (items.length > maxSize) {
throw new ValidationError(`Máximo ${maxSize} itens por requisição`);
}
return items;
}
}
// Uso
async function buscarEmpresasSeguro(cnpjs) {
// Validar entrada
InputValidator.validateBatchSize(cnpjs, 10);
const validCNPJs = cnpjs.map(cnpj => InputValidator.validateCNPJ(cnpj));
return await client.buscarCNPJ(validCNPJs);
}
Performance
1. Cache Inteligente
Copy
class IntelligentCache {
constructor(options = {}) {
this.cache = new Map();
this.hitCount = 0;
this.missCount = 0;
this.defaultTTL = options.defaultTTL || 3600000; // 1 hora
this.maxSize = options.maxSize || 1000;
// TTLs específicos por tipo de dados
this.ttlByType = {
'cnpj': 24 * 60 * 60 * 1000, // 24 horas - dados empresariais mudam pouco
'validation': 12 * 60 * 60 * 1000, // 12 horas - validações são estáveis
'leads': 4 * 60 * 60 * 1000 // 4 horas - leads podem mudar mais
};
this.startCleanup();
}
generateKey(type, identifier) {
return `${type}:${identifier}`;
}
set(type, identifier, data, customTTL = null) {
const key = this.generateKey(type, identifier);
const ttl = customTTL || this.ttlByType[type] || this.defaultTTL;
const item = {
data,
timestamp: Date.now(),
ttl,
accessCount: 0,
lastAccess: Date.now()
};
// Implementar LRU se cache estiver cheio
if (this.cache.size >= this.maxSize) {
this.evictLRU();
}
this.cache.set(key, item);
console.debug(`📋 Cache SET: ${key} (TTL: ${ttl / 1000}s)`);
}
get(type, identifier) {
const key = this.generateKey(type, identifier);
const item = this.cache.get(key);
if (!item) {
this.missCount++;
console.debug(`📋 Cache MISS: ${key}`);
return null;
}
// Verificar se expirou
if (Date.now() - item.timestamp > item.ttl) {
this.cache.delete(key);
this.missCount++;
console.debug(`📋 Cache EXPIRED: ${key}`);
return null;
}
// Atualizar estatísticas de acesso
item.accessCount++;
item.lastAccess = Date.now();
this.hitCount++;
console.debug(`📋 Cache HIT: ${key} (acesso #${item.accessCount})`);
return item.data;
}
has(type, identifier) {
return this.get(type, identifier) !== null;
}
evictLRU() {
let oldestKey = null;
let oldestTime = Date.now();
for (const [key, item] of this.cache.entries()) {
if (item.lastAccess < oldestTime) {
oldestTime = item.lastAccess;
oldestKey = key;
}
}
if (oldestKey) {
this.cache.delete(oldestKey);
console.debug(`📋 Cache EVICTED: ${oldestKey}`);
}
}
getStats() {
const totalRequests = this.hitCount + this.missCount;
const hitRate = totalRequests > 0 ? (this.hitCount / totalRequests) * 100 : 0;
return {
size: this.cache.size,
maxSize: this.maxSize,
hitCount: this.hitCount,
missCount: this.missCount,
hitRate: hitRate.toFixed(2) + '%',
totalRequests
};
}
startCleanup() {
// Limpeza periódica de itens expirados
setInterval(() => {
const now = Date.now();
let cleanedCount = 0;
for (const [key, item] of this.cache.entries()) {
if (now - item.timestamp > item.ttl) {
this.cache.delete(key);
cleanedCount++;
}
}
if (cleanedCount > 0) {
console.debug(`📋 Cache cleanup: ${cleanedCount} itens removidos`);
}
}, 5 * 60 * 1000); // A cada 5 minutos
}
}
const cache = new IntelligentCache({
maxSize: 5000,
defaultTTL: 3600000
});
// Integração com cliente
async function buscarCNPJComCache(cnpj) {
// Verificar cache primeiro
const cached = cache.get('cnpj', cnpj);
if (cached) {
return cached;
}
// Buscar na API
const result = await client.buscarCNPJ([cnpj]);
// Cachear resultado
if (result && result.length > 0) {
cache.set('cnpj', cnpj, result[0]);
}
return result;
}
2. Processamento em Lote
Copy
class BatchProcessor {
constructor(options = {}) {
this.batchSize = options.batchSize || 10;
this.concurrency = options.concurrency || 3;
this.delayBetweenBatches = options.delayBetweenBatches || 1000;
this.queue = [];
this.processing = false;
}
async processCNPJs(cnpjs, onProgress = null) {
const results = [];
const batches = this.createBatches(cnpjs);
console.log(`🔄 Processando ${cnpjs.length} CNPJs em ${batches.length} lotes`);
for (let i = 0; i < batches.length; i += this.concurrency) {
const concurrentBatches = batches.slice(i, i + this.concurrency);
// Processar lotes concorrentemente
const batchPromises = concurrentBatches.map(async (batch, batchIndex) => {
try {
console.log(`📦 Processando lote ${i + batchIndex + 1}/${batches.length}`);
const batchResults = await this.processBatch(batch);
if (onProgress) {
onProgress({
currentBatch: i + batchIndex + 1,
totalBatches: batches.length,
processed: (i + batchIndex + 1) * this.batchSize,
total: cnpjs.length,
results: batchResults
});
}
return batchResults;
} catch (error) {
console.error(`❌ Erro no lote ${i + batchIndex + 1}:`, error.message);
return []; // Retornar array vazio em caso de erro
}
});
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults.flat());
// Aguardar entre grupos de lotes para respeitar rate limits
if (i + this.concurrency < batches.length) {
console.log(`⏳ Aguardando ${this.delayBetweenBatches / 1000}s...`);
await this.sleep(this.delayBetweenBatches);
}
}
console.log(`✅ Processamento concluído: ${results.length} resultados`);
return results;
}
createBatches(items) {
const batches = [];
for (let i = 0; i < items.length; i += this.batchSize) {
batches.push(items.slice(i, i + this.batchSize));
}
return batches;
}
async processBatch(cnpjs) {
try {
const result = await client.buscarCNPJ(cnpjs);
// Cachear resultados individuais
result.forEach(empresa => {
if (empresa && empresa.cnpj) {
cache.set('cnpj', empresa.cnpj, empresa);
}
});
return result;
} catch (error) {
// Tentar processar individualmente em caso de erro do lote
console.warn('⚠️ Erro no lote, tentando processamento individual...');
return await this.processIndividually(cnpjs);
}
}
async processIndividually(cnpjs) {
const results = [];
for (const cnpj of cnpjs) {
try {
const result = await buscarCNPJComCache(cnpj);
if (result) {
results.push(result);
}
// Pequeno delay entre requisições individuais
await this.sleep(200);
} catch (error) {
console.error(`❌ Erro ao processar CNPJ ${cnpj}:`, error.message);
}
}
return results;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Uso do processador em lote
const batchProcessor = new BatchProcessor({
batchSize: 10,
concurrency: 2,
delayBetweenBatches: 1500
});
async function processarGrandeVolume(cnpjs) {
return await batchProcessor.processCNPJs(cnpjs, (progress) => {
console.log(`📊 Progresso: ${progress.processed}/${progress.total} (${((progress.processed / progress.total) * 100).toFixed(1)}%)`);
});
}
3. Connection Pooling
Copy
class ConnectionPool {
constructor(options = {}) {
this.maxConnections = options.maxConnections || 10;
this.timeout = options.timeout || 30000;
this.keepAlive = options.keepAlive !== false;
// Configurar agente HTTP com pool de conexões
this.httpAgent = new require('http').Agent({
keepAlive: this.keepAlive,
maxSockets: this.maxConnections,
timeout: this.timeout
});
this.httpsAgent = new require('https').Agent({
keepAlive: this.keepAlive,
maxSockets: this.maxConnections,
timeout: this.timeout
});
this.setupAxios();
}
setupAxios() {
axios.defaults.httpAgent = this.httpAgent;
axios.defaults.httpsAgent = this.httpsAgent;
// Configurar timeouts globais
axios.defaults.timeout = this.timeout;
console.log(`🔌 Connection pool configurado: ${this.maxConnections} conexões máximas`);
}
getStats() {
return {
httpSockets: this.httpAgent.sockets,
httpRequests: this.httpAgent.requests,
httpsSocket: this.httpsAgent.sockets,
httpsRequests: this.httpsAgent.requests
};
}
}
const connectionPool = new ConnectionPool({
maxConnections: 15,
timeout: 10000,
keepAlive: true
});
Monitoramento e Observabilidade
1. Métricas Detalhadas
Copy
class MetricsCollector {
constructor() {
this.metrics = {
// Contadores
requests_total: 0,
requests_success: 0,
requests_error: 0,
cache_hits: 0,
cache_misses: 0,
// Histogramas
response_times: [],
batch_sizes: [],
// Gauges
active_connections: 0,
queue_size: 0,
// Por endpoint
endpoints: {},
// Por código de erro
error_codes: {},
// Por horário
hourly_stats: {}
};
this.startTime = Date.now();
this.lastReport = Date.now();
}
recordRequest(endpoint, success, responseTime, statusCode, batchSize = 1) {
this.metrics.requests_total++;
if (success) {
this.metrics.requests_success++;
} else {
this.metrics.requests_error++;
// Contar por código de erro
this.metrics.error_codes[statusCode] =
(this.metrics.error_codes[statusCode] || 0) + 1;
}
// Registrar por endpoint
if (!this.metrics.endpoints[endpoint]) {
this.metrics.endpoints[endpoint] = {
total: 0,
success: 0,
error: 0,
avg_response_time: 0,
response_times: []
};
}
const endpointMetrics = this.metrics.endpoints[endpoint];
endpointMetrics.total++;
if (success) {
endpointMetrics.success++;
} else {
endpointMetrics.error++;
}
// Response times
this.metrics.response_times.push(responseTime);
endpointMetrics.response_times.push(responseTime);
// Manter apenas os últimos 1000 tempos de resposta
if (this.metrics.response_times.length > 1000) {
this.metrics.response_times.shift();
}
if (endpointMetrics.response_times.length > 100) {
endpointMetrics.response_times.shift();
}
// Batch sizes
this.metrics.batch_sizes.push(batchSize);
if (this.metrics.batch_sizes.length > 1000) {
this.metrics.batch_sizes.shift();
}
// Estatísticas horárias
const hour = new Date().getHours();
if (!this.metrics.hourly_stats[hour]) {
this.metrics.hourly_stats[hour] = { requests: 0, errors: 0 };
}
this.metrics.hourly_stats[hour].requests++;
if (!success) {
this.metrics.hourly_stats[hour].errors++;
}
// Atualizar médias
this.updateAverages();
}
recordCacheHit() {
this.metrics.cache_hits++;
}
recordCacheMiss() {
this.metrics.cache_misses++;
}
updateAverages() {
// Calcular médias globais
if (this.metrics.response_times.length > 0) {
this.metrics.avg_response_time =
this.metrics.response_times.reduce((a, b) => a + b, 0) /
this.metrics.response_times.length;
}
// Calcular médias por endpoint
Object.values(this.metrics.endpoints).forEach(endpoint => {
if (endpoint.response_times.length > 0) {
endpoint.avg_response_time =
endpoint.response_times.reduce((a, b) => a + b, 0) /
endpoint.response_times.length;
}
});
}
getReport() {
const now = Date.now();
const uptime = now - this.startTime;
const timeSinceLastReport = now - this.lastReport;
const report = {
timestamp: new Date().toISOString(),
uptime_seconds: Math.floor(uptime / 1000),
period_seconds: Math.floor(timeSinceLastReport / 1000),
// Taxas
success_rate: this.metrics.requests_total > 0 ?
(this.metrics.requests_success / this.metrics.requests_total * 100).toFixed(2) + '%' : '0%',
cache_hit_rate: (this.metrics.cache_hits + this.metrics.cache_misses) > 0 ?
(this.metrics.cache_hits / (this.metrics.cache_hits + this.metrics.cache_misses) * 100).toFixed(2) + '%' : '0%',
requests_per_second: timeSinceLastReport > 0 ?
(this.metrics.requests_total / (timeSinceLastReport / 1000)).toFixed(2) : '0',
// Performance
avg_response_time: this.metrics.avg_response_time?.toFixed(2) + 'ms' || '0ms',
p95_response_time: this.calculatePercentile(this.metrics.response_times, 95)?.toFixed(2) + 'ms' || '0ms',
max_response_time: this.metrics.response_times.length > 0 ?
Math.max(...this.metrics.response_times) + 'ms' : '0ms',
// Contadores
total_requests: this.metrics.requests_total,
successful_requests: this.metrics.requests_success,
failed_requests: this.metrics.requests_error,
cache_hits: this.metrics.cache_hits,
cache_misses: this.metrics.cache_misses,
// Detalhes
endpoints: Object.fromEntries(
Object.entries(this.metrics.endpoints).map(([endpoint, stats]) => [
endpoint,
{
total: stats.total,
success_rate: stats.total > 0 ?
(stats.success / stats.total * 100).toFixed(2) + '%' : '0%',
avg_response_time: stats.avg_response_time?.toFixed(2) + 'ms' || '0ms'
}
])
),
error_codes: this.metrics.error_codes,
hourly_distribution: this.metrics.hourly_stats
};
this.lastReport = now;
return report;
}
calculatePercentile(values, percentile) {
if (values.length === 0) return 0;
const sorted = [...values].sort((a, b) => a - b);
const index = Math.ceil((percentile / 100) * sorted.length) - 1;
return sorted[index];
}
reset() {
this.metrics = {
requests_total: 0,
requests_success: 0,
requests_error: 0,
cache_hits: 0,
cache_misses: 0,
response_times: [],
batch_sizes: [],
active_connections: 0,
queue_size: 0,
endpoints: {},
error_codes: {},
hourly_stats: {}
};
this.startTime = Date.now();
this.lastReport = Date.now();
}
}
const metricsCollector = new MetricsCollector();
// Relatório periódico
setInterval(() => {
const report = metricsCollector.getReport();
console.log('📊 Relatório de métricas:', JSON.stringify(report, null, 2));
// Enviar para sistema de monitoramento
// sendToDataDog(report);
// sendToPrometheus(report);
}, 60000); // A cada 1 minuto
2. Health Checks
Copy
class HealthChecker {
constructor(client) {
this.client = client;
this.checks = new Map();
this.lastResults = new Map();
this.registerChecks();
}
registerChecks() {
this.checks.set('api_connectivity', async () => {
try {
const start = Date.now();
await this.client.buscarCNPJ(['21071712000171']);
const responseTime = Date.now() - start;
return {
status: 'healthy',
responseTime,
message: 'API respondendo normalmente'
};
} catch (error) {
return {
status: 'unhealthy',
error: error.message,
message: 'Falha na conectividade com a API'
};
}
});
this.checks.set('cache_performance', async () => {
const stats = cache.getStats();
const hitRate = parseFloat(stats.hitRate.replace('%', ''));
if (hitRate < 30) {
return {
status: 'degraded',
hitRate: stats.hitRate,
message: 'Taxa de cache hit baixa'
};
}
return {
status: 'healthy',
hitRate: stats.hitRate,
message: 'Cache funcionando adequadamente'
};
});
this.checks.set('rate_limits', async () => {
const metrics = metricsCollector.getReport();
const rps = parseFloat(metrics.requests_per_second);
if (rps > 90) { // Próximo do limite de 100/min
return {
status: 'warning',
requestsPerSecond: rps,
message: 'Próximo do limite de rate limit'
};
}
return {
status: 'healthy',
requestsPerSecond: rps,
message: 'Rate limits sob controle'
};
});
}
async runCheck(checkName) {
const checkFn = this.checks.get(checkName);
if (!checkFn) {
throw new Error(`Health check '${checkName}' não encontrado`);
}
try {
const result = await checkFn();
this.lastResults.set(checkName, {
...result,
timestamp: new Date().toISOString(),
checkName
});
return this.lastResults.get(checkName);
} catch (error) {
const result = {
status: 'error',
error: error.message,
timestamp: new Date().toISOString(),
checkName
};
this.lastResults.set(checkName, result);
return result;
}
}
async runAllChecks() {
const results = {};
for (const checkName of this.checks.keys()) {
results[checkName] = await this.runCheck(checkName);
}
const overallStatus = this.calculateOverallStatus(results);
return {
status: overallStatus,
timestamp: new Date().toISOString(),
checks: results
};
}
calculateOverallStatus(results) {
const statuses = Object.values(results).map(r => r.status);
if (statuses.includes('unhealthy') || statuses.includes('error')) {
return 'unhealthy';
} else if (statuses.includes('degraded') || statuses.includes('warning')) {
return 'degraded';
} else {
return 'healthy';
}
}
getLastResults() {
return Object.fromEntries(this.lastResults);
}
}
const healthChecker = new HealthChecker(client);
// Health check periódico
setInterval(async () => {
const health = await healthChecker.runAllChecks();
if (health.status !== 'healthy') {
console.warn('⚠️ Health check falhou:', health);
// Enviar alerta
// sendAlert(health);
}
}, 30000); // A cada 30 segundos
Otimização de Custos
1. Estratégia de Cache por Prioridade
Copy
class CostOptimizedCache extends IntelligentCache {
constructor(options = {}) {
super(options);
// Definir custos por operação (em créditos)
this.operationCosts = {
'cnpj': 1,
'validation': 1,
'leads': 5
};
// TTLs otimizados para economizar créditos
this.ttlByType = {
'cnpj': 7 * 24 * 60 * 60 * 1000, // 7 dias - dados mudam pouco
'validation': 24 * 60 * 60 * 1000, // 24 horas - validações são estáveis
'leads': 6 * 60 * 60 * 1000 // 6 horas - leads podem mudar
};
this.creditsSaved = 0;
}
get(type, identifier) {
const result = super.get(type, identifier);
if (result) {
// Calcular créditos economizados
const cost = this.operationCosts[type] || 1;
this.creditsSaved += cost;
console.debug(`💰 Créditos economizados: ${cost} (total: ${this.creditsSaved})`);
}
return result;
}
getCostReport() {
const stats = this.getStats();
const avgCostPerOperation = 1.5; // Custo médio estimado
return {
...stats,
creditsEstimated: this.creditsSaved,
costSavingsEstimated: this.creditsSaved * 0.05, // R$ 0,05 por crédito
hitRateImpact: `${((this.creditsSaved / (stats.totalRequests * avgCostPerOperation)) * 100).toFixed(1)}% de economia`
};
}
}
const costOptimizedCache = new CostOptimizedCache({
maxSize: 10000,
defaultTTL: 24 * 60 * 60 * 1000 // 24 horas padrão
});
2. Rate Limiting Inteligente
Copy
class IntelligentRateLimiter {
constructor(options = {}) {
this.limits = {
peak_hours: { // 9h-18h
requests_per_minute: 80, // 80% do limite para dar margem
concurrent_requests: 5
},
off_hours: { // Demais horários
requests_per_minute: 100,
concurrent_requests: 8
}
};
this.queue = [];
this.activeRequests = 0;
this.requestHistory = [];
}
isPeakHour() {
const hour = new Date().getHours();
return hour >= 9 && hour <= 18;
}
getCurrentLimits() {
return this.isPeakHour() ? this.limits.peak_hours : this.limits.off_hours;
}
async acquire() {
return new Promise((resolve, reject) => {
this.queue.push({ resolve, reject, timestamp: Date.now() });
this.processQueue();
});
}
release() {
this.activeRequests--;
this.processQueue();
}
processQueue() {
const limits = this.getCurrentLimits();
// Verificar limites de concorrência
if (this.activeRequests >= limits.concurrent_requests) {
return;
}
// Verificar rate limit por minuto
const now = Date.now();
const oneMinuteAgo = now - 60000;
this.requestHistory = this.requestHistory.filter(time => time > oneMinuteAgo);
if (this.requestHistory.length >= limits.requests_per_minute) {
// Reagendar para quando o limite resetar
const oldestRequest = Math.min(...this.requestHistory);
const waitTime = 60000 - (now - oldestRequest);
setTimeout(() => this.processQueue(), waitTime);
return;
}
// Processar próxima requisição na fila
if (this.queue.length > 0) {
const { resolve } = this.queue.shift();
this.activeRequests++;
this.requestHistory.push(now);
resolve();
// Continuar processando se houver mais na fila
if (this.queue.length > 0) {
setImmediate(() => this.processQueue());
}
}
}
getStatus() {
const limits = this.getCurrentLimits();
return {
activeRequests: this.activeRequests,
queueSize: this.queue.length,
currentLimits: limits,
isPeakHour: this.isPeakHour(),
requestsLastMinute: this.requestHistory.length
};
}
}
const rateLimiter = new IntelligentRateLimiter();
3. Otimização de Consultas
Copy
class QueryOptimizer {
constructor() {
this.queryPatterns = new Map();
this.optimizationRules = [
this.deduplicateRule,
this.batchingRule,
this.cachingRule,
this.priorityRule
];
}
async optimizeQuery(operation, params) {
let optimizedParams = { ...params };
// Aplicar regras de otimização
for (const rule of this.optimizationRules) {
optimizedParams = await rule.call(this, operation, optimizedParams);
}
return optimizedParams;
}
deduplicateRule(operation, params) {
if (operation === 'buscar_cnpj' && Array.isArray(params.cnpjs)) {
// Remover duplicatas
const uniqueCNPJs = [...new Set(params.cnpjs)];
if (uniqueCNPJs.length !== params.cnpjs.length) {
console.log(`🔧 Otimização: Removidas ${params.cnpjs.length - uniqueCNPJs.length} duplicatas`);
}
return { ...params, cnpjs: uniqueCNPJs };
}
return params;
}
batchingRule(operation, params) {
if (operation === 'buscar_cnpj' && Array.isArray(params.cnpjs)) {
// Otimizar tamanho do lote baseado no cache
const uncachedCNPJs = params.cnpjs.filter(cnpj => !cache.has('cnpj', cnpj));
if (uncachedCNPJs.length < params.cnpjs.length) {
console.log(`🔧 Otimização: ${params.cnpjs.length - uncachedCNPJs.length} CNPJs encontrados em cache`);
return { ...params, cnpjs: uncachedCNPJs };
}
}
return params;
}
cachingRule(operation, params) {
// Regra de cache já implementada em outras partes
return params;
}
priorityRule(operation, params) {
if (operation === 'validacao' && Array.isArray(params.data)) {
// Priorizar validações mais importantes
const prioritized = params.data.sort((a, b) => {
const priorityA = this.getValidationPriority(a.type);
const priorityB = this.getValidationPriority(b.type);
return priorityB - priorityA;
});
return { ...params, data: prioritized };
}
return params;
}
getValidationPriority(type) {
const priorities = { 'cnpj': 3, 'email': 2, 'phone': 1 };
return priorities[type] || 0;
}
}
const queryOptimizer = new QueryOptimizer();
Implementação em Produção
1. Cliente de Produção Completo
Copy
class ProductionSpeedioClient {
constructor(options = {}) {
// Validar configuração
this.validateConfig(options);
// Inicializar componentes
this.credentialManager = new CredentialManager();
this.cache = new CostOptimizedCache(options.cache);
this.rateLimiter = new IntelligentRateLimiter(options.rateLimiting);
this.retryManager = new RetryManager(options.retry);
this.circuitBreaker = new CircuitBreaker(options.circuitBreaker);
this.metricsCollector = new MetricsCollector();
this.healthChecker = new HealthChecker(this);
this.queryOptimizer = new QueryOptimizer();
this.connectionPool = new ConnectionPool(options.connectionPool);
// Configuração
this.baseUrl = options.baseUrl || 'https://api-get-leads.speedio.com.br';
this.defaultTimeout = options.timeout || 10000;
this.startBackgroundTasks();
}
validateConfig(options) {
if (!process.env.SPEEDIO_USERNAME || !process.env.SPEEDIO_PASSWORD) {
throw new Error('Credenciais SPEEDIO_USERNAME e SPEEDIO_PASSWORD são obrigatórias');
}
if (process.env.NODE_ENV === 'production') {
if (!options.monitoring?.endpoint) {
console.warn('⚠️ Endpoint de monitoramento não configurado para produção');
}
}
}
async buscarCNPJ(cnpjs, options = {}) {
// Otimizar query
const optimizedParams = await this.queryOptimizer.optimizeQuery('buscar_cnpj', { cnpjs });
if (optimizedParams.cnpjs.length === 0) {
console.log('✅ Todos os CNPJs encontrados em cache');
return cnpjs.map(cnpj => this.cache.get('cnpj', cnpj)).filter(Boolean);
}
return await this.executeRequest(
'buscar_cnpj',
async () => {
await this.rateLimiter.acquire();
try {
const response = await axios.get(`${this.baseUrl}/search_enriched_leads/cnpj`, {
params: { cnpjs: JSON.stringify(optimizedParams.cnpjs) },
headers: await this.getHeaders(),
timeout: options.timeout || this.defaultTimeout
});
// Cachear resultados
response.data.forEach(empresa => {
if (empresa && empresa.cnpj) {
this.cache.set('cnpj', empresa.cnpj, empresa);
}
});
return response.data;
} finally {
this.rateLimiter.release();
}
},
{ cnpjs: optimizedParams.cnpjs, originalCount: cnpjs.length }
);
}
async executeRequest(operation, fn, context = {}) {
const startTime = Date.now();
try {
const result = await this.circuitBreaker.execute(
() => this.retryManager.executeWithRetry(fn)
);
const responseTime = Date.now() - startTime;
this.metricsCollector.recordRequest(operation, true, responseTime, 200, context.originalCount || 1);
return result;
} catch (error) {
const responseTime = Date.now() - startTime;
this.metricsCollector.recordRequest(operation, false, responseTime, error.status || 0, context.originalCount || 1);
throw error;
}
}
async getHeaders() {
const credentials = await this.credentialManager.getCredentials();
const auth = Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64');
return {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json',
'User-Agent': 'Speedio-Client-Production/1.0',
'X-Client-Version': process.env.npm_package_version || '1.0.0'
};
}
startBackgroundTasks() {
// Health checks
setInterval(async () => {
const health = await this.healthChecker.runAllChecks();
if (health.status !== 'healthy') {
console.warn('⚠️ Health check:', health);
this.sendAlert('health_check_failed', health);
}
}, 60000);
// Relatórios de métricas
setInterval(() => {
const metrics = this.metricsCollector.getReport();
const cacheReport = this.cache.getCostReport();
console.log('📊 Métricas:', metrics);
console.log('💰 Cache:', cacheReport);
this.sendMetrics(metrics, cacheReport);
}, 300000); // A cada 5 minutos
// Limpeza de cache
setInterval(() => {
const sizeBefore = this.cache.getStats().size;
// Implementar limpeza se necessário
console.log(`🧹 Cache cleanup: ${sizeBefore} itens`);
}, 3600000); // A cada 1 hora
}
sendAlert(type, data) {
// Implementar envio de alertas (Slack, PagerDuty, etc.)
console.error(`🚨 ALERTA [${type}]:`, data);
}
sendMetrics(metrics, cacheReport) {
// Implementar envio para sistema de monitoramento
console.log('📈 Enviando métricas para sistema de monitoramento');
}
getStatus() {
return {
client: 'ProductionSpeedioClient',
version: '1.0.0',
uptime: process.uptime(),
memory: process.memoryUsage(),
circuitBreaker: this.circuitBreaker.getStatus(),
rateLimiter: this.rateLimiter.getStatus(),
cache: this.cache.getCostReport(),
health: this.healthChecker.getLastResults(),
metrics: this.metricsCollector.getReport(),
timestamp: new Date().toISOString()
};
}
async shutdown() {
console.log('🔄 Iniciando shutdown graceful...');
// Aguardar requisições ativas terminarem
let attempts = 0;
while (this.rateLimiter.getStatus().activeRequests > 0 && attempts < 30) {
console.log(`⏳ Aguardando ${this.rateLimiter.getStatus().activeRequests} requisições terminarem...`);
await new Promise(resolve => setTimeout(resolve, 1000));
attempts++;
}
// Relatório final
const finalReport = this.getStatus();
console.log('📊 Relatório final:', finalReport);
console.log('✅ Shutdown concluído');
}
}
// Configuração para produção
const prodClient = new ProductionSpeedioClient({
cache: {
maxSize: 50000,
defaultTTL: 24 * 60 * 60 * 1000
},
rateLimiting: {
peak_hours: { requests_per_minute: 80, concurrent_requests: 5 },
off_hours: { requests_per_minute: 100, concurrent_requests: 8 }
},
retry: {
maxRetries: 3,
baseDelay: 1000,
maxDelay: 30000
},
circuitBreaker: {
failureThreshold: 5,
recoveryTimeout: 60000
},
connectionPool: {
maxConnections: 15,
timeout: 10000,
keepAlive: true
},
monitoring: {
endpoint: process.env.MONITORING_ENDPOINT
}
});
// Graceful shutdown
process.on('SIGTERM', async () => {
await prodClient.shutdown();
process.exit(0);
});
process.on('SIGINT', async () => {
await prodClient.shutdown();
process.exit(0);
});
export default prodClient;
Próximos Passos
Documentação de Endpoints
Explore todos os endpoints disponíveis
Tratamento de Erros
Implemente tratamento robusto de erros
Suporte
Entre em contato para dúvidas ou suporte
Dashboard
Monitore seu uso no dashboard
Dica de Produção: Implemente sempre monitoramento robusto, cache inteligente e tratamento de erros em produção. A API Speedio é construída para alta disponibilidade, mas sua aplicação deve estar preparada para cenários de degradação.
