# Projections

This guide shows how to define projections, which turn events into views, how to run them, how to resume them where they stopped, and how to apply events in batches.

## Defining Projections

A projection turns events into a view. Define a type and implement the `Apply` function, which receives every event as it is stored. This makes the type a `Projection`:

```go
type CatalogProjection struct {
  catalog *architecturekit.ItemView[BookItem]
}

func (p CatalogProjection) Apply(ctx context.Context, event eventsourcingdb.Event) error {
  values, ok := bookSubject.Match(event.Subject)
  if !ok {
    return nil
  }

  bookID := values["book"]
  isBook := func(item BookItem) bool {
    return item.ID == bookID
  }

  switch event.Type {
  case BookAcquired{}.EventType():
    var data BookAcquired
    if err := json.Unmarshal(event.Data, &data); err != nil {
      return err
    }

    p.catalog.Insert(BookItem{
      ID:      bookID,
      Title:   data.Title,
      Author:  data.Author,
      EventID: event.ID,
    })

  case BookBorrowed{}.EventType():
    var data BookBorrowed
    if err := json.Unmarshal(event.Data, &data); err != nil {
      return err
    }

    p.catalog.Update(isBook, func(item *BookItem) {
      item.IsBorrowed = true
      item.BorrowedUntil = data.BorrowedUntil
      item.EventID = event.ID
    })

  case BookReturned{}.EventType():
    p.catalog.Update(isBook, func(item *BookItem) {
      item.IsBorrowed = false
      item.BorrowedUntil = ""
      item.EventID = event.ID
    })
  }

  return nil
}

catalogProjection := CatalogProjection{catalog: catalog}
```

For a projection that needs no type of its own, use `ProjectionFunc`, which turns a function into a projection:

```go
logProjection := architecturekit.ProjectionFunc(func(ctx context.Context, event eventsourcingdb.Event) error {
  log.Println(event.Subject, event.Type)
  return nil
})
```

## Running Projections

To run a projection, call the `RunProjection` function with a context, the store, the subject, whether to read recursively, and the projection. The function first applies all events that are already stored, then observes new events until the context is canceled. Since it blocks, run it in a goroutine:

```go
ctx, cancel := context.WithCancel(context.TODO())

go func() {
  err := architecturekit.RunProjection(ctx, store, "/books", true, catalogProjection)
  if err != nil {
    // ...
  }
}()

// Somewhere else, cancel the context, which will cause
// the projection to stop.
cancel()
```

Canceling the context is not an error. If `Apply` returns an error, the function stops and returns it.

To only apply the events that are already stored, call the `CatchUpProjection` function instead. It takes the same arguments and returns once all stored events have been applied:

```go
err := architecturekit.CatchUpProjection(context.TODO(), store, "/books", true, catalogProjection)
if err != nil {
  // ...
}
```

## Resuming Projections

By default, a projection starts from the first event every time it runs, which fits a view held in memory. For a view that keeps its data, the projection can resume where it stopped instead.

If the view can store a checkpoint, but not together with the data, additionally implement the `Resumable` interface. `Checkpoint` returns the ID of the last event saved, or an empty string if there is none, and `SaveCheckpoint` saves it:

```go
type BookTableProjection struct {
  // ...
}

func (p *BookTableProjection) Apply(ctx context.Context, event eventsourcingdb.Event) error {
  // ...
}

func (p *BookTableProjection) Checkpoint(ctx context.Context) (string, error) {
  // ...
}

func (p *BookTableProjection) SaveCheckpoint(ctx context.Context, eventID string) error {
  // ...
}
```

*Note that the checkpoint is saved after the events have been applied. After a crash, events may therefore be applied a second time, so `Apply` must be idempotent.*

To find out how a projection will be run, call the `ModeOf` function. It returns a `Mode`, which is `ModeRebuild` or `ModeResumable`:

```go
mode := architecturekit.ModeOf(catalogProjection)
// architecturekit.ModeRebuild
```

*Note that the mode depends on which interfaces a projection implements. If a function's signature does not match, the projection silently runs in `ModeRebuild`. To catch that, check the mode in a test (see [Testing Projections](/docs/architecturekit/testing-projections)).*

If the view can store the data and the checkpoint together, as a relational database can, implement the `Transactional` interface instead of `Projection`. A transactional projection applies events only within a transaction, so it has no `Apply` function of its own. `Begin` starts a transaction and returns a `Tx`, which applies the events, and commits them together with the ID of the last event, or rolls them back:

```go
type TransactionalBookTableProjection struct {
  // ...
}

func (p *TransactionalBookTableProjection) Checkpoint(ctx context.Context) (string, error) {
  // ...
}

func (p *TransactionalBookTableProjection) Begin(ctx context.Context) (architecturekit.Tx, error) {
  // ...
}

type bookTableTx struct {
  // ...
}

func (tx *bookTableTx) Apply(ctx context.Context, event eventsourcingdb.Event) error {
  // ...
}

func (tx *bookTableTx) Commit(ctx context.Context, lastEventID string) error {
  // ...
}

func (tx *bookTableTx) Rollback(ctx context.Context) error {
  // ...
}
```

To run a transactional projection, call the `RunTransactionalProjection` or the `CatchUpTransactionalProjection` function instead of `RunProjection` or `CatchUpProjection`. They take the same arguments:

```go
err := architecturekit.RunTransactionalProjection(ctx, store, "/books", true, &TransactionalBookTableProjection{})
if err != nil {
  // ...
}
```

*Note that `RunProjection`, `CatchUpProjection`, and `Tracking` panic for a projection that implements `Transactional` in addition to `Apply`, since calling `Apply` would bypass the transactions.*

## Batching Events

By default, the checkpoint is saved, or the transaction is committed, after every event. To do so less often, implement the `Batched` interface on a resumable or transactional projection, and return how many events to apply at once, separately for catching up and for observing:

```go
func (p *BookTableProjection) BatchSizes() (catchUp, live int) {
  return 1000, 1
}
```

Values below `1` count as `1`.

*Note that a resumable projection may apply up to that many events a second time after a crash.*
