Skip to main content
In enterprise software systems, storing only raw domain events (like MoneyDeposited { amount: 250 }) is insufficient. To maintain security, auditability, and traceability across microservice boundaries, we need to know:
  • Who initiated this transaction? (Actor ID)
  • Which specific HTTP request caused this event? (Request ID)
  • How can we trace this transaction through our asynchronous background projections? (Correlation and Causation IDs)
  • What tenant does this customer belong to in our SaaS environment? (Tenant ID)
To support this without cluttering our domain-specific events, our framework wraps all committed events inside an envelope that carries extensible, structural Metadata.

The Metadata Structure

Our framework provides a built-in Metadata struct. It is fully serialized into your SQLite or PostgreSQL tables alongside your event payload.
pub struct Metadata {
    /// Correlates all work belonging to the same business request.
    pub correlation_id: Option<String>,
    /// Identifies the command or event that caused this event.
    pub causation_id: Option<String>,
    /// Identifies the user, service, or process that initiated the change.
    pub actor_id: Option<String>,
    /// Identifies the tenant when applications use multi-tenancy.
    pub tenant_id: Option<String>,
    /// Identifies the external request that initiated the change.
    pub request_id: Option<String>,
    /// Additional adapter or application-specific metadata.
    pub headers: BTreeMap<String, String>,
}

Attaching Metadata to Command Executions

Our Metadata struct implements a clean, fluent builder pattern. You can attach correlation headers inside your web router or application handler and pass them directly to the repository’s execute method:
use ddd_cqrs_es::{Metadata, Repository};

fn handle_web_request(
    repo: &Repository<BankAccount, PostgresEventStore<BankAccount>>,
    account_id: &str,
    deposit_amount: u64,
    current_user_id: &str,
    correlation_id: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    
    // 1. Construct audit tracking metadata
    let metadata = Metadata::new()
        .with_actor_id(current_user_id)
        .with_correlation_id(correlation_id)
        .with_tenant_id("tenant-north-america")
        .with_header("client-ip", "192.168.1.45"); // Custom ad-hoc header

    // 2. Dispatch command with metadata
    repo.execute(
        account_id,
        BankAccountCommand::DepositMoney { amount: deposit_amount },
        metadata,
    )?;

    Ok(())
}

The Power of Causality Tracking

By utilizing correlation and causation IDs, you can construct extremely powerful diagnostics:
  • Audit Trails: Show customer support exactly which administrator authorized a refund.
  • Causality Trees: Trace an asynchronous chain of event reactions (e.g., Command A -> Event B -> Process Manager C -> Command D -> Event E) back to the single root request that triggered them.
  • Performance Tracing: Correlate telemetry metrics and log statements across write databases, event stores, and background read projections.