feat: with many producers

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2023-09-24 00:00:41 +02:00
parent 7b08b16cdb
commit 0a78124489
Signed by: kjuulh
GPG Key ID: 9AA7BC13CE474394
5 changed files with 195 additions and 85 deletions

View File

@ -53,3 +53,9 @@ pub enum PersistenceError {
#[error("failed to publish item {0}")] #[error("failed to publish item {0}")]
UpdatePublished(anyhow::Error), UpdatePublished(anyhow::Error),
} }
#[derive(Error, Debug)]
pub enum BuilderError {
#[error("dependency not added to builder: {0}")]
DependencyError(anyhow::Error),
}

View File

@ -1,6 +1,7 @@
use crunch::errors::*; use crunch::errors::*;
use crunch::traits::Event; use crunch::traits::Event;
#[derive(Clone)]
struct SomeEvent { struct SomeEvent {
name: String, name: String,
} }
@ -36,50 +37,49 @@ impl crunch::traits::Event for SomeEvent {
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let in_memory = crunch::Persistence::in_memory(); let crunch = crunch::builder::Builder::default().build()?;
let transport = crunch::Transport::in_memory(); let counter = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
crunch::OutboxHandler::new(in_memory.clone(), transport.clone()).spawn();
let publisher = crunch::Publisher::new(in_memory);
let subscriber = crunch::Subscriber::new(transport);
subscriber let inner_counter = counter.clone();
.subscribe(|item: SomeEvent| async move { crunch
.subscribe(move |item: SomeEvent| {
let counter = inner_counter.clone();
async move {
tracing::info!( tracing::info!(
"subscription got event: {}, info: {}", "subscription got event: {}, info: {}",
item.name, item.name,
item.int_event_info(), item.int_event_info(),
); );
counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Ok(()) Ok(())
}
}) })
.await?; .await?;
publisher let event = SomeEvent {
.publish(SomeEvent {
name: "something".into(), name: "something".into(),
}) };
.await?;
publisher
.publish(SomeEvent {
name: "something".into(),
})
.await?;
publisher
.publish(SomeEvent {
name: "something".into(),
})
.await?;
publisher
.publish(SomeEvent {
name: "something".into(),
})
.await?;
publisher
.publish(SomeEvent {
name: "something".into(),
})
.await?;
tokio::time::sleep(std::time::Duration::from_secs(5)).await; for _ in 0..50 {
tokio::spawn({
let event = event.clone();
let crunch = crunch.clone();
async move {
loop {
crunch.publish(event.clone()).await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
}
}
});
}
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
let amount_run = counter.load(std::sync::atomic::Ordering::SeqCst);
tracing::error!("ran {} amount of times", amount_run);
Ok(()) Ok(())
} }

View File

