Defining Commands
A command describes what someone wants to do. Define it as a struct and implement the Command trait with two methods: subject, which returns the subject the command acts on, and preconditions, which returns the conditions under which its events may be written.
This makes the struct a Command:
#[derive(Clone)]
struct AcquireBook {
book: Subject,
title: String,
author: String,
isbn: String,
}
impl Command for AcquireBook {
fn subject(&self) -> Subject {
self.book.clone()
}
fn preconditions(&self) -> Vec<Precondition> {
vec![Precondition::on_state_read()]
}
}
#[derive(Clone)]
struct BorrowBook {
book: Subject,
reader_id: String,
borrowed_until: String,
}
impl Command for BorrowBook {
fn subject(&self) -> Subject {
self.book.clone()
}
fn preconditions(&self) -> Vec<Precondition> {
vec![Precondition::on_state_read()]
}
}
#[derive(Clone)]
struct ReturnBook {
book: Subject,
}
impl Command for ReturnBook {
fn subject(&self) -> Subject {
self.book.clone()
}
fn preconditions(&self) -> Vec<Precondition> {
vec![Precondition::on_state_read()]
}
}
on_state_read makes sure that the events are only written if the state the command is decided on still holds. For the other preconditions, see Using Preconditions.
The subject is a Subject, which is a path that EventSourcingDB accepts, such as /books/42. A value that is no such path can not be a Subject at all, so the command holds its subject as one, and a value that can not be part of a subject is refused when the command is built, not when it is executed. Subject::new panics for such a value, which fits a subject written in the code, while parse and SubjectScheme::try_build refuse it with an error, which fits one that comes from a request (see Composing Subjects).
Note that a command derives Clone, since executing it may decide on it once more after a conflict, which takes the command anew (see Guarding Against Concurrent Changes).