Dead Letter Queue Generator
Creates Dead Letter Queue pattern infrastructure for capturing and retrying failed messages.
When to Use
| Scenario | Example |
|---|---|
| Poison messages | Messages that always cause handler failures |
| Processing failures | Temporary infrastructure issues (DB down, timeout) |
| Audit trail | Track all failed message processing attempts |
| Retry mechanism | Automatic retry with exponential backoff |
| Monitoring | Alert on DLQ threshold exceeded |
Component Characteristics
DeadLetterMessage
- Entity capturing failed message details
- Original message body, headers, routing key
- Error message and stack trace
- Attempt count and timestamps
- Failure classification (Transient, Permanent, Unknown)
DeadLetterStoreInterface
- Application layer port
- store(DeadLetterMessage): void
- findRetryable(int limit): array — find messages eligible for retry
- markRetried(string id): void — increment attempt count
- markResolved(string id): void — mark as successfully reprocessed
- purge(DateTimeImmutable before): int — remove old entries
DeadLetterHandler
- Catches exceptions from message handlers
- Classifies failure type (transient vs permanent)
- Stores to DLQ with full context
- Logs failure details
RetryStrategy
- Configurable max attempts
- Exponential backoff with jitter
- Failure type classification
- Skip permanent failures
FailureClassifier
- Classifies exceptions as Transient or Permanent
- Configurable exception mapping
- Default: ConnectionException=Transient, ValidationException=Permanent
DlqProcessor
- Processes retryable messages from DLQ
- Applies RetryStrategy for backoff
- Removes successfully processed messages
- Re-stores still-failing messages
Generation Process
Step 1: Analyze Request
Determine:
- Storage backend (Database)
- Max retry attempts (default: 5)
- Backoff strategy (exponential with jitter)
Step 2: Generate Core Components
Domain Layer (
src/Domain/Shared/DeadLetter/)FailureType.php— Enum (Transient, Permanent, Unknown)DeadLetterMessage.php— Message entity
Application Layer (
src/Application/Shared/DeadLetter/)DeadLetterStoreInterface.php— Storage portDeadLetterHandler.php— Exception handlerRetryStrategy.php— Retry configuration and backoff calculationFailureClassifier.php— Exception classificationDlqProcessor.php— Retry processor
Infrastructure Layer (
src/Infrastructure/DeadLetter/)DatabaseDeadLetterStore.php— PDO implementation- Database migration
Tests
DeadLetterMessageTest.phpRetryStrategyTest.phpFailureClassifierTest.phpDlqProcessorTest.php
File Placement
| Layer | Path |
|---|---|
| Domain Types | src/Domain/Shared/DeadLetter/ |
| Application | src/Application/Shared/DeadLetter/ |
| Infrastructure | src/Infrastructure/DeadLetter/ |
| Unit Tests | tests/Unit/{Layer}/{Path}/ |
Key Principles
Failure Classification
- Transient: Connection errors, timeouts, rate limits — eligible for retry
- Permanent: Validation errors, business rule violations — never retry
- Unknown: Unclassified exceptions — retry with caution
Retry Strategy
- Max attempts configurable (default: 5)
- Exponential backoff: delay = baseDelay * 2^attempt
- Jitter: random ±25% to prevent thundering herd
- Skip messages exceeding max attempts
Message Lifecycle
Message → Handler fails → DeadLetterHandler → Store to DLQ
↓
DlqProcessor retries
↓
Success → Remove from DLQ
Failure → Re-store with incremented attempts
Max exceeded → Mark as permanently failed
Naming Conventions
| Component | Pattern | Example |
|---|---|---|
| Failure Enum | FailureType |
FailureType |
| Message Entity | DeadLetterMessage |
DeadLetterMessage |
| Store Interface | DeadLetterStoreInterface |
DeadLetterStoreInterface |
| Handler | DeadLetterHandler |
DeadLetterHandler |
| Strategy | RetryStrategy |
RetryStrategy |
| Classifier | FailureClassifier |
FailureClassifier |
| Processor | DlqProcessor |
DlqProcessor |
| Test | {ClassName}Test |
RetryStrategyTest |
Quick Template Reference
DeadLetterMessage
final class DeadLetterMessage
{
public function __construct(
public readonly string $id,
public readonly string $originalBody,
public readonly string $originalRoutingKey,
public readonly array $originalHeaders,
public readonly string $errorMessage,
public readonly string $errorTrace,
public readonly FailureType $failureType,
public readonly int $attemptCount,
public readonly \DateTimeImmutable $failedAt,
public readonly ?\DateTimeImmutable $nextRetryAt,
public readonly ?\DateTimeImmutable $resolvedAt = null,
) {}
public function isRetryable(int $maxAttempts): bool;
public function withIncrementedAttempt(\DateTimeImmutable $nextRetryAt): self;
}
DeadLetterStoreInterface
interface DeadLetterStoreInterface
{
public function store(DeadLetterMessage $message): void;
/** @return array<DeadLetterMessage> */
public function findRetryable(int $limit = 100): array;
public function markRetried(string $id, \DateTimeImmutable $nextRetryAt): void;
public function markResolved(string $id): void;
public function purge(\DateTimeImmutable $before): int;
public function countByType(FailureType $type): int;
}
Database Schema
CREATE TABLE dead_letter_messages (
id VARCHAR(255) PRIMARY KEY,
original_body TEXT NOT NULL,
original_routing_key VARCHAR(255) NOT NULL,
original_headers JSONB NOT NULL DEFAULT '{}',
error_message TEXT NOT NULL,
error_trace TEXT,
failure_type VARCHAR(50) NOT NULL,
attempt_count INTEGER NOT NULL DEFAULT 1,
failed_at TIMESTAMP(6) NOT NULL,
next_retry_at TIMESTAMP(6),
resolved_at TIMESTAMP(6)
);
CREATE INDEX idx_dlq_retryable ON dead_letter_messages (failure_type, next_retry_at)
WHERE resolved_at IS NULL AND failure_type != 'permanent';
CREATE INDEX idx_dlq_failed_at ON dead_letter_messages (failed_at);
References
For complete PHP templates and test examples, see:
references/templates.md— All component templatesreferences/examples.md— Message broker integration example and unit tests
Converted and distributed by TomeVault — claim your Tome and manage your conversions.