Files
NLnetLabs-krill/src/storage/events.rs
T

676 lines
20 KiB
Rust

use std::any::Any;
use std::collections::HashMap;
use std::fmt::Display;
use std::fs::File;
use std::io;
use std::io::Write;
use std::ops::Deref;
use std::path::PathBuf;
use std::sync::RwLock;
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json;
use crate::util::file;
use std::sync::Arc;
//------------ Storable ------------------------------------------------------
pub trait Storable: Serialize + DeserializeOwned + Sized {}
impl<T: Serialize + DeserializeOwned + Sized> Storable for T { }
//------------ AggregateId ---------------------------------------------------
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct AggregateId(String);
impl AggregateId {
pub fn new(s: &str) -> Self {
AggregateId(s.to_string())
}
}
//------------ Aggregate -----------------------------------------------------
pub trait Aggregate: Storable + Clone {
type Command: Command<Event = Self::Event>;
type Event: Event;
type InitEvent: Event;
type Error: std::error::Error;
/// Creates a new instance. Expects an event with data needed to
/// initialise the instance. Typically this means that a specific
/// 'create' event is passed, with all the needed data, or just an empty
/// marker if no data is needed. Implementations must return an error in
/// case the instance cannot be created.
fn init(event: Self::InitEvent) -> Result<Self, Self::Error>;
/// Returns the current version of the aggregate.
fn version(&self) -> u64;
/// Moves this, and applies the event to this. This MUST not result in
/// any errors. Applying the event just updates data and is side-effect
/// free.
///
/// Note that both self and the event are moved. This is done because we
/// want to enable moving data into the new aggregate without the need for
/// additional allocations.
fn apply(&mut self, event: Self::Event);
/// Processes a command. I.e. validate the command, and return a list of
/// events that will result in the desired new state, but do not apply
/// these event here. The command processing must be side-effect free.
///
/// The command is moved, because we want to enable moving its data
/// without reallocating.
fn process_command(&self, command: Self::Command) -> Result<Vec<Self::Event>, Self::Error>;
}
//------------ Event --------------------------------------------------------
pub trait Event: Storable {
/// Identifies the aggregate, useful when storing and retrieving the event.
fn id(&self) -> &AggregateId;
/// The version of the aggregate that this event updates.
/// In other words, an aggregate that is currently at version x, will get
/// version x + 1, when the event for version x is applied.
fn version(&self) -> u64;
}
#[derive(Deserialize, Serialize)]
pub struct StoredEvent<E: Storable> {
id: AggregateId,
version: u64,
#[serde(deserialize_with = "E::deserialize")]
details: E
}
impl<E: Storable> StoredEvent<E> {
pub fn new(id: &AggregateId, version: u64, event: E) -> Self {
StoredEvent { id: id.clone(), version, details: event }
}
pub fn event(&self) -> &E { & self.details }
pub fn into_details(self) -> E { self.details }
pub fn unwrap(self) -> (AggregateId, u64, E) {
(self.id, self.version, self.details)
}
}
impl<E: Storable> Event for StoredEvent<E> {
fn id(&self) -> &AggregateId {
&self.id
}
fn version(&self) -> u64 {
self.version
}
}
//------------ Command -------------------------------------------------------
/// Commands are used to send an intent to change an aggregate.
///
/// Think of this as the data container for your update API, plus some
/// meta-data to ensure that the command is sent to the right instance of an
/// Aggregate, and that concurrency issues are handled.
pub trait Command: Storable {
/// Identify the type of event returned by the aggregate that uses this
/// command. This is needed because we may need to check whether a
/// command conflicts with recent events.
type Event: Event;
/// Identifies the aggregate, useful when storing and retrieving the event.
fn id(&self) -> &AggregateId;
/// The version of the aggregate that this command updates. If this
/// command should update whatever the latest version happens to be, then
/// use None here.
fn version(&self) -> Option<u64>;
/// In case of concurrent processing of commands, the aggregate may be
/// outdated when a command is applied. In such cases this method expects
/// the list of events that happened since the ['affected_version'] and
/// will return whether there is a conflict. If there is no conflict that
/// the command may be applied again.
///
/// Note that this defaults to true, which is the safe choice when in
/// doubt. If you choose to implement this, then you will also need to
/// implement the ['set_affected_version'] function.
fn conflicts(&self, _events: &[Self::Event]) -> bool { true }
}
//------------ SentCommand ---------------------------------------------------
/// Convenience wrapper so that implementations can just implement
/// ['CommandDetails'] and leave the id and version boilerplate.
#[derive(Deserialize, Serialize)]
pub struct SentCommand<C: CommandDetails> {
id: AggregateId,
version: Option<u64>,
#[serde(deserialize_with = "C::deserialize")]
details: C
}
impl<C: CommandDetails> Command for SentCommand<C> {
type Event = C::Event;
fn id(&self) -> &AggregateId {
&self.id
}
fn version(&self) -> Option<u64> {
self.version
}
}
impl<C: CommandDetails> SentCommand<C> {
pub fn new(id: &AggregateId, version: Option<u64>, details: C) -> Self {
SentCommand { id: id.clone(), version, details }
}
pub fn into_details(self) -> C { self.details }
}
//------------ CommandDetails ------------------------------------------------
/// Implement this for an enum with CommandDetails, so you you can reuse the
/// id and version boilerplate from ['SentCommand'].
pub trait CommandDetails: Storable {
type Event: Event;
}
//------------ AggregateRef --------------------------------------------------
/// This type wraps the Aggregate references returned by the AggregateManager,
/// so that we can change the implementation details of the of the latter.
/// This derefs to the Aggregate.
pub struct AggregateRef<A: Aggregate> {
agg: Arc<A>
}
impl<A: Aggregate> Deref for AggregateRef<A> {
type Target = A;
fn deref(&self) -> &'_ Self::Target {
&self.agg
}
}
impl<A: Aggregate> AsRef<A> for AggregateRef<A> {
fn as_ref(&self) -> &A {
&self.agg
}
}
//------------ AggregateManager ----------------------------------------------
/// This type is responsible for managing Aggregates. I.e. creating new
/// Aggregate instances, returning a reference for reading, dispatching
/// commands to an aggregate, and storing them.
pub struct AggregateManager<A:Aggregate, S: KeyStore> {
/// Stores a the most recent aggregate.
///
/// Note. We may wish to change this in future. E.g. just remember the
/// version of the aggregate and get it from storage as needed. Or even
/// make this a choice - keep things we use often in memory, but not
/// other things.
///
/// We may then also wish to change the return types, and maybe we will
/// have to push the write locking down to the keystore.. I.e. any new
/// save already requires a new key - it is a write once store. So, if we
/// get an error then that is an indication of a concurrency issue.
cache: RwLock<HashMap<AggregateId, Arc<A>>>,
store: S
}
impl<A: 'static + Aggregate, S: 'static + KeyStore> AggregateManager<A, S> {
pub fn new(store: S) -> Self {
let values = RwLock::new(HashMap::new());
AggregateManager {
cache: values, store
}
}
fn update_cache(
&self,
id: &AggregateId,
mut force: bool
) -> Result<(), AggMgrErr<A::Error, S::Error>> {
let mut cache = self.cache.write().unwrap();
let mut has_key = cache.contains_key(id);
if ! has_key {
let init_key = S::key_for_event(id, 0);
if let Some(init) = self.store.get::<A::InitEvent>(&init_key)
.map_err(AggMgrErr::KeyStoreError)? {
let mut agg = A::init(init).map_err(AggMgrErr::AggregateError)?;
cache.insert(id.clone(), Arc::new(agg));
force = true;
has_key = true;
}
}
if has_key && force {
// We MUST have an entry now
let arc = cache.get_mut(id).unwrap();
let agg = Arc::make_mut(arc);
loop {
let ver = agg.version();
let key = S::key_for_event(id, ver);
if let Some(event) = self.store.get::<A::Event>(&key)
.map_err(AggMgrErr::KeyStoreError)? {
agg.apply(event)
} else {
break
}
}
}
Ok(())
}
/// Get a reference to the latest version of the aggregate.
#[allow(clippy::type_complexity)]
pub fn get_latest(
&self,
id: &AggregateId
) -> Result<Option<AggregateRef<A>>, AggMgrErr<A::Error, S::Error>> {
self.update_cache(id, false)?;
Ok(self.cache.read().unwrap().get(id)
.map(|arc| AggregateRef { agg: arc.clone() } ))
}
#[allow(clippy::type_complexity)]
pub fn create(
&self,
id: &AggregateId,
event: A::InitEvent
) -> Result<(), AggMgrErr<A::Error, S::Error>> {
self.update_cache(id, true)?;
let mut cache = self.cache.write().unwrap();
if cache.contains_key(id) {
Err(AggMgrErr::AggregateAlreadyExists)
} else {
let key = S::key_for_event(id, 0);
self.store.store(&key, &event).map_err(AggMgrErr::KeyStoreError)?;
let agg = A::init(event).map_err(AggMgrErr::AggregateError)?;
cache.insert(id.clone(), Arc::new(agg));
Ok(())
}
}
/// Apply a command to the latest aggregate, save the events and return
/// the updated aggregate.
#[allow(clippy::type_complexity)]
pub fn apply(
&self,
command: A::Command
) -> Result<(), AggMgrErr<A::Error, S::Error>> {
let id = command.id().clone();
self.update_cache(&id, true)?;
let mut cache = self.cache.write().unwrap();
match cache.get_mut(&id) {
None => Err(AggMgrErr::AggregateDoesNotExist),
Some(agg) => {
let agg = Arc::make_mut(agg);
if let Some(version) = command.version() {
if version != agg.version() {
// TODO check conflicts
return Err(AggMgrErr::ConcurrentModification)
}
}
let events = agg.process_command(command)
.map_err(AggMgrErr::AggregateError)?;
for e in events {
let key = S::key_for_event(&id, e.version());
self.store.store(&key, &e).map_err(AggMgrErr::KeyStoreError)?;
agg.apply(e);
}
Ok(())
}
}
}
}
//------------ AggMgrErr -----------------------------------------------------
#[derive(Debug, Display)]
pub enum AggMgrErr<A: Display, K: Display> {
#[display(fmt = "Aggregate does not exist")]
AggregateDoesNotExist,
#[display(fmt = "Aggregate already exists")]
AggregateAlreadyExists,
#[display(fmt = "Concurrent modification. Command rejected")]
ConcurrentModification,
#[display(fmt = "{}", _0)]
AggregateError(A),
#[display(fmt = "{}", _0)]
KeyStoreError(K),
}
//------------ KeyStore ------------------------------------------------------
/// Generic KeyStore for AggregateManager
pub trait KeyStore {
type Key;
type Error: std::error::Error;
fn key_for_snapshot(id: &AggregateId, version: u64) -> Self::Key;
fn key_for_event(id: &AggregateId, version: u64) -> Self::Key;
/// Returns whether a key already exists.
fn has_key(&self, key: &Self::Key) -> bool;
/// Throws an error if the key already exists.
fn store<V: Any + Serialize>(&self, key: &Self::Key, value: &V) -> Result<(), Self::Error>;
/// Get the value for this key, if any exists.
fn get<V: Any + Storable>(&self, key: &Self::Key) -> Result<Option<V>, Self::Error>;
}
//------------ DiskKeyStore --------------------------------------------------
/// This type can store and retrieve values to/from disk, using json
/// serialization.
pub struct DiskKeyStore {
dir: PathBuf
}
impl KeyStore for DiskKeyStore {
type Key = PathBuf;
type Error = DiskKeyStoreError;
fn key_for_snapshot(id: &AggregateId, version: u64) -> Self::Key {
PathBuf::from(format!("snapshot-{}-{}", id.0, version))
}
fn key_for_event(id: &AggregateId, version: u64) -> Self::Key {
PathBuf::from(format!("delta-{}-{}", id.0, version))
}
fn has_key(&self, key: &Self::Key) -> bool {
self.file_path(key).exists()
}
fn store<V: Any + Serialize>(&self, key: &Self::Key, value: &V) -> Result<(),
Self::Error> {
if self.has_key(key) {
Err(DiskKeyStoreError::KeyExists(key.to_string_lossy().to_string()))
} else {
let mut f = file::create_file_with_path(&self.file_path(key))?;
let json = serde_json::to_string(value)?;
f.write_all(json.as_ref())?;
Ok(())
}
}
fn get<V: Any + Storable>(&self, key: &Self::Key) -> Result<Option<V>, Self::Error> {
if self.has_key(key) {
let f = File::open(self.file_path(&key))?;
let v: V = serde_json::from_reader(f)?;
Ok(Some(v))
} else {
Ok(None)
}
}
}
impl DiskKeyStore {
pub fn new(dir: PathBuf) -> Self {
DiskKeyStore { dir }
}
fn file_path(&self, key: &<Self as KeyStore>::Key) -> PathBuf {
let mut file_path = self.dir.clone();
file_path.push(key);
file_path
}
}
//------------ DiskKeyStoreError ---------------------------------------------
/// This type defines possible Errors for KeyStore
#[derive(Debug, Display)]
pub enum DiskKeyStoreError {
#[display(fmt = "{}", _0)]
IoError(io::Error),
#[display(fmt = "{}", _0)]
JsonError(serde_json::Error),
#[display(fmt = "Key already exists: {}", _0)]
KeyExists(String)
}
impl From<io::Error> for DiskKeyStoreError {
fn from(e: io::Error) -> Self { DiskKeyStoreError::IoError(e) }
}
impl From<serde_json::Error> for DiskKeyStoreError {
fn from(e: serde_json::Error) -> Self { DiskKeyStoreError::JsonError(e) }
}
impl std::error::Error for DiskKeyStoreError { }
//------------ Tests ---------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
use crate::util::test;
#[derive(Clone, Deserialize, Serialize)]
struct Person {
id: AggregateId,
version: u64,
name: String,
age: u8
}
impl Person {
pub fn id(&self) -> &AggregateId { &self.id }
pub fn version(&self) -> u64 { self.version }
pub fn name(&self) -> &String { &self.name }
pub fn age(&self) -> u8 { self.age }
}
#[derive(Deserialize, Serialize)]
pub struct InitPersonDetails {
pub name: String
}
#[derive(Deserialize, Serialize)]
enum PersonEventDetails {
NameChanged(String),
HadBirthday
}
#[derive(Deserialize, Serialize)]
enum PersonCommandDetails {
ChangeName(String),
GoAroundTheSun
}
impl CommandDetails for PersonCommandDetails {
type Event = PersonEvent;
}
type InitPersonEvent = StoredEvent<InitPersonDetails>;
impl InitPersonEvent {
pub fn init(id: &AggregateId, name: &str) -> Self {
StoredEvent::new(id, 0, InitPersonDetails { name: name.to_string()})
}
}
type PersonEvent = StoredEvent<PersonEventDetails>;
impl PersonEvent {
pub fn had_birthday(p: &Person) -> Self {
StoredEvent::new(p.id(), p.version, PersonEventDetails::HadBirthday)
}
pub fn name_changed(p: &Person, name: String) -> Self {
StoredEvent::new(
p.id(),
p.version,
PersonEventDetails::NameChanged(name))
}
}
type PersonCommand = SentCommand<PersonCommandDetails>;
impl PersonCommand {
pub fn go_around_sun(id: &AggregateId, version: Option<u64>) -> Self {
Self::new(id, version, PersonCommandDetails::GoAroundTheSun)
}
pub fn change_name(id: &AggregateId, version: Option<u64>, s: &str) -> Self {
let details = PersonCommandDetails::ChangeName(s.to_string());
Self::new(id, version, details)
}
}
#[derive(Clone, Debug, Display)]
enum PersonError {
#[display(fmt = "No person can live longer than 255 years")]
TooOld
}
impl std::error::Error for PersonError {}
impl Aggregate for Person {
type Command = PersonCommand;
type Event = PersonEvent;
type InitEvent = InitPersonEvent;
type Error = PersonError;
fn init(event: Self::InitEvent) -> Result<Self, Self::Error> {
let (id, _version, init) = event.unwrap();
Ok(Person {
id, version: 1, name: init.name, age: 0
})
}
fn version(&self) -> u64 {
self.version
}
fn apply(&mut self, event: Self::Event) {
match event.into_details() {
PersonEventDetails::NameChanged(name) => { self.name = name },
PersonEventDetails::HadBirthday => { self.age = self.age + 1 }
}
self.version = self.version + 1;
}
fn process_command(&self, command: Self::Command) -> Result<Vec<Self::Event>, Self::Error> {
match command.into_details() {
PersonCommandDetails::ChangeName(name) => {
let event = PersonEvent::name_changed(&self, name);
Ok(vec![event])
},
PersonCommandDetails::GoAroundTheSun => {
if self.age == 255 {
Err(PersonError::TooOld)
} else {
let event = PersonEvent::had_birthday(&self);
Ok(vec![event])
}
}
}
}
}
type PersonManager = AggregateManager<Person, DiskKeyStore>;
#[test]
fn test() {
test::test_with_tmp_dir(|d| {
let storage = DiskKeyStore::new(d.clone());
let manager = PersonManager::new(storage);
let id_alice = AggregateId::new("alice");
let alice_init = InitPersonEvent::init(&id_alice, "alice smith");
manager.create(&id_alice, alice_init).unwrap();
let alice = manager.get_latest(&id_alice).unwrap().unwrap();
assert_eq!(alice.name(), "alice smith");
assert_eq!(alice.age(), 0);
let mut age = 0;
loop {
manager.apply(
PersonCommand::go_around_sun(&id_alice, None)
).unwrap();
age = age + 1;
if age == 21 {
break
}
}
let alice = manager.get_latest(&id_alice).unwrap().unwrap();
assert_eq!(alice.name(), "alice smith");
assert_eq!(alice.age(), 21);
manager.apply(
PersonCommand::change_name(&id_alice, Some(22), "alice smith-doe")
).unwrap();
let alice = manager.get_latest(&id_alice).unwrap().unwrap();
assert_eq!(alice.name(), "alice smith-doe");
assert_eq!(alice.age(), 21);
// Should read state from disk
let storage = DiskKeyStore::new(d);
let manager = PersonManager::new(storage);
let alice = manager.get_latest(&id_alice).unwrap().unwrap();
assert_eq!(alice.name(), "alice smith-doe");
assert_eq!(alice.age(), 21);
})
}
}