返回 Skill 列表
extension
分类: 开发与工程无需 API Key

acc-create-read-model

为PHP 8.5生成读模型/投影。创建针对CQRS读取端的优化查询模型,包括投影和非规范化处理。包含单元测试。

person作者: jakexiaohubgithub

Read Model / Projection Generator

Creates Read Model infrastructure for CQRS read side with optimized query models.

When to Use

| Scenario | Example | |----------|---------| | CQRS read side | Separate query models | | Denormalized views | Dashboard aggregates | | Complex queries | Multi-entity joins | | Event-driven updates | Event projections |

Component Characteristics

Read Model

  • Optimized for queries
  • Denormalized data
  • Eventually consistent
  • No business logic

Projection

  • Builds read models from events
  • Handles event streams
  • Maintains synchronization
  • Idempotent processing

Repository

  • Query-focused methods
  • Returns read models
  • No write operations

Generation Process

Step 1: Generate Domain Read Model

Path: src/Domain/{BoundedContext}/ReadModel/

  1. {Name}ReadModel.php — Immutable read model with fromArray/toArray
  2. {Name}ReadModelRepositoryInterface.php — Query-focused repository interface

Step 2: Generate Application Projection

Path: src/Application/{BoundedContext}/Projection/

  1. {Name}ProjectionInterface.php — Projection contract
  2. {Name}Projection.php — Event handlers building read model

Step 3: Generate Infrastructure

Path: src/Infrastructure/{BoundedContext}/

  1. Projection/{Name}Store.php — Store for insert/update/upsert
  2. ReadModel/Doctrine{Name}Repository.php — Repository implementation

Step 4: Generate Tests

  1. {Name}ReadModelTest.php — Read model serialization tests
  2. {Name}ProjectionTest.php — Projection event handling tests

File Placement

| Component | Path | |-----------|------| | Read Model | src/Domain/{BoundedContext}/ReadModel/ | | Repository Interface | src/Domain/{BoundedContext}/ReadModel/ | | Projection Interface | src/Application/{BoundedContext}/Projection/ | | Projection | src/Application/{BoundedContext}/Projection/ | | Store | src/Infrastructure/{BoundedContext}/Projection/ | | Repository Impl | src/Infrastructure/{BoundedContext}/ReadModel/ | | Unit Tests | tests/Unit/ |


Naming Conventions

| Component | Pattern | Example | |-----------|---------|---------| | Read Model | {Name}ReadModel | OrderSummaryReadModel | | Repository Interface | {Name}ReadModelRepositoryInterface | OrderSummaryReadModelRepositoryInterface | | Projection Interface | {Name}ProjectionInterface | OrderSummaryProjectionInterface | | Projection | {Name}Projection | OrderSummaryProjection | | Store | {Name}Store | OrderSummaryStore | | Test | {ClassName}Test | OrderSummaryProjectionTest |


Quick Template Reference

Read Model

final readonly class {Name}ReadModel
{
    public function __construct(
        public string $id,
        // ... denormalized properties
        public \DateTimeImmutable $createdAt,
        public \DateTimeImmutable $updatedAt
    ) {}

    public static function fromArray(array $data): self;
    public function toArray(): array;
}

Projection

final class {Name}Projection implements {Name}ProjectionInterface
{
    public function project(DomainEventInterface $event): void
    {
        match ($event::class) {
            OrderCreated::class => $this->whenOrderCreated($event),
            OrderShipped::class => $this->whenOrderShipped($event),
            default => null,
        };
    }

    public function reset(): void;
    public function subscribedEvents(): array;
}

Usage Example

// Query read model
$orders = $orderSummaryRepository->findByCustomerId($customerId);

// Project event
$projection->project($orderCreatedEvent);

// Reset projection for rebuild
$projection->reset();

Database Schema

CREATE TABLE order_summaries (
    id VARCHAR(36) PRIMARY KEY,
    order_number VARCHAR(50) NOT NULL UNIQUE,
    customer_id VARCHAR(36) NOT NULL,
    customer_name VARCHAR(255) NOT NULL,
    status VARCHAR(50) NOT NULL,
    total_cents BIGINT NOT NULL,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL,

    INDEX idx_customer (customer_id),
    INDEX idx_status (status)
);

Anti-patterns to Avoid

| Anti-pattern | Problem | Solution | |--------------|---------|----------| | Business Logic | Read model has behavior | Keep data-only | | Write Operations | Modifying read models | Use projections only | | Non-idempotent | Re-projection breaks data | Idempotent event handling | | Missing Reset | Can't rebuild | Add reset() method | | Tight Coupling | Projection depends on domain | Use events only |


References

For complete PHP templates and examples, see:

  • references/templates.md — Read model, projection, store templates
  • references/examples.md — OrderSummary example and tests