# Using Preconditions

Every command declares at least one precondition, so that writing without any check is always a decision, never an oversight.

There are these kinds:

- `OnStateRead` guards the state the command is decided on.
- `OnPristineSubject`, `OnPopulatedSubject`, and `OnEventID` require the subject to have no events yet, to have events, or to be on a given event, for example the revision the caller hands over.
- `Require` turns any other precondition of the client SDK into one of the command, such as an EventQL query.
- `Unconditionally` writes without any check.

Preconditions can be combined, and all of them must hold. If a precondition does not hold, nothing is written, and `Execute` returns an error of the category `ErrConflict` (see **[Handling Errors](/docs/architecturekit/v2.0/handling-errors)**). If a command declares no preconditions, requires a precondition that is `nil`, as with `Require(nil)`, combines `Unconditionally` with others, or declares a zero `Precondition`, which none of these functions returns, `Execute` returns an error of the category `ErrPermanent` before reading anything. To check the preconditions of a command this way without executing it, call the `CheckPreconditions` function with the command, which returns the same error, or `nil`. The `CheckReadOptions` function does the same for the options of a read (see **[Reading Events](/docs/architecturekit/v2.0/reading-events)**).

To find out what kind a precondition is, call its `IsOnStateRead` or `IsUnconditional` function. Its `Database` function returns the precondition of the client SDK that the database checks for it: the one `Require` made it from, which is how `OnPristineSubject` and `OnPopulatedSubject` make theirs, or the one of `NewIsSubjectOnEventIDPrecondition` for `OnEventID`. For any other, it returns `false`.

## Guarding Against Concurrent Changes

If a command may only write events in case nothing has been written to its subject since `Execute` read the state, use the `OnStateRead` function. This fits most commands, since the decider decides on exactly that state:

```go
func (c ReturnBook) Preconditions() []architecturekit.Precondition {
  return []architecturekit.Precondition{
    architecturekit.OnStateRead(),
  }
}
```

`Execute` fills in the ID of the last event it has read. For a subject without any events, it requires the subject to still be pristine instead. This also holds with several processes writing to the same subjects, and with a state cache (see **[Caching States](/docs/architecturekit/v2.0/caching-states)**).

If something else was written in between, the command fails with an error of the category `ErrConflict`. Since the decider decided on the state that `Execute` read, deciding again on the new state is what would have happened had the command arrived a moment later. To have `Execute` do so, hand over the `WithConflictRetries` option when creating the store, with the number of times to decide again:

```go
store := architecturekit.NewStore(client, "https://library.eventsourcingdb.io", architecturekit.WithConflictRetries(4))
```

`Execute` then reads the state anew, and the decider decides on it, up to four more times. Once the retries are used up, the command fails with `ErrConflict`, as without the option.

*Note that a negative number of retries makes `WithConflictRetries` panic.*

*Note that the store decides again only on a conflict. A write whose outcome is unknown is never tried again, since its events may have been stored (see **[Handling Errors](/docs/architecturekit/v2.0/handling-errors)**).*

