- 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.
139 lines
3.9 KiB
PHP
139 lines
3.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\EventSourcing\Snapshots;
|
|
|
|
use App\Framework\EventSourcing\AggregateId;
|
|
use App\Framework\EventSourcing\AggregateRoot;
|
|
|
|
/**
|
|
* Snapshotable Aggregate Helper
|
|
*
|
|
* Extends AggregateRoot functionality with snapshot support
|
|
* without modifying the core AggregateRoot class
|
|
*/
|
|
final readonly class SnapshotableAggregate
|
|
{
|
|
public function __construct(
|
|
private SnapshotStore $store,
|
|
private SnapshotStrategy $strategy
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Load aggregate with snapshot optimization
|
|
*/
|
|
public function load(
|
|
AggregateId $aggregateId,
|
|
callable $eventStreamLoader,
|
|
callable $aggregateFactory
|
|
): AggregateRoot {
|
|
// Try to load latest snapshot
|
|
$snapshot = $this->store->getLatest($aggregateId);
|
|
|
|
if ($snapshot) {
|
|
// Load from snapshot + events since snapshot
|
|
$eventsSinceSnapshot = $eventStreamLoader($snapshot->getVersion());
|
|
$state = $this->reconstructState($snapshot);
|
|
|
|
return AggregateRoot::rehydrate(
|
|
$aggregateId,
|
|
$state,
|
|
$eventsSinceSnapshot
|
|
);
|
|
}
|
|
|
|
// No snapshot - load all events
|
|
$events = $eventStreamLoader(0);
|
|
|
|
return $aggregateFactory($aggregateId, $events);
|
|
}
|
|
|
|
/**
|
|
* Save aggregate and create snapshot if strategy says so
|
|
*/
|
|
public function saveWithSnapshot(AggregateRoot $aggregate): void
|
|
{
|
|
// Check if snapshot should be taken
|
|
if ($this->strategy->shouldTakeSnapshot($aggregate)) {
|
|
$snapshot = $this->createSnapshot($aggregate);
|
|
$this->store->save($snapshot);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Force snapshot creation
|
|
*/
|
|
public function forceSnapshot(AggregateRoot $aggregate): Snapshot
|
|
{
|
|
$snapshot = $this->createSnapshot($aggregate);
|
|
$this->store->save($snapshot);
|
|
|
|
return $snapshot;
|
|
}
|
|
|
|
/**
|
|
* Get aggregate version from event recorder
|
|
*/
|
|
public function getVersion(AggregateRoot $aggregate): int
|
|
{
|
|
return $aggregate->eventRecorder->getVersion();
|
|
}
|
|
|
|
/**
|
|
* Create snapshot from aggregate
|
|
*/
|
|
private function createSnapshot(AggregateRoot $aggregate): Snapshot
|
|
{
|
|
return Snapshot::take(
|
|
aggregateId: $aggregate->id,
|
|
version: $this->getVersion($aggregate),
|
|
state: $this->extractState($aggregate),
|
|
aggregateClass: get_class($aggregate->state)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Extract state array from aggregate
|
|
*/
|
|
private function extractState(AggregateRoot $aggregate): array
|
|
{
|
|
// Use reflection to extract state properties
|
|
$reflection = new \ReflectionObject($aggregate->state);
|
|
$state = [];
|
|
|
|
foreach ($reflection->getProperties() as $property) {
|
|
$property->setAccessible(true);
|
|
$value = $property->getValue($aggregate->state);
|
|
|
|
// Serialize Value Objects and complex types
|
|
if (is_object($value)) {
|
|
if (method_exists($value, 'toArray')) {
|
|
$state[$property->getName()] = $value->toArray();
|
|
} elseif (method_exists($value, 'toString')) {
|
|
$state[$property->getName()] = $value->toString();
|
|
} else {
|
|
$state[$property->getName()] = serialize($value);
|
|
}
|
|
} else {
|
|
$state[$property->getName()] = $value;
|
|
}
|
|
}
|
|
|
|
return $state;
|
|
}
|
|
|
|
/**
|
|
* Reconstruct state object from snapshot
|
|
*/
|
|
private function reconstructState(SnapshotInterface $snapshot): object
|
|
{
|
|
$stateClass = $snapshot->getAggregateClass();
|
|
$stateData = $snapshot->getState();
|
|
|
|
// Simple reconstruction - would need enhancement for complex VOs
|
|
return new $stateClass(...$stateData);
|
|
}
|
|
}
|