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}