From 87e7d8523932cb70e8e2fea110ad5b19b4bf7315 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 12 Jul 2026 01:29:40 +0200 Subject: [PATCH] domain events: DomainEvent enum w/ ID-only variants --- crates/domain/src/events/mod.rs | 123 ++++++++++++++++++++++++++++++ crates/domain/src/lib.rs | 2 + crates/domain/src/ports/events.rs | 25 +----- 3 files changed, 126 insertions(+), 24 deletions(-) create mode 100644 crates/domain/src/events/mod.rs diff --git a/crates/domain/src/events/mod.rs b/crates/domain/src/events/mod.rs new file mode 100644 index 0000000..3540faa --- /dev/null +++ b/crates/domain/src/events/mod.rs @@ -0,0 +1,123 @@ +//! Domain events emitted when important state transitions occur. +//! +//! Events carry only IDs — consumers fetch the data they need from +//! repositories. This keeps `Clone` cheap and avoids coupling events +//! to model internals. + +use crate::value_objects::{ChannelId, ScheduleId, SlotId}; + +/// Events emitted by domain aggregates on state transitions. +/// +/// Must be `Clone + Send + 'static` so publishers can fan out cheaply. +/// Marked `#[non_exhaustive]` — downstream consumers must handle unknown +/// variants gracefully (wildcard arm). +#[derive(Clone, Debug)] +#[non_exhaustive] +pub enum DomainEvent { + /// The active broadcast switched to a new slot. + BroadcastTransition { + channel_id: ChannelId, + slot_id: SlotId, + }, + /// No content is currently airing on the channel. + NoSignal { channel_id: ChannelId }, + /// A new schedule was generated for a channel. + ScheduleGenerated { + channel_id: ChannelId, + schedule_id: ScheduleId, + }, + /// A channel was created. + ChannelCreated { channel_id: ChannelId }, + /// A channel configuration was updated. + ChannelUpdated { channel_id: ChannelId }, + /// A channel was deleted. + ChannelDeleted { channel_id: ChannelId }, +} + +#[cfg(test)] +mod tests { + use super::*; + use uuid::Uuid; + + #[test] + fn broadcast_transition_carries_ids() { + let ch = ChannelId::from(Uuid::new_v4()); + let sl = SlotId::from(Uuid::new_v4()); + let event = DomainEvent::BroadcastTransition { + channel_id: ch, + slot_id: sl, + }; + match event { + DomainEvent::BroadcastTransition { + channel_id, + slot_id, + } => { + assert_eq!(channel_id, ch); + assert_eq!(slot_id, sl); + } + _ => panic!("wrong variant"), + } + } + + #[test] + fn no_signal_carries_channel_id() { + let ch = ChannelId::from(Uuid::new_v4()); + let event = DomainEvent::NoSignal { channel_id: ch }; + match event { + DomainEvent::NoSignal { channel_id } => assert_eq!(channel_id, ch), + _ => panic!("wrong variant"), + } + } + + #[test] + fn schedule_generated_carries_ids() { + let ch = ChannelId::from(Uuid::new_v4()); + let sc = ScheduleId::from(Uuid::new_v4()); + let event = DomainEvent::ScheduleGenerated { + channel_id: ch, + schedule_id: sc, + }; + match event { + DomainEvent::ScheduleGenerated { + channel_id, + schedule_id, + } => { + assert_eq!(channel_id, ch); + assert_eq!(schedule_id, sc); + } + _ => panic!("wrong variant"), + } + } + + #[test] + fn channel_lifecycle_events() { + let ch = ChannelId::from(Uuid::new_v4()); + + let created = DomainEvent::ChannelCreated { channel_id: ch }; + let updated = DomainEvent::ChannelUpdated { channel_id: ch }; + let deleted = DomainEvent::ChannelDeleted { channel_id: ch }; + + // All three carry the same channel_id. + for event in [created, updated, deleted] { + match event { + DomainEvent::ChannelCreated { channel_id } + | DomainEvent::ChannelUpdated { channel_id } + | DomainEvent::ChannelDeleted { channel_id } => { + assert_eq!(channel_id, ch); + } + _ => panic!("wrong variant"), + } + } + } + + #[test] + fn event_is_clone() { + let ch = ChannelId::from(Uuid::new_v4()); + let event = DomainEvent::NoSignal { channel_id: ch }; + let cloned = event.clone(); + match cloned { + DomainEvent::NoSignal { channel_id } => assert_eq!(channel_id, ch), + _ => panic!("wrong variant"), + } + } +} diff --git a/crates/domain/src/lib.rs b/crates/domain/src/lib.rs index 46fdb40..3622c59 100644 --- a/crates/domain/src/lib.rs +++ b/crates/domain/src/lib.rs @@ -1,8 +1,10 @@ pub mod errors; +pub mod events; pub mod models; pub mod ports; pub mod value_objects; pub use errors::{DomainError, DomainResult}; +pub use events::DomainEvent; pub use models::*; pub use value_objects::*; diff --git a/crates/domain/src/ports/events.rs b/crates/domain/src/ports/events.rs index b705dc8..06f5074 100644 --- a/crates/domain/src/ports/events.rs +++ b/crates/domain/src/ports/events.rs @@ -8,30 +8,7 @@ use async_trait::async_trait; use crate::errors::DomainResult; -use crate::value_objects::ChannelId; - -/// A domain event emitted by aggregate operations. -/// -/// New variants will be added as the system grows. Downstream consumers -/// pattern-match and ignore unknown variants. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub enum DomainEvent { - /// A new schedule was generated for a channel. - ScheduleGenerated { - channel_id: ChannelId, - generation: u32, - }, - /// A library sync completed for a provider. - LibrarySyncCompleted { - provider_id: String, - items_found: u32, - }, - /// A channel was created. - ChannelCreated { channel_id: ChannelId }, - /// A channel was deleted. - ChannelDeleted { channel_id: ChannelId }, -} +pub use crate::events::DomainEvent; /// Port for publishing domain events. ///