Docs / ArchitectureKit / Rust / Guides / Caching States

Caching States

If no event carries the whole state, so that the state can not start from the latest event of one type (see Reading Long Streams), the store can keep the states of the most recently used subjects in memory instead, so that the next command on one of them reads only the events written since.

To do so, call state_cache on the builder of the store, with the number of subjects to keep:

let store =
  Store::builder(client, "https://library.eventsourcingdb.io").state_cache(10_000).build();

Once the cache is full, the least recently used subject makes room. Such a subject is read in full again the next time, or from the latest event of the type that from_latest returns, if there is one (see Reading Long Streams).

The cache only holds what was read, never what a command has written. Every command reads all events after the ones its state was built from, including those written by other processes, so the cache stays correct if several processes write to the same subjects.

The cache tells states apart by their type and their subject. A type implements State once, with one enum of events, one evolve, and one set of upcasters, so two states that read the same subject in different ways are two types anyway: if one of them counts the loans of a book and the other one its returns, they are types such as LoanCount and ReturnCount, and the cache keeps them apart.

A cached state is handed to several commands, possibly at the same time, each as a clone of its own, so that a command can not change what the cache holds. Every state is Clone, so a state that holds collections is cached like any other. For example, a shelf collects the IDs of the books that BookShelved events put on it, each in its field book_id. It evolves by the enum ShelfEvent, whose variant BookShelved holds such an event, and derives Clone, which copies the Vec along with the shelf:

#[derive(Clone, Default)]
struct Shelf {
  book_ids: Vec<String>,
}

impl State for Shelf {
  type Event = ShelfEvent;

  fn evolve(mut self, event: &ShelfEvent) -> Self {
    match event {
      ShelfEvent::BookShelved(shelved) => self.book_ids.push(shelved.book_id.clone()),
    }

    self
  }
}

step and step_stored clone the state they get the same way, so that it stays unchanged (see Stepping Through States), and every read without a cached state starts from a new value of Default.

Note that a clone copies only what Clone copies. A field whose clone shares its data, such as an Arc around a Mutex, would be shared by the cached state and every command it is handed to, so keep a state to values whose clones are copies of their own.

Note that a number of 0 keeps no states, the same as leaving out state_cache, so that a configuration can turn the cache off.