# Projection Introduction

## The Problem

Once you start storing storing events instead of updating rows — you will quickly find out your users still need a ticket list page, a dashboard, a report. How do you turn a stream of "what happened" into a table you can query?

In traditional applications, when a ticket is created you run an `INSERT`, when it's closed you run an `UPDATE`. The database always holds the current state. But with Event Sourcing, you store **what happened** — `TicketWasRegistered`, `TicketWasClosed` — as an append-only log of events.

Think of it like a bank account: instead of storing "balance = 500", you store every deposit and withdrawal. The balance is derived by replaying the history.

But your users don't want to replay history every time they load a page. They need a ready-to-query table. That's what **Projections** do.

## What is a Projection?

A **Projection** reads events from an **Event Stream** (the append-only log) and builds a read-optimized view from them — a database table, a document, a cache entry. Think of it as a **materialized view** built from events.

Another analogy: the Event Stream is like your **Git history** — every commit ever made. The Projection is like your **working directory** — the current state of the files, derived from that history.

The views built by Projections are called **Read Models**. They exist only for reading and can be rebuilt at any time from the Event Stream.

<figure><img src="https://1452285857-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LmAUnBnyZgZuLF2eWLn%2Fuploads%2Fgit-blob-3ea0314690966a341613baa424e2654a4909599c%2Fticket_event_stream_2.png?alt=media" alt=""><figcaption><p>Events stored in the Event Stream</p></figcaption></figure>

From these events, we want to build a list of all tickets with their current status:

<figure><img src="https://1452285857-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LmAUnBnyZgZuLF2eWLn%2Fuploads%2Fgit-blob-5e2c01c66953eb38eed62b33177a5119efd462ce%2Fticket-list%20(1).png?alt=media" alt=""><figcaption><p>Read Model: list of tickets with current status</p></figcaption></figure>

## Building Your First Projection

Let's say we have a `Ticket` Event Sourced Aggregate that produces two events — `TicketWasRegistered` and `TicketWasClosed`. We want to build a read model table showing all in-progress tickets.

```php
#[ProjectionV2('ticket_list')]
#[FromAggregateStream(Ticket::class)]
class TicketListProjection
{
    public function __construct(private Connection $connection) {}

    #[ProjectionInitialization]
    public function init(): void
    {
        $this->connection->executeStatement(<<<SQL
            CREATE TABLE IF NOT EXISTS ticket_list (
                ticket_id VARCHAR(36) PRIMARY KEY,
                ticket_type VARCHAR(25),
                status VARCHAR(25)
            )
        SQL);
    }

    #[EventHandler]
    public function onTicketRegistered(TicketWasRegistered $event): void
    {
        $this->connection->insert('ticket_list', [
            'ticket_id' => $event->ticketId,
            'ticket_type' => $event->type,
            'status' => 'open',
        ]);
    }

    #[EventHandler]
    public function onTicketClosed(TicketWasClosed $event): void
    {
        $this->connection->update(
            'ticket_list',
            ['status' => 'closed'],
            ['ticket_id' => $event->ticketId]
        );
    }

    #[ProjectionDelete]
    public function delete(): void
    {
        $this->connection->executeStatement('DROP TABLE IF EXISTS ticket_list');
    }

    #[ProjectionReset]
    public function reset(): void
    {
        $this->connection->executeStatement('DELETE FROM ticket_list');
    }
}
```

That's all you need. Let's break down what each part does:

1. `#[ProjectionV2('ticket_list')]` — marks this class as a Projection with name `ticket_list`
2. `#[FromAggregateStream(Ticket::class)]` — tells the Projection to read events from the Ticket aggregate's stream
3. `#[ProjectionInitialization]` — called when the Projection is first set up (creates the table)
4. `#[EventHandler]` — subscribes to specific event types. Ecotone routes events by the type-hint.
5. `#[ProjectionDelete]` and `#[ProjectionReset]` — called when the projection is deleted or reset

There is no additional configuration needed. Ecotone takes care of delivering events, initializing, and triggering the Projection.

## Position Tracking

Each Projection remembers **where it left off** in the Event Stream — like a bookmark in a book. When a new event triggers the Projection, it fetches only the events after its last position.

This means:

