Docs / ArchitectureKit / Rust / Fundamentals / Defining State

Defining State

The state holds what a command needs to decide on.

List the events it evolves by in an enum, with one variant per event type, and derive Events for it. Then define the state as a struct that derives Clone and Default, and implement the State trait, whose evolve method takes the state one event further:

#[derive(Events)]
enum BookEvent {
  Acquired(BookAcquired),
  Borrowed(BookBorrowed),
  Returned(BookReturned),
}

#[derive(Clone, Default)]
struct Book {
  is_acquired: bool,
  is_borrowed: bool,
}

impl State for Book {
  type Event = BookEvent;

  fn evolve(self, event: &BookEvent) -> Self {
    match event {
      BookEvent::Acquired(_) => Book { is_acquired: true, ..self },
      BookEvent::Borrowed(_) => Book { is_borrowed: true, ..self },
      BookEvent::Returned(_) => Book { is_borrowed: false, ..self },
    }
  }
}

The event type of every variant is taken from the event it holds, so it does not have to be repeated. The enum is the closed set of event types the state knows, and since match has to cover every variant, the compiler checks that evolve handles every event the state may see.

Note that every variant holds exactly one event, and that two variants of the same event type fail to compile.

Every read starts from the Default of the state, and evolve takes the state by value and returns the next one, so a read never changes what the next read starts from. The trait asks for Clone, so that the store can keep states in a cache (see Caching States), and for Send and 'static, so that a decider can take the state into a future that runs on any thread.

Reading an event of a type that the enum has no variant for fails with a permanent failure (see Handling Errors), since it usually points to a missing variant or a wrong subject. If a subject holds events that matter for no decision, such as BookInspected, which only records that somebody looked at a book, give them a variant all the same, whose arm returns the state unchanged. The arm then says that the event changes nothing, rather than a missing variant that makes reading fail:

#[derive(Events)]
enum BookEvent {
  Acquired(BookAcquired),
  // ...
  Inspected(BookInspected),
}

impl State for Book {
  type Event = BookEvent;

  fn evolve(self, event: &BookEvent) -> Self {
    match event {
      BookEvent::Acquired(_) => Book { is_acquired: true, ..self },
      // ...
      BookEvent::Inspected(_) => self,
    }
  }
}

Note that the data of such an event is decoded like that of every other event, and that its schema is part of BookEvent::schemas(), since the event is still written (see Registering Event Schemas).

Note that a decider returns variants of the enum, so it can only return events that the state has a rule for, which the compiler checks (see Executing Commands). To find out whether a state has a rule for an event type, call has_rule on the enum with the event type, as in BookEvent::has_rule("io.eventsourcingdb.library.book-borrowed"). Upcasters play no part in it, since a decider returns events of current types.