Back to skills
SKILL.md
Acc Create Outbox Pattern
ASecurityGenerates Transactional Outbox pattern components for PHP 8.5. Creates OutboxMessage entity, repository, publisher, and processor with unit tests.
- 666 stars
- 0 votes
- 0 copies
- 0 views
- Added September 2, 2026
Security analysis
100/100npx -y skills add majiayu000/claude-skill-registry --skill acc-create-outbox-pattern --agent claude-codeAre you the author of Acc Create Outbox Pattern?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/majiayu000-acc-create-outbox-pattern-claude-skill-registry)---
name: acc-create-outbox-pattern
description: Generates Transactional Outbox pattern components for PHP 8.5. Creates OutboxMessage entity, repository, publisher, and processor with unit tests.
---
# Outbox Pattern Generator
Creates Transactional Outbox pattern infrastructure for reliable event publishing.
## When to Use
- Need reliable event publishing across transaction boundaries
- Prevent message loss if broker is down
- Ensure exactly-once or at-least-once delivery
- Maintain consistency between database and message broker
## Component Characteristics
### OutboxMessage Entity
- Immutable value object in Domain layer
- Contains: id, aggregateType, aggregateId, eventType, payload, timestamps
- Supports reconstitution for persistence
- Methods: withProcessed(), withRetryIncremented()
### OutboxRepository
- Interface in Domain layer
- Implementation in Infrastructure layer
- Methods: save, findUnprocessed, markAsProcessed, incrementRetry, delete
### OutboxProcessor
- Application layer service
- Polls for unprocessed messages
- Publishes to message broker
- Handles failures with retry and dead letter
### Console Command
- Infrastructure layer
- Runs as daemon or one-shot
- Configurable batch size and interval
---
## Generation Process
### Step 1: Generate Domain Layer
**Path:** `src/Domain/Shared/Outbox/`
1. `OutboxMessage.php` — Immutable message entity
2. `OutboxRepositoryInterface.php` — Repository contract
### Step 2: Generate Application Layer
**Path:** `src/Application/Shared/`
1. `Port/Output/MessagePublisherInterface.php` — Publisher port
2. `Port/Output/DeadLetterRepositoryInterface.php` — Dead letter port
3. `Outbox/ProcessingResult.php` — Result value object
4. `Outbox/MessageResult.php` — Result enum
5. `Outbox/OutboxProcessor.php` — Processing service
### Step 3: Generate Infrastructure Layer
**Path:** `src/Infrastructure/`
1. `Persistence/Doctrine/Repository/DoctrineOutboxRepository.php`
2. `Console/OutboxProcessCommand.php`
3. Database migration
### Step 4: Generate Tests
1. `tests/Unit/Domain/Shared/Outbox/OutboxMessageTest.php`
2. `tests/Unit/Application/Shared/Outbox/OutboxProcessorTest.php`
---
## Key Principles
### Transactional Consistency
```php
// In UseCase - save outbox message in SAME transaction
$this->connection->transactional(function () use ($order, $event) {
$this->orderRepository->save($order);
$this->outboxRepository->save(
OutboxMessage::create(
id: Uuid::uuid4()->toString(),
aggregateType: 'Order',
aggregateId: $order->id()->toString(),
eventType: 'order.placed',
payload: $event->toArray()
)
);
});
```
### Retry with Dead Letter
1. Retry up to MAX_RETRIES times
2. Exponential backoff between retries
3. Move to dead letter queue after max retries
4. Log all failures with context
### Message Headers
Include metadata for tracing:
- message_id, correlation_id, causation_id
- aggregate_type, aggregate_id
- created_at
---
## File Placement
| Layer | Path |
|-------|------|
| Domain Entity | `src/Domain/Shared/Outbox/` |
| Domain Interface | `src/Domain/Shared/Outbox/` |
| Application Service | `src/Application/Shared/Outbox/` |
| Application Port | `src/Application/Shared/Port/Output/` |
| Infrastructure Repo | `src/Infrastructure/Persistence/Doctrine/Repository/` |
| Infrastructure Console | `src/Infrastructure/Console/` |
| Unit Tests | `tests/Unit/{Layer}/{Path}/` |
---
## Naming Conventions
| Component | Pattern | Example |
|-----------|---------|---------|
| Entity | `{Name}` | `OutboxMessage` |
| Repository Interface | `{Name}RepositoryInterface` | `OutboxRepositoryInterface` |
| Repository Impl | `Doctrine{Name}Repository` | `DoctrineOutboxRepository` |
| Service | `{Name}Processor` | `OutboxProcessor` |
| Command | `{Name}Command` | `OutboxProcessCommand` |
| Test | `{ClassName}Test` | `OutboxMessageTest` |
---
## Quick Template Reference
### OutboxMessage
```php
final readonly class OutboxMessage
{
public static function create(
string $id,
string $aggregateType,
string $aggregateId,
string $eventType,
array $payload,
?string $correlationId = null,
?string $causationId = null
): self;
public function isProcessed(): bool;
public function isPoisoned(int $maxRetries): bool;
public function payloadAsArray(): array;
public function withProcessed(): self;
public function withRetryIncremented(): self;
}
```
### OutboxRepositoryInterface
```php
interface OutboxRepositoryInterface
{
public function save(OutboxMessage $message): void;
public function findUnprocessed(int $limit = 100): array;
public function markAsProcessed(string $id): void;
public function incrementRetry(string $id): void;
public function delete(string $id): void;
}
```
### OutboxProcessor
```php
final readonly class OutboxProcessor
{
public function process(int $batchSize = 100): ProcessingResult;
}
```
---
## Usage Example
### Saving to Outbox
```php
// In UseCase
$message = OutboxMessage::create(
id: Uuid::uuid4()->toString(),
aggregateType: 'Order',
aggregateId: $order->id()->toString(),
eventType: 'order.placed',
payload: [
'order_id' => $order->id()->toString(),
'customer_id' => $order->customerId()->toString(),
'total' => $order->total()->amount(),
],
correlationId: $command->correlationId
);
$this->outboxRepository->save($message);
```
### Console Command
```bash
# One-shot processing
php bin/console outbox:process --batch-size=100
# Daemon mode
php bin/console outbox:process --daemon --interval=1000
```
---
## DI Configuration
```yaml
# Symfony services.yaml
Domain\Shared\Outbox\OutboxRepositoryInterface:
alias: Infrastructure\Persistence\Doctrine\Repository\DoctrineOutboxRepository
Application\Shared\Port\Output\MessagePublisherInterface:
alias: Infrastructure\Messaging\RabbitMq\RabbitMqPublisher
Application\Shared\Outbox\OutboxProcessor:
arguments:
$maxRetries: 5
```
---
## Database Schema
```sql
CREATE TABLE outbox_messages (
id VARCHAR(36) PRIMARY KEY,
aggregate_type VARCHAR(255) NOT NULL,
aggregate_id VARCHAR(255) NOT NULL,
event_type VARCHAR(255) NOT NULL,
payload JSONB NOT NULL,
correlation_id VARCHAR(255),
causation_id VARCHAR(255),
created_at TIMESTAMP(6) NOT NULL,
processed_at TIMESTAMP(6),
retry_count INT NOT NULL DEFAULT 0
);
CREATE INDEX idx_outbox_unprocessed
ON outbox_messages (processed_at, created_at)
WHERE processed_at IS NULL;
```
---
## References
For complete PHP templates and test examples, see:
- `references/templates.md` — All component templates
- `references/tests.md` — Unit test examples
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.