@ -1,6 +1,7 @@
mod impls; mod impls;
mod outbox; mod outbox;
mod publisher; mod publisher;
mod subscriber;
mod transport; mod transport;
#[cfg(feature = "traits")] #[cfg(feature = "traits")]
@ -12,67 +13,115 @@ pub mod errors {
pub use crunch_traits::errors::*; pub use crunch_traits::errors::*;
} }
use crunch_traits::Event;
pub use impls::Persistence; pub use impls::Persistence;
pub use outbox::OutboxHandler; pub use outbox::OutboxHandler;
pub use publisher::Publisher; pub use publisher::Publisher;
pub use subscriber::Subscriber; pub use subscriber::Subscriber;
pub use transport::Transport; pub use transport::Transport;
mod subscriber { #[derive(Clone)]
use crunch_traits::{Event, EventInfo}; pub struct Crunch {
use futures::StreamExt; publisher: Publisher,
subscriber: Subscriber,
use crate::{errors, Transport}; }
impl Crunch {
pub struct Subscriber { pub fn new(publisher: Publisher, subscriber: Subscriber) -> Self {
transport: Transport, Self {
publisher,
subscriber,
}
} }
impl Subscriber { pub async fn subscribe<I, F, Fut>(&self, callback: F) -> Result<(), errors::SubscriptionError>
pub fn new(transport: Transport) -> Self {
Self { transport }
}
pub async fn subscribe<I, F, Fut>(
&self,
callback: F,
) -> Result<(), errors::SubscriptionError>
where where
F: Fn(I) -> Fut + Send + Sync + 'static, F: Fn(I) -> Fut + Send + Sync + 'static,
Fut: futures::Future<Output = Result<(), errors::SubscriptionError>> + Send + 'static, Fut: futures::Future<Output = Result<(), errors::SubscriptionError>> + Send + 'static,
I: Event + Send + 'static, I: Event + Send + 'static,
{ {
let mut stream = self self.subscriber.subscribe(callback).await
}
}
impl std::ops::Deref for Crunch {
type Target = Publisher;
fn deref(&self) -> &Self::Target {
&self.publisher
}
}
pub mod builder {
use crate::{errors, Crunch, OutboxHandler, Persistence, Publisher, Subscriber, Transport};
#[derive(Clone)]
pub struct Builder {
persistence: Option<Persistence>,
transport: Option<Transport>,
outbox_enabled: bool,
}
impl Builder {
#[cfg(feature = "in-memory")]
pub fn with_in_memory_persistence(&mut self) -> &mut Self {
self.persistence = Some(Persistence::in_memory());
self
}
#[cfg(feature = "in-memory")]
pub fn with_in_memory_transport(&mut self) -> &mut Self {
self.transport = Some(Transport::in_memory());
self
}
pub fn with_outbox(&mut self, enabled: bool) -> &mut Self {
self.outbox_enabled = enabled;
self
}
pub fn build(&mut self) -> Result<Crunch, errors::BuilderError> {
let persistence =
self.persistence
.clone()
.ok_or(errors::BuilderError::DependencyError(anyhow::anyhow!(
"persistence was not set"
)))?;
let transport = self
.transport .transport
.subscriber(&I::event_info()) .clone()
.await .ok_or(errors::BuilderError::DependencyError(anyhow::anyhow!(
.map_err(errors::SubscriptionError::ConnectionFailed)? "transport was not set"
.ok_or(errors::SubscriptionError::FailedToSubscribe( )))?;
anyhow::anyhow!("failed to find channel to subscribe to"),
))?;
tokio::spawn(async move { let publisher = Publisher::new(persistence.clone());
while let Some(item) = stream.next().await { let subscriber = Subscriber::new(transport.clone());
let item = match I::deserialize(item) if self.outbox_enabled {
.map_err(errors::SubscriptionError::DeserializationFailed) OutboxHandler::new(persistence.clone(), transport.clone()).spawn();
}
Ok(Crunch::new(publisher, subscriber))
}
}
impl Default for Builder {
fn default() -> Self {
#[cfg(feature = "in-memory")]
{ {
Ok(i) => i, return Self {
Err(e) => { outbox_enabled: true,
tracing::warn!("deserialization failed: {}", e); persistence: None,
continue; transport: None,
}
.with_in_memory_persistence()
.with_in_memory_transport()
.clone();
} }
};
match callback(item).await { Self {
Ok(_) => {} persistence: None,
Err(e) => { transport: None,
tracing::error!("subscription callback failed: {}", e) outbox_enabled: true,
} }
} }
} }
});
Ok(())
}
}
} }

View File

@ -2,6 +2,7 @@ use crunch_traits::{errors::PublishError, Event};
use crate::Persistence; use crate::Persistence;
#[derive(Clone)]
pub struct Publisher { pub struct Publisher {
persistence: Persistence, persistence: Persistence,
} }

View File

@ -0,0 +1,54 @@
use crunch_traits::{Event, EventInfo};
use futures::StreamExt;
use crate::{errors, Transport};
#[derive(Clone)]
pub struct Subscriber {
transport: Transport,
}
impl Subscriber {
pub fn new(transport: Transport) -> Self {
Self { transport }
}
pub async fn subscribe<I, F, Fut>(&self, callback: F) -> Result<(), errors::SubscriptionError>
where
F: Fn(I) -> Fut + Send + Sync + 'static,
Fut: futures::Future<Output = Result<(), errors::SubscriptionError>> + Send + 'static,
I: Event + Send + 'static,
{
let mut stream = self
.transport
.subscriber(&I::event_info())
.await
.map_err(errors::SubscriptionError::ConnectionFailed)?
.ok_or(errors::SubscriptionError::FailedToSubscribe(
anyhow::anyhow!("failed to find channel to subscribe to"),
))?;
tokio::spawn(async move {
while let Some(item) = stream.next().await {
let item = match I::deserialize(item)
.map_err(errors::SubscriptionError::DeserializationFailed)
{
Ok(i) => i,
Err(e) => {
tracing::warn!("deserialization failed: {}", e);
continue;
}
};
match callback(item).await {
Ok(_) => {}
Err(e) => {
tracing::error!("subscription callback failed: {}", e)
}
}
}
});
Ok(())
}
}