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
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:
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, orarray
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,
)
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 initializedsource_module: The module responsible for the initializerevent_id: The ID of the triggering eventresults_count: The number of results generatedexecution_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:
<?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();
}
}
}