Estándares de Implementación de Jobs de Sincronización¶
Este documento define los criterios obligatorios para implementar jobs de sincronización en el sistema integrador.
Checklist Obligatorio¶
Todo job de sincronización DEBE cumplir con:
| # | Requisito | Descripción |
|---|---|---|
| 1 | $tries |
Definir número máximo de reintentos (recomendado: 3) |
| 2 | $backoff |
Definir delay entre reintentos en segundos (recomendado: 15-60) |
| 3 | TracksProductSyncFailures |
Usar el trait para registro automático de fallos |
| 4 | try/catch con throw | Capturar excepciones, loggear, y RE-LANZAR |
| 5 | withoutRelations() |
Evitar serializar relaciones que pueden ser eliminadas |
| 6 | Logging estructurado | Usar canales específicos con contexto completo |
Plantilla de Job de Sincronización¶
<?php
namespace App\Jobs;
use App\Constants\LogChannels;
use App\Constants\SyncChannels;
use App\Jobs\Concerns\TracksProductSyncFailures;
use App\Models\ProductItem;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class SyncToExampleJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
use TracksProductSyncFailures;
// ============================================================
// CONFIGURACIÓN DE REINTENTOS (OBLIGATORIO)
// ============================================================
/**
* Número máximo de intentos antes de marcar como fallido.
*/
public int $tries = 3;
/**
* Segundos de espera entre reintentos.
* Puede ser array para backoff exponencial: [10, 30, 60]
*/
public int $backoff = 30;
// ============================================================
// PROPIEDADES (evitar serializar relaciones)
// ============================================================
protected ProductItem $productItem;
protected int $productItemId; // ID primitivo como backup
// ============================================================
// CONSTRUCTOR
// ============================================================
public function __construct(ProductItem $productItem)
{
// Guardar modelo SIN relaciones para evitar ModelNotFoundException
$this->productItem = $productItem->withoutRelations();
// Guardar ID primitivo como backup
$this->productItemId = $productItem->id;
}
// ============================================================
// HANDLE (OBLIGATORIO: try/catch con throw)
// ============================================================
public function handle(): void
{
// Refrescar datos al inicio del handle
$this->productItem->refresh();
Log::channel(LogChannels::SYNC_STRATEGY)->info(
"[EXAMPLE] Iniciando sync para SKU {$this->productItem->sku}",
$this->logContext()
);
try {
// === LÓGICA DE SINCRONIZACIÓN ===
$this->performSync();
Log::channel(LogChannels::SYNC_STRATEGY)->info(
"[EXAMPLE] Sync completado para SKU {$this->productItem->sku}",
$this->logContext()
);
} catch (\Exception $e) {
Log::channel(LogChannels::SYNC_STRATEGY)->error(
"[EXAMPLE] Error en sync: {$e->getMessage()}",
$this->logContext() + ['exception' => $e->getMessage()]
);
// CRÍTICO: Siempre relanzar para que el job falle correctamente
// y el trait TracksProductSyncFailures registre el fallo
throw $e;
}
}
// ============================================================
// MÉTODOS REQUERIDOS POR TracksProductSyncFailures
// ============================================================
protected function getProductId(): ?int
{
return $this->productItemId;
}
protected function getSyncChannel(): string
{
return SyncChannels::EXAMPLE; // Usar constante apropiada
}
// ============================================================
// HELPERS PRIVADOS
// ============================================================
private function performSync(): void
{
// Implementar lógica específica de sincronización
}
private function logContext(): array
{
return [
'product_item_id' => $this->productItemId,
'sku' => $this->productItem->sku ?? 'N/A',
'channel' => $this->getSyncChannel(),
];
}
}
Errores Comunes a Evitar¶
❌ NO: Tragar excepciones¶
// MAL - El job se marca como exitoso aunque falló
public function handle(): void
{
try {
$this->doSync();
} catch (\Exception $e) {
Log::error($e->getMessage());
// Falta: throw $e;
}
}
❌ NO: Serializar relaciones¶
// MAL - Si la relación se elimina, el job falla al deserializar
public function __construct(SupplierProduct $product)
{
$this->product = $product; // Puede tener relaciones cargadas
}
❌ NO: Olvidar $tries¶
// MAL - Default es 1, falla sin reintentos
class SyncJob implements ShouldQueue
{
// Falta: public int $tries = 3;
}
❌ NO: Ignorar el método failed()¶
// MAL - Sin TracksProductSyncFailures, los fallos no se registran en ProductSyncLog
class SyncJob implements ShouldQueue
{
// Falta: use TracksProductSyncFailures;
}
Manejo de Errores HTTP Específicos¶
Para jobs que llaman APIs externas, manejar códigos HTTP específicamente:
public function handle(): void
{
try {
$this->callExternalApi();
} catch (ClientException $e) {
$code = $e->getResponse()?->getStatusCode();
// Errores recuperables: re-encolar con delay
if ($code === 429 || ($code >= 500 && $code < 600)) {
Log::warning("[SYNC] HTTP {$code}, re-encolando en 60s");
$this->release(60);
return;
}
// Errores no recuperables: fallar definitivamente
throw $e;
}
}
Constantes de Canal¶
Usar siempre las constantes definidas en App\Constants\SyncChannels:
namespace App\Constants;
class SyncChannels
{
public const WOO = 'woocommerce';
public const TN = 'Tienda Naranja';
public const CONTI = 'contimarket';
public const ALGOLIA = 'algolia reindex';
public const MEDUSA = 'medusa';
}
Jobs Opcionales: ShouldBeUnique¶
Para jobs que no deben duplicarse en la cola:
use Illuminate\Contracts\Queue\ShouldBeUnique;
class EvaluateProductJob implements ShouldQueue, ShouldBeUnique
{
public int $uniqueFor = 120; // Lock por 2 minutos
public function uniqueId(): string
{
return 'evaluate-' . $this->productItemId;
}
}
Verificación de Cumplimiento¶
Antes de hacer merge de un nuevo job o modificación, verificar:
- [ ] ¿Tiene
$triesdefinido? - [ ] ¿Tiene
$backoffdefinido? - [ ] ¿Usa
TracksProductSyncFailurestrait? - [ ] ¿El catch block hace
throw $e? - [ ] ¿Usa
withoutRelations()en constructor? - [ ] ¿Los logs tienen contexto estructurado?
- [ ] ¿Maneja errores HTTP recuperables (429, 5xx)?
Referencias¶
- Laravel Queues - Dealing With Failed Jobs
- Laravel Queues - Job Middleware
- Arquitectura de sincronización
- Backlog de mejoras de sync: working file en
docs/_borradores/(no publicado)