@@ -4,11 +4,16 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
crunch-envelope.workspace = true
|
||||
|
||||
anyhow.workspace = true
|
||||
tracing.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-stream.workspace = true
|
||||
thiserror.workspace = true
|
||||
async-trait.workspace = true
|
||||
uuid.workspace = true
|
||||
futures.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tracing-subscriber.workspace = true
|
28
crates/crunch/src/errors.rs
Normal file
28
crates/crunch/src/errors.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SerializeError {
|
||||
#[error("failed to serialize")]
|
||||
FailedToSerialize(anyhow::Error),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DeserializeError {
|
||||
#[error("failed to serialize")]
|
||||
FailedToDeserialize(anyhow::Error),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PublishError {
|
||||
#[error("failed to serialize")]
|
||||
SerializeError(#[source] SerializeError),
|
||||
|
||||
#[error("failed to commit to database")]
|
||||
DbError(#[source] anyhow::Error),
|
||||
|
||||
#[error("transaction failed")]
|
||||
DbTxError(#[source] anyhow::Error),
|
||||
|
||||
#[error("failed to connect to database")]
|
||||
ConnectionError(#[source] anyhow::Error),
|
||||
}
|
104
crates/crunch/src/impls.rs
Normal file
104
crates/crunch/src/impls.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use std::{collections::VecDeque, future::Future, ops::Deref, pin::Pin, sync::Arc, task::Poll};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use tokio::sync::{Mutex, OnceCell, RwLock};
|
||||
use tokio_stream::Stream;
|
||||
|
||||
use crate::{traits, EventInfo};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum MsgState {
|
||||
Pending,
|
||||
Published,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Msg {
|
||||
id: String,
|
||||
info: EventInfo,
|
||||
msg: Vec<u8>,
|
||||
state: MsgState,
|
||||
}
|
||||
|
||||
pub struct InMemoryPersistence {
|
||||
outbox: Arc<RwLock<VecDeque<Msg>>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl traits::Persistence for InMemoryPersistence {
|
||||
async fn insert(&self, event_info: &EventInfo, content: Vec<u8>) -> anyhow::Result<()> {
|
||||
let msg = crunch_envelope::proto::wrap(event_info.domain, event_info.entity_type, &content);
|
||||
|
||||
let mut outbox = self.outbox.write().await;
|
||||
|
||||
outbox.push_back(Msg {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
info: event_info.clone(),
|
||||
msg,
|
||||
state: MsgState::Pending,
|
||||
});
|
||||
|
||||
tracing::info!(
|
||||
event_info = event_info.to_string(),
|
||||
content_len = content.len(),
|
||||
"inserted event"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn next(&self) -> Option<String> {
|
||||
let mut outbox = self.outbox.write().await;
|
||||
outbox.pop_front().map(|i| i.id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Persistence {
|
||||
inner: Arc<dyn traits::Persistence + Send + Sync + 'static>,
|
||||
pending: Arc<Option<Pin<Box<dyn Future<Output = Option<OnceCell<String>>> + Send + Sync>>>>,
|
||||
}
|
||||
|
||||
impl Persistence {
|
||||
pub fn in_memory() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(InMemoryPersistence {
|
||||
outbox: Arc::default(),
|
||||
}),
|
||||
pending: Arc::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Persistence {
|
||||
type Target = Arc<dyn traits::Persistence + Send + Sync + 'static>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for Persistence {
|
||||
type Item = OnceCell<String>;
|
||||
|
||||
fn poll_next(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
let mut pending = self.pending;
|
||||
|
||||
if pending.is_none() {
|
||||
*pending = Some(Box::pin(self.inner.next()));
|
||||
}
|
||||
|
||||
let fut = pending.as_mut().unwrap();
|
||||
|
||||
match fut.as_mut().poll(cx) {
|
||||
Poll::Ready(v) => {
|
||||
*pending = None;
|
||||
Poll::Ready(v)
|
||||
}
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,119 +1,36 @@
|
||||
mod traits {
|
||||
use std::fmt::Display;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{DeserializeError, SerializeError};
|
||||
|
||||
#[async_trait]
|
||||
pub trait Persistence {
|
||||
async fn insert(&self, event_info: &EventInfo, content: Vec<u8>) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
pub trait Serializer {
|
||||
fn serialize(&self) -> Result<Vec<u8>, SerializeError>;
|
||||
}
|
||||
|
||||
pub trait Deserializer {
|
||||
fn deserialize(raw: Vec<u8>) -> Result<Self, DeserializeError>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct EventInfo {
|
||||
pub domain: &'static str,
|
||||
pub entity_type: &'static str,
|
||||
}
|
||||
|
||||
impl Display for EventInfo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&format!(
|
||||
"domain: {}, entity_type: {}",
|
||||
self.domain, self.entity_type
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Event: Serializer + Deserializer {
|
||||
fn event_info(&self) -> EventInfo;
|
||||
}
|
||||
}
|
||||
|
||||
mod impls {
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{traits, EventInfo};
|
||||
|
||||
pub struct InMemoryPersistence {}
|
||||
|
||||
#[async_trait]
|
||||
impl traits::Persistence for InMemoryPersistence {
|
||||
async fn insert(&self, event_info: &EventInfo, content: Vec<u8>) -> anyhow::Result<()> {
|
||||
tracing::info!(
|
||||
event_info = event_info.to_string(),
|
||||
content_len = content.len(),
|
||||
"inserted event"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Persistence(Arc<dyn traits::Persistence + Send + Sync + 'static>);
|
||||
|
||||
impl Persistence {
|
||||
pub fn in_memory() -> Self {
|
||||
Self(Arc::new(InMemoryPersistence {}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Persistence {
|
||||
type Target = Arc<dyn traits::Persistence + Send + Sync + 'static>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod errors {
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SerializeError {
|
||||
#[error("failed to serialize")]
|
||||
FailedToSerialize(anyhow::Error),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DeserializeError {
|
||||
#[error("failed to serialize")]
|
||||
FailedToDeserialize(anyhow::Error),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PublishError {
|
||||
#[error("failed to serialize")]
|
||||
SerializeError(#[source] SerializeError),
|
||||
|
||||
#[error("failed to commit to database")]
|
||||
DbError(#[source] anyhow::Error),
|
||||
|
||||
#[error("transaction failed")]
|
||||
DbTxError(#[source] anyhow::Error),
|
||||
|
||||
#[error("failed to connect to database")]
|
||||
ConnectionError(#[source] anyhow::Error),
|
||||
}
|
||||
}
|
||||
mod errors;
|
||||
mod impls;
|
||||
mod traits;
|
||||
|
||||
pub use errors::*;
|
||||
pub use impls::Persistence;
|
||||
pub use traits::{Deserializer, Event, EventInfo, Serializer};
|
||||
|
||||
mod outbox {
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use crate::Persistence;
|
||||
|
||||
pub struct OutboxHandler {
|
||||
persistence: Persistence,
|
||||
}
|
||||
|
||||
impl OutboxHandler {
|
||||
pub fn new(persistence: Persistence) -> Self {
|
||||
Self { persistence }
|
||||
}
|
||||
|
||||
pub async fn spawn(&mut self) {
|
||||
let p = self.persistence.clone();
|
||||
tokio::spawn(async move {
|
||||
let p = p;
|
||||
|
||||
while let Some(item) = p.next().await {}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Publisher {
|
||||
persistence: Persistence,
|
||||
}
|
||||
|
41
crates/crunch/src/traits.rs
Normal file
41
crates/crunch/src/traits.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
use crate::{DeserializeError, SerializeError};
|
||||
|
||||
#[async_trait]
|
||||
pub trait Persistence {
|
||||
async fn insert(&self, event_info: &EventInfo, content: Vec<u8>) -> anyhow::Result<()>;
|
||||
async fn next(&self) -> Option<OnceCell<String>>;
|
||||
}
|
||||
|
||||
pub trait Serializer {
|
||||
fn serialize(&self) -> Result<Vec<u8>, SerializeError>;
|
||||
}
|
||||
|
||||
pub trait Deserializer {
|
||||
fn deserialize(raw: Vec<u8>) -> Result<Self, DeserializeError>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct EventInfo {
|
||||
pub domain: &'static str,
|
||||
pub entity_type: &'static str,
|
||||
}
|
||||
|
||||
impl Display for EventInfo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&format!(
|
||||
"domain: {}, entity_type: {}",
|
||||
self.domain, self.entity_type
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Event: Serializer + Deserializer {
|
||||
fn event_info(&self) -> EventInfo;
|
||||
}
|
Reference in New Issue
Block a user