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

راهنمای پیاده‌سازی سیستم کش

این راهنما دستورالعمل‌های گام به گام برای پیاده‌سازی سیستم کش در مدل‌ها و ماژول‌های شما ارائه می‌دهد.

پیش‌نیازها

قبل از پیاده‌سازی سیستم کش، اطمینان حاصل کنید که دارید:

  1. مدلی با ستون JSON cache در پایگاه داده
  2. رویدادهای کسب‌وکار که باید به‌روزرسانی کش را راه‌اندازی کنند
  3. درک واضح از اینکه چه داده‌هایی باید کش شوند

مرحله 1: آماده‌سازی مدل

اضافه کردن ستون مورد نیاز

اگر مدل شما قبلاً ستون cache ندارد، آن را از طریق migration اضافه کنید:

database/migrations/xxxx_xx_xx_add_cache_to_products_table.php
<?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 به‌روزرسانی کنید:

app/Models/Product.php
<?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() مدل خود اضافه کنید:

app/Models/Product.php
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: ایجاد کنترل‌کننده‌های کش

برای هر کلید کش یک کنترل‌کننده کش ایجاد کنید:

app/Modules/Product/Services/CacheHandlers/RelatedProductsCacheHandler.php
<?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: ایجاد مقداردهی‌کننده‌های کش (اختیاری)

اگر نیاز به مقداردهی اولیه داده‌های کش برای موجودیت‌های جدید دارید، مقداردهی‌کننده ایجاد کنید:

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\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 ثبت کنید:

app/Providers/CacheServiceProvider.php
<?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: ایجاد رویدادهای کسب‌وکار

رویدادهای کسب‌وکاری ایجاد کنید که به‌روزرسانی کش را راه‌اندازی خواهند کرد:

app/Events/ProductUpdatedEvent.php
<?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: ارسال رویدادها

رویدادها را هنگام وقوع اعمال مرتبط ارسال کنید:

app/Http/Controllers/ProductController.php
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
{
// منطق به‌روزرسانی کامل
}
}

مراحل بعدی

پس از پیاده‌سازی سیستم کش، موارد زیر را در نظر بگیرید:

  1. بهترین شیوه‌ها - الگوها و رویکردهای توصیه شده را دنبال کنید
  2. عیب‌یابی - مسائل رایج را حل کنید
  3. نظارت بر عملکرد - عملکرد کش را نظارت و بهینه‌سازی کنید