Reading Events
Some reads need the events themselves rather than a state, and fit neither load nor a projection, for example one page of a long history, the events up to a certain one, or a single event. To read the events as they are stored, call the read function of the store with the subjects to read and the options of the read. Say which subjects with Subjects::tree or Subjects::exact, as for a projection (see Running Projections).
It returns a stream of the events, each of them a Result. To step through it, use StreamExt of the futures crate, which you add with cargo add futures:
use std::pin::pin;
use architecturekit::{ReadOptions, Subject, Subjects};
use futures::StreamExt;
let subjects = Subjects::exact(Subject::new("/books/42"));
let options = ReadOptions::default();
let events = store.read(&subjects, &options).await?;
let mut events = pin!(events);
while let Some(event) = events.next().await {
let event = event?;
// ...
}
With the default options, read hands out every event of the subjects, oldest first. To read fewer of them, or in the other order, set the fields of ReadOptions, one lower bound, one upper bound, and one order, so that options that contradict each other can not be written:
LowerBound::FromEventreads from the event with the given ID on, including it, andLowerBound::AfterEventreads the events after it.UpperBound::UpToEventreads up to the event with the given ID, including it, andUpperBound::BeforeEventreads the events before it.Order::NewestFirsthands out the newest event first. The bounds stay what they are.LowerBound::FromLatestEventstarts at the latest event of a type on a subject, including it, asfrom_latestdoes for a state (see Reading Long Streams). If the subject has no such event,IfLatestEventIsMissing::ReadEverythingreads all events, andIfLatestEventIsMissing::ReadNothingreads none.
For example, to read the next page of the history of a book, hand over the ID of the last event of the page before, here last_event. A bound takes the ID as a Revision, while an event of the client SDK holds it as text, so read it with parse, which fails with NotARevision for a text that is no event ID (see below):
use architecturekit::{LowerBound, Revision};
let last_event_id: Revision = last_event.id().parse()?;
let options =
ReadOptions { lower_bound: LowerBound::AfterEvent(last_event_id), ..ReadOptions::default() };
To read the history of a book from its latest audit on, up to a certain event:
use architecturekit::{IfLatestEventIsMissing, UpperBound};
let options = ReadOptions {
lower_bound: LowerBound::FromLatestEvent {
subject: Subject::new("/books/42"),
event_type: BookAudited::EVENT_TYPE.to_owned(),
if_missing: IfLatestEventIsMissing::ReadEverything,
},
upper_bound: UpperBound::UpToEvent(event_id),
..ReadOptions::default()
};
The IDs are revisions, Revision, as everywhere else in the kit. The database hands them out as one ascending sequence across all subjects, so a bound does not have to be an event of the subjects that are read. An ID that comes from a request is read with parse, which refuses one that is not the ID of an event, such as abc or an empty one, with a NotARevision that names it, as in not a revision: "abc", before anything is read. That way, an ID that comes from a request can be told apart from a failure of the database, and the httpapi module answers it with 400 Bad Request and the error as the message (see Mapping Errors to Status Codes).
Note that the database reads from the latest event of a type only oldest first, so LowerBound::FromLatestEvent together with Order::NewestFirst makes read panic.
Note that LowerBound::FromLatestEvent looks for the event on the given subject alone, not below it, and that this subject does not have to be one of those that are read.
Bounds that leave no room for any event are a mistake, since no event can ever lie between them, such as BeforeEvent of 0, AfterEvent and BeforeEvent with two neighboring IDs, a lower bound above the upper one, or AfterEvent of the largest revision, 9223372036854775807, after which no event can ever come. read refuses them with ReadError::EmptyRange, which names the values, before the database is asked, as in empty range: no event can lie after "0" and before "1" or empty range: no event can lie from "2" up to "1". The error names the values only, never the options, since a caller of an API does not know them, and the httpapi module answers it with 400 Bad Request and the error as the message (see Mapping Errors to Status Codes). A range that is only empty for now, on the other hand, such as the one after the last event written so far, is no mistake, since events can still come there, so read hands out no events for it, without an error. So a reader that pages backwards with BeforeEvent and NewestFirst stops at a page that ends with the event 0, since no event can lie before it.
Note that the lower bound of FromLatestEvent is only known once the database has found the latest event of the type, so together with it, only an upper bound that leaves no room on its own, BeforeEvent of 0, is refused that way. The database refuses an upper bound before the latest event of the type, and read then fails with ReadError::Permanent, since trying again never helps, and its source, the error of the client SDK, keeps the description the database gives.
To check the options of a read this way without reading, call the check_read_options function with the options, which returns the same refusal as a ReadOptionsError, or Ok(()), as check_preconditions does for the preconditions of a command (see Using Preconditions). That way, bounds that come from a request can be refused before doing something that must not happen for a read that read refuses, for example recording who reads the history of a book:
use architecturekit::check_read_options;
let options = ReadOptions {
lower_bound: LowerBound::AfterEvent(after),
upper_bound: UpperBound::BeforeEvent(before),
..ReadOptions::default()
};
check_read_options(&options)?;
// Record who reads the history of the book.
let subjects = Subjects::exact(Subject::new("/books/42"));
let events = store.read(&subjects, &options).await?;
Note that check_read_options panics where read does, for FromLatestEvent together with NewestFirst, before it checks any ID. It takes no subjects, since none of the checks depends on them. Options that pass can still make the database refuse the read, such as an upper bound before the latest event of the type of FromLatestEvent.
Every event is verified, like everything else the store reads, before the loop sees it (see Verifying Events). A failure belongs to a category, as with load (see Handling Errors), and ends the stream. A read that the database refuses at once fails already when awaiting read, rather than as the first element of the stream. Dropping the stream stops reading, so breaking out of the loop after a page is fine.
The events come as they are stored, as events of the client SDK, without upcasters or a state, since there is no state. To get at the data of an event, call the decode function (see Executing Commands).
The options of read cover those of a read of EventSourcingDB in full. To read with the client SDK all the same, call read_events on a Client of the client SDK, which takes the options of the database as they are. The events it hands out are not verified then, and a failure is an error of the client SDK, which belongs to no category.