Skip to main content

backbone_messaging/
envelope.rs

1//! Event Envelope - Metadata wrapper for domain events
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use crate::DomainEvent;
8
9/// Wrapper for domain events with metadata
10///
11/// EventEnvelope adds infrastructure concerns (IDs, timestamps, tracing)
12/// around domain events without polluting the domain model.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct EventEnvelope<E: DomainEvent> {
15    /// Unique event ID
16    pub id: String,
17    /// Event type name
18    pub event_type: &'static str,
19    /// Aggregate ID this event belongs to
20    pub aggregate_id: String,
21    /// Aggregate type name
22    pub aggregate_type: &'static str,
23    /// The domain event payload
24    #[serde(skip)]
25    event: Option<E>,
26    /// When the event occurred in the domain
27    pub occurred_at: DateTime<Utc>,
28    /// When the event was published to the bus
29    pub published_at: DateTime<Utc>,
30    /// Correlation ID for distributed tracing
31    pub correlation_id: Option<String>,
32    /// Causation ID (parent event that caused this one)
33    pub causation_id: Option<String>,
34    /// Event schema version
35    pub version: u32,
36    /// Additional metadata
37    #[serde(default)]
38    pub metadata: std::collections::HashMap<String, String>,
39}
40
41impl<E: DomainEvent> EventEnvelope<E> {
42    /// Create a new event envelope from a domain event
43    pub fn new(event: E) -> Self {
44        Self {
45            id: Uuid::new_v4().to_string(),
46            event_type: event.event_type(),
47            aggregate_id: event.aggregate_id().to_string(),
48            aggregate_type: event.aggregate_type(),
49            occurred_at: event.occurred_at(),
50            published_at: Utc::now(),
51            version: event.version(),
52            event: Some(event),
53            correlation_id: None,
54            causation_id: None,
55            metadata: std::collections::HashMap::new(),
56        }
57    }
58
59    /// Get the wrapped event
60    pub fn event(&self) -> Option<&E> {
61        self.event.as_ref()
62    }
63
64    /// Take ownership of the wrapped event
65    pub fn into_event(self) -> Option<E> {
66        self.event
67    }
68
69    /// Set correlation ID for distributed tracing
70    pub fn with_correlation_id(mut self, id: impl Into<String>) -> Self {
71        self.correlation_id = Some(id.into());
72        self
73    }
74
75    /// Set causation ID (parent event)
76    pub fn with_causation_id(mut self, id: impl Into<String>) -> Self {
77        self.causation_id = Some(id.into());
78        self
79    }
80
81    /// Add metadata
82    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
83        self.metadata.insert(key.into(), value.into());
84        self
85    }
86
87    /// Check if this event is correlated with another
88    pub fn is_correlated_with(&self, other: &Self) -> bool {
89        match (&self.correlation_id, &other.correlation_id) {
90            (Some(a), Some(b)) => a == b,
91            _ => false,
92        }
93    }
94
95    /// Check if this event was caused by another
96    pub fn was_caused_by(&self, other: &Self) -> bool {
97        match &self.causation_id {
98            Some(causation) => causation == &other.id,
99            None => false,
100        }
101    }
102}
103
104/// Builder for creating event envelopes with metadata
105pub struct EventEnvelopeBuilder<E: DomainEvent> {
106    envelope: EventEnvelope<E>,
107}
108
109impl<E: DomainEvent> EventEnvelopeBuilder<E> {
110    /// Start building an envelope from an event
111    pub fn new(event: E) -> Self {
112        Self {
113            envelope: EventEnvelope::new(event),
114        }
115    }
116
117    /// Set correlation ID
118    pub fn correlation_id(mut self, id: impl Into<String>) -> Self {
119        self.envelope.correlation_id = Some(id.into());
120        self
121    }
122
123    /// Set causation ID
124    pub fn causation_id(mut self, id: impl Into<String>) -> Self {
125        self.envelope.causation_id = Some(id.into());
126        self
127    }
128
129    /// Add metadata entry
130    pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
131        self.envelope.metadata.insert(key.into(), value.into());
132        self
133    }
134
135    /// Build the envelope
136    pub fn build(self) -> EventEnvelope<E> {
137        self.envelope
138    }
139}