Skip to main content

allsource_core/domain/entities/
event.rs

1use crate::{
2    domain::value_objects::{EntityId, EventType, TenantId},
3    error::Result,
4};
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9/// Domain Entity: Event
10///
11/// Core event structure representing a domain event in the event store.
12/// This is an immutable, timestamped record of something that happened.
13///
14/// Domain Rules:
15/// - Events are immutable once created
16/// - Event type must follow naming convention (enforced by EventType value object)
17/// - Entity ID cannot be empty (enforced by EntityId value object)
18/// - Tenant ID cannot be empty (enforced by TenantId value object)
19/// - Timestamp must not be in the future
20/// - Version starts at 1
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
22pub struct Event {
23    pub id: Uuid,
24    pub event_type: EventType,
25    pub entity_id: EntityId,
26    #[serde(default = "default_tenant_id")]
27    pub tenant_id: TenantId,
28    pub payload: serde_json::Value,
29    pub timestamp: DateTime<Utc>,
30    pub metadata: Option<serde_json::Value>,
31    pub version: i64,
32}
33
34fn default_tenant_id() -> TenantId {
35    TenantId::default_tenant()
36}
37
38impl Event {
39    /// Create a new Event with value objects (recommended)
40    pub fn new(
41        event_type: EventType,
42        entity_id: EntityId,
43        tenant_id: TenantId,
44        payload: serde_json::Value,
45    ) -> Self {
46        Self {
47            id: Uuid::new_v4(),
48            event_type,
49            entity_id,
50            tenant_id,
51            payload,
52            timestamp: Utc::now(),
53            metadata: None,
54            version: 1,
55        }
56    }
57
58    /// Create event with optional metadata
59    pub fn with_metadata(
60        event_type: EventType,
61        entity_id: EntityId,
62        tenant_id: TenantId,
63        payload: serde_json::Value,
64        metadata: serde_json::Value,
65    ) -> Self {
66        Self {
67            id: Uuid::new_v4(),
68            event_type,
69            entity_id,
70            tenant_id,
71            payload,
72            timestamp: Utc::now(),
73            metadata: Some(metadata),
74            version: 1,
75        }
76    }
77
78    /// Create event with default tenant (for single-tenant use)
79    pub fn with_default_tenant(
80        event_type: EventType,
81        entity_id: EntityId,
82        payload: serde_json::Value,
83    ) -> Self {
84        Self::new(event_type, entity_id, TenantId::default_tenant(), payload)
85    }
86
87    /// Create event from strings (for backward compatibility)
88    ///
89    /// This validates the strings and creates value objects.
90    /// Use the value object constructor for new code.
91    pub fn from_strings(
92        event_type: String,
93        entity_id: String,
94        tenant_id: String,
95        payload: serde_json::Value,
96        metadata: Option<serde_json::Value>,
97    ) -> Result<Self> {
98        let event_type = EventType::new(event_type)?;
99        let entity_id = EntityId::new(entity_id)?;
100        let tenant_id = TenantId::new(tenant_id)?;
101
102        Ok(Self {
103            id: Uuid::new_v4(),
104            event_type,
105            entity_id,
106            tenant_id,
107            payload,
108            timestamp: Utc::now(),
109            metadata,
110            version: 1,
111        })
112    }
113
114    /// Reconstruct an Event from storage (bypasses validation for stored events)
115    pub fn reconstruct(
116        id: Uuid,
117        event_type: EventType,
118        entity_id: EntityId,
119        tenant_id: TenantId,
120        payload: serde_json::Value,
121        timestamp: DateTime<Utc>,
122        metadata: Option<serde_json::Value>,
123        version: i64,
124    ) -> Self {
125        Self {
126            id,
127            event_type,
128            entity_id,
129            tenant_id,
130            payload,
131            timestamp,
132            metadata,
133            version,
134        }
135    }
136
137    /// Reconstruct from raw strings (for loading from old storage)
138    pub fn reconstruct_from_strings(
139        id: Uuid,
140        event_type: String,
141        entity_id: String,
142        tenant_id: String,
143        payload: serde_json::Value,
144        timestamp: DateTime<Utc>,
145        metadata: Option<serde_json::Value>,
146        version: i64,
147    ) -> Self {
148        Self {
149            id,
150            event_type: EventType::new_unchecked(event_type),
151            entity_id: EntityId::new_unchecked(entity_id),
152            tenant_id: TenantId::new_unchecked(tenant_id),
153            payload,
154            timestamp,
155            metadata,
156            version,
157        }
158    }
159
160    // Getters (Events are immutable)
161
162    pub fn id(&self) -> Uuid {
163        self.id
164    }
165
166    pub fn event_type(&self) -> &EventType {
167        &self.event_type
168    }
169
170    pub fn event_type_str(&self) -> &str {
171        self.event_type.as_str()
172    }
173
174    pub fn entity_id(&self) -> &EntityId {
175        &self.entity_id
176    }
177
178    pub fn entity_id_str(&self) -> &str {
179        self.entity_id.as_str()
180    }
181
182    pub fn tenant_id(&self) -> &TenantId {
183        &self.tenant_id
184    }
185
186    pub fn tenant_id_str(&self) -> &str {
187        self.tenant_id.as_str()
188    }
189
190    pub fn payload(&self) -> &serde_json::Value {
191        &self.payload
192    }
193
194    pub fn timestamp(&self) -> DateTime<Utc> {
195        self.timestamp
196    }
197
198    pub fn metadata(&self) -> Option<&serde_json::Value> {
199        self.metadata.as_ref()
200    }
201
202    pub fn version(&self) -> i64 {
203        self.version
204    }
205
206    /// Approximate resident size of this event in memory, in bytes.
207    /// Used by the per-tenant memory cache (Step 3 of the
208    /// sustainable data strategy) to make eviction decisions
209    /// against a configurable byte budget.
210    ///
211    /// The estimator uses serialized-JSON payload length plus a
212    /// fixed 256-byte overhead for the rest of the Event struct
213    /// (UUID, event_type, entity_id, tenant_id, timestamp,
214    /// version, plus DashMap and Vec slot overhead). It's
215    /// intentionally cheap and order-of-magnitude correct rather
216    /// than bytes-precise — the budget logic compares against a
217    /// 2 GiB target, so an estimator that's off by 30% per event
218    /// still produces a sensible cap.
219    ///
220    /// Cost: O(payload size) — one serde serialization per
221    /// event. Called once per event on the cache append path,
222    /// not on the query path.
223    pub fn estimated_size_bytes(&self) -> u64 {
224        const FIXED_OVERHEAD: u64 = 256;
225        let payload_bytes = serde_json::to_vec(&self.payload).map_or(0, |v| v.len() as u64);
226        let metadata_bytes = self
227            .metadata
228            .as_ref()
229            .and_then(|m| serde_json::to_vec(m).ok())
230            .map_or(0, |v| v.len() as u64);
231        FIXED_OVERHEAD + payload_bytes + metadata_bytes
232    }
233
234    // Domain behavior methods
235
236    /// Check if this event belongs to a specific tenant
237    pub fn belongs_to_tenant(&self, tenant_id: &TenantId) -> bool {
238        &self.tenant_id == tenant_id
239    }
240
241    /// Check if this event belongs to a tenant (by string)
242    pub fn belongs_to_tenant_str(&self, tenant_id: &str) -> bool {
243        self.tenant_id.as_str() == tenant_id
244    }
245
246    /// Check if this event relates to a specific entity
247    pub fn relates_to_entity(&self, entity_id: &EntityId) -> bool {
248        &self.entity_id == entity_id
249    }
250
251    /// Check if this event relates to an entity (by string)
252    pub fn relates_to_entity_str(&self, entity_id: &str) -> bool {
253        self.entity_id.as_str() == entity_id
254    }
255
256    /// Check if this event is of a specific type
257    pub fn is_type(&self, event_type: &EventType) -> bool {
258        &self.event_type == event_type
259    }
260
261    /// Check if this event is of a type (by string)
262    pub fn is_type_str(&self, event_type: &str) -> bool {
263        self.event_type.as_str() == event_type
264    }
265
266    /// Check if this event is in a specific namespace
267    pub fn is_in_namespace(&self, namespace: &str) -> bool {
268        self.event_type.is_in_namespace(namespace)
269    }
270
271    /// Check if this event occurred within a time range
272    pub fn occurred_between(&self, start: DateTime<Utc>, end: DateTime<Utc>) -> bool {
273        self.timestamp >= start && self.timestamp <= end
274    }
275
276    /// Check if event occurred before a specific time
277    pub fn occurred_before(&self, time: DateTime<Utc>) -> bool {
278        self.timestamp < time
279    }
280
281    /// Check if event occurred after a specific time
282    pub fn occurred_after(&self, time: DateTime<Utc>) -> bool {
283        self.timestamp > time
284    }
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290    use serde_json::json;
291
292    fn test_event_type() -> EventType {
293        EventType::new("user.created".to_string()).unwrap()
294    }
295
296    fn test_entity_id() -> EntityId {
297        EntityId::new("user-123".to_string()).unwrap()
298    }
299
300    fn test_tenant_id() -> TenantId {
301        TenantId::new("tenant-1".to_string()).unwrap()
302    }
303
304    #[test]
305    fn test_estimated_size_bytes_grows_with_payload() {
306        // Two events, identical except payload size — the larger
307        // payload must produce a larger estimate. Exact bytes
308        // aren't specified; ordering is.
309        let small = Event::new(
310            test_event_type(),
311            test_entity_id(),
312            test_tenant_id(),
313            json!({"k": "v"}),
314        );
315        let large_payload = json!({"data": "x".repeat(10_000)});
316        let large = Event::new(
317            test_event_type(),
318            test_entity_id(),
319            test_tenant_id(),
320            large_payload,
321        );
322
323        assert!(small.estimated_size_bytes() < large.estimated_size_bytes());
324        // The fixed overhead floors a tiny event at 256+ bytes;
325        // the large one with a 10k-char string must clear 10k.
326        assert!(small.estimated_size_bytes() >= 256);
327        assert!(large.estimated_size_bytes() > 10_000);
328    }
329
330    #[test]
331    fn test_estimated_size_bytes_includes_metadata() {
332        let no_meta = Event::new(
333            test_event_type(),
334            test_entity_id(),
335            test_tenant_id(),
336            json!({"k": "v"}),
337        );
338        let with_meta = Event::with_metadata(
339            test_event_type(),
340            test_entity_id(),
341            test_tenant_id(),
342            json!({"k": "v"}),
343            json!({"correlation_id": "abc-123-def-456-very-long"}),
344        );
345        assert!(with_meta.estimated_size_bytes() > no_meta.estimated_size_bytes());
346    }
347
348    #[test]
349    fn test_event_creation_with_value_objects() {
350        let event = Event::new(
351            test_event_type(),
352            test_entity_id(),
353            test_tenant_id(),
354            json!({"name": "Alice"}),
355        );
356
357        assert_eq!(event.event_type_str(), "user.created");
358        assert_eq!(event.entity_id_str(), "user-123");
359        assert_eq!(event.tenant_id_str(), "tenant-1");
360        assert_eq!(event.version(), 1);
361    }
362
363    #[test]
364    fn test_event_creation_from_strings() {
365        let event = Event::from_strings(
366            "user.created".to_string(),
367            "user-123".to_string(),
368            "tenant-1".to_string(),
369            json!({"name": "Alice"}),
370            None,
371        );
372
373        assert!(event.is_ok());
374        let event = event.unwrap();
375        assert_eq!(event.event_type_str(), "user.created");
376        assert_eq!(event.entity_id_str(), "user-123");
377        assert_eq!(event.tenant_id_str(), "tenant-1");
378    }
379
380    #[test]
381    fn test_event_with_metadata() {
382        let event = Event::with_metadata(
383            test_event_type(),
384            test_entity_id(),
385            test_tenant_id(),
386            json!({"name": "Bob"}),
387            json!({"source": "api"}),
388        );
389
390        assert!(event.metadata().is_some());
391        assert_eq!(event.metadata().unwrap(), &json!({"source": "api"}));
392    }
393
394    #[test]
395    fn test_event_with_default_tenant() {
396        let event = Event::with_default_tenant(test_event_type(), test_entity_id(), json!({}));
397
398        assert_eq!(event.tenant_id_str(), "default");
399    }
400
401    #[test]
402    fn test_from_strings_validates_event_type() {
403        // Invalid: uppercase
404        let result = Event::from_strings(
405            "User.Created".to_string(),
406            "e1".to_string(),
407            "t1".to_string(),
408            json!({}),
409            None,
410        );
411        assert!(result.is_err());
412
413        // Invalid: empty
414        let result = Event::from_strings(
415            String::new(),
416            "e1".to_string(),
417            "t1".to_string(),
418            json!({}),
419            None,
420        );
421        assert!(result.is_err());
422    }
423
424    #[test]
425    fn test_from_strings_validates_entity_id() {
426        // Invalid: empty entity_id
427        let result = Event::from_strings(
428            "user.created".to_string(),
429            String::new(),
430            "t1".to_string(),
431            json!({}),
432            None,
433        );
434        assert!(result.is_err());
435    }
436
437    #[test]
438    fn test_from_strings_validates_tenant_id() {
439        // Invalid: empty tenant_id
440        let result = Event::from_strings(
441            "user.created".to_string(),
442            "e1".to_string(),
443            String::new(),
444            json!({}),
445            None,
446        );
447        assert!(result.is_err());
448    }
449
450    #[test]
451    fn test_belongs_to_tenant() {
452        let tenant1 = TenantId::new("tenant-1".to_string()).unwrap();
453        let tenant2 = TenantId::new("tenant-2".to_string()).unwrap();
454
455        let event = Event::new(
456            test_event_type(),
457            test_entity_id(),
458            tenant1.clone(),
459            json!({}),
460        );
461
462        assert!(event.belongs_to_tenant(&tenant1));
463        assert!(!event.belongs_to_tenant(&tenant2));
464    }
465
466    #[test]
467    fn test_belongs_to_tenant_str() {
468        let event = Event::new(
469            test_event_type(),
470            test_entity_id(),
471            test_tenant_id(),
472            json!({}),
473        );
474
475        assert!(event.belongs_to_tenant_str("tenant-1"));
476        assert!(!event.belongs_to_tenant_str("tenant-2"));
477    }
478
479    #[test]
480    fn test_relates_to_entity() {
481        let entity1 = EntityId::new("order-456".to_string()).unwrap();
482        let entity2 = EntityId::new("order-789".to_string()).unwrap();
483
484        let event = Event::new(
485            EventType::new("order.placed".to_string()).unwrap(),
486            entity1.clone(),
487            test_tenant_id(),
488            json!({}),
489        );
490
491        assert!(event.relates_to_entity(&entity1));
492        assert!(!event.relates_to_entity(&entity2));
493    }
494
495    #[test]
496    fn test_relates_to_entity_str() {
497        let event = Event::new(
498            EventType::new("order.placed".to_string()).unwrap(),
499            EntityId::new("order-456".to_string()).unwrap(),
500            test_tenant_id(),
501            json!({}),
502        );
503
504        assert!(event.relates_to_entity_str("order-456"));
505        assert!(!event.relates_to_entity_str("order-789"));
506    }
507
508    #[test]
509    fn test_is_type() {
510        let type1 = EventType::new("order.placed".to_string()).unwrap();
511        let type2 = EventType::new("order.cancelled".to_string()).unwrap();
512
513        let event = Event::new(type1.clone(), test_entity_id(), test_tenant_id(), json!({}));
514
515        assert!(event.is_type(&type1));
516        assert!(!event.is_type(&type2));
517    }
518
519    #[test]
520    fn test_is_type_str() {
521        let event = Event::new(
522            EventType::new("order.placed".to_string()).unwrap(),
523            test_entity_id(),
524            test_tenant_id(),
525            json!({}),
526        );
527
528        assert!(event.is_type_str("order.placed"));
529        assert!(!event.is_type_str("order.cancelled"));
530    }
531
532    #[test]
533    fn test_is_in_namespace() {
534        let event = Event::new(
535            EventType::new("order.placed".to_string()).unwrap(),
536            test_entity_id(),
537            test_tenant_id(),
538            json!({}),
539        );
540
541        assert!(event.is_in_namespace("order"));
542        assert!(!event.is_in_namespace("user"));
543    }
544
545    #[test]
546    fn test_time_range_queries() {
547        let event = Event::new(
548            test_event_type(),
549            test_entity_id(),
550            test_tenant_id(),
551            json!({}),
552        );
553
554        let past = Utc::now() - chrono::Duration::hours(1);
555        let future = Utc::now() + chrono::Duration::hours(1);
556
557        assert!(event.occurred_after(past));
558        assert!(event.occurred_before(future));
559        assert!(event.occurred_between(past, future));
560    }
561
562    #[test]
563    fn test_serde_serialization() {
564        let event = Event::new(
565            test_event_type(),
566            test_entity_id(),
567            test_tenant_id(),
568            json!({"test": "data"}),
569        );
570
571        // Should be able to serialize
572        let json = serde_json::to_string(&event);
573        assert!(json.is_ok());
574
575        // Should be able to deserialize
576        let deserialized = serde_json::from_str::<Event>(&json.unwrap());
577        assert!(deserialized.is_ok());
578
579        let deserialized = deserialized.unwrap();
580        assert_eq!(deserialized.event_type_str(), "user.created");
581        assert_eq!(deserialized.entity_id_str(), "user-123");
582    }
583
584    #[test]
585    fn test_reconstruct_from_strings() {
586        let id = Uuid::new_v4();
587        let timestamp = Utc::now();
588
589        let event = Event::reconstruct_from_strings(
590            id,
591            "order.placed".to_string(),
592            "order-123".to_string(),
593            "tenant-1".to_string(),
594            json!({"amount": 100}),
595            timestamp,
596            Some(json!({"source": "api"})),
597            1,
598        );
599
600        assert_eq!(event.id(), id);
601        assert_eq!(event.event_type_str(), "order.placed");
602        assert_eq!(event.entity_id_str(), "order-123");
603        assert_eq!(event.tenant_id_str(), "tenant-1");
604        assert_eq!(event.version(), 1);
605    }
606}