پرش به مطلب اصلی

استانداردهای مقداردهی کش

اصول اساسی

جداسازی دغدغه‌ها

سیستم کش از جداسازی دقیق دغدغه‌ها پیروی می‌کند:

  • مقداردهی کش: فقط مسئول بازیابی داده از ماژول‌های دیگر
  • سیستم Bus: مدیریت ارتباط بین ماژولی
  • سیستم کش: مدیریت ذخیره‌سازی و بازیابی کش

این جداسازی معماری تمیز و کد قابل نگهداری را تضمین می‌کند.

ارتباط بین ماژولی

تمام ارتباطات بین ماژول‌ها باید از این اصول پیروی کنند:

  • فقط از طریق سیستم Bus
  • الگوی ارتباط مبتنی بر Query
  • ایمنی نوع در تمام ارتباطات
  • عدم دسترسی مستقیم پایگاه داده در مرزهای ماژول

ساختار فایل

مکان دایرکتوری

مقداردهی‌کننده‌های کش باید در ساختار دایرکتوری زیر قرار گیرند:

app/Modules/{ModuleName}/Services/CacheHandlers/{CacheKey}Initializer.php
نکته مهم

در حال حاضر، مقداردهی‌کننده‌ها در دایرکتوری CacheHandlers قرار دارند، نه در دایرکتوری جداگانه CacheInitializers. این الگو در پیاده‌سازی فعلی استفاده می‌شود و باید در تمام ماژول‌ها دنبال شود.

قرارداد نام‌گذاری

  • الگو: {CacheKey}Initializer
  • مثال: RelatedProductsInitializer
  • از تبدیل PascalCase از کلیدهای کش snake_case استفاده کنید

پیاده‌سازی

پیاده‌سازی رابط

تمام مقداردهی‌کننده‌های کش باید رابط CacheInitializerInterface را پیاده‌سازی کنند:

app/Modules/Product/Services/CacheHandlers/RelatedProductsInitializer.php
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, // پردازش ناهمزمان
)

مثال کامل

در اینجا مثال کاملی از یک مقداردهی‌کننده کش آمده است:

app/Modules/Product/Services/CacheHandlers/RelatedProductsInitializer.php
<?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();
}
}
}