* **New Projections** start from the beginning of the stream and catch up to the present
* **Existing Projections** only process new events they haven't seen yet
* **After a failure**, the Projection resumes from its last successfully committed position

This is what makes it possible to deploy a new Projection at any point in time and have it automatically build up from the full event history.

## Feature Overview

Ecotone Projections come in two editions. The open-source edition covers the full projection lifecycle for globally tracked projections. Enterprise adds scaling, advanced operations, and deployment strategies.

| Feature                                                                                                                                                            | Open Source | Enterprise |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------: | :--------: |
| **Global (non-partitioned) projection**                                                                                                                            |     Yes     |     Yes    |
| [Synchronous event-driven execution](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/execution-modes)                                    |     Yes     |     Yes    |
| [Asynchronous event-driven execution](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/execution-modes)                                   |     Yes     |     Yes    |
| [Lifecycle management](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/lifecycle-management) (init, delete, reset, trigger)              |     Yes     |     Yes    |
| [Multiple event streams](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/event-streams-and-handlers)                                     |     Yes     |     Yes    |
| [Projection state](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/projections-with-state)                                               |     Yes     |     Yes    |
| [Event emission](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/emitting-events) (EventStreamEmitter)                                   |     Yes     |     Yes    |
| [Sync backfill](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/backfill-and-rebuild)                                                    |     Yes     |     Yes    |
| [Batch size configuration](https://docs.ecotone.tech/modelling/event-sourcing/execution-modes#batch-size-and-flushing)                                             |     Yes     |     Yes    |
| [Gap detection](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/gap-detection-and-consistency)                                           |     Yes     |     Yes    |
| [Self-healing / automatic recovery](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/failure-handling)                                    |     Yes     |     Yes    |
| [Polling execution](https://docs.ecotone.tech/modelling/event-sourcing/scaling-and-advanced#polling-projections)                                                   |      —      |     Yes    |
| [Partitioned projections](https://docs.ecotone.tech/modelling/event-sourcing/scaling-and-advanced#partitioned-projections)                                         |      —      |     Yes    |
| [Streaming projections](https://docs.ecotone.tech/modelling/event-sourcing/scaling-and-advanced#streaming-projections) (Kafka, RabbitMQ)                           |      —      |     Yes    |
| [Async backfill](https://docs.ecotone.tech/modelling/event-sourcing/backfill-and-rebuild#async-backfill-enterprise) (parallel workers for partitioned)             |      —      |     Yes    |
| [Rebuild](https://docs.ecotone.tech/modelling/event-sourcing/backfill-and-rebuild#rebuild--reset-and-replay-enterprise) (sync and async with parallel workers)     |      —      |     Yes    |
| [Blue-green deployments](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/blue-green-deployments)                                         |      —      |     Yes    |
| [High-performance flush state](https://docs.ecotone.tech/modelling/event-sourcing/projections-with-state#high-performance-projections-with-flush-state-enterprise) |      —      |     Yes    |
| [Multi-tenant projections](https://docs.ecotone.tech/modelling/event-sourcing/scaling-and-advanced#multi-tenant-projections)                                       |      —      |     Yes    |
| Custom extensions (StreamSource, StateStorage, PartitionProvider)                                                                                                  |      —      |     Yes    |

## What's Next

* [Event Streams and Handlers](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/event-streams-and-handlers) — control which events reach your projection
* [Execution Modes](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/execution-modes) — sync, async, and when to use each
* [Lifecycle Management](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/lifecycle-management) — CLI commands, initialization, reset
* [Projections with State](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/projections-with-state) — keep state between events without external storage
* [Emitting Events](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/emitting-events) — notify after the projection is up to date
* [Backfill and Rebuild](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/backfill-and-rebuild) — populate with historical data
* [Failure Handling](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/failure-handling) — transactions, rollback, self-healing
* [Gap Detection](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/gap-detection-and-consistency) — how Ecotone guarantees no events are lost
* [Scaling and Advanced](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/scaling-and-advanced) — partitioned, streaming, polling (Enterprise)
* [Blue-Green Deployments](https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections/blue-green-deployments) — zero-downtime projection changes (Enterprise)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.ecotone.tech/modelling/event-sourcing/setting-up-projections.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
