Awesome-claude-code create-idempotent-consumer
Generates Idempotent Consumer components for PHP 8.4. Creates message deduplication infrastructure with idempotency key management, storage backends, middleware, and unit tests.
install
source · Clone the upstream repo
git clone https://github.com/dykyi-roman/awesome-claude-code
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/dykyi-roman/awesome-claude-code "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/create-idempotent-consumer" ~/.claude/skills/dykyi-roman-awesome-claude-code-create-idempotent-consumer && rm -rf "$T"
manifest:
skills/create-idempotent-consumer/SKILL.mdsource content
Idempotent Consumer Generator
Creates Idempotent Consumer pattern infrastructure for exactly-once message processing guarantees.
When to Use
| Scenario | Example |
|---|---|
| Payment processing | Prevent double charges on message replay |
| Inventory updates | Avoid duplicate stock deductions |
| Event handlers | Process domain events exactly once |
| Webhook processing | Handle duplicate webhook deliveries |
| Message queue consumers | Deduplicate redelivered messages |
Component Characteristics
IdempotencyKey
- Value object combining messageId + handlerName
- Deterministic key generation
- Immutable with toString() representation
IdempotencyStoreInterface
- Application layer port
- has(IdempotencyKey): bool — check if already processed
- mark(IdempotencyKey, DateTimeImmutable ttl): void — mark as processed
- remove(IdempotencyKey): void — remove entry (for reprocessing)
DatabaseIdempotencyStore
- PDO-based implementation
- TTL-based automatic cleanup
- Unique constraint prevents race conditions
RedisIdempotencyStore
- Redis SETNX-based implementation
- TTL via Redis EXPIRE
- Atomic check-and-set
IdempotentConsumerMiddleware
- Wraps message handler
- Checks idempotency before processing
- Marks as processed after success
- Returns ProcessingResult enum
ProcessingResult
- Enum: Processed, Duplicate, Failed
- Includes original result data on success
- Includes reason on duplicate/failure
Generation Process
Step 1: Analyze Request
Determine:
- Storage backend (Database, Redis, or both)
- TTL for idempotency keys (default: 7 days)
- Integration point (middleware, decorator, manual)
Step 2: Generate Core Components
-
Domain Layer (
)src/Domain/Shared/Idempotency/
— Key value objectIdempotencyKey.php
— Result value object with statusProcessingResult.php
— Status enum (Processed/Duplicate/Failed)ProcessingStatus.php
-
Application Layer (
)src/Application/Shared/Idempotency/
— Storage portIdempotencyStoreInterface.php
— Middleware wrapperIdempotentConsumerMiddleware.php
-
Infrastructure Layer (
)src/Infrastructure/Idempotency/
— PDO implementationDatabaseIdempotencyStore.php
— Redis implementationRedisIdempotencyStore.php- Database migration
-
Tests
IdempotencyKeyTest.phpProcessingResultTest.phpIdempotentConsumerMiddlewareTest.phpDatabaseIdempotencyStoreTest.php
File Placement
| Layer | Path |
|---|---|
| Domain Types | |
| Application Port | |
| Infrastructure | |
| Unit Tests | |
Key Principles
Idempotency Strategy
- Generate deterministic key from message ID + handler name
- Check store BEFORE processing
- Mark as processed AFTER successful handling
- Use database unique constraint as safety net
- TTL cleanup prevents unbounded growth
Race Condition Handling
- Use INSERT ... ON CONFLICT (database)
- Use SETNX (Redis)
- If duplicate detected mid-processing, return Duplicate result
TTL Management
- Default TTL: 7 days (configurable)
- Cleanup via cron/scheduled command
- Redis handles TTL automatically via EXPIRE
Naming Conventions
| Component | Pattern | Example |
|---|---|---|
| Key VO | | |
| Result VO | | |
| Status Enum | | |
| Store Interface | | |
| DB Store | | |
| Redis Store | | |
| Middleware | | |
| Test | | |
Quick Template Reference
IdempotencyKey
final readonly class IdempotencyKey { public function __construct( public string $messageId, public string $handlerName, ) {} public static function fromMessage(string $messageId, string $handlerName): self; public function toString(): string; public function equals(self $other): bool; }
IdempotencyStoreInterface
interface IdempotencyStoreInterface { public function has(IdempotencyKey $key): bool; public function mark(IdempotencyKey $key, \DateTimeImmutable $expiresAt): void; public function remove(IdempotencyKey $key): void; }
Usage
$middleware = new IdempotentConsumerMiddleware($store); $result = $middleware->process( key: IdempotencyKey::fromMessage($message->id, 'handle_payment'), handler: fn() => $paymentHandler->handle($message), ttl: new \DateTimeImmutable('+7 days') ); match ($result->status) { ProcessingStatus::Processed => $logger->info('Processed'), ProcessingStatus::Duplicate => $logger->info('Skipped duplicate'), ProcessingStatus::Failed => $logger->error('Failed', ['error' => $result->error]), };
Database Schema
CREATE TABLE idempotency_keys ( key VARCHAR(255) PRIMARY KEY, handler_name VARCHAR(255) NOT NULL, processed_at TIMESTAMP(6) NOT NULL, expires_at TIMESTAMP(6) NOT NULL ); CREATE INDEX idx_idempotency_expires ON idempotency_keys (expires_at);
References
For complete PHP templates and test examples, see:
— All component templatesreferences/templates.md
— Payment handler example and unit testsreferences/examples.md