refactor (v2): better arch

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-06-07 21:19:54 +02:00
parent 0753f3d256
commit 839308ec19
166 changed files with 8553 additions and 884 deletions

View File

@@ -0,0 +1,11 @@
[package]
name = "event-publisher-memory"
version = "0.1.0"
edition = "2024"
[dependencies]
domain = { workspace = true }
async-trait = { workspace = true }
futures = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }

View File

@@ -0,0 +1,85 @@
use std::sync::Arc;
use async_trait::async_trait;
use futures::stream::BoxStream;
use tokio::sync::broadcast;
use domain::{
errors::DomainError,
events::{DomainEvent, EventConsumer, EventEnvelope, EventPublisher},
};
const CHANNEL_CAPACITY: usize = 256;
/// Shared in-memory event bus backed by a tokio broadcast channel.
/// Create one bus, then hand out publisher and consumer handles from it.
pub struct MemoryEventBus {
sender: broadcast::Sender<DomainEvent>,
}
impl MemoryEventBus {
pub fn new() -> Self {
let (sender, _) = broadcast::channel(CHANNEL_CAPACITY);
Self { sender }
}
pub fn publisher(&self) -> Arc<MemoryEventPublisher> {
Arc::new(MemoryEventPublisher {
sender: self.sender.clone(),
})
}
pub fn consumer(&self) -> Arc<MemoryEventConsumer> {
Arc::new(MemoryEventConsumer {
sender: self.sender.clone(),
})
}
}
impl Default for MemoryEventBus {
fn default() -> Self {
Self::new()
}
}
pub struct MemoryEventPublisher {
sender: broadcast::Sender<DomainEvent>,
}
#[async_trait]
impl EventPublisher for MemoryEventPublisher {
async fn publish(&self, event: &DomainEvent) -> Result<(), DomainError> {
// send() only fails when there are no receivers; that is fine in dev/test.
let _ = self.sender.send(event.clone());
Ok(())
}
}
pub struct MemoryEventConsumer {
sender: broadcast::Sender<DomainEvent>,
}
impl EventConsumer for MemoryEventConsumer {
fn consume(&self) -> BoxStream<'_, Result<EventEnvelope, DomainError>> {
let rx = self.sender.subscribe();
Box::pin(futures::stream::unfold(rx, |mut rx| async move {
loop {
match rx.recv().await {
Ok(event) => {
let envelope = EventEnvelope::noop(event);
return Some((Ok(envelope), rx));
}
Err(broadcast::error::RecvError::Lagged(n)) => {
tracing::warn!("memory event bus: consumer lagged, skipped {n} messages");
}
Err(broadcast::error::RecvError::Closed) => return None,
}
}
}))
}
}
#[cfg(test)]
#[path = "tests/lib.rs"]
mod tests;

View File

@@ -0,0 +1,75 @@
use futures::StreamExt;
use domain::{
events::{DomainEvent, EventConsumer, EventPublisher},
note::entity::NoteId,
user::entity::UserId,
};
use crate::MemoryEventBus;
fn note_updated() -> DomainEvent {
DomainEvent::NoteUpdated {
note_id: NoteId::new(),
user_id: UserId::new(),
}
}
#[tokio::test]
async fn published_event_is_received_by_consumer() {
let bus = MemoryEventBus::new();
let publisher = bus.publisher();
let consumer = bus.consumer();
let event = note_updated();
let mut stream = consumer.consume();
publisher.publish(&event).await.unwrap();
let envelope = stream.next().await.unwrap().unwrap();
assert!(matches!(envelope.event, DomainEvent::NoteUpdated { .. }));
}
#[tokio::test]
async fn ack_on_memory_envelope_is_noop() {
let bus = MemoryEventBus::new();
let publisher = bus.publisher();
let consumer = bus.consumer();
// Subscribe before publishing — broadcast drops messages sent before subscribe.
let mut stream = consumer.consume();
publisher.publish(&note_updated()).await.unwrap();
let envelope = stream.next().await.unwrap().unwrap();
envelope.ack().await.unwrap();
}
#[tokio::test]
async fn multiple_consumers_each_receive_the_event() {
let bus = MemoryEventBus::new();
let publisher = bus.publisher();
let c1 = bus.consumer();
let c2 = bus.consumer();
let mut s1 = c1.consume();
let mut s2 = c2.consume();
publisher.publish(&note_updated()).await.unwrap();
assert!(matches!(
s1.next().await.unwrap().unwrap().event,
DomainEvent::NoteUpdated { .. }
));
assert!(matches!(
s2.next().await.unwrap().unwrap().event,
DomainEvent::NoteUpdated { .. }
));
}
#[tokio::test]
async fn publish_with_no_consumer_does_not_error() {
let bus = MemoryEventBus::new();
let publisher = bus.publisher();
// No consumer — publish should silently succeed.
publisher.publish(&note_updated()).await.unwrap();
}