Appearance
Handlers: Implementación de Nuevos Jobs
◄ Anterior: API Endpoints | Índice | Siguiente: Multi-Tenant ►
Tabla de Contenidos
- Interface JobHandlerInterface
- Ejemplo: BatchInvoicingJobHandler
- Patrón Wrapper
- Guía para Agregar Nuevos Handlers
Interface JobHandlerInterface
Ubicación: Core/Interfaces/JobHandlerInterface.php
Namespace: App\Core\Interfaces
Propósito: Contrato que deben cumplir todos los handlers de jobs
Contrato:
php
interface JobHandlerInterface
{
/**
* Obtener tipo de job que maneja este handler
*
* @return string Tipo de job (ej: 'batch_invoicing')
*/
public function getType(): string;
/**
* Ejecutar job con payload dado
*
* @param array $payload Datos necesarios para ejecutar
* @return array Resultado del job
* @throws Exception Si falla la ejecución
*/
public function handle(array $payload): array;
}Ejemplo: BatchInvoicingJobHandler
Ubicación: Ventas/Handlers/BatchInvoicingJobHandler.php
Namespace: App\Ventas\Handlers
Propósito: Handler para facturación masiva (ejemplo de implementación)
Type: 'batch_invoicing'
Payload esperado:
php
[
'cliente_ids' => [1, 2, 3, 4, 5],
'fecha' => '2026-02-05',
'concepto' => 'Facturación mensual',
'monto_base' => 1000.00
]Result retornado:
php
[
'facturas_creadas' => 5,
'monto_total' => 5000.00,
'factura_ids' => [101, 102, 103, 104, 105],
'errores' => [] // Clientes que fallaron
]Implementación:
php
class BatchInvoicingJobHandler implements JobHandlerInterface
{
private FacturaService $facturaService;
public function __construct(FacturaService $facturaService)
{
$this->facturaService = $facturaService;
}
public function getType(): string
{
return 'batch_invoicing';
}
public function handle(array $payload): array
{
// 1. Validar payload
$this->validatePayload($payload);
// 2. Extraer datos
$clienteIds = $payload['cliente_ids'];
$fecha = $payload['fecha'];
$concepto = $payload['concepto'];
$montoBase = $payload['monto_base'];
// 3. Procesar batch
$facturasCreadas = [];
$errores = [];
foreach ($clienteIds as $clienteId) {
try {
// 4. Reconstruir DTO que espera FacturaService::insert()
$facturaDTO = new CreateFacturaDTO(
cliente_id: $clienteId,
fecha: $fecha,
items: [
[
'concepto' => $concepto,
'monto' => $montoBase
]
]
);
// 5. Delegar a service existente (NO modificado)
$factura = $this->facturaService->insert($facturaDTO);
$facturasCreadas[] = $factura->id;
} catch (Exception $e) {
$errores[] = [
'cliente_id' => $clienteId,
'error' => $e->getMessage()
];
}
}
// 6. Retornar resultado consolidado
return [
'facturas_creadas' => count($facturasCreadas),
'monto_total' => $montoBase * count($facturasCreadas),
'factura_ids' => $facturasCreadas,
'errores' => $errores
];
}
private function validatePayload(array $payload): void
{
$required = ['cliente_ids', 'fecha', 'concepto', 'monto_base'];
foreach ($required as $field) {
if (!isset($payload[$field])) {
throw new InvalidArgumentException("Campo requerido: {$field}");
}
}
}
}Patrón Wrapper
Concepto: El handler NO modifica servicios existentes. En su lugar, envuelve (wraps) el service existente.
Puntos clave:
- NO modifica service existente:
FacturaServicequeda intacto - Reconstruye request DTO: Crea el mismo DTO que usaría el controller síncrono
- Delega lógica compleja: El service hace TODO el trabajo (validaciones, transacciones, etc.)
- Acumula resultados: Handler solo itera y consolida
- Manejo de errores: Captura exceptions por item, NO falla todo el batch
Ventajas del patrón:
- ✅ CERO impacto en código existente
- ✅ Service puede usarse sincrónicamente (original) o asincrónicamente (via handler)
- ✅ Feature flag controlado (rollback instantáneo)
- ✅ Fácil testing (unit tests del handler con mock del service)
Flujo visual:
Controller (síncrono) JobHandler (asíncrono)
↓ ↓
CreateDTO CreateDTO (x N)
↓ ↓
Service::insert() ← SHARED → Service::insert() (x N)
↓ ↓
Return DTO Accumulate resultsGuía para Agregar Nuevos Handlers
Paso 1: Crear Clase Handler
Ubicación: {Modulo}/Handlers/{NombreJobHandler}.php
php
namespace App\{Modulo}\Handlers;
use App\Core\Interfaces\JobHandlerInterface;
class {NombreJobHandler} implements JobHandlerInterface
{
public function __construct(
// Inyectar services necesarios
private {RelevantService} $service
) {}
public function getType(): string
{
return '{tipo_job}'; // Ej: 'generate_report'
}
public function handle(array $payload): array
{
// 1. Validar payload
// 2. Extraer datos
// 3. Procesar (delegar a service)
// 4. Retornar resultado
}
}Paso 2: Registrar Handler en DI Container
Ubicación: config/dependencies.php
php
$container->set(JobExecutor::class, function (ContainerInterface $c) {
$executor = new JobExecutor(
$c->get(JobRepository::class),
$c->get(NotificationRepository::class),
$c->get(ConnectionManager::class)
);
// Registrar handlers existentes
$executor->registerHandler($c->get(BatchInvoicingJobHandler::class));
// Registrar NUEVO handler
$executor->registerHandler($c->get({NombreJobHandler}::class));
return $executor;
});Paso 3: Testing
Ubicación: Tests/Unit/{Modulo}/{NombreJobHandler}Test.php
php
public function testHandleProcessesPayloadCorrectly(): void
{
// Arrange
$mockService = $this->createMock({RelevantService}::class);
$mockService->expects($this->once())
->method('processItem')
->willReturn($expectedResult);
$handler = new {NombreJobHandler}($mockService);
$payload = ['test' => 'data'];
// Act
$result = $handler->handle($payload);
// Assert
$this->assertEquals($expectedResult, $result);
}Paso 4: Documentar Tipo de Job
Agregar a: docs/backend/background-jobs-handlers.md
markdown
### {tipo_job}
**Handler**: `{NombreJobHandler}`
**Payload**:
- `campo1` (tipo): Descripción
- `campo2` (tipo): Descripción
**Result**:
- `resultado1` (tipo): Descripción
- `resultado2` (tipo): Descripción
**Ejemplo**:
POST /api/jobs/{tipo_job}
{
"payload": {
"campo1": "valor"
}
}Checklist de Implementación
- [ ] Crear clase handler que implemente
JobHandlerInterface - [ ] Inyectar services necesarios via constructor
- [ ] Implementar
getType()retornando tipo único - [ ] Implementar
handle(array $payload): array - [ ] Validar payload con método privado
- [ ] Delegar procesamiento a service existente (patrón wrapper)
- [ ] Acumular resultados y errores
- [ ] Retornar array con resultado consolidado
- [ ] Registrar handler en
JobExecutor(dependencies.php) - [ ] Crear unit test mockeando service
- [ ] Documentar tipo de job con payload y result
- [ ] Probar flujo end-to-end (dispatch → execute → notify)
◄ Anterior: API Endpoints | Índice | Siguiente: Multi-Tenant ►