تخطَّ إلى المحتوى

Chaos: منظومة حتمية لسيناريوهات المرونة

وحدة ⁨Chaos⁩ هي منظومة مدمجة لاختبار المرونة. تسجِّل سيناريوهات حقن الأعطال التي تنفِّذ واجهة تضم طريقة واحدة، ثم تُشغِّلها وتجمع تقرير ⁨pass/fail⁩ منظَّمًا. وتبقى صغيرة عمدًا، بخمس فئات، ومكانها الطبيعي هو مجموعات اختبار المرونة وتمارين يوم الفوضى، لا مسار إنتاج المستندات.

الاستقرار: تجريبي. هذه أداة للاختبار والمرونة، وليست واجهة ⁨API⁩ أساسية لتنسيق مستندات ⁨PDF⁩. واجهة موفّر الخدمة (⁨SPI⁩) صغيرة وذات شكل مستقر، لكن نطاق الوحدة والسيناريوهات المرفقة بها يتطوران. لا تَبْنِ تدفق تحكم إنتاجي يعتمد عليها.

Terminal window
composer require nextpdf/core:^3

يتحقق اختبار المرونة من أن المحرك يتدهور على نحو صحيح عند فشل إحدى الاعتماديات. تمنح وحدة ⁨Chaos⁩ هذا الاختبار بنية واضحة. ChaosScenarioInterface هي عقد السيناريو: تحدِّد name() هوية السيناريو، وتُعيد simulate() كائن ChaosOutcome. يغلِّف كل سيناريو عطلًا واحدًا، مثل انقسام الشبكة أو دفقة من استجابات 5⁨xx⁩ من واجهة الاسترجاع الخلفية، ويُبلِغ بما حدث.

ChaosScenarioRunner يُنسِّق التشغيل. سجِّل السيناريوهات عبر register()، واستدعِ run() لتنفيذها بالتسلسل وفق ترتيب التسجيل، ثم اقرأ الإجماليات عبر outcomes() وallPassed() وpassCount() وfailCount(). لا يرمي المُشغِّل استثناءً مطلقًا عند فشل سيناريو: الفشل بيانات مُلتقَطة في كائن ChaosOutcome، لا استثناء. ولا يرمي استثناءً إلا عند تعطُّل بنيته التحتية ذاتها، مثل تسجيل سيناريو غير صالح أو تعذُّر كتابة ملف التقرير ‏(ChaosReportWriteException). السيناريو الذي يتعذَّر عليه الوصول إلى المورد الذي يختبره يُظهِر RetrievalUnavailableException. الوحدة @since 3.2.0.

ChaosOutcome يخزِّن نتيجة كل سيناريو: حالة ⁨pass/fail⁩، والمدة، ومُخرَج toArray() للتقرير المنظَّم. ولأن النتيجة تسجِّل مدة زمن الساعة الفعلي، فإن سمة قابلية إعادة إنتاج التقرير هي structural، لا bitwise.

النوعالأعضاء الرئيسيونالدور
ChaosScenarioInterfacename(): string، simulate(): ChaosOutcomeعقد السيناريو ‏(@since 3.2.0)
ChaosScenarioRunnerregister()، run()، outcomes()، allPassed()، passCount()، failCount()مُنسِّق سيناريوهات تسلسلي ‏(@since 3.2.0)
ChaosOutcomedurationSeconds()، toArray()نتيجة ⁨pass/fail⁩ لكل سيناريو ‏(@since 3.2.0)
RetrievalUnavailableExceptionتعذَّر الوصول إلى مورد قيد الاختبار
ChaosReportWriteExceptionتعذَّرت كتابة ملف التقرير

شغِّل composer docs:generate-api-php -- --module=Chaos لتوليد جدول ⁨PHPDoc⁩ الكامل.

سجِّل سيناريو، ثم شغِّل المجموعة.

<?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";

أدِر المنظومة من مهمة مرونة، وأرجِع رمز خروج غير صفري لأي فشل، دون السماح بأن يظهر فشل سيناريو في هيئة استثناء.

<?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() لا يرمي استثناءً مطلقًا بسبب فشل سيناريو. الفشل مُسجَّل في ChaosOutcome. إذا أحطت run() بـ ⁨try/catch⁩ متوقعًا الأعطال هناك، فلن ترصدها. اقرأ failCount() / allPassed() بدلًا من ذلك.
  • لا يرمي المُشغِّل استثناءً إلا عند أعطال البنية التحتية: تسجيل غير صالح، أو ChaosReportWriteException عندما تتعذَّر الكتابة في مسار التقرير. عالِج تلك الأعطال على نحو منفصل عن نتائج السيناريوهات.
  • تُشغَّل السيناريوهات بالتسلسل وفق ترتيب التسجيل. لا توجد معالجة متوازية. قد يكون للترتيب أهمية عندما تتشارك السيناريوهات حالة خارجية.
  • هذه الوحدة مخصَّصة لاختبار المرونة. لا تستورد المُشغِّل إلى مسار إنتاج المستندات بوصفه آلية تحكُّم.

يضيف المُشغِّل عبئًا ضئيلًا. سلوك السيناريو هو ما يحدِّد التكلفة. ولأن السيناريوهات تحقن الأعطال وقد تنتظر انتهاء المهل الزمنية، فقد يكون تشغيل الفوضى بطيئًا بحكم التصميم. performance_budget هنا هو الرقم المرجعي للمحرك، لا حدًّا لمدة السيناريو. سمة قابلية إعادة الإنتاج هي structural: يسجِّل التقرير مدد زمن الساعة الفعلي، لذا تختلف تلك الحقول بين التشغيلات.

تحقن السيناريوهات الأعطال وقد تختبر مسارات الفشل في الاعتماديات. شغِّل المنظومة في بيئة اختبار أو تجهيز فقط، ببيانات اعتماد ونقاط نهاية مقيَّدة بتلك البيئة. لا تشغِّلها مطلقًا على أنظمة الإنتاج. قد يحتوي التقرير على تفاصيل تشخيصية عن أنماط الفشل. تعامل معه باعتباره داخليًا، وطبِّق التزام المشروع بتنقية السجلّات قبل مشاركته. راجع نموذج تهديد المحرك في /modules/core/security/.

لا تقدِّم هذه الوحدة أي ادعاء معياري بشأن مواصفة ⁨PDF.⁩ إنها أداة مرونة. ولا تنفِّذ أي بروتوكول معياري يجب الاستشهاد ببنوده. يتولى الأوراكل والمجموعات الذهبية الموصوفة في /modules/core/conformance/ التحقق من مطابقة المحرك.