use std::{hash::Hash, sync::Arc};
use ulid::Ulid;
use crate::{
cursor::{Args, ReadResult, Value},
Event, RoutingKey, WriteError,
};
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct EventFilter {
pub aggregate_type: String,
pub aggregate_id: Option<String>,
pub name: Option<String>,
}
impl EventFilter {
pub fn exact(
aggregate_type: impl Into<String>,
id: impl Into<String>,
name: impl Into<String>,
) -> Self {
Self {
aggregate_type: aggregate_type.into(),
aggregate_id: Some(id.into()),
name: Some(name.into()),
}
}
pub fn by_type(value: impl Into<String>) -> Self {
Self {
aggregate_type: value.into(),
aggregate_id: None,
name: None,
}
}
pub fn by_id(aggregate_type: impl Into<String>, id: impl Into<String>) -> Self {
Self {
aggregate_type: aggregate_type.into(),
aggregate_id: Some(id.into()),
name: None,
}
}
pub fn by_event(aggregate_type: impl Into<String>, name: impl Into<String>) -> Self {
Self {
aggregate_type: aggregate_type.into(),
aggregate_id: None,
name: Some(name.into()),
}
}
}
impl Hash for EventFilter {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.aggregate_type.hash(state);
self.aggregate_id.hash(state);
self.name.hash(state);
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct SubscriberStatus {
pub running: bool,
pub cursor: Option<Value>,
}
#[async_trait::async_trait]
pub trait Executor: Send + Sync + 'static {
fn default_routing_key(&self) -> Option<&str> {
None
}
async fn write(&self, events: Vec<Event>) -> Result<(), WriteError>;
async fn replicate(&self, events: Vec<Event>) -> Result<(), WriteError> {
self.write(events).await
}
fn write_watch(&self) -> Option<tokio::sync::watch::Receiver<u64>> {
None
}
async fn stable_timestamp(&self) -> anyhow::Result<Option<u64>> {
Ok(None)
}
async fn get_subscriber_cursor(&self, key: String) -> anyhow::Result<Option<Value>>;
async fn is_subscriber_running(&self, key: String, worker_id: Ulid) -> anyhow::Result<bool>;
async fn subscriber_status(
&self,
key: String,
worker_id: Ulid,
) -> anyhow::Result<SubscriberStatus> {
if !self.is_subscriber_running(key.clone(), worker_id).await? {
return Ok(SubscriberStatus {
running: false,
cursor: None,
});
}
let cursor = self.get_subscriber_cursor(key).await?;
Ok(SubscriberStatus {
running: true,
cursor,
})
}
async fn latest_version(
&self,
aggregate_type: String,
aggregate_id: String,
) -> anyhow::Result<u16> {
const PAGE_SIZE: u16 = 4096;
let filters: Arc<[EventFilter]> =
Arc::from([EventFilter::by_id(aggregate_type, aggregate_id)]);
let mut max = 0u16;
let mut after = None;
loop {
let result = self
.read(
Some(filters.clone()),
None,
Args::forward(PAGE_SIZE, after),
None,
)
.await?;
if let Some(page_max) = result.edges.iter().map(|e| e.node.version).max() {
max = max.max(page_max);
}
if !result.page_info.has_next_page {
break;
}
match result.page_info.end_cursor {
Some(cursor) => after = Some(cursor),
None => break,
}
}
Ok(max)
}
async fn stream_routing_key(
&self,
aggregate_type: String,
aggregate_id: String,
) -> anyhow::Result<Option<Option<String>>> {
let result = self
.read(
Some(Arc::from([EventFilter::by_id(
aggregate_type,
aggregate_id,
)])),
None,
Args::forward(1, None),
None,
)
.await?;
Ok(result.edges.first().map(|e| e.node.routing_key.clone()))
}
async fn upsert_subscriber(&self, key: String, worker_id: Ulid) -> anyhow::Result<()>;
async fn acknowledge(
&self,
key: String,
worker_id: Ulid,
cursor: Value,
lag: u64,
) -> anyhow::Result<bool>;
async fn read(
&self,
aggregators: Option<Arc<[EventFilter]>>,
routing_key: Option<RoutingKey>,
args: Args,
to_micros: Option<u64>,
) -> anyhow::Result<ReadResult<Event>>;
async fn latest_timestamp(
&self,
aggregators: Option<Arc<[EventFilter]>>,
routing_key: Option<RoutingKey>,
) -> anyhow::Result<u64>;
async fn get_snapshot(
&self,
aggregate_type: String,
aggregate_revision: String,
id: String,
) -> anyhow::Result<Option<(Vec<u8>, Value)>>;
async fn save_snapshot(
&self,
aggregate_type: String,
aggregate_revision: String,
id: String,
data: Vec<u8>,
cursor: Value,
) -> anyhow::Result<()>;
async fn delete_snapshot(&self, aggregate_type: String, id: String) -> anyhow::Result<()>;
}
pub struct Evento {
inner: Arc<Box<dyn Executor>>,
default_routing_key: Option<String>,
}
impl Clone for Evento {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
default_routing_key: self.default_routing_key.clone(),
}
}
}
#[async_trait::async_trait]
impl Executor for Evento {
fn default_routing_key(&self) -> Option<&str> {
self.default_routing_key.as_deref()
}
async fn write(&self, events: Vec<Event>) -> Result<(), WriteError> {
self.inner.write(events).await
}
async fn replicate(&self, events: Vec<Event>) -> Result<(), WriteError> {
self.inner.replicate(events).await
}
fn write_watch(&self) -> Option<tokio::sync::watch::Receiver<u64>> {
self.inner.write_watch()
}
async fn stable_timestamp(&self) -> anyhow::Result<Option<u64>> {
self.inner.stable_timestamp().await
}
async fn read(
&self,
aggregators: Option<Arc<[EventFilter]>>,
routing_key: Option<RoutingKey>,
args: Args,
to_micros: Option<u64>,
) -> anyhow::Result<ReadResult<Event>> {
self.inner
.read(aggregators, routing_key, args, to_micros)
.await
}
async fn latest_timestamp(
&self,
aggregators: Option<Arc<[EventFilter]>>,
routing_key: Option<RoutingKey>,
) -> anyhow::Result<u64> {
self.inner.latest_timestamp(aggregators, routing_key).await
}
async fn get_subscriber_cursor(&self, key: String) -> anyhow::Result<Option<Value>> {
self.inner.get_subscriber_cursor(key).await
}
async fn is_subscriber_running(&self, key: String, worker_id: Ulid) -> anyhow::Result<bool> {
self.inner.is_subscriber_running(key, worker_id).await
}
async fn subscriber_status(
&self,
key: String,
worker_id: Ulid,
) -> anyhow::Result<SubscriberStatus> {
self.inner.subscriber_status(key, worker_id).await
}
async fn latest_version(
&self,
aggregate_type: String,
aggregate_id: String,
) -> anyhow::Result<u16> {
self.inner
.latest_version(aggregate_type, aggregate_id)
.await
}
async fn stream_routing_key(
&self,
aggregate_type: String,
aggregate_id: String,
) -> anyhow::Result<Option<Option<String>>> {
self.inner
.stream_routing_key(aggregate_type, aggregate_id)
.await
}
async fn upsert_subscriber(&self, key: String, worker_id: Ulid) -> anyhow::Result<()> {
self.inner.upsert_subscriber(key, worker_id).await
}
async fn acknowledge(
&self,
key: String,
worker_id: Ulid,
cursor: Value,
lag: u64,
) -> anyhow::Result<bool> {
self.inner.acknowledge(key, worker_id, cursor, lag).await
}
async fn get_snapshot(
&self,
aggregate_type: String,
aggregate_revision: String,
id: String,
) -> anyhow::Result<Option<(Vec<u8>, Value)>> {
self.inner
.get_snapshot(aggregate_type, aggregate_revision, id)
.await
}
async fn save_snapshot(
&self,
aggregate_type: String,
aggregate_revision: String,
id: String,
data: Vec<u8>,
cursor: Value,
) -> anyhow::Result<()> {
self.inner
.save_snapshot(aggregate_type, aggregate_revision, id, data, cursor)
.await
}
async fn delete_snapshot(&self, aggregate_type: String, id: String) -> anyhow::Result<()> {
self.inner.delete_snapshot(aggregate_type, id).await
}
}
impl Evento {
pub fn new<E: Executor>(executor: E) -> Self {
Self {
inner: Arc::new(Box::new(executor)),
default_routing_key: None,
}
}
pub fn default_routing_key(mut self, key: impl Into<String>) -> Self {
self.default_routing_key = Some(key.into());
self
}
}
#[cfg(feature = "group")]
#[derive(Clone, Default)]
pub struct EventoGroup {
executors: Vec<Evento>,
}
#[cfg(feature = "group")]
impl EventoGroup {
pub fn executor(mut self, executor: impl Into<Evento>) -> Self {
self.executors.push(executor.into());
self
}
pub fn first(&self) -> &Evento {
self.executors
.first()
.expect("EventoGroup must have at least one executor")
}
}
#[cfg(feature = "group")]
#[async_trait::async_trait]
impl Executor for EventoGroup {
fn default_routing_key(&self) -> Option<&str> {
self.first().default_routing_key()
}
async fn write(&self, events: Vec<Event>) -> Result<(), WriteError> {
self.first().write(events).await
}
async fn replicate(&self, events: Vec<Event>) -> Result<(), WriteError> {
self.first().replicate(events).await
}
fn write_watch(&self) -> Option<tokio::sync::watch::Receiver<u64>> {
self.first().write_watch()
}
async fn stable_timestamp(&self) -> anyhow::Result<Option<u64>> {
self.first().stable_timestamp().await
}
async fn read(
&self,
aggregators: Option<Arc<[EventFilter]>>,
routing_key: Option<RoutingKey>,
args: Args,
to_micros: Option<u64>,
) -> anyhow::Result<ReadResult<Event>> {
use crate::cursor;
let futures = self.executors.iter().map(|e| {
e.read(
aggregators.to_owned(),
routing_key.to_owned(),
args.clone(),
to_micros,
)
});
let results = futures_util::future::join_all(futures).await;
let mut events = vec![];
let mut child_has_next = false;
let mut child_has_previous = false;
for res in results {
let res = res?;
child_has_next |= res.page_info.has_next_page;
child_has_previous |= res.page_info.has_previous_page;
for edge in res.edges {
events.push(edge.node);
}
}
let mut merged = cursor::Reader::new(events).args(args).execute()?;
merged.page_info.has_next_page |= child_has_next;
merged.page_info.has_previous_page |= child_has_previous;
Ok(merged)
}
async fn latest_timestamp(
&self,
aggregators: Option<Arc<[EventFilter]>>,
routing_key: Option<RoutingKey>,
) -> anyhow::Result<u64> {
let futures = self
.executors
.iter()
.map(|e| e.latest_timestamp(aggregators.to_owned(), routing_key.to_owned()));
let results = futures_util::future::join_all(futures).await;
let mut max = 0u64;
for res in results {
let ts = res?;
if ts > max {
max = ts;
}
}
Ok(max)
}
async fn get_subscriber_cursor(&self, key: String) -> anyhow::Result<Option<Value>> {
self.first().get_subscriber_cursor(key).await
}
async fn is_subscriber_running(&self, key: String, worker_id: Ulid) -> anyhow::Result<bool> {
self.first().is_subscriber_running(key, worker_id).await
}
async fn subscriber_status(
&self,
key: String,
worker_id: Ulid,
) -> anyhow::Result<SubscriberStatus> {
self.first().subscriber_status(key, worker_id).await
}
async fn upsert_subscriber(&self, key: String, worker_id: Ulid) -> anyhow::Result<()> {
self.first().upsert_subscriber(key, worker_id).await
}
async fn acknowledge(
&self,
key: String,
worker_id: Ulid,
cursor: Value,
lag: u64,
) -> anyhow::Result<bool> {
self.first().acknowledge(key, worker_id, cursor, lag).await
}
async fn get_snapshot(
&self,
aggregate_type: String,
aggregate_revision: String,
id: String,
) -> anyhow::Result<Option<(Vec<u8>, Value)>> {
self.first()
.get_snapshot(aggregate_type, aggregate_revision, id)
.await
}
async fn save_snapshot(
&self,
aggregate_type: String,
aggregate_revision: String,
id: String,
data: Vec<u8>,
cursor: Value,
) -> anyhow::Result<()> {
self.first()
.save_snapshot(aggregate_type, aggregate_revision, id, data, cursor)
.await
}
async fn delete_snapshot(&self, aggregate_type: String, id: String) -> anyhow::Result<()> {
self.first().delete_snapshot(aggregate_type, id).await
}
}
#[cfg(feature = "rw")]
pub struct Rw<R: Executor, W: Executor> {
r: R,
w: W,
}
#[cfg(feature = "rw")]
impl<R: Executor + Clone, W: Executor + Clone> Clone for Rw<R, W> {
fn clone(&self) -> Self {
Self {
r: self.r.clone(),
w: self.w.clone(),
}
}
}
#[cfg(feature = "rw")]
#[async_trait::async_trait]
impl<R: Executor, W: Executor> Executor for Rw<R, W> {
fn default_routing_key(&self) -> Option<&str> {
self.w.default_routing_key()
}
async fn write(&self, events: Vec<Event>) -> Result<(), WriteError> {
self.w.write(events).await
}
async fn replicate(&self, events: Vec<Event>) -> Result<(), WriteError> {
self.w.replicate(events).await
}
fn write_watch(&self) -> Option<tokio::sync::watch::Receiver<u64>> {
self.w.write_watch()
}
async fn stable_timestamp(&self) -> anyhow::Result<Option<u64>> {
self.r.stable_timestamp().await
}
async fn read(
&self,
aggregators: Option<Arc<[EventFilter]>>,
routing_key: Option<RoutingKey>,
args: Args,
to_micros: Option<u64>,
) -> anyhow::Result<ReadResult<Event>> {
self.r.read(aggregators, routing_key, args, to_micros).await
}
async fn latest_timestamp(
&self,
aggregators: Option<Arc<[EventFilter]>>,
routing_key: Option<RoutingKey>,
) -> anyhow::Result<u64> {
self.r.latest_timestamp(aggregators, routing_key).await
}
async fn get_subscriber_cursor(&self, key: String) -> anyhow::Result<Option<Value>> {
self.r.get_subscriber_cursor(key).await
}
async fn is_subscriber_running(&self, key: String, worker_id: Ulid) -> anyhow::Result<bool> {
self.r.is_subscriber_running(key, worker_id).await
}
async fn subscriber_status(
&self,
key: String,
worker_id: Ulid,
) -> anyhow::Result<SubscriberStatus> {
self.r.subscriber_status(key, worker_id).await
}
async fn latest_version(
&self,
aggregate_type: String,
aggregate_id: String,
) -> anyhow::Result<u16> {
self.r.latest_version(aggregate_type, aggregate_id).await
}
async fn stream_routing_key(
&self,
aggregate_type: String,
aggregate_id: String,
) -> anyhow::Result<Option<Option<String>>> {
self.r
.stream_routing_key(aggregate_type, aggregate_id)
.await
}
async fn upsert_subscriber(&self, key: String, worker_id: Ulid) -> anyhow::Result<()> {
self.w.upsert_subscriber(key, worker_id).await
}
async fn acknowledge(
&self,
key: String,
worker_id: Ulid,
cursor: Value,
lag: u64,
) -> anyhow::Result<bool> {
self.w.acknowledge(key, worker_id, cursor, lag).await
}
async fn get_snapshot(
&self,
aggregate_type: String,
aggregate_revision: String,
id: String,
) -> anyhow::Result<Option<(Vec<u8>, Value)>> {
self.r
.get_snapshot(aggregate_type, aggregate_revision, id)
.await
}
async fn save_snapshot(
&self,
aggregate_type: String,
aggregate_revision: String,
id: String,
data: Vec<u8>,
cursor: Value,
) -> anyhow::Result<()> {
self.w
.save_snapshot(aggregate_type, aggregate_revision, id, data, cursor)
.await
}
async fn delete_snapshot(&self, aggregate_type: String, id: String) -> anyhow::Result<()> {
self.w.delete_snapshot(aggregate_type, id).await
}
}
#[cfg(feature = "rw")]
impl<R: Executor, W: Executor> From<(R, W)> for Rw<R, W> {
fn from((r, w): (R, W)) -> Self {
Self { r, w }
}
}