domain ports: CQRS-split traits for all bounded contexts

This commit is contained in:
2026-07-12 01:23:38 +02:00
parent 616c60e213
commit 0166e829c1
13 changed files with 693 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
//! Schedule persistence ports (CQRS split).
use std::collections::HashMap;
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use uuid::Uuid;
use crate::errors::DomainResult;
use crate::models::{GeneratedSchedule, PlaybackRecord};
use crate::value_objects::{BlockId, ChannelId, MediaItemId};
/// Write-side port for schedule and playback persistence.
#[async_trait]
pub trait ScheduleCommand: Send + Sync {
/// Insert or replace a generated schedule.
async fn save(&self, schedule: &GeneratedSchedule) -> DomainResult<()>;
/// Persist a playback record (item was aired on a channel).
async fn save_playback_record(&self, record: &PlaybackRecord) -> DomainResult<()>;
/// Delete all schedules with generation > `target_generation` for this channel.
///
/// Also deletes matching playback_records (no DB cascade between those tables).
/// `scheduled_slots` cascade via FK from `generated_schedules`.
async fn delete_schedules_after(
&self,
channel_id: ChannelId,
target_generation: u32,
) -> DomainResult<()>;
}
/// Read-side port for schedule and playback persistence.
#[async_trait]
pub trait ScheduleQuery: Send + Sync {
/// Find the schedule whose `[valid_from, valid_until)` window contains `at`.
async fn find_active(
&self,
channel_id: ChannelId,
at: DateTime<Utc>,
) -> DomainResult<Option<GeneratedSchedule>>;
/// Find the most recently generated schedule for a channel.
/// Used to derive the next generation number.
async fn find_latest(
&self,
channel_id: ChannelId,
) -> DomainResult<Option<GeneratedSchedule>>;
/// All playback records for a channel, used by the recycle policy engine.
async fn find_playback_history(
&self,
channel_id: ChannelId,
) -> DomainResult<Vec<PlaybackRecord>>;
/// Return the most recent slot per block_id across ALL schedules for a channel.
///
/// Resilient to any single generation having empty slots for a block.
async fn find_last_slot_per_block(
&self,
channel_id: ChannelId,
) -> DomainResult<HashMap<BlockId, MediaItemId>>;
/// List all generated schedule headers for a channel, newest first.
async fn list_schedule_history(
&self,
channel_id: ChannelId,
) -> DomainResult<Vec<GeneratedSchedule>>;
/// Fetch a specific schedule with its slots, verifying channel ownership.
async fn get_schedule_by_id(
&self,
channel_id: ChannelId,
schedule_id: Uuid,
) -> DomainResult<Option<GeneratedSchedule>>;
}