استانداردهای مقداردهی کش
اصول اساسی
جداسازی دغدغهها
سیستم کش از جداسازی دقیق دغدغهها پیروی میکند:
- مقداردهی کش: فقط مسئول بازیابی داده از ماژولهای دیگر
- سیستم Bus: مدیریت ارتباط بین ماژولی
- سیستم کش: مدیریت ذخیرهسازی و بازیابی کش
این جداسازی معماری تمیز و کد قابل نگهداری را تضمین میکند.
ارتباط بین ماژولی
تمام ارتباطات بین ماژولها باید از این اصول پیروی کنند:
- فقط از طریق سیستم Bus
- الگوی ارتباط مبتنی بر Query
- ایمنی نوع در تمام ارتباطات
- عدم دسترسی مستقیم پایگاه داده در مرزهای ماژول
ساختار فایل
مکان دایرکتوری
مقداردهیکنندههای کش باید در ساختار دایرکتوری زیر قرار گیرند:
app/Modules/{ModuleName}/Services/CacheHandlers/{CacheKey}Initializer.php
در حال حاضر، مقداردهیکنندهها در دایرکتوری CacheHandlers قرار دارند، نه در دایرکتوری جداگانه CacheInitializers. این الگو در پیادهسازی فعلی استفاده میشود و باید در تمام ماژولها دنبال شود.
قرارداد نامگذاری
- الگو:
{CacheKey}Initializer - مثال:
RelatedProductsInitializer - از تبدیل PascalCase از کلیدهای کش snake_case استفاده کنید
پیادهسازی
پیادهسازی رابط
تمام مقداردهیکنندههای کش باید رابط CacheInitializerInterface را پیادهسازی کنند:
final readonly class RelatedProductsInitializer implements CacheInitializerInterface
{
public function __construct(
private QueryBusInterface $queryBus,
private LoggerInterface $logger
) {}
public function initialize(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
// پیادهسازی
}
}
وابستگیهای مورد نیاز
مقداردهیکنندههای کش باید وابستگیهای زیر را داشته باشند:
- QueryBusInterface: برای ارتباط با ماژولهای دیگر
- LoggerInterface: برای لاگگیری جامع
نوع بازگشتی
- همیشه
CacheHandlerResultCollectionبرگردانید - هرگز
void،mixedیاarrayبرنگردانید
یکپارچگی Bus
الگوی Query
از الگوی query برای ارتباط بین ماژولی استفاده کنید:
$query = new GetRelatedProductsQuery($productId, $limit);
$response = $this->queryBus->dispatch($query);
if (!$response->isSuccess()) {
$this->logger->warning('دریافت محصولات مرتبط شکست خورد', [
'product_id' => $productId,
'error' => $response->getErrorMessage()
]);
return new CacheHandlerResultCollection();
}
$relatedProducts = $response->getData();
مدیریت خطا
از این اصول مدیریت خطا پیروی کنید:
- بدون بلوکهای try-catch تو در تو
- تنزل تدریجی (برگرداندن مجموعه خالی در صورت شکست)
- لاگگیری جامع با زمینه
try {
$query = new GetRelatedProductsQuery($productId, $limit);
$response = $this->queryBus->dispatch($query);
if (!$response->isSuccess()) {
throw new RuntimeException($response->getErrorMessage());
}
return $this->transformToResultCollection($response->getData(), $productId);
} catch (Exception $e) {
$this->logger->warning('مقداردهی اولیه کش محصولات مرتبط شکست خورد', [
'product_id' => $productId,
'exception' => $e->getMessage(),
'cache_key' => 'related_products'
]);
return new CacheHandlerResultCollection();
}
پیکربندی
CacheConfigDTO
مقداردهیکنندهها را در پیکربندی کش مدل تنظیم کنید:
new CacheConfigDTO(
key: 'related_products',
relatedEvents: [],
initializerEvents: [ProductCreatedEvent::class, ProductUpdatedEvent::class],
sourceModule: 'Product',
sourceEntity: Product::class,
mode: CacheModeEnum::SYNC,
cast: CacheCastEnum::ARRAY,
)
به پارامتر initializerEvents توجه کنید که مشخص میکند کدام رویدادها باید مقداردهیکننده را راهاندازی کنند.
فرمت داده
ساختار خروجی
مقداردهیکنندهها باید دادهها را در ساختار زیر برگردانند:
$data = [
'data' => $transformedData,
'metadata' => [
'total_count' => $count,
'last_updated' => now()->toIso8601String(),
'source_module' => 'Product',
'cache_version' => '1.0',
]
];
return new CacheHandlerResultCollection([
new CacheHandlerResult($productId, $data)
]);
لاگگیری
زمینه مورد نیاز
تمام پیامهای لاگ باید زمینه زیر را شامل شوند:
cache_key: کلید کش در حال مقداردهیsource_module: ماژول مسئول مقداردهیکنندهevent_id: شناسه رویداد راهاندازresults_count: تعداد نتایج تولید شدهexecution_time: زمان صرف شده برای اجرای مقداردهیکننده
$startTime = microtime(true);
// منطق مقداردهیکننده...
$executionTime = microtime(true) - $startTime;
$this->logger->info('کش با موفقیت مقداردهی شد', [
'cache_key' => 'related_products',
'source_module' => 'Product',
'event_id' => $eventDto->getEventId(),
'results_count' => $resultCollection->count(),
'execution_time' => $executionTime
]);
لاگگیری خطا
لاگهای خطا باید شامل موارد زیر باشند:
- جزئیات استثنا
- اطلاعات trace
- دادههای زمینه
$this->logger->error('مقداردهی کش شکست خورد', [
'cache_key' => 'related_products',
'source_module' => 'Product',
'event_id' => $eventDto->getEventId(),
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
'entity_id' => $eventDto->getEntityId()
]);
امنیت
دسترسی داده
از این اصول امنیتی پیروی کنید:
- قبل از دسترسی به دادهها بررسی مجوز انجام دهید
- دادهها را بر اساس مجوزها فیلتر کنید
- تمام دادههای ورودی را پاکسازی کنید
// بررسی مجوز
if (!$this->authorizationService->canAccessProduct($productId)) {
$this->logger->warning('تلاش دسترسی غیرمجاز', [
'product_id' => $productId,
'user_id' => $this->authService->getCurrentUserId()
]);
return new CacheHandlerResultCollection();
}
// ادامه بازیابی داده
امنیت بین ماژولی
- عدم دسترسی مستقیم پایگاه داده در مرزهای ماژول
- فقط از ارتباط Bus استفاده کنید
- بررسی مجوز را در کنترلکنندههای Query پیادهسازی کنید
عملکرد
تکنیکهای بهینهسازی
- بارگذاری انتخابی: فقط دادههای ضروری را بارگذاری کنید
- صفحهبندی: برای مجموعه دادههای بزرگ از صفحهبندی استفاده کنید
- ایندکسگذاری مناسب: اطمینان حاصل کنید پرسوجوهای پایگاه داده بهینهسازی شدهاند
// مثال بارگذاری انتخابی
$query = new GetProductDataQuery(
$productId,
[
'include_reviews' => false,
'include_images' => true,
'include_variants' => true,
'max_related' => 5
]
);
پردازش ناهمزمان
برای عملیات سنگین:
- از پردازش async استفاده کنید
- مدیریت صف را پیادهسازی کنید
- مکانیزمهای بازیابی خطا فراهم کنید
// در CacheConfigDTO
new CacheConfigDTO(
key: 'product_analytics',
// سایر پیکربندیها...
mode: CacheModeEnum::ASYNC, // پردازش ناهمزمان
)
مثال کامل
در اینجا مثال کاملی از یک مقداردهیکننده کش آمده است:
<?php
namespace App\Modules\Product\Services\CacheHandlers;
use App\Services\Cache\Contracts\CacheInitializerInterface;
use App\Services\Cache\DTOs\CacheEventDTO;
use App\Services\Cache\DTOs\CacheHandlerResult;
use App\Services\Cache\DTOs\CacheHandlerResultCollection;
use App\Modules\Product\Queries\GetRelatedProductsQuery;
use App\Services\Bus\Contracts\QueryBusInterface;
use Psr\Log\LoggerInterface;
use Exception;
use RuntimeException;
final readonly class RelatedProductsInitializer implements CacheInitializerInterface
{
public function __construct(
private QueryBusInterface $queryBus,
private LoggerInterface $logger
) {}
public function initialize(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$startTime = microtime(true);
$productId = $eventDto->getEntityId();
try {
$query = new GetRelatedProductsQuery($productId, 10);
$response = $this->queryBus->dispatch($query);
if (!$response->isSuccess()) {
throw new RuntimeException($response->getErrorMessage());
}
$relatedProducts = $response->getData();
$resultData = [
'product_ids' => array_column($relatedProducts, 'id'),
'metadata' => [
'total_count' => count($relatedProducts),
'last_updated' => now()->toIso8601String(),
'source_module' => 'Product',
'cache_version' => '1.0',
]
];
$result = new CacheHandlerResultCollection([
new CacheHandlerResult($productId, $resultData)
]);
$executionTime = microtime(true) - $startTime;
$this->logger->info('کش محصولات مرتبط مقداردهی شد', [
'cache_key' => 'related_products',
'source_module' => 'Product',
'event_id' => $eventDto->getEventId(),
'results_count' => count($relatedProducts),
'execution_time' => $executionTime,
'product_id' => $productId
]);
return $result;
} catch (Exception $e) {
$executionTime = microtime(true) - $startTime;
$this->logger->error('مقداردهی کش محصولات مرتبط شکست خورد', [
'cache_key' => 'related_products',
'source_module' => 'Product',
'event_id' => $eventDto->getEventId(),
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
'execution_time' => $executionTime,
'product_id' => $productId
]);
return new CacheHandlerResultCollection();
}
}
}