سیستم ComputedValues - راهنمای توسعهدهنده
نمای کلی معماری
سیستم ComputedValues بر اساس اصول معماری رویدادمحور با تفکیک وظایف روشن و پایبندی به اصول SOLID ساخته شده است.
الگوهای طراحی
الگوی استراتژی
حالت پردازش (همزمان/ناهمزمان) در زمان اجرا بر اساس پیکربندی تعیین میشود.
ComputedValueManagerبه استراتژیهای پردازش همزمان یا ناهمزمان واگذار میکند- امکان جابجایی بین پردازش فوری و صفبندی شده بدون تغییر کد
الگوی ناظر
معماری رویدادمحور با استفاده از سیستم رویداد لاراول.
ComputedValueEventSubscriberهم رویدادهای BusinessEvents و هم رویدادهای Eloquent را مشاهده میکند- اتصال شل بین منابع رویداد و بهروزرسانی مقادیر محاسبه شده
الگوی آداپتور
EloquentEventAdapter رویدادهای Eloquent را به EventDTO استاندارد تبدیل میکند.
- رویدادهای مدل Eloquent را به فرمت رویداد یکپارچه تبدیل میکند
- پردازش سازگار را صرفنظر از منبع رویداد امکانپذیر میکند
الگوی متد قالب
ویژگی HasComputedValuesField الگوریتم اسکلتی را تعریف میکند.
- ویژگی عملکرد پایه را با نقاط گسترش ارائه میدهد
- مدلها رفتار را از طریق متد پیکربندی سفارشی میکنند
اصول SOLID
- SRP - مسئولیت واحد
- OCP - باز/بسته
- LSP - جایگزینی لیسکوف
- ISP - تفکیک اینترفیس
- DIP - وارونگی وابستگی
هر کلاس مسئولیت واحد و به خوبی تعریف شده دارد:
- ComputedValueManager: جریان پردازش را هماهنگ میکند
- ComputedValueStorageService: ماندگاری پایگاه داده را مدیریت میکند
- ComputedValueCastingService: تبدیل نوع را مدیریت میکند
- ModelDiscoveryService: پیکربندیها را کشف و نگاشت میکند
- EloquentEventAdapter: رویدادهای Eloquent را به DTO تبدیل میکند
سیستم برای گسترش باز، برای اصلاح بسته است:
- انواع تبدیل جدید از طریق گسترش enum اضافه میشوند
- انواع رویداد جدید از طریق پیکربندی پشتیبانی میشوند
- کنترلکنندههای جدید بدون تغییر سیستم اصلی اضافه میشوند
همه کنترلکنندهها اینترفیس ComputedValueHandler را پیادهسازی میکنند:
- هر کنترلکننده میتواند بدون شکستن سیستم جایگزین شود
- قرارداد سازگار در تمام پیادهسازیهای کنترلکننده
اینترفیسهای کوچک و متمرکز:
HasComputedValues: متد واحد برای پیکربندیComputedValueHandler: متد واحد برای پردازش- هیچ کلاینی مجبور به وابستگی به متدهای استفاده نشده نیست
تزریق وابستگی در سراسر:
- همه سرویسها به انتزاعات (اینترفیسها) وابسته هستند
- وابستگیها از طریق سازنده تزریق میشوند
- آسان برای تست و شبیهسازی
اجزای اصلی
۱. ComputedValueManager
مکان: app/Services/ComputedValues/Services/ComputedValueManager.php
مسئولیتها:
- فرآیند بهروزرسانی مقدار محاسبه شده را هماهنگ میکند
- کلاسهای کنترلکننده را با استفاده از قرارداد نامگذاری تفکیک میکند
- کنترلکنندهها را با تزریق وابستگی اجرا میکند
- به پردازش همزمان یا ناهمزمان بر اساس پیکربندی مسیر میدهد
- متدهای دسترسی به داده را برای مقادیر محاسبه شده ارائه میدهد
الگوریتم تفکیک کنترلکننده
ورودی: ComputedValueEvent, EntityClass
1. استخراج مسیر ماژول از فضای نام کلاس موجودیت
- مثال: App\Modules\CMS\Entities\Post → CMS
- مثال: App\Modules\LMS\Product\Entities\Product → LMS\Product
2. ساخت نام کنترلکننده از کلید مقدار محاسبه شده
- تبدیل snake_case به StudlyCase
- الحاق 'ComputedValueHandler'
- مثال: media_count → MediaCountComputedValueHandler
3. امتحان مسیرها با اولویت:
اولویت ۱: زیرپوشه موجودیت
- App\Modules\{Module}\Services\ComputedValueHandlers\{Entity}\{Handler}
- اجازه نامهای کلیدی یکسان در موجودیتهای مختلف
اولویت ۲: مسیر مستقیم
- App\Modules\{Module}\Services\ComputedValueHandlers\{Handler}
- برای کنترلکنندههای منحصر به فرد
همچنین پشتیبانی میکند:
- App\Core\{Module}\Services\ComputedValueHandlers\{Handler}
- App\Services\ComputedValueHandlers\{Module}\{Handler}
4. بازگرداندن اولین کلاس موجود یا پرتاب استثنا
متدهای کلیدی
| متد | هدف |
|---|---|
processComputedValueUpdate() | نقطه ورودی اصلی برای پردازش |
executeComputedValueHandler() | تفکیک و اجرای کنترلکننده |
buildHandlerClassName() | ساخت نام کلاس کنترلکننده |
extractModulePathFromEntityClass() | تجزیه فضای نام موجودیت |
processSyncUpdate() | مدیریت پردازش همزمان |
processAsyncUpdate() | ارسال کار ناهمزمان |
۲. ComputedValueStorageService
مکان: app/Services/ComputedValues/Services/ComputedValueStorageService.php
مسئولیتها:
- نتایج مقدار محاسبه شده را در پایگاه داده ماندگار میکند
- قفلگذاری خوشبینانه را برای پیشگیری از شرایط رقابتی پیادهسازی میکند
- تراکنشهای پایگاه داده را برای اتمیcity مدیریت میکند
- متدهای بازیابی و پاک کردن داده را ارائه میدهد
ساختار ذخیرهسازی
ستون JSON computed_values داده را در این قالب ذخیره میکند:
{
"media_count": {
"data": 42,
"updated_at": "2024-01-15T10:30:00+00:00"
},
"view_statistics": {
"data": {"total": 1523, "today": 42},
"updated_at": "2024-01-15T10:35:00+00:00"
}
}
پیشگیری از شرایط رقابتی
// الگوریتم قفلگذاری خوشبینانه
1. دریافت داده موجود با برچسب زمانی
2. تجزیه هر دو برچسب زمانی (موجود و فعلی)
3. مقایسه برچسبهای زمانی:
if (existingTimestamp > currentTimestamp) {
// نادیده گرفتن بهروزرسانی - داده جدیدتر وجود دارد
لاگ هشدار با تفاوت زمانی
return
}
4. بهروزرسانی با داده جدید و برچسب زمانی
5. بستهبندی در تراکنش پایگاه داده
هشدارهای شرایط رقابتی در لاگها عادی هستند و نشان میدهند سیستم درست کار میکند. آنها به این معنی هستند که بهروزرسانی جدیدتری از قبل وجود دارد که از بازنویسی داده تازه با داده قدیمی جلوگیری میکند.
متدهای کلیدی
| متد | هدف |
|---|---|
storeComputedValueResults() | متد ذخیرهسازی اصلی با تراکنش |
processStorageTransaction() | پردازش دسته نتایج |
updateEntityComputedValue() | بهروزرسانی موجودیت واحد با قفلگذاری |
getExistingEntities() | بازیابی موجودیتها بر اساس شناسه |
getComputedValueData() | بازیابی مقدار محاسبه شده خاص |
clearComputedValueData() | حذف ورودی مقدار محاسبه شده |
- دادهها به همان شکلی که هستند بدون تبدیل ذخیره میشوند
- تبدیل هنگام خواندن توسط ویژگی HasComputedValuesField اعمال میشود
- از
withBypassedComputedValueProtection()برای بهروزرسانیهای مشروع استفاده میکند - همه عملیات در تراکنشهای پایگاه داده بستهبندی میشوند
۳. ModelDiscoveryService
مکان: app/Services/ComputedValues/Services/ModelDiscoveryService.php
مسئولیتها:
- تمام مدلهایی را که HasComputedValues را پیادهسازی میکنند کشف میکند
- نگاشتهای رویداد به پیکربندی را میسازد
- نتایج کشف را برای عملکرد کش میکند
- هم رویدادهای BusinessEvents و هم رویدادهای Eloquent را مدیریت میکند
الگوریتم کشف
دایرکتوریهای اسکن شده:
app/Models/app/Modules/*/Entities/app/Modules/*/Models/app/Core/*/Entities/app/Core/*/Models/
مدیریت کش
| ویژگی | مقدار |
|---|---|
| کلید کش | computed_values_system:model_discovery |
| TTL | 86400 ثانیه (24 ساعت) |
| درایور کش | درایور کش پیشفرض Laravel |
متدهای کلیدی
| متد | هدف |
|---|---|
discoverModelsWithComputedValues() | متد کشف اصلی |
getDiscoveryWithFallback() | کشف امن با بازگشت |
clearDiscoveryCache() | پاک کردن کش کشف |
findModelsWithComputedValues() | اسکن دایرکتوریها |
scanDirectoryForClasses() | اسکن دایرکتوری واحد |
processModelClass() | پردازش مدل واحد |
resolveEventIdentifier() | تفکیک رویداد به شناسه |
۴. ComputedValueCastingService
مکان: app/Services/ComputedValues/Services/ComputedValueCastingService.php
مسئولیتها:
- دادهها را به انواع مشخص هنگام خواندن تبدیل میکند
- موارد لبه تبدیل نوع را مدیریت میکند
- اجبار نوع هوشمند را ارائه میدهد
- شکستهای تبدیل را با زمینه لاگ میکند
انواع تبدیل پشتیبانی شده
- آرایه
- بولین
- عدد صحیح
- مجموعه
- DateTime
نوع PHP: array
مثالهای تبدیل:
[1, 2, 3] → [1, 2, 3]
'{"a":1}' → ['a' => 1]
stdClass → (array) $object
نوع PHP: bool
تبدیل هوشمند:
"true" → true
"1" → true
"yes" → true
"on" → true
1 → true
0 → false
"false" → false
"no" → false
نوع PHP: int
مثالهای تبدیل:
"123" → 123
true → 1
false → 0
12.7 → 12
نوع PHP: Illuminate\Support\Collection
مثالهای تبدیل:
[1, 2, 3] → collect([1, 2, 3])
'{"a":1}' → collect(['a' => 1])
Collection → عبور مستقیم
نوع PHP: Carbon\Carbon
مثالهای تبدیل:
"2024-01-15" → نمونه Carbon
1705334400 → نمونه Carbon
نمونه Carbon → عبور مستقیم
مدیریت خطا
ComputedValueCastingExceptionرا در صورت شکست پرتاب میکند- زمینه کامل شامل نوع داده و نوع تبدیل را لاگ میکند
- شامل ردیابی پشته برای اشکالزدایی
۵. EloquentEventAdapter
مکان: app/Services/ComputedValues/Services/EloquentEventAdapter.php
مسئولیتها:
- رویدادهای مدل Eloquent را به فرمت EventDTO تبدیل میکند
- payload رویداد جامع را میسازد
- شناسههای همبستگی برای ردیابی تولید میکند
- فیلتر بهروزرسانی شرطی را ارائه میدهد
فرآیند تبدیل
ساختار payload
[
'model_class' => 'App\Modules\CMS\Entities\Post',
'model_id' => 123,
'event_type' => 'updated',
'attributes' => ['id' => 123, 'title' => 'عنوان جدید', ...],
'original' => ['title' => 'عنوان قدیمی', ...], // برای بهروزرسانیها
'changes' => ['title' => 'عنوان جدید'], // برای بهروزرسانیها
'soft_deleted' => false // در صورت وجود
]
متدهای کلیدی
| متد | هدف |
|---|---|
adaptToEventDTO() | متد تبدیل اصلی |
buildEventName() | ساخت نام رویداد |
buildPayload() | ساخت payload جامع |
generateCorrelationId() | ایجاد شناسه همبستگی |
shouldTriggerUpdate() | بررسی بهروزرسانی شرطی |
جریان پردازش رویداد
توسعه کنترلکننده
قالب
final class MediaStatsComputedValueHandler implements ComputedValueHandler
{
public function __construct(
private readonly LoggerInterface $logger
) {}
public function handle(ComputedValueEvent $eventDto): ComputedValueResultCollection
{
$payload = $eventDto->getPayload();
$postId = $payload->get('post_id');
if ($postId === null) {
return new ComputedValueResultCollection([]);
}
$count = MediaLibrary::where('post_id', $postId)->count();
return new ComputedValueResultCollection([
new ComputedValueResult($postId, $count),
]);
}
}
بهترین شیوهها
- تزریق وابستگی
- لاگ جامع
- اجتناب از کوئریهای N+1
- مدیریت موارد لبه
- پردازش دستهای
public function __construct(
private readonly LoggerInterface $logger,
private readonly SomeRepository $repository
) {}
$this->logger->error('کنترلکننده شکست خورد', [
'entity_id' => $entityId,
'exception' => $e,
'trace' => $e->getTraceAsString(),
]);
// ✅ خوب
$counts = MediaLibrary::whereIn('post_id', $postIds)
->groupBy('post_id')
->selectRaw('post_id, COUNT(*) as count')
->pluck('count', 'post_id');
// ❌ بد
foreach ($postIds as $postId) {
$count = MediaLibrary::where('post_id', $postId)->count();
}
if ($entityId === null) {
return new ComputedValueResultCollection([]);
}
$entity = Entity::find($entityId);
if ($entity === null) {
$this->logger->warning('موجودیت یافت نشد', ['id' => $entityId]);
return new ComputedValueResultCollection([]);
}
$affectedIds = $this->findAffectedEntities($eventDto);
$entities = Entity::whereIn('id', $affectedIds)->get();
$results = [];
foreach ($entities as $entity) {
$results[] = new ComputedValueResult(
$entity->id,
$this->compute($entity)
);
}
return new ComputedValueResultCollection($results);
پیکربندیهای پیشرفته
منابع رویداد چندگانه
new ComputedValueConfig(
key: 'engagement_score',
relatedEvents: [
PostViewedEvent::class, // رویداد کسبوکار
CommentAddedEvent::class, // رویداد کسبوکار
new EloquentEventConfig( // رویداد Eloquent
modelClass: Comment::class,
events: [
EloquentEventEnum::CREATED,
EloquentEventEnum::DELETED
]
),
],
mode: ComputedValueModeEnum::ASYNC,
cast: ComputedValueCastEnum::Float
)
پردازش شرطی
public function handle(ComputedValueEvent $eventDto): ComputedValueResultCollection
{
$changes = $eventDto->getPayload()->get('changes', []);
// فقط در صورت تغییر ویژگیهای خاص پردازش کن
if (!isset($changes['status']) && !isset($changes['published_at'])) {
return new ComputedValueResultCollection([]);
}
// ادامه پردازش...
}
ساختارهای داده پیچیده
return new ComputedValueResultCollection([
new ComputedValueResult($postId, [
'views' => ['total' => 1523, 'today' => 42],
'engagement' => ['likes' => 89, 'comments' => 23],
'metadata' => ['last_calculated' => now()->toIso8601String()],
]),
]);
بهینهسازی عملکرد
۱. از ASYNC برای محاسبات سنگین استفاده کنید
mode: ComputedValueModeEnum::ASYNC
۲. مدلهای خاص را هدف قرار دهید
// ✅ کارآمد - فقط بهروزرسانیهای MediaLibrary
new EloquentEventConfig(
modelClass: MediaLibrary::class,
events: [EloquentEventEnum::UPDATED]
)
۳. عملیات دستهای
چندین موجودیت را در اجرای کنترلکننده واحد پردازش کنید.
۴. پس از تغییرات پیکربندی کشف را اجرا کنید
# کشف به طور خودکار قبل از اسکن کش را پاک میکند
php artisan computed-values:discover
۵. کارگران صف را پیکربندی کنید
php artisan queue:work --queue=computed-values --tries=3
اشکالزدایی
بررسی کشف
php artisan computed-values:discover
بازرسی مقادیر محاسبه شده
$post = Post::find(1);
dd($post->getAllCacheData());
dd($post->getCacheStatistics());
dd($post->getComputedValueConfigSummary());
پایش صف
php artisan queue:failed
php artisan queue:retry all
فعال کردن لاگ اشکالزدایی
$this->logger->debug('پردازش', [
'payload' => $eventDto->getPayload()->toArray(),
]);
تست
تست واحد کنترلکننده
public function test_handler_computes_correctly()
{
$event = new ComputedValueEvent(
eventName: 'MediaCreated',
eventClass: MediaUploadedEvent::class,
originalEventData: new EventDTO(...),
cacheKey: 'media_stats'
);
$handler = new MediaStatsComputedValueHandler();
$results = $handler->handle($event);
$this->assertInstanceOf(ComputedValueResultCollection::class, $results);
}
تست یکپارچه
public function test_computed_value_updates_on_event()
{
$post = Post::factory()->create();
event(new MediaUploadedEvent($media));
$post->refresh();
$this->assertTrue($post->hasComputedValue('media_stats'));
$this->assertEquals(1, $post->media_stats);
}
گسترش سیستم
اضافه کردن نوع تبدیل جدید
- اضافه کردن به
ComputedValueCastEnum - پیادهسازی تبدیل در
ComputedValueCastingService
// در ComputedValueCastEnum
case Decimal = 'decimal';
// در ComputedValueCastingService
private function castToDecimal(mixed $data): string
{
return number_format((float) $data, 2, '.', '');
}
آداپتور رویداد سفارشی
final class CustomEventAdapter
{
public function adaptToComputedValueEvent($event): ComputedValueEvent
{
return new ComputedValueEvent(...);
}
}
کشف سفارشی
final class CustomDiscovery extends ModelDiscoveryService
{
protected function getModelDirectories(): array
{
$dirs = parent::getModelDirectories();
$dirs[] = app_path('CustomModels');
return $dirs;
}
}
APIهای داخلی
ComputedValueManager
processComputedValueUpdate(
ComputedValueEvent $event,
ComputedValueConfig $config,
string $entityClass
): void
getComputedValueData(
string $entityClass,
int|string $entityId,
string $key
): ?array
clearComputedValueData(
string $entityClass,
int|string $entityId,
string $key
): bool
ComputedValueStorageService
storeComputedValueResults(
string $entityClass,
string $cacheKey,
Collection $results,
ComputedValueConfig $config,
?string $eventTimestamp
): void
getComputedValueData(
string $entityClass,
int|string $entityId,
string $key
): ?array
clearComputedValueData(
string $entityClass,
int|string $entityId,
string $key
): bool
ModelDiscoveryService
discoverModelsWithComputedValues(): array
clearDiscoveryCache(): void
ComputedValueCastingService
castData(mixed $data, ComputedValueCastEnum $cast): mixed
امنیت
- محافظت از فیلد از دستکاری مستقیم جلوگیری میکند
- اعتبارسنجی روی تمام پیکربندیها
- ایمنی نوع مبتنی بر enum از پیکربندیهای نامعتبر جلوگیری میکند
- مکانیسم دور زدن فقط برای سرویس ذخیرهسازی
- ردیابی حسابرسی با برچسبهای زمانی و شناسههای همبستگی
- محافظت از تخصیص انبوه از طریق ویژگی
- محافظت از کوئری از طریق محدوده سراسری
- ایزولهسازی کنترلکننده با تزریق وابستگی
فیلد computed_values در سطوح چندگانه محافظت شده تا یکپارچگی داده تضمین شود. فقط سرویس ذخیرهسازی میتواند از طریق مکانیزم فراخوانی کنترل شده محافظت را دور بزند.
مسائل رایج
کنترلکننده یافت نشد
- بررسی نامگذاری:
{CacheKey}ComputedValueHandler - تأیید مکان با ساختار ماژول مطابقت دارد
- اطمینان از پیادهسازی اینترفیس
ComputedValueHandler
مقادیر بهروز نمیشوند
- بررسی پیادهسازی
HasComputedValuesتوسط مدل - تأیید وجود ستون
computed_values - اجرای کشف:
php artisan computed-values:discover - بررسی لاگها برای خطاها
هشدارهای شرایط رقابتی
رفتار عادی - نشان میدهد داده جدیدتری وجود دارد، از بهروزرسانیهای قدیمی جلوگیری میکند.
نویسنده: بهنام مرادی
نسخه: 1.0
آخرین بهروزرسانی: دسامبر 2024