Skip to main content

Crate ferro_projection

Crate ferro_projection 

Source
Expand description

§ferro-projection

Live read-model runtime: subscribe to domain events, persist per-key snapshots, broadcast deltas.

Not to be confused with ferro-projections (plural). That crate is the Service Projection abstraction (ServiceDef → IntentGraph → JsonUiRenderer). This crate (ferro-projection, singular) is the live read-model runtime that subscribes to domain events, maintains a materialized state, and broadcasts deltas. The two abstractions are orthogonal — most apps will use both for different reasons.

ferro-projection is the live-read-model primitive. ferro-events says something happened. ferro-broadcast says something is visible to clients. ferro-projection composes the two: events fold into per-key state, deltas land on projection.{name}.{key} channels.

§Per-key serialization

 Event::dispatch() ─┐
                    │
      ProjectionListener<P> ──┐
                              │
                              ▼
 ┌── per-key Mutex (DashMap<String, Arc<Mutex<()>>>) ──┐
 │   1. load snapshot from projection_snapshots        │
 │   2. apply(&mut state, &event) → Delta              │
 │   3. upsert snapshot (state, version+1)             │
 │   4. broadcast on projection.{name}.{key}            │
 └─────────────────────────────────────────────────────┘
                               │
                               ▼
                 WebSocket clients receive the delta

§Schema and migration

ferro-projection ships a SeaORM migration as CreateProjectionSnapshotsTable. Register it in your consumer-side Migrator:

impl MigratorTrait for Migrator {
    fn migrations() -> Vec<Box<dyn MigrationTrait>> {
        vec![
            Box::new(ferro_projection::CreateProjectionSnapshotsTable),
            // ... your app migrations
        ]
    }
}

§Operational footguns

  1. Broadcast failure does NOT roll back state. If Broadcast::send returns Err, the snapshot row is already persisted; the runtime logs at tracing::warn! and surfaces ProjectionError::Broadcast. Subscribers reconcile by re-reading the snapshot.
  2. Single-instance assumption. v0 assumes a single application instance owns each projection’s listener. Multi-instance deployments must elect a single projection-runner node or accept last-writer-wins behavior on concurrent applies to the same key from different nodes.
  3. register is not idempotent on Arc identity. Calling Arc<ProjectionRuntime<P>>::register() twice registers two listeners — both fire on each dispatch (same semantic as Laravel’s Event::listen). Register once at app startup.

Structs§

CreateProjectionSnapshotsTable
ProjectionKey
Opaque stringly-typed identifier (D-11).
ProjectionRuntime
Live read-model runtime owning the DB connection, the broadcaster handle, the projection impl, and the per-key Mutex registry (D-13).
ProjectionSnapshotActiveModel
Generated by sea-orm-macros
ProjectionSnapshotEntity
Generated by sea-orm-macros
ProjectionSnapshotModel

Enums§

ProjectionError

Traits§

Projection
Consumer-implemented live read-model (D-06).