From acc
Generates PHP 8.4 snapshot infrastructure for event-sourced aggregates. Optimizes rebuild performance on long event streams via configurable strategies, Doctrine DBAL store, migrations, and unit tests.
How this skill is triggered — by the user, by Claude, or both
Slash command
/acc:create-snapshotThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Creates Snapshot infrastructure for optimizing event sourcing aggregate rebuilds.
Creates Snapshot infrastructure for optimizing event sourcing aggregate rebuilds.
| Scenario | Example |
|---|---|
| Long event streams | Aggregates with 100+ events in their history |
| Slow aggregate rebuild | Event replay taking too long for read operations |
| Frequent reads | Aggregates loaded many times per second |
| Large aggregates | Complex state requiring many events to reconstruct |
fromArray() / toArray() for serializationsave(Snapshot $snapshot): void — persist snapshot (upsert)load(string $aggregateId): ?Snapshot — retrieve latest snapshotdelete(string $aggregateId): void — remove all snapshots for aggregateshouldTakeSnapshot(int $eventsSinceLastSnapshot): boolPath: src/Domain/{BC}/Snapshot/
Snapshot.php — Immutable snapshot value objectSnapshotStoreInterface.php — Repository interface for snapshot persistencePath: src/Application/{BC}/Snapshot/
SnapshotStrategy.php — Configurable threshold strategyAggregateSnapshotter.php — Application service for snapshot operationsPath: src/Infrastructure/{BC}/Snapshot/
DoctrineSnapshotStore.php — Doctrine DBAL implementation of SnapshotStoreInterfaceSnapshotTest.php — Construction, serialization, validation testsSnapshotStrategyTest.php — Threshold behavior testsAggregateSnapshotterTest.php — Load and take snapshot tests| Component | Path |
|---|---|
| Snapshot, SnapshotStoreInterface | src/Domain/{BC}/Snapshot/ |
| SnapshotStrategy, AggregateSnapshotter | src/Application/{BC}/Snapshot/ |
| DoctrineSnapshotStore | src/Infrastructure/{BC}/Snapshot/ |
| Unit Tests | tests/Unit/Domain/{BC}/Snapshot/, tests/Unit/Application/{BC}/Snapshot/ |
| Component | Pattern | Example |
|---|---|---|
| Value Object | Snapshot | Snapshot |
| Store Interface | SnapshotStoreInterface | SnapshotStoreInterface |
| Strategy | SnapshotStrategy | SnapshotStrategy |
| Application Service | AggregateSnapshotter | AggregateSnapshotter |
| Infrastructure Store | DoctrineSnapshotStore | DoctrineSnapshotStore |
| Test | {ClassName}Test | SnapshotTest |
final readonly class Snapshot
{
public function __construct(
public string $aggregateId,
public string $aggregateType,
public int $version,
public string $state,
public \DateTimeImmutable $createdAt
) {
// version >= 1 validation
}
public static function fromArray(array $data): self;
public function toArray(): array;
}
interface SnapshotStoreInterface
{
public function save(Snapshot $snapshot): void;
public function load(string $aggregateId): ?Snapshot;
public function delete(string $aggregateId): void;
}
final readonly class SnapshotStrategy
{
public function __construct(
private int $eventThreshold = 100
) {}
public function shouldTakeSnapshot(int $eventsSinceLastSnapshot): bool;
}
$snapshotter = new AggregateSnapshotter($snapshotStore, $strategy);
// Load aggregate from snapshot + remaining events
$result = $snapshotter->loadWithSnapshot($aggregateId, $eventStore);
$snapshot = $result['snapshot'];
$remainingEvents = $result['remainingEvents'];
$aggregate = $snapshot !== null
? Order::fromSnapshot($snapshot)
: new Order($aggregateId);
foreach ($remainingEvents as $event) {
$aggregate->apply($event);
}
// Take snapshot if threshold reached
$snapshotter->takeSnapshotIfNeeded(
aggregateId: $aggregateId,
aggregateType: 'Order',
version: $aggregate->getVersion(),
state: $aggregate->toSnapshot(),
eventsSinceSnapshot: count($remainingEvents)
);
CREATE TABLE snapshots (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
aggregate_id VARCHAR(36) NOT NULL,
aggregate_type VARCHAR(255) NOT NULL,
version INT UNSIGNED NOT NULL,
state JSON NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE INDEX idx_snapshots_aggregate_id (aggregate_id),
INDEX idx_snapshots_aggregate_type (aggregate_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
| Anti-pattern | Problem | Solution |
|---|---|---|
| Snapshot Every Event | Storage bloat, no performance gain | Use threshold strategy (e.g., every 100 events) |
| Mutable Snapshots | State corruption, debugging nightmares | Make Snapshot immutable (final readonly) |
| Missing Version | Cannot determine event replay start point | Always track version in snapshot |
| No Cleanup | Unbounded storage growth | Implement retention policy, keep only latest |
| Tight Coupling | Snapshot tied to infrastructure | Domain interface, infrastructure implementation |
| Skipping Validation | Invalid snapshots persisted | Validate version >= 1, non-empty state |
For complete PHP templates and examples, see:
references/templates.md — Snapshot, SnapshotStoreInterface, SnapshotStrategy, AggregateSnapshotter, DoctrineSnapshotStore templatesreferences/examples.md — OrderAggregate integration, event sourcing examples and testsnpx claudepluginhub dykyi-roman/awesome-claude-code --plugin accGenerates PHP 8.4 Event Store for event sourcing with Doctrine DBAL implementation, event streams, stored events, optimistic locking, version tracking, and unit tests. For audit trails, event replay, and temporal queries.
Designs event-sourced systems with event store, projections, versioning patterns. Guides when to use for audits, complex domains, collaboration, event-driven architectures.
Implements Clean Architecture, Hexagonal (Ports & Adapters), and Domain-Driven Design patterns in PHP 8.3+ with Symfony 7.x. For enterprise app architecture, legacy refactoring, DDD, and testable backends.