*(Changed in 2.1)* *Note that this only applies to commands whose preconditions include `OnStateRead`. A command that checks only a revision the caller hands over is never decided again, since the caller has to learn about the conflict, and deciding again would fail the same way (see **[Checking the Revision of the Caller](/docs/architecturekit/v2.0/using-preconditions#checking-the-revision-of-the-caller)**). A command that checks both is decided again, since the database does not say which precondition did not hold. If the revision of the caller is outdated, every attempt fails the same way, until the retries are used up. On the same subject, one of the two is enough: if the revision of the caller holds, so does `OnStateRead`.*

## Checking the Revision of the Caller

If a command may only write events in case its subject has not changed since the caller last read it, for example in a user interface, use the `OnEventID` function with the subject and the ID of the last event the caller has seen. For that, add a field for the ID:

```go
type BorrowBook struct {
  BookID          string
  ReaderID        string
  BorrowedUntil   string
  ExpectedEventID string
}

func (c BorrowBook) Preconditions() []architecturekit.Precondition {
  return []architecturekit.Precondition{
    architecturekit.OnEventID(c.Subject(), c.ExpectedEventID),
  }
}
```

*Note that the caller has to provide the event ID. A view can keep it for that purpose, as long as its items follow every event of their subject (see **[Defining Views](/docs/architecturekit/v2.0/defining-views)**).*

*Note that the event ID usually comes from the caller, so `Execute` checks it before it reads anything. An empty event ID, or one that is not an event ID at all, makes `Execute` fail with an error that wraps `ErrNotARevision` and names the ID, as in `not a revision: "abc"`, as `Read` does for a bound (see **[Reading Events](/docs/architecturekit/v2.0/reading-events)**), and the `httpapi` package answers it with `400 Bad Request` and the error as the message (see **[Mapping Errors to Status Codes](/docs/architecturekit/v2.0/mapping-errors-to-status-codes)**). It does so only once nothing else is wrong with the preconditions, so that a mistake in the code is not hidden behind one of the caller, and `Write`, `CheckPreconditions`, and the test fixture of `architecturekittest` refuse the ID the same way. `Require` with `NewIsSubjectOnEventIDPrecondition` of the client SDK leaves the ID to the database instead, which refuses such an ID as a malformed request, so that `Execute` fails with an error of the category `ErrPermanent`.*

## Preventing Duplicates

If a command may only write events in case its subject does not yet have any events, use the `OnPristineSubject` function:

```go
func (c AcquireBook) Preconditions() []architecturekit.Precondition {
  return []architecturekit.Precondition{
    architecturekit.OnPristineSubject(c.Subject()),
  }
}
```

## Requiring an Existing Subject *(Requires EventSourcingDB 1.2)*

If a command may only write events in case its subject already has at least one event, use the `OnPopulatedSubject` function. Combine it with `OnStateRead` to also guard the state:

```go
func (c ReturnBook) Preconditions() []architecturekit.Precondition {
  return []architecturekit.Precondition{
    architecturekit.OnPopulatedSubject(c.Subject()),
    architecturekit.OnStateRead(),
  }
}
```

*Note that a database without the `isSubjectPopulated` precondition refuses the write with `400 Bad Request`, so that `Execute` and `Write` fail with an error of the category `ErrPermanent`.*

## Enforcing Rules Across Subjects

If a command may only write events depending on an EventQL query, use the `NewIsEventQLQueryTruePrecondition` function of the client SDK, and wrap it with the `Require` function. For example, to acquire every ISBN only once, extend the preconditions of `AcquireBook`:

```go
func (c AcquireBook) Preconditions() []architecturekit.Precondition {
  return []architecturekit.Precondition{
    architecturekit.OnPristineSubject(c.Subject()),
    architecturekit.Require(eventsourcingdb.NewIsEventQLQueryTruePrecondition(fmt.Sprintf(
      "FROM e IN events WHERE e.type == 'io.eventsourcingdb.library.book-acquired' AND e.data.isbn == '%s' PROJECT INTO COUNT() == 0",
      c.ISBN,
    ))),
  }
}
```

*Note that the query must return a single row with a single value, which is interpreted as a boolean.*

The ISBN comes from outside, but becomes part of the query as text, so check it first. `Execute` sends the query to the database only together with the events, once the decider has decided, so a check in the decider keeps every value that is not an ISBN out of the query. Here, an ISBN consists of digits and hyphens, and may end with an `X`:

```go
var isbnPattern = regexp.MustCompile(`^[0-9][0-9-]*[0-9X]$`)

var acquireBook = architecturekit.NewDecider(bookState,
  func(ctx context.Context, cmd AcquireBook, book Book) ([]architecturekit.Event, error) {
    if !isbnPattern.MatchString(cmd.ISBN) {
      return nil, architecturekit.NewDomainError("%q is not an ISBN", cmd.ISBN)
    }

    // ...
  })
```

*Note that a value must never go into an EventQL query unchecked. A quote, as in `it's`, breaks the query, so that the write fails, and a value made up for that purpose changes what the query checks.*

*(Changed in 2.0; Changed in 2.1)* When the ISBN is taken, the database refuses the write with the code `precondition-violated`, the same answer as for a subject that has changed since it was read. Which precondition did not hold is only in the details of the answer, so the kit does not tell the two apart:

- The command fails with an error of the category `ErrConflict`, which is transient (see **[Handling Errors](/docs/architecturekit/v2.0/handling-errors)**).
- The `httpapi` package answers it with `409 Conflict` and the message `conflict: the data has changed since it was read`, which tells the caller to try again, although that never helps (see **[Handling Commands over HTTP](/docs/architecturekit/v2.0/handling-commands-over-http)**).
- *(Changed in 2.1)* If the command declares `OnStateRead` as well, and the store decides again on conflicts, `Execute` decides again and again in vain, until the retries are used up (see **[Guarding Against Concurrent Changes](/docs/architecturekit/v2.0/using-preconditions#guarding-against-concurrent-changes)**).

To answer a taken ISBN with a domain error instead, check it in the decider, against a view that holds the acquired books by their ISBN (see **[Defining Views](/docs/architecturekit/v2.0/defining-views)**). The catalog does not hold the ISBN, so here, a small view of its own does, which a projection fills (see **[Defining Projections](/docs/architecturekit/v2.0/projections#defining-projections)**):

```go
type ISBNItem struct {
  ISBN   string
  BookID string
}

func newISBNs() *architecturekit.InMemoryView[string, ISBNItem] {
  return architecturekit.NewInMemoryView(func(item ISBNItem) string { return item.ISBN })
}

func newISBNProjection(isbns *architecturekit.InMemoryView[string, ISBNItem]) *architecturekit.TypedProjection {
  return architecturekit.NewTypedProjection().
    On(func(ctx context.Context, event architecturekit.Envelope[BookAcquired]) error {
      _, err := isbns.Insert(ctx, event.ID, ISBNItem{
        ISBN:   event.Data.ISBN,
        BookID: bookIDOf(event.Subject),
      })
      return err
    })
}
```

The decider gets the view from a function that creates it, as it gets a clock (see **[Using the Current Time](/docs/architecturekit/v2.0/using-the-current-time)**). If the ISBN is taken, it returns a domain error, which the `httpapi` package answers with `422 Unprocessable Entity` and the error as the message, as in `ISBN has already been acquired: 978-0756906788`:

```go
var ErrISBNAlreadyAcquired = architecturekit.NewDomainError("ISBN has already been acquired")

func acquireBookDecider(isbns architecturekit.KeyedView[string, ISBNItem]) architecturekit.Decider[AcquireBook, Book] {
  return architecturekit.NewDecider(bookState,
    func(ctx context.Context, cmd AcquireBook, book Book) ([]architecturekit.Event, error) {
      if !isbnPattern.MatchString(cmd.ISBN) {
        return nil, architecturekit.NewDomainError("%q is not an ISBN", cmd.ISBN)
      }

      _, isTaken, err := isbns.Get(ctx, cmd.ISBN)
      if err != nil {
        return nil, err
      }
      if isTaken {
        return nil, fmt.Errorf("%w: %s", ErrISBNAlreadyAcquired, cmd.ISBN)
      }

      // ...
    })
}

isbns := newISBNs()
acquireBook := acquireBookDecider(isbns)
```

Keep the EventQL precondition all the same, as the guard. A view lags behind the events, so when two acquisitions of the same ISBN arrive at the same moment, both pass the view, since it has seen neither of them. The database writes only one of them, and the other fails with `ErrConflict`, which is the right answer in that rare case: once the view has seen the first acquisition, trying again gets the domain error.

## Writing Unconditionally

If a command may write its events whatever has been written to its subject in the meantime, for example `CommentOnBook`, which only records a comment that does not depend on the state, use the `Unconditionally` function:

```go
func (c CommentOnBook) Preconditions() []architecturekit.Precondition {
  return []architecturekit.Precondition{
    architecturekit.Unconditionally(),
  }
}
```

*Note that `Unconditionally` can not be combined with other preconditions.*
