Skip to main content

backbone_outbox/
record.rs

1//! The durable event record — the wire shape carried from a producer's outbox to a consumer's inbox.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7/// A serialized domain event staged in the outbox and carried to consumers.
8///
9/// The `id` is the single end-to-end dedup key (the `backbone-messaging` envelope id): the producer
10/// stages it, the relay carries it, the consumer dedups on it via [`crate::inbox::once`].
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct OutboxRecord {
13    /// The event id — the dedup key. Take it from the messaging `EventEnvelope::id`.
14    pub id: Uuid,
15    /// The event type name, e.g. `"PaymentSettled"`.
16    pub event_type: String,
17    /// The aggregate type the event belongs to, e.g. `"Payment"`.
18    pub aggregate_type: String,
19    /// The aggregate id (as a string — aggregates key on uuid, code, or number).
20    pub aggregate_id: String,
21    /// The owning tenant — the outbox_events table is fenced by this (ADR-0011). Every staged event
22    /// carries the company it belongs to so the RLS policy can isolate tenant event streams.
23    pub company_id: Uuid,
24    /// The serialized domain event payload.
25    pub payload: serde_json::Value,
26    /// When the event occurred in the domain.
27    pub occurred_at: DateTime<Utc>,
28    /// Correlation id for tracing a business flow across events.
29    pub correlation_id: Option<String>,
30    /// Causation id (the parent event that caused this one).
31    pub causation_id: Option<String>,
32    /// Event schema version (see `extension-contract.md` §4).
33    pub version: i32,
34}
35
36impl OutboxRecord {
37    /// Build a record with a fresh id, no correlation/causation, version 1, `occurred_at = now`.
38    /// (`now` is passed in so callers stay deterministic under test.) `company_id` is the owning
39    /// tenant — required, since the outbox_events table is fenced by it (ADR-0011).
40    pub fn new(
41        event_type: impl Into<String>,
42        aggregate_type: impl Into<String>,
43        aggregate_id: impl Into<String>,
44        company_id: Uuid,
45        payload: serde_json::Value,
46        occurred_at: DateTime<Utc>,
47    ) -> Self {
48        Self {
49            id: Uuid::new_v4(),
50            event_type: event_type.into(),
51            aggregate_type: aggregate_type.into(),
52            aggregate_id: aggregate_id.into(),
53            company_id,
54            payload,
55            occurred_at,
56            correlation_id: None,
57            causation_id: None,
58            version: 1,
59        }
60    }
61
62    /// Set the event id explicitly (e.g. reuse the messaging envelope id).
63    pub fn with_id(mut self, id: Uuid) -> Self {
64        self.id = id;
65        self
66    }
67
68    /// Set the correlation id.
69    pub fn with_correlation_id(mut self, id: impl Into<String>) -> Self {
70        self.correlation_id = Some(id.into());
71        self
72    }
73}