Skip to main content

Cache Initializer Standards

Core Principles

Separation of Concerns

The cache system follows strict separation of concerns:

  • Cache Initializer: Responsible only for retrieving data from other modules
  • Bus System: Handles cross-module communication
  • Cache System: Manages cache storage and retrieval

This separation ensures clean architecture and maintainable code.

Cross-Module Communication

All communication between modules must follow these principles:

  • Only through the Bus System
  • Query-based communication pattern
  • Type safety in all communications
  • No direct database access across module boundaries

File Structure

Directory Location

Cache initializers should be placed in the following directory structure:

app/Modules/{ModuleName}/Services/CacheHandlers/{CacheKey}Initializer.php
Important Note

Currently, initializers are located in the CacheHandlers directory, not in a separate CacheInitializers directory. This pattern is used in the current implementation and should be followed across all modules.

Naming Convention

  • Pattern: {CacheKey}Initializer
  • Example: RelatedProductsInitializer
  • Use PascalCase conversion from snake_case cache keys

Implementation

Interface Implementation

All cache initializers must implement the 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
{
// Implementation
}
}

Required Dependencies

Cache initializers should have the following dependencies:

  • QueryBusInterface: For communication with other modules
  • LoggerInterface: For comprehensive logging

Return Type

  • Always return CacheHandlerResultCollection
  • Never return void, mixed, or array

Bus Integration

Query Pattern

Use the query pattern for cross-module communication:

$query = new GetRelatedProductsQuery($productId, $limit);
$response = $this->queryBus->dispatch($query);

if (!$response->isSuccess()) {
$this->logger->warning('Failed to get related products', [
'product_id' => $productId,
'error' => $response->getErrorMessage()
]);
return new CacheHandlerResultCollection();
}

$relatedProducts = $response->getData();

Error Handling

Follow these error handling principles:

  • No nested try-catch blocks
  • Graceful degradation (return empty collection on failure)
  • Comprehensive logging with context
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('Failed to initialize related products cache', [
'product_id' => $productId,
'exception' => $e->getMessage(),
'cache_key' => 'related_products'
]);

return new CacheHandlerResultCollection();
}

Configuration

CacheConfigDTO

Configure initializers in the model's cache configuration:

new CacheConfigDTO(
key: 'related_products',
relatedEvents: [],
initializerEvents: [ProductCreatedEvent::class, ProductUpdatedEvent::class],
sourceModule: 'Product',
sourceEntity: Product::class,
mode: CacheModeEnum::SYNC,
cast: CacheCastEnum::ARRAY,
)
info

Note the initializerEvents parameter, which specifies which events should trigger the initializer.

Data Format

Output Structure

Initializers should return data in the following structure:

$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)
]);

Logging

Required Context

All log messages should include the following context:

  • cache_key: The cache key being initialized
  • source_module: The module responsible for the initializer
  • event_id: The ID of the triggering event
  • results_count: The number of results generated
  • execution_time: Time taken to execute the initializer
$startTime = microtime(true);

// Initializer logic...

$executionTime = microtime(true) - $startTime;
$this->logger->info('Cache initialized successfully', [
'cache_key' => 'related_products',
'source_module' => 'Product',
'event_id' => $eventDto->getEventId(),
'results_count' => $resultCollection->count(),
'execution_time' => $executionTime
]);

Error Logging

Error logs should include:

  • Exception details
  • Trace information
  • Context data
$this->logger->error('Cache initialization failed', [
'cache_key' => 'related_products',
'source_module' => 'Product',
'event_id' => $eventDto->getEventId(),
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
'entity_id' => $eventDto->getEntityId()
]);

Security

Data Access

Follow these security principles:

  • Perform authorization checks before accessing data
  • Filter data based on permissions
  • Sanitize all input data
// Check authorization
if (!$this->authorizationService->canAccessProduct($productId)) {
$this->logger->warning('Unauthorized access attempt', [
'product_id' => $productId,
'user_id' => $this->authService->getCurrentUserId()
]);
return new CacheHandlerResultCollection();
}

// Proceed with data retrieval

Cross-Module Security

  • No direct database access across module boundaries
  • Use only Bus communication
  • Implement permission checks in Query handlers

Performance

Optimization Techniques

  • Selective Loading: Only load necessary data
  • Pagination: Use pagination for large datasets
  • Proper Indexing: Ensure database queries are optimized
// Selective loading example
$query = new GetProductDataQuery(
$productId,
[
'include_reviews' => false,
'include_images' => true,
'include_variants' => true,
'max_related' => 5
]
);

Asynchronous Processing

For heavy operations:

  • Use async processing
  • Implement queue management
  • Provide error recovery mechanisms
// In CacheConfigDTO
new CacheConfigDTO(
key: 'product_analytics',
// other config...
mode: CacheModeEnum::ASYNC, // Process asynchronously
)

Complete Example

Here's a complete example of a cache initializer:

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('Related products cache initialized', [
'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('Failed to initialize related products cache', [
'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();
}
}
}