use crate::db::Pool;
use crate::event_store::Event;
use crate::shared::Tag;
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
struct ContextWindow {
events: VecDeque<Event>,
}
impl ContextWindow {
fn highest_known_sequence(&self) -> Option<i64> {
self.events.back().map(|e| e.sequence)
}
fn covers_from(&self) -> Option<i64> {
self.events.front().map(|e| e.sequence)
}
fn push(&mut self, event: Event, capacity: usize) {
self.events.push_back(event);
while self.events.len() > capacity {
self.events.pop_front();
}
}
}
#[derive(Clone)]
pub struct EventCache {
capacity: usize,
contexts: Arc<tokio::sync::RwLock<HashMap<String, ContextWindow>>>,
}
impl EventCache {
pub fn new(capacity: usize) -> Self {
Self {
capacity,
contexts: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
}
}
pub async fn warm(&self, pool: &Pool, bounded_context: &str) -> crate::error::Result<()> {
let recent =
crate::db::list_recent_events_for_bounded_context(pool, bounded_context, self.capacity)
.await?;
let mut contexts = self.contexts.write().await;
contexts.insert(
bounded_context.to_string(),
ContextWindow {
events: recent.into(),
},
);
Ok(())
}
pub async fn append(&self, event: &Event) {
let mut contexts = self.contexts.write().await;
let window = contexts
.entry(event.bounded_context.name.clone())
.or_insert_with(|| ContextWindow {
events: VecDeque::new(),
});
window.push(event.clone(), self.capacity);
}
async fn freshen(&self, pool: &Pool, bounded_context: &str) -> crate::error::Result<()> {
let known = {
let contexts = self.contexts.read().await;
contexts
.get(bounded_context)
.and_then(|w| w.highest_known_sequence())
};
let latest = crate::db::latest_sequence(pool, bounded_context).await?;
if known == latest {
return Ok(());
}
let delta = crate::db::list_events_for_bounded_context_from(
pool,
bounded_context,
known.unwrap_or(-1),
)
.await?;
let mut contexts = self.contexts.write().await;
let window = contexts
.entry(bounded_context.to_string())
.or_insert_with(|| ContextWindow {
events: VecDeque::new(),
});
for event in delta {
window.push(event, self.capacity);
}
Ok(())
}
pub async fn try_events_after(
&self,
pool: &Pool,
bounded_context: &str,
after_sequence: i64,
) -> crate::error::Result<Option<Vec<Event>>> {
self.freshen(pool, bounded_context).await?;
let contexts = self.contexts.read().await;
let Some(window) = contexts.get(bounded_context) else {
return Ok(Some(Vec::new()));
};
match window.covers_from() {
None => Ok(Some(Vec::new())), Some(covers_from) if after_sequence >= covers_from - 1 => Ok(Some(
window
.events
.iter()
.filter(|e| e.sequence > after_sequence)
.cloned()
.collect(),
)),
Some(_) => Ok(None), }
}
pub async fn try_events_matching_tags(
&self,
pool: &Pool,
bounded_context: &str,
tags: &[Tag],
) -> crate::error::Result<Option<Vec<Event>>> {
let Some(events) = self.try_events_after(pool, bounded_context, -1).await? else {
return Ok(None);
};
Ok(Some(
events
.into_iter()
.filter(|e| tags.iter().any(|t| e.tags.contains(t)))
.collect(),
))
}
pub async fn try_event_by_sequence(
&self,
pool: &Pool,
bounded_context: &str,
sequence: i64,
) -> crate::error::Result<Option<Event>> {
self.freshen(pool, bounded_context).await?;
let contexts = self.contexts.read().await;
let Some(window) = contexts.get(bounded_context) else {
return Ok(None);
};
Ok(window
.events
.iter()
.find(|e| e.sequence == sequence)
.cloned())
}
}