# `DDD.Projection`
[🔗](https://github.com/blackuprise/vicander/blob/v0.1.1/lib/ddd/persistence/projection.ex#L1)

A read model: a denormalized table kept up to date by domain events.

Queries read the table directly instead of loading and folding whole aggregates. A projection
is an event handler (`DDD.EventHandler`), so it is fed by the outbox and inherits its
guarantees: each event is applied in its own transaction together with the delivery record,
so a retry or a duplicate delivery never counts an event twice.

    defmodule MyApp.Projections.ListSummary do
      use DDD.Projection,
        table: "todo_list_summaries",
        key: :list_id,
        events: [ListCreated, ItemAdded, ItemCompleted, ItemRemoved]

      projection do
        description "One row per list: title, owner, open and done counts"
        field :list_id, :uuid
        field :owner_id, :uuid
        field :title, :string
        field :open_count, :integer, default: 0
        field :done_count, :integer, default: 0
      end

      on %ListCreated{} = e, do: {:upsert, %{owner_id: e.owner_id, title: e.title}}
      on %ItemAdded{}, do: {:inc, :open_count, 1}
      on %ItemCompleted{}, do: {:inc, open_count: -1, done_count: 1}
      on %ItemRemoved{}, do: {:inc, :open_count, -1}
    end

    MyApp.Projections.ListSummary.subscribe()      # before the outbox poller starts

## Options

  * `:table` - the table (required)
  * `:key` - the field that is the primary key, one row per key (required)
  * `:events` - the event modules the projection reacts to (required)
  * `:repo` - default: the configured repo (`config :vicander, repo: MyApp.Repo`)
  * `:key_from` - the event field holding the key, default `:aggregate_id`, which is right
    when the projection is one row per aggregate

## What an `on` clause returns

`on` matches an event like a function head, and returns what to do to the row of that event's
key (a list of these is applied in order):

| return | effect |
|---|---|
| `{:upsert, %{field: value}}` | create the row (with the field defaults), or set these fields |
| `{:update, %{field: value}}` | set these fields; nothing if there is no row |
| `{:inc, :field, n}` / `{:inc, field: n, other: m}` | add `n` to numeric fields, creating the row if needed |
| `:delete` | delete the row |
| `:skip` | do nothing |
| `{:error, reason}` | like any handler: a business error is a rejection, an infrastructure error is retried |

An event the projection lists but has no `on` clause for is skipped.

## The table

`mix vic.gen.migration MyApp.Projections.ListSummary` writes the migration, like for an
aggregate (the key is the primary key). Apply new fields the same way.

## Reading

The module gets `get/1` (`{:ok, map}` or `{:error, :not_found}`), and the listing functions
`list/1`, `page/1`, `slice/1`, `count/1` with filters, ordering and cursors (see
`DDD.Listing`). Rows come back as plain maps with the declared fields.

    {:ok, page} = ListSummary.page(filter: [owner_id: id, open_count: {:gt, 0}], order: :title)

A projection is eventually consistent. Decisions belong to the aggregate; never base a
business rule on a projection.

## Operating it

  * `lag/0` - how many events are waiting for this projection and how long the oldest has
    waited (`DDD.Health` and `DDD.Telemetry` report it).
  * `rebuild/0` - empty the table and replay every stored event into it, in order. Use it
    after fixing a projection, or after adding one for events that already happened. It needs
    the events to still be in `domain_events`. Readers see the table fill up again while it
    runs, so run it in a quiet period; run it again if it is interrupted. `mix
    vic.projection.rebuild` runs it from the command line.

# `description`
*macro* 

Describe the projection in a sentence.

# `field`
*macro* 

A column of the projection table. Options: `:default`, `:description`.

# `on`
*macro* 

What to do to a row when an event matching `pattern` arrives (see the module docs).

# `projection`
*macro* 

Declares the projection's fields. `description` and `field` go inside.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
