Files
michaelschiemer/src/Framework/Queue/PersistentQueue.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

205 lines
5.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Queue;
use App\Framework\Queue\ValueObjects\JobId;
use App\Framework\Queue\ValueObjects\JobPayload;
use App\Framework\Queue\ValueObjects\QueueType;
/**
* Persistent Queue Decorator
*
* Wraps any Queue implementation with job persistence and tracking
*/
final readonly class PersistentQueue implements Queue
{
public function __construct(
private Queue $baseQueue,
private JobPersistenceLayer $persistence,
private QueueType $queueType
) {
}
/**
* {@inheritdoc}
*/
public function push(JobPayload $payload): void
{
// Generate job ID if not exists
$jobId = $payload->getJobId() ?? JobId::generate();
// Store job in persistence layer
$this->persistence->storeJob(
jobId: $jobId,
queueType: $this->queueType,
jobData: $payload->job,
maxAttempts: $payload->retryStrategy->maxAttempts,
metadata: $payload->metadata
);
// Add job ID to payload and push to base queue
$payloadWithId = $payload->withJobId($jobId);
$this->baseQueue->push($payloadWithId);
}
/**
* {@inheritdoc}
*/
public function pop(): ?JobPayload
{
$payload = $this->baseQueue->pop();
if ($payload === null) {
return null;
}
$jobId = $payload->getJobId();
if ($jobId !== null) {
// Mark job as processing in persistence layer
try {
$this->persistence->markAsProcessing($jobId);
} catch (\Throwable $e) {
// If we can't mark as processing, put it back in queue
$this->baseQueue->push($payload);
throw $e;
}
}
return $payload;
}
/**
* {@inheritdoc}
*/
public function peek(): ?JobPayload
{
return $this->baseQueue->peek();
}
/**
* {@inheritdoc}
*/
public function size(): int
{
return $this->baseQueue->size();
}
/**
* {@inheritdoc}
*/
public function clear(): int
{
$count = $this->baseQueue->clear();
// Note: We don't clear persistence layer here as it maintains job history
// Use separate cleanup methods for persistence if needed
return $count;
}
/**
* {@inheritdoc}
*/
public function getStats(): array
{
$baseStats = $this->baseQueue->getStats();
$persistenceStats = $this->persistence->getStats();
return array_merge($baseStats, [
'persistence' => $persistenceStats,
'queue_type' => $this->queueType->value,
]);
}
/**
* Mark job as completed
*/
public function markJobCompleted(JobId $jobId, array $result = []): void
{
$this->persistence->markAsCompleted($jobId, $result);
}
/**
* Mark job as failed
*/
public function markJobFailed(JobId $jobId, string $errorMessage, ?\Throwable $exception = null): void
{
$jobState = $this->persistence->markAsFailed($jobId, $errorMessage, $exception);
// If job can be retried, put it back in queue
if ($jobState->canRetry()) {
// Get original job data from metadata
$jobData = $jobState->metadata['job_data'] ?? null;
if ($jobData !== null) {
$retryPayload = JobPayload::create($jobData, $jobState->priority)
->withJobId($jobId)
->withMetadata($jobState->metadata);
$this->baseQueue->push($retryPayload);
$this->persistence->markForRetry($jobId, $errorMessage);
}
}
}
/**
* Get job state from persistence
*/
public function getJobState(JobId $jobId): ?\App\Framework\Queue\ValueObjects\JobState
{
return $this->persistence->getJobState($jobId);
}
/**
* Get the underlying base queue
*/
public function getBaseQueue(): Queue
{
return $this->baseQueue;
}
/**
* Get the persistence layer
*/
public function getPersistenceLayer(): JobPersistenceLayer
{
return $this->persistence;
}
/**
* Get retryable failed jobs and re-queue them
*/
public function requeueFailedJobs(int $limit = 50): int
{
$retryableJobs = $this->persistence->getRetryableJobs($limit);
$requeued = 0;
foreach ($retryableJobs as $jobState) {
$jobData = $jobState->metadata['job_data'] ?? null;
if ($jobData === null) {
continue;
}
$retryPayload = JobPayload::create($jobData, $jobState->priority)
->withJobId($jobState->jobId)
->withMetadata($jobState->metadata);
$this->baseQueue->push($retryPayload);
$this->persistence->markForRetry($jobState->jobId, 'Manual retry');
$requeued++;
}
return $requeued;
}
/**
* Clean up old job records
*/
public function cleanupJobs(?\App\Framework\Core\ValueObjects\Duration $olderThan = null): int
{
return $this->persistence->cleanup($olderThan);
}
}