راهنمای پیادهسازی سیستم کش
این راهنما دستورالعملهای گام به گام برای پیادهسازی سیستم کش در مدلها و ماژولهای شما ارائه میدهد.
پیشنیازها
قبل از پیادهسازی سیستم کش، اطمینان حاصل کنید که دارید:
- مدلی با ستون JSON
cacheدر پایگاه داده - رویدادهای کسبوکار که باید بهروزرسانی کش را راهاندازی کنند
- درک واضح از اینکه چه دادههایی باید کش شوند
مرحله 1: آمادهسازی مدل
اضافه کردن ستون مورد نیاز
اگر مدل شما قبلاً ستون cache ندارد، آن را از طریق migration اضافه کنید:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('products', function (Blueprint $table) {
$table->json('cache')->nullable();
});
}
public function down(): void
{
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('cache');
});
}
};
پیادهسازی رابطها و Trait های مورد نیاز
مدل خود را برای پیادهسازی رابط CacheableModel و استفاده از trait HasCacheField بهروزرسانی کنید:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Services\Cache\Contracts\CacheableModel;
use App\Services\Cache\Traits\HasCacheField;
use App\Services\Cache\DTOs\CacheConfigCollection;
use App\Services\Cache\DTOs\CacheConfigDTO;
use App\Services\Cache\Enums\CacheModeEnum;
use App\Services\Cache\Enums\CacheCastEnum;
class Product extends Model implements CacheableModel
{
use HasCacheField;
protected $fillable = [
'name',
'description',
'price',
// 'cache' را اینجا قرار ندهید
];
protected $casts = [
// 'cache' را اینجا قرار ندهید
];
// پیادهسازی رابط CacheableModel
public function getCacheConfig(): CacheConfigCollection
{
return new CacheConfigCollection([
// پیکربندیهای کش اینجا قرار خواهند گرفت
]);
}
}
مرحله 2: تعریف پیکربندیهای کش
پیکربندیهای کش را به متد getCacheConfig() مدل خود اضافه کنید:
public function getCacheConfig(): CacheConfigCollection
{
return new CacheConfigCollection([
new CacheConfigDTO(
key: 'related_products',
relatedEvents: [
ProductUpdatedEvent::class,
ProductCategoryChangedEvent::class,
],
initializerEvents: [
ProductCreatedEvent::class,
],
sourceModule: 'Product',
sourceEntity: Product::class,
mode: CacheModeEnum::SYNC,
cast: CacheCastEnum::ARRAY,
),
new CacheConfigDTO(
key: 'view_statistics',
relatedEvents: [
ProductViewedEvent::class,
],
sourceModule: 'Analytics',
sourceEntity: Product::class,
mode: CacheModeEnum::ASYNC,
cast: CacheCastEnum::OBJECT,
),
]);
}
مرحله 3: ایجاد کنترلکنندههای کش
برای هر کلید کش یک کنترلکننده کش ایجاد کنید:
<?php
namespace App\Modules\Product\Services\CacheHandlers;
use App\Services\Cache\Contracts\CacheHandler;
use App\Services\Cache\DTOs\CacheEventDTO;
use App\Services\Cache\DTOs\CacheHandlerResult;
use App\Services\Cache\DTOs\CacheHandlerResultCollection;
use App\Models\Product;
use Psr\Log\LoggerInterface;
final readonly class RelatedProductsCacheHandler implements CacheHandler
{
public function __construct(
private LoggerInterface $logger
) {}
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
$product = Product::find($productId);
if (!$product) {
return new CacheHandlerResultCollection();
}
// منطق دریافت محصولات مرتبط
$relatedProductIds = $this->getRelatedProductIds($product);
// برگرداندن نتیجه
return new CacheHandlerResultCollection([
new CacheHandlerResult(
$productId,
$relatedProductIds
)
]);
}
private function getRelatedProductIds(Product $product): array
{
// پیادهسازی منطق محصولات مرتبط
// این فقط یک مثال است
return Product::where('category_id', $product->category_id)
->where('id', '!=', $product->id)
->limit(5)
->pluck('id')
->toArray();
}
}
مرحله 4: ایجاد مقداردهیکنندههای کش (اختیاری)
اگر نیاز به مقداردهی اولیه دادههای کش برای موجودیتهای جدید دارید، مقداردهیکننده ایجاد کنید:
<?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\Models\Product;
use Psr\Log\LoggerInterface;
final readonly class RelatedProductsInitializer implements CacheInitializerInterface
{
public function __construct(
private LoggerInterface $logger
) {}
public function initialize(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
$product = Product::find($productId);
if (!$product) {
return new CacheHandlerResultCollection();
}
// منطق دریافت محصولات مرتبط (مشابه کنترلکننده)
$relatedProductIds = $this->getRelatedProductIds($product);
// برگرداندن نتیجه
return new CacheHandlerResultCollection([
new CacheHandlerResult(
$productId,
$relatedProductIds
)
]);
}
private function getRelatedProductIds(Product $product): array
{
// پیادهسازی منطق محصولات مرتبط
return Product::where('category_id', $product->category_id)
->where('id', '!=', $product->id)
->limit(5)
->pluck('id')
->toArray();
}
}
مرحله 5: ثبت سرویسها در Service Provider
کنترلکنندهها و مقداردهیکنندههای کش خود را در یک service provider ثبت کنید:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Services\Cache\Contracts\CacheHandler;
use App\Services\Cache\Contracts\CacheInitializerInterface;
use App\Modules\Product\Services\CacheHandlers\RelatedProductsCacheHandler;
use App\Modules\Product\Services\CacheHandlers\RelatedProductsInitializer;
class CacheServiceProvider extends ServiceProvider
{
public function register(): void
{
// ثبت کنترلکنندههای کش
$this->app->tag([
RelatedProductsCacheHandler::class,
], 'cache.handlers');
// ثبت مقداردهیکنندههای کش
$this->app->tag([
RelatedProductsInitializer::class,
], 'cache.initializers');
}
}
مرحله 6: ایجاد رویدادهای کسبوکار
رویدادهای کسبوکاری ایجاد کنید که بهروزرسانی کش را راهاندازی خواهند کرد:
<?php
namespace App\Events;
use App\Models\Product;
use App\Services\Cache\Contracts\BusinessEvent;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ProductUpdatedEvent implements BusinessEvent
{
use Dispatchable, SerializesModels;
public function __construct(
private readonly Product $product
) {}
public function getEntityId(): int|string
{
return $this->product->id;
}
public function getEventName(): string
{
return self::class;
}
public function getEventId(): string
{
return (string) $this->product->id . '_' . time();
}
public function getProduct(): Product
{
return $this->product;
}
}
مرحله 7: ارسال رویدادها
رویدادها را هنگام وقوع اعمال مرتبط ارسال کنید:
public function update(UpdateProductRequest $request, Product $product)
{
$validated = $request->validated();
$product->update([
'name' => $validated['name'],
'description' => $validated['description'],
'price' => $validated['price'],
'category_id' => $validated['category_id'],
]);
// ارسال رویداد برای راهاندازی بهروزرسانی کش
event(new ProductUpdatedEvent($product));
return response()->json([
'message' => 'محصول با موفقیت بهروزرسانی شد',
'product' => $product
]);
}
مرحله 8: دسترسی به دادههای کش شده
با استفاده از دسترسیکنندههای خودکار به دادههای کش شده دسترسی پیدا کنید:
// کنترلکننده یا سرویس
public function getProductDetails(int $productId)
{
$product = Product::find($productId);
if (!$product) {
return response()->json(['error' => 'محصول یافت نشد'], 404);
}
// دسترسی به دادههای کش شده با استفاده از دسترسیکننده خودکار
$relatedProducts = $product->related_products;
$viewStatistics = $product->view_statistics;
return response()->json([
'product' => $product,
'related_products' => $relatedProducts,
'statistics' => $viewStatistics,
]);
}
مرحله 9: مدیریت دادههای کش گم شده
مدیریت تدریجی برای دادههای کش گم شده پیادهسازی کنید:
public function getProductDetails(int $productId)
{
$product = Product::find($productId);
if (!$product) {
return response()->json(['error' => 'محصول یافت نشد'], 404);
}
// بررسی وجود داده کش
if (!$product->hasCacheData('related_products')) {
// راهاندازی مقداردهی اولیه کش
event(new ProductCreatedEvent($product));
// فعلاً آرایه خالی برگردان
$relatedProducts = [];
} else {
$relatedProducts = $product->related_products;
}
return response()->json([
'product' => $product,
'related_products' => $relatedProducts,
]);
}
چکلیست پیادهسازی
از این چکلیست برای اطمینان از تکمیل تمام مراحل ضروری استفاده کنید:
- ستون JSON
cacheبه جدول پایگاه داده اضافه شده - رابط
CacheableModelدر مدل پیادهسازی شده - trait
HasCacheFieldبه مدل اضافه شده - پیکربندیهای کش در
getCacheConfig()تعریف شده - کنترلکنندههای کش برای هر کلید کش ایجاد شده
- مقداردهیکنندهها برای کش موجودیت جدید ایجاد شده (در صورت نیاز)
- کنترلکنندهها و مقداردهیکنندهها در service provider ثبت شده
- رویدادهای کسبوکار که
BusinessEventرا پیادهسازی میکنند ایجاد شده - ارسال رویداد در مکانهای مناسب اضافه شده
- مدیریت تدریجی برای دادههای کش گم شده پیادهسازی شده
الگوهای رایج پیادهسازی
الگوی 1: کنترلکننده کش ساده
برای کش کردن دادههای ساده:
final readonly class SimpleStatsCacheHandler implements CacheHandler
{
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
// محاسبه داده ساده
$stats = [
'view_count' => rand(100, 1000),
'last_viewed_at' => now()->toIso8601String(),
];
return new CacheHandlerResultCollection([
new CacheHandlerResult($productId, $stats)
]);
}
}
الگوی 2: کنترلکننده کش چند موجودیتی
برای بهروزرسانی کش روی چندین موجودیت مرتبط:
final readonly class CategoryProductsCacheHandler implements CacheHandler
{
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
$product = Product::find($productId);
if (!$product || !$product->category_id) {
return new CacheHandlerResultCollection();
}
// دریافت تمام محصولات در همان دستهبندی
$categoryProducts = Product::where('category_id', $product->category_id)
->pluck('id')
->toArray();
// ایجاد نتیجه برای دستهبندی
$categoryResult = new CacheHandlerResult(
$product->category_id,
[
'product_count' => count($categoryProducts),
'product_ids' => $categoryProducts,
'updated_at' => now()->toIso8601String(),
]
);
return new CacheHandlerResultCollection([$categoryResult]);
}
}
الگوی 3: پردازش سنگین ناهمزمان
برای عملیات محاسباتی فشرده:
// در پیکربندی مدل
new CacheConfigDTO(
key: 'product_recommendations',
relatedEvents: [ProductViewedEvent::class],
sourceModule: 'Recommendations',
sourceEntity: Product::class,
mode: CacheModeEnum::ASYNC, // پردازش ناهمزمان
cast: CacheCastEnum::ARRAY,
)
// در کنترلکننده کش
final readonly class ProductRecommendationsCacheHandler implements CacheHandler
{
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
// محاسبه سنگین که در صف اجرا خواهد شد
$recommendations = $this->recommendationService->generateRecommendations($productId);
return new CacheHandlerResultCollection([
new CacheHandlerResult($productId, $recommendations)
]);
}
}
پیادهسازی پیشرفته
بهروزرسانیهای کش بین ماژولی
برای کشی که به دادههای چندین ماژول وابسته است:
final readonly class ProductFullDetailsCacheHandler implements CacheHandler
{
public function __construct(
private QueryBusInterface $queryBus
) {}
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
// دریافت جزئیات محصول از ماژول Product
$productQuery = new GetProductDetailsQuery($productId);
$productResponse = $this->queryBus->dispatch($productQuery);
if (!$productResponse->isSuccess()) {
return new CacheHandlerResultCollection();
}
// دریافت جزئیات موجودی از ماژول Inventory
$inventoryQuery = new GetProductInventoryQuery($productId);
$inventoryResponse = $this->queryBus->dispatch($inventoryQuery);
// دریافت جزئیات قیمتگذاری از ماژول Pricing
$pricingQuery = new GetProductPricingQuery($productId);
$pricingResponse = $this->queryBus->dispatch($pricingQuery);
// ترکیب تمام دادهها
$fullDetails = [
'basic' => $productResponse->getData(),
'inventory' => $inventoryResponse->isSuccess() ? $inventoryResponse->getData() : null,
'pricing' => $pricingResponse->isSuccess() ? $pricingResponse->getData() : null,
];
return new CacheHandlerResultCollection([
new CacheHandlerResult($productId, $fullDetails)
]);
}
}
بهروزرسانیهای کش شرطی
برای بهینهسازی بهروزرسانیهای کش بر اساس شرایط:
final readonly class ConditionalCacheHandler implements CacheHandler
{
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
$product = Product::find($productId);
if (!$product) {
return new CacheHandlerResultCollection();
}
// بررسی اینکه آیا رویداد برای این کش مرتبط است
$event = $eventDto->getEvent();
if ($event instanceof ProductPriceChangedEvent) {
// فقط دادههای کش مربوط به قیمت را بهروزرسانی کن
return $this->handlePriceChange($product);
}
if ($event instanceof ProductStockChangedEvent) {
// فقط دادههای کش مربوط به موجودی را بهروزرسانی کن
return $this->handleStockChange($product);
}
// پیشفرض: بهروزرسانی کامل
return $this->handleFullUpdate($product);
}
private function handlePriceChange(Product $product): CacheHandlerResultCollection
{
// منطق بهروزرسانی مخصوص قیمت
}
private function handleStockChange(Product $product): CacheHandlerResultCollection
{
// منطق بهروزرسانی مخصوص موجودی
}
private function handleFullUpdate(Product $product): CacheHandlerResultCollection
{
// منطق بهروزرسانی کامل
}
}
مراحل بعدی
پس از پیادهسازی سیستم کش، موارد زیر را در نظر بگیرید:
- بهترین شیوهها - الگوها و رویکردهای توصیه شده را دنبال کنید
- عیبیابی - مسائل رایج را حل کنید
- نظارت بر عملکرد - عملکرد کش را نظارت و بهینهسازی کنید