Ir al contenido

Chaos: arnés determinista para escenarios de resiliencia

El módulo Chaos es un pequeño arnés para pruebas de resiliencia. Permite registrar escenarios de inyección de fallos que implementan una interfaz de un solo método, ejecutarlos y recopilar un informe estructurado con resultados pass/fail. Es deliberadamente minimalista —cinco clases— y está pensado para suites de resiliencia y ejercicios de día de caos, no para la ruta de producción de documentos.

Estabilidad: experimental. Esta es una superficie de utilidades de prueba y resiliencia, no una API PDF del núcleo. La SPI es pequeña y estable en su forma, pero el alcance del módulo y los escenarios incluidos evolucionan. No construir flujos de control de producción sobre ella.

Ventana de terminal
composer require nextpdf/core:^3

Una prueba de resiliencia plantea una pregunta: cuando una dependencia falla, ¿el motor se degrada correctamente? El módulo Chaos aporta estructura a esa prueba. ChaosScenarioInterface es el contrato que implementa un escenario: un name() y un simulate() que devuelve un ChaosOutcome. Un escenario encapsula un único fallo (una partición de red, una ráfaga de respuestas 5xx del backend de recuperación) e informa de lo ocurrido.

ChaosScenarioRunner es el orquestador. Se registran escenarios con register(), se llama a run() para ejecutarlos secuencialmente en orden de registro y se consulta el agregado resultante: outcomes(), allPassed(), passCount(), failCount(). El ejecutor nunca lanza una excepción ante el fallo de un escenario: un fallo es un dato capturado en un ChaosOutcome, no una excepción. Solo lanza una excepción cuando falla su propia infraestructura: un registro de escenario no válido, o la imposibilidad de escribir el archivo de informe (ChaosReportWriteException). Un escenario que no puede alcanzar el recurso que está probando expone una RetrievalUnavailableException. Todo el módulo es @since 3.2.0.

ChaosOutcome es el resultado de cada escenario: pass/fail, una duración y un toArray() para el informe estructurado. Como un resultado registra la duración de reloj de pared, el perfil de reproducibilidad del informe es structural, no bitwise.

TipoMiembros claveFunción
ChaosScenarioInterfacename(): string, simulate(): ChaosOutcomeEl contrato del escenario (@since 3.2.0)
ChaosScenarioRunnerregister(), run(), outcomes(), allPassed(), passCount(), failCount()Orquestador secuencial de escenarios (@since 3.2.0)
ChaosOutcomedurationSeconds(), toArray()Resultado pass/fail por escenario (@since 3.2.0)
RetrievalUnavailableExceptionNo se pudo alcanzar un recurso probado
ChaosReportWriteExceptionNo se pudo escribir el archivo de informe

Ejecutar composer docs:generate-api-php -- --module=Chaos para obtener la tabla completa de PHPDoc.

Registrar un escenario y ejecutar la suite.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Chaos\ChaosOutcome;
use NextPDF\Chaos\ChaosScenarioInterface;
use NextPDF\Chaos\ChaosScenarioRunner;
final class TimeoutScenario implements ChaosScenarioInterface
{
public function name(): string
{
return 'dependency-timeout';
}
public function simulate(): ChaosOutcome
{
// Inject the fault, observe the engine's degradation, return the verdict.
return new ChaosOutcome(/* name, passed, durationSeconds, details */);
}
}
$runner = new ChaosScenarioRunner();
$runner->register(new TimeoutScenario());
$runner->run();
echo $runner->allPassed() ? "Resilient.\n" : "{$runner->failCount()} scenario(s) failed.\n";

Controlar el arnés desde un trabajo de resiliencia y tratar cualquier fallo como una salida distinta de cero, sin dejar que el fallo de un escenario se propague como una excepción.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Chaos\ChaosScenarioRunner;
use NextPDF\Chaos\Exception\ChaosReportWriteException;
use Psr\Log\LoggerInterface;
final readonly class ChaosJob
{
/** @param list<\NextPDF\Chaos\ChaosScenarioInterface> $scenarios */
public function __construct(
private array $scenarios,
private LoggerInterface $logger,
) {}
public function run(string $reportPath): int
{
$runner = new ChaosScenarioRunner();
foreach ($this->scenarios as $scenario) {
$runner->register($scenario);
}
$runner->run(); // never throws on scenario failure
try {
$runner->writeReport($reportPath);
} catch (ChaosReportWriteException $e) {
$this->logger->error('Chaos report could not be written.', ['error' => $e->getMessage()]);
return 2;
}
return $runner->allPassed() ? 0 : 1;
}
}
  • run() nunca lanza una excepción porque un escenario haya fallado. Un fallo reside en ChaosOutcome. Si se envuelve run() en un try/catch con la expectativa de capturar allí los fallos, no aparecerán: leer failCount() / allPassed() en su lugar.
  • El ejecutor lanza una excepción solo ante fallos de infraestructura: un registro incorrecto, o una ChaosReportWriteException cuando la ruta del informe no se puede escribir. Gestionarlos de forma diferenciada de los resultados de los escenarios.
  • Los escenarios se ejecutan secuencialmente en orden de registro. No hay paralelismo. El orden puede importar si los escenarios comparten estado externo.
  • Este módulo es para pruebas de resiliencia. No importar el ejecutor en la ruta de producción de documentos como mecanismo de control.

La sobrecarga del ejecutor es insignificante. El coste está en lo que hagan los escenarios. Como los escenarios inyectan fallos y pueden esperar a que se agoten tiempos de espera, una ejecución de caos puede ser lenta por diseño. El performance_budget aquí es la cifra de referencia del motor, no un límite para la duración del escenario. El perfil de reproducibilidad es structural: el informe registra duraciones de reloj de pared, por lo que dos ejecuciones difieren en esos campos.

Los escenarios inyectan fallos y pueden ejercitar rutas de fallo en las dependencias. Ejecutar el arnés únicamente en un entorno de prueba o de preproducción con credenciales y endpoints acotados a ese entorno; nunca contra sistemas de producción. El informe puede contener detalles de diagnóstico sobre los modos de fallo. Tratarlo como información interna y aplicar el requisito del proyecto sobre depuración de registros antes de compartirlo. Consultar el modelo de amenazas del motor en /modules/core/security/.

Este módulo no formula ninguna afirmación normativa sobre la especificación PDF. Es una utilidad de resiliencia. No implementa ningún protocolo estandarizado cuyas cláusulas deban citarse. La conformidad del motor se valida mediante las suites de oráculo y de referencia (golden) descritas en /modules/core/conformance/.