domain events: DomainEvent enum w/ ID-only variants
This commit is contained in:
123
crates/domain/src/events/mod.rs
Normal file
123
crates/domain/src/events/mod.rs
Normal file
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user