Skip to main content

made_core/ports/
deliberation_observer.rs

1//! [`DeliberationObserverPort`] — per-call hook for observing a
2//! deliberation's lifecycle.
3//!
4//! Unlike [`MessagingPort`](super::MessagingPort), which broadcasts
5//! persistent domain events (NATS), this port is for **ephemeral
6//! per-call observation**: the server-streaming `StreamDeliberation`
7//! RPC wires an adapter that forwards events to an `mpsc::channel`
8//! becoming the response stream, and discards them when the stream
9//! closes. There is no subscription / replay semantics; the observer
10//! is call-scoped.
11//!
12//! Keeping this separate from `MessagingPort` means `DeliberateUseCase`
13//! can stay oblivious to whether a caller is listening on a stream,
14//! and messaging adapters stay oblivious to live stream semantics.
15//!
16//! The port is implementation-optional: callers that do not care pass
17//! [`NullObserver`], which discards every event.
18
19use async_trait::async_trait;
20use time::OffsetDateTime;
21
22use crate::entities::DeliberationPhase;
23use crate::value_objects::TaskId;
24
25#[async_trait]
26pub trait DeliberationObserverPort: Send + Sync {
27    /// Called when the aggregate transitions into `phase`.
28    ///
29    /// Observers must not block or the use case stalls; adapters that
30    /// need unbounded work should spawn it. Adapters whose sink has
31    /// closed (receiver dropped) should become a no-op silently —
32    /// the use case does not care whether anyone is still listening.
33    async fn on_phase_changed(
34        &self,
35        task_id: &TaskId,
36        phase: DeliberationPhase,
37        emitted_at: OffsetDateTime,
38    );
39}
40
41/// No-op observer. Use this in composition when no stream is active.
42#[derive(Debug, Default, Clone)]
43pub struct NullObserver;
44
45#[async_trait]
46impl DeliberationObserverPort for NullObserver {
47    async fn on_phase_changed(
48        &self,
49        _task_id: &TaskId,
50        _phase: DeliberationPhase,
51        _emitted_at: OffsetDateTime,
52    ) {
53    }